From 1b3f1aa71c665e20f8c43027ed44168673191556 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Mon, 5 Jan 2026 08:21:11 -0500 Subject: [PATCH 01/38] Add Content Standards Protocol for content safety evaluation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces the Content Standards Protocol with four tasks: - list_content_features: Discover available content safety features - get_content_standards: Retrieve content safety policies - check_content: Evaluate content context against safety policies - validate_content_delivery: Batch validate delivery records Features use a generic type system (binary, quantitative, categorical) aligned with property governance patterns. Policies use prompt-based evaluation (like Scope3) rather than keyword lists. Standards can be scoped by country, brand, channel, and product. πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .changeset/content-standards-protocol.md | 11 ++ docs.json | 15 ++ docs/governance/content-standards/index.mdx | 122 +++++++++++++ .../content-standards/tasks/check_content.mdx | 164 ++++++++++++++++++ .../tasks/get_content_standards.mdx | 82 +++++++++ .../tasks/list_content_features.mdx | 117 +++++++++++++ .../tasks/validate_content_delivery.mdx | 151 ++++++++++++++++ .../check-content-request.json | 53 ++++++ .../check-content-response.json | 93 ++++++++++ .../get-content-standards-request.json | 20 +++ .../get-content-standards-response.json | 117 +++++++++++++ .../list-content-features-request.json | 19 ++ .../list-content-features-response.json | 106 +++++++++++ .../validate-content-delivery-request.json | 66 +++++++ .../validate-content-delivery-response.json | 92 ++++++++++ static/schemas/source/index.json | 45 +++++ 16 files changed, 1273 insertions(+) create mode 100644 .changeset/content-standards-protocol.md create mode 100644 docs/governance/content-standards/index.mdx create mode 100644 docs/governance/content-standards/tasks/check_content.mdx create mode 100644 docs/governance/content-standards/tasks/get_content_standards.mdx create mode 100644 docs/governance/content-standards/tasks/list_content_features.mdx create mode 100644 docs/governance/content-standards/tasks/validate_content_delivery.mdx create mode 100644 static/schemas/source/content-standards/check-content-request.json create mode 100644 static/schemas/source/content-standards/check-content-response.json create mode 100644 static/schemas/source/content-standards/get-content-standards-request.json create mode 100644 static/schemas/source/content-standards/get-content-standards-response.json create mode 100644 static/schemas/source/content-standards/list-content-features-request.json create mode 100644 static/schemas/source/content-standards/list-content-features-response.json create mode 100644 static/schemas/source/content-standards/validate-content-delivery-request.json create mode 100644 static/schemas/source/content-standards/validate-content-delivery-response.json diff --git a/.changeset/content-standards-protocol.md b/.changeset/content-standards-protocol.md new file mode 100644 index 000000000..4002b20e2 --- /dev/null +++ b/.changeset/content-standards-protocol.md @@ -0,0 +1,11 @@ +--- +"adcontextprotocol": minor +--- + +Add Content Standards Protocol for content safety and suitability evaluation. + +Provides four tasks: +- `list_content_features`: Discover available content safety features +- `get_content_standards`: Retrieve content safety policies +- `check_content`: Evaluate content context against safety policies +- `validate_content_delivery`: Batch validate delivery records diff --git a/docs.json b/docs.json index 58153f374..c66d89681 100644 --- a/docs.json +++ b/docs.json @@ -101,6 +101,21 @@ } ] }, + { + "group": "Content Standards Protocol", + "pages": [ + "docs/governance/content-standards/index", + { + "group": "Tasks", + "pages": [ + "docs/governance/content-standards/tasks/list_content_features", + "docs/governance/content-standards/tasks/get_content_standards", + "docs/governance/content-standards/tasks/check_content", + "docs/governance/content-standards/tasks/validate_content_delivery" + ] + } + ] + }, { "group": "Curation Protocol", "pages": [ diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx new file mode 100644 index 000000000..b458cf744 --- /dev/null +++ b/docs/governance/content-standards/index.mdx @@ -0,0 +1,122 @@ +--- +title: Overview +sidebar_position: 1 +--- + +# Content Standards Protocol + +The Content Standards Protocol controls *content safety and suitability* - what content contexts are appropriate for a brand's ads. + +## What It Does + +Content Standards agents evaluate placement contexts against brand safety policies. This enables: + +- **Brand safety verification** - Is this content safe for the brand? +- **Suitability scoring** - How well does this content fit the brand? +- **Competitive separation** - Avoid appearing near competitor content +- **Sentiment analysis** - Content tone and mood evaluation + +## Reference Pattern + +When placing a media buy, reference your content standards: + +```json +{ + "campaign": {...}, + "governance": { + "content_standards": { + "agent_url": "https://safety.ias.com/adcp", + "standards_id": "nike_brand_safety", + "token": "..." + } + } +} +``` + +- **agent_url** - The Content Standards service +- **standards_id** - Which standards configuration to use +- **token** - Access token (may be optional for public standards) + +## Prompt-Based Policies + +Content Standards uses **natural language prompts** rather than rigid keyword lists: + +```json +{ + "policy": "Avoid content about violence, controversial politics, or adult themes. Sports news is excellent. Entertainment is generally acceptable. Avoid hard news unless positive sentiment.", + + "exclusions_prompt": "Block content containing hate speech, illegal activities, explicit adult content, or ongoing litigation against our company.", + + "context_evaluation_prompt": "Consider the overall tone. Sports and fitness content is ideal. Lifestyle content about health is good. Avoid content portraying sedentary lifestyle positively." +} +``` + +This enables AI-powered verification agents to understand context and nuance. + +## Scoped Standards + +Standards are scoped by country, brand, channel, and product: + +```json +{ + "standards_id": "coke_uk_tv_zero", + "scope": { + "brands": ["Coca-Cola Zero", "Diet Coke"], + "countries": ["GB"], + "channels": ["ctv", "linear_tv"], + "products": ["zero-calorie beverages"], + "description": "UK TV - zero-calorie beverages" + } +} +``` + +This allows different standards for different contexts (e.g., UK TV has different regulations than US display). + +## Tasks + +| Task | Description | +|------|-------------| +| [list_content_features](/docs/governance/content-standards/tasks/list_content_features) | Discover features the agent can evaluate | +| [get_content_standards](/docs/governance/content-standards/tasks/get_content_standards) | Retrieve content safety policies | +| [check_content](/docs/governance/content-standards/tasks/check_content) | Evaluate a single content context | +| [validate_content_delivery](/docs/governance/content-standards/tasks/validate_content_delivery) | Batch validation of delivery records | + +## Typical Providers + +- **IAS** - Integral Ad Science +- **DoubleVerify** - Brand safety and verification +- **Scope3** - Sustainability-focused brand safety with prompt-based policies +- **Custom** - Brand-specific implementations + +## Feature-Based Results + +All verification tasks return **feature-based results** rather than simple pass/fail: + +```json +{ + "summary": { + "total_features": 5, + "passed_features": 4, + "failed_features": 1 + }, + "results": [ + { + "feature_id": "brand_safety", + "status": "passed", + "value": "safe" + }, + { + "feature_id": "competitor_adjacency", + "status": "failed", + "message": "Content mentions competitor brand", + "suggestion": "Avoid placements on this page" + } + ] +} +``` + +## Related + +- [Property Governance](/docs/governance/property) - Controls *where* ads can run +- [Creative Standards](/docs/governance/creative-standards) - Controls *creative execution* guidelines +- [Brand Manifest](/docs/creative/brand-manifest) - Static brand identity that can link to standards agents diff --git a/docs/governance/content-standards/tasks/check_content.mdx b/docs/governance/content-standards/tasks/check_content.mdx new file mode 100644 index 000000000..94f3c4179 --- /dev/null +++ b/docs/governance/content-standards/tasks/check_content.mdx @@ -0,0 +1,164 @@ +--- +title: check_content +sidebar_position: 3 +--- + +# check_content + +Evaluate a single content context against content safety policies. Returns per-feature evaluation results. + +**Response time**: < 500ms + +## Request + +**Schema**: [check-content-request.json](https://adcontextprotocol.org/schemas/v2/content-standards/check-content-request.json) + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `standards_id` | string | Yes | Standards configuration to evaluate against | +| `content` | object | Yes | Content context to evaluate | +| `feature_ids` | array | No | Specific features to check (defaults to all) | + +### Content Object + +```json +{ + "url": "https://example.com/article/sports-news", + "title": "Championship Finals Preview", + "description": "Expert analysis of the upcoming championship match", + "categories": ["sports", "news"], + "text_content": "The championship finals are set to be an exciting..." +} +``` + +## Response + +**Schema**: [check-content-response.json](https://adcontextprotocol.org/schemas/v2/content-standards/check-content-response.json) + +### Passing Response + +```json +{ + "summary": { + "total_features": 5, + "passed_features": 5, + "failed_features": 0, + "warning_features": 0, + "unevaluated_features": 0 + }, + "results": [ + { + "feature_id": "brand_safety", + "status": "passed", + "value": "safe", + "message": "Content classified as safe" + }, + { + "feature_id": "sentiment", + "status": "passed", + "value": "positive", + "message": "Positive sports coverage" + }, + { + "feature_id": "competitor_adjacency", + "status": "passed", + "value": false + }, + { + "feature_id": "garm_category", + "status": "passed", + "value": "safe" + }, + { + "feature_id": "news_quality", + "status": "passed", + "value": 85, + "message": "High-quality sports journalism" + } + ] +} +``` + +### Failing Response + +```json +{ + "summary": { + "total_features": 5, + "passed_features": 3, + "failed_features": 2, + "warning_features": 0, + "unevaluated_features": 0 + }, + "results": [ + { + "feature_id": "brand_safety", + "status": "failed", + "value": "high_risk", + "message": "Content contains controversial political topics", + "rule_id": "safety.political_content", + "suggestion": "Avoid placements on politically charged content" + }, + { + "feature_id": "competitor_adjacency", + "status": "failed", + "value": true, + "message": "Content mentions competitor brand 'Adidas'", + "rule_id": "competitive.brand_mention", + "suggestion": "Exclude placements where competitors are featured" + }, + { + "feature_id": "sentiment", + "status": "passed", + "value": "neutral" + }, + { + "feature_id": "garm_category", + "status": "passed", + "value": "safe" + }, + { + "feature_id": "news_quality", + "status": "passed", + "value": 72 + } + ] +} +``` + +### Result Fields + +| Field | Description | +|-------|-------------| +| `feature_id` | Which feature was evaluated | +| `status` | `passed`, `failed`, `warning`, or `unevaluated` | +| `value` | The evaluated value (type depends on feature) | +| `message` | Human-readable explanation | +| `rule_id` | Which rule triggered this result | +| `suggestion` | Recommended action if failed | + +## Example Usage + +```python +# Check a content URL before placing an ad +result = content_standards_agent.check_content( + standards_id="nike_emea_safety", + content={ + "url": "https://news.example.com/sports/basketball", + "title": "NBA Finals Game 7 Recap", + "categories": ["sports", "basketball"] + } +) + +if result["summary"]["failed_features"] > 0: + # Content has issues - review or skip + for r in result["results"]: + if r["status"] == "failed": + print(f"Failed: {r['feature_id']} - {r['message']}") +``` + +## Related Tasks + +- [list_content_features](/docs/governance/content-standards/tasks/list_content_features) - Discover available features +- [validate_content_delivery](/docs/governance/content-standards/tasks/validate_content_delivery) - Batch validation of delivery records +- [get_content_standards](/docs/governance/content-standards/tasks/get_content_standards) - Retrieve the policies being checked diff --git a/docs/governance/content-standards/tasks/get_content_standards.mdx b/docs/governance/content-standards/tasks/get_content_standards.mdx new file mode 100644 index 000000000..2f95cc551 --- /dev/null +++ b/docs/governance/content-standards/tasks/get_content_standards.mdx @@ -0,0 +1,82 @@ +--- +title: get_content_standards +sidebar_position: 2 +--- + +# get_content_standards + +Retrieve content safety policies for a specific standards configuration. + +**Response time**: < 500ms + +## Request + +**Schema**: [get-content-standards-request.json](https://adcontextprotocol.org/schemas/v2/content-standards/get-content-standards-request.json) + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `standards_id` | string | Yes | Identifier for the standards configuration | + +## Response + +**Schema**: [get-content-standards-response.json](https://adcontextprotocol.org/schemas/v2/content-standards/get-content-standards-response.json) + +### Success Response + +```json +{ + "standards_id": "nike_emea_safety", + "version": "1.2.0", + "effective_date": "2025-01-01T00:00:00Z", + "scope": { + "brands": ["Nike"], + "regions": ["EMEA"], + "channels": ["display", "video", "ctv"], + "description": "Nike EMEA - all digital channels" + }, + "policy": "Avoid content about violence, controversial political topics, or adult themes. Sports news is excellent. Entertainment content is generally acceptable. Avoid hard news unless positive sentiment about athletes or sports.", + "exclusions_prompt": "Block any content containing hate speech, illegal activities, explicit adult content, or content disparaging athletes or athletic achievement.", + "context_evaluation_prompt": "Consider the overall tone. Sports and fitness content is ideal. Lifestyle content about health and wellness is good. Avoid content that portrays sedentary lifestyle positively.", + "competitive_separation": ["adidas", "puma", "under_armour", "reebok"], + "floor": "garm_floor" +} +``` + +### Policy Fields + +| Field | Description | +|-------|-------------| +| `policy` | Primary natural language safety policy | +| `exclusions_prompt` | Hard exclusions - content that should always be blocked | +| `context_evaluation_prompt` | Guidance for evaluating contextual appropriateness | +| `competitive_separation` | Competitor brands to avoid adjacency with | +| `floor` | Safety floor baseline (`garm_floor` = GARM Brand Safety Floor) | + +### Scope Fields + +| Field | Description | +|-------|-------------| +| `brands` | Brand names these standards apply to | +| `regions` | Geographic regions (EMEA, APAC, etc.) | +| `countries` | ISO country codes | +| `channels` | Ad channels (display, video, ctv, audio) | +| `products` | Product lines or categories | +| `description` | Human-readable scope description | + +### Error Response + +```json +{ + "errors": [ + { + "code": "STANDARDS_NOT_FOUND", + "message": "No standards found with ID 'invalid_id'" + } + ] +} +``` + +## Related Tasks + +- [list_content_features](/docs/governance/content-standards/tasks/list_content_features) - Discover available features +- [check_content](/docs/governance/content-standards/tasks/check_content) - Evaluate content against these standards diff --git a/docs/governance/content-standards/tasks/list_content_features.mdx b/docs/governance/content-standards/tasks/list_content_features.mdx new file mode 100644 index 000000000..2584dd08c --- /dev/null +++ b/docs/governance/content-standards/tasks/list_content_features.mdx @@ -0,0 +1,117 @@ +--- +title: list_content_features +sidebar_position: 1 +--- + +# list_content_features + +Discover what features a Content Standards agent can evaluate. Features define the verification capabilities available. + +**Response time**: < 500ms + +## Request + +**Schema**: [list-content-features-request.json](https://adcontextprotocol.org/schemas/v2/content-standards/list-content-features-request.json) + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `standards_id` | string | No | Filter features available for a specific standards configuration | + +## Response + +**Schema**: [list-content-features-response.json](https://adcontextprotocol.org/schemas/v2/content-standards/list-content-features-response.json) + +### Success Response + +```json +{ + "features": [ + { + "feature_id": "brand_safety", + "name": "Brand Safety", + "description": "Overall brand safety classification", + "type": "categorical", + "allowed_values": ["safe", "low_risk", "medium_risk", "high_risk", "blocked"], + "severity": "block" + }, + { + "feature_id": "sentiment", + "name": "Content Sentiment", + "description": "Overall sentiment of the content", + "type": "categorical", + "allowed_values": ["positive", "neutral", "negative"], + "severity": "warn" + }, + { + "feature_id": "sentiment_score", + "name": "Sentiment Score", + "description": "Numerical sentiment score", + "type": "quantitative", + "range": { "min": -1.0, "max": 1.0 }, + "severity": "info" + }, + { + "feature_id": "competitor_adjacency", + "name": "Competitor Adjacency", + "description": "Whether content mentions competitor brands", + "type": "binary", + "severity": "block" + }, + { + "feature_id": "garm_category", + "name": "GARM Category", + "description": "GARM Brand Safety Floor category", + "type": "categorical", + "allowed_values": ["adult_explicit", "arms_ammunition", "crime_harmful", "death_injury", "online_piracy", "hate_speech", "terrorism", "spam", "safe"], + "severity": "block" + }, + { + "feature_id": "news_quality", + "name": "News Quality Score", + "description": "Quality assessment for news content", + "type": "quantitative", + "range": { "min": 0, "max": 100 }, + "severity": "warn" + } + ] +} +``` + +### Feature Types + +| Type | Description | Example | +|------|-------------|---------| +| `binary` | True/false evaluation | `competitor_adjacency` | +| `quantitative` | Numeric value in a range | `sentiment_score` (-1.0 to 1.0) | +| `categorical` | One of allowed values | `brand_safety` (safe, low_risk, etc.) | + +### Severity Levels + +| Severity | Meaning | +|----------|---------| +| `block` | Failed features with this severity should block the placement | +| `warn` | Advisory warning, may not block | +| `info` | Informational, for reporting only | + +### Regional Features + +Some features may only be available in certain regions: + +```json +{ + "feature_id": "imagery_policy", + "name": "Imagery Policy Compliance", + "description": "Compliance with regional imagery policies", + "type": "binary", + "severity": "block", + "coverage": { + "regions": ["EMEA", "APAC"], + "markets": ["SA", "AE", "KW"] + } +} +``` + +## Related Tasks + +- [check_content](/docs/governance/content-standards/tasks/check_content) - Uses these features for evaluation +- [validate_content_delivery](/docs/governance/content-standards/tasks/validate_content_delivery) - Batch verification with feature results diff --git a/docs/governance/content-standards/tasks/validate_content_delivery.mdx b/docs/governance/content-standards/tasks/validate_content_delivery.mdx new file mode 100644 index 000000000..f5a808836 --- /dev/null +++ b/docs/governance/content-standards/tasks/validate_content_delivery.mdx @@ -0,0 +1,151 @@ +--- +title: validate_content_delivery +sidebar_position: 4 +--- + +# validate_content_delivery + +Validate delivery records against content safety policies. Designed for batch auditing of where ads were actually delivered. + +**Response time**: < 10s (batch of 10,000 records) + +## Request + +**Schema**: [validate-content-delivery-request.json](https://adcontextprotocol.org/schemas/v2/content-standards/validate-content-delivery-request.json) + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `standards_id` | string | Yes | Standards configuration to validate against | +| `records` | array | Yes | Delivery records to validate (max 10,000) | +| `feature_ids` | array | No | Specific features to evaluate (defaults to all) | +| `include_passed` | boolean | No | Include passed records in results (default: true) | + +### Delivery Record + +```json +{ + "record_id": "imp_12345", + "timestamp": "2025-01-15T10:30:00Z", + "content": { + "url": "https://example.com/article", + "title": "Article Title", + "categories": ["news", "sports"] + }, + "creative_id": "creative_abc" +} +``` + +## Response + +**Schema**: [validate-content-delivery-response.json](https://adcontextprotocol.org/schemas/v2/content-standards/validate-content-delivery-response.json) + +### Success Response + +```json +{ + "summary": { + "total_records": 1000, + "passed_records": 950, + "failed_records": 50, + "total_features": 5000, + "passed_features": 4750, + "failed_features": 250 + }, + "results": [ + { + "record_id": "imp_12345", + "features": [ + { + "feature_id": "brand_safety", + "status": "passed", + "value": "safe" + }, + { + "feature_id": "competitor_adjacency", + "status": "passed", + "value": false + } + ] + }, + { + "record_id": "imp_12346", + "features": [ + { + "feature_id": "brand_safety", + "status": "failed", + "value": "high_risk", + "message": "Content contains violence", + "suggestion": "Review placement policies" + }, + { + "feature_id": "competitor_adjacency", + "status": "passed", + "value": false + } + ] + } + ] +} +``` + +## Use Cases + +### Post-Campaign Audit + +```python +def audit_campaign_delivery(campaign_id, standards_id, content_standards_agent): + """Audit all delivery records from a campaign.""" + # Fetch delivery records from your ad server + records = fetch_delivery_records(campaign_id) + + # Validate in batches + batch_size = 10000 + all_results = [] + + for i in range(0, len(records), batch_size): + batch = records[i:i + batch_size] + response = content_standards_agent.validate_content_delivery( + standards_id=standards_id, + records=batch + ) + all_results.extend(response["results"]) + + return all_results +``` + +### Real-Time Monitoring Sample + +```python +import random + +def sample_and_validate(records, standards_id, sample_size=1000): + """Validate a random sample for real-time monitoring.""" + sample = random.sample(records, min(sample_size, len(records))) + return content_standards_agent.validate_content_delivery( + standards_id=standards_id, + records=sample + ) +``` + +### Filter for Issues Only + +```python +# Only get failed records to reduce response size +response = content_standards_agent.validate_content_delivery( + standards_id="nike_emea_safety", + records=delivery_records, + include_passed=False # Only return failures +) + +for result in response["results"]: + print(f"Issue with {result['record_id']}") + for feature in result["features"]: + if feature["status"] == "failed": + print(f" - {feature['feature_id']}: {feature['message']}") +``` + +## Related Tasks + +- [list_content_features](/docs/governance/content-standards/tasks/list_content_features) - Discover available features +- [check_content](/docs/governance/content-standards/tasks/check_content) - Single content check +- [get_content_standards](/docs/governance/content-standards/tasks/get_content_standards) - Retrieve the policies diff --git a/static/schemas/source/content-standards/check-content-request.json b/static/schemas/source/content-standards/check-content-request.json new file mode 100644 index 000000000..e4f523af2 --- /dev/null +++ b/static/schemas/source/content-standards/check-content-request.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/content-standards/check-content-request.json", + "title": "Check Content Request", + "description": "Request parameters for evaluating content context against safety policies", + "type": "object", + "properties": { + "standards_id": { + "type": "string", + "description": "Standards configuration to evaluate against" + }, + "content": { + "type": "object", + "description": "Content context to evaluate", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "URL of the content" + }, + "title": { + "type": "string", + "description": "Content title" + }, + "description": { + "type": "string", + "description": "Content description or summary" + }, + "categories": { + "type": "array", + "items": { "type": "string" }, + "description": "Content categories (IAB or custom)" + }, + "text_content": { + "type": "string", + "description": "Full text content for analysis" + } + } + }, + "feature_ids": { + "type": "array", + "items": { "type": "string" }, + "description": "Specific features to check (defaults to all)" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["standards_id", "content"] +} diff --git a/static/schemas/source/content-standards/check-content-response.json b/static/schemas/source/content-standards/check-content-response.json new file mode 100644 index 000000000..a75530519 --- /dev/null +++ b/static/schemas/source/content-standards/check-content-response.json @@ -0,0 +1,93 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/content-standards/check-content-response.json", + "title": "Check Content Response", + "description": "Response payload with per-feature evaluation results", + "type": "object", + "oneOf": [ + { + "type": "object", + "description": "Success response", + "properties": { + "summary": { + "type": "object", + "description": "Summary counts of feature evaluations", + "properties": { + "total_features": { "type": "integer" }, + "passed_features": { "type": "integer" }, + "failed_features": { "type": "integer" }, + "warning_features": { "type": "integer" }, + "unevaluated_features": { "type": "integer" } + }, + "required": ["total_features", "passed_features", "failed_features"] + }, + "results": { + "type": "array", + "description": "Per-feature evaluation results", + "items": { + "type": "object", + "properties": { + "feature_id": { + "type": "string", + "description": "Which feature was evaluated" + }, + "status": { + "type": "string", + "enum": ["passed", "failed", "warning", "unevaluated"], + "description": "Evaluation status" + }, + "value": { + "description": "The evaluated value (type depends on feature)" + }, + "message": { + "type": "string", + "description": "Human-readable explanation" + }, + "rule_id": { + "type": "string", + "description": "Which rule triggered this result" + }, + "suggestion": { + "type": "string", + "description": "Recommended action if failed" + } + }, + "required": ["feature_id", "status"] + } + }, + "errors": { + "not": {}, + "description": "Field must not be present in success response" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["summary", "results"] + }, + { + "type": "object", + "description": "Error response", + "properties": { + "errors": { + "type": "array", + "items": { "$ref": "/schemas/core/error.json" } + }, + "summary": { + "not": {}, + "description": "Field must not be present in error response" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["errors"] + } + ] +} diff --git a/static/schemas/source/content-standards/get-content-standards-request.json b/static/schemas/source/content-standards/get-content-standards-request.json new file mode 100644 index 000000000..7790d22e0 --- /dev/null +++ b/static/schemas/source/content-standards/get-content-standards-request.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/content-standards/get-content-standards-request.json", + "title": "Get Content Standards Request", + "description": "Request parameters for retrieving content safety policies", + "type": "object", + "properties": { + "standards_id": { + "type": "string", + "description": "Identifier for the standards configuration to retrieve" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["standards_id"] +} diff --git a/static/schemas/source/content-standards/get-content-standards-response.json b/static/schemas/source/content-standards/get-content-standards-response.json new file mode 100644 index 000000000..d079f35dd --- /dev/null +++ b/static/schemas/source/content-standards/get-content-standards-response.json @@ -0,0 +1,117 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/content-standards/get-content-standards-response.json", + "title": "Get Content Standards Response", + "description": "Response payload with content safety policies", + "type": "object", + "oneOf": [ + { + "type": "object", + "description": "Success response", + "properties": { + "standards_id": { + "type": "string", + "description": "Identifier for this standards configuration" + }, + "version": { + "type": "string", + "description": "Version of this standards configuration" + }, + "effective_date": { + "type": "string", + "format": "date-time", + "description": "When this version became effective" + }, + "scope": { + "type": "object", + "description": "What this standards configuration applies to", + "properties": { + "brands": { + "type": "array", + "items": { "type": "string" }, + "description": "Brand names covered" + }, + "countries": { + "type": "array", + "items": { "type": "string" }, + "description": "ISO 3166-1 alpha-2 country codes" + }, + "regions": { + "type": "array", + "items": { "type": "string" }, + "description": "Named regions (EMEA, APAC, etc.)" + }, + "channels": { + "type": "array", + "items": { "type": "string" }, + "description": "Advertising channels" + }, + "products": { + "type": "array", + "items": { "type": "string" }, + "description": "Product lines or categories" + }, + "description": { + "type": "string", + "description": "Human-readable scope description" + } + } + }, + "policy": { + "type": "string", + "description": "Primary natural language safety policy" + }, + "exclusions_prompt": { + "type": "string", + "description": "Hard exclusions - content that should always be blocked" + }, + "context_evaluation_prompt": { + "type": "string", + "description": "Guidance for evaluating contextual appropriateness" + }, + "competitive_separation": { + "type": "array", + "items": { "type": "string" }, + "description": "Competitor brands to avoid adjacency with" + }, + "floor": { + "type": "string", + "enum": ["garm_floor", "custom"], + "description": "Safety floor baseline" + }, + "errors": { + "not": {}, + "description": "Field must not be present in success response" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["standards_id", "version"] + }, + { + "type": "object", + "description": "Error response", + "properties": { + "errors": { + "type": "array", + "items": { "$ref": "/schemas/core/error.json" } + }, + "standards_id": { + "not": {}, + "description": "Field must not be present in error response" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["errors"] + } + ] +} diff --git a/static/schemas/source/content-standards/list-content-features-request.json b/static/schemas/source/content-standards/list-content-features-request.json new file mode 100644 index 000000000..07d4b67f2 --- /dev/null +++ b/static/schemas/source/content-standards/list-content-features-request.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/content-standards/list-content-features-request.json", + "title": "List Content Features Request", + "description": "Request parameters for discovering features the agent can evaluate", + "type": "object", + "properties": { + "standards_id": { + "type": "string", + "description": "Filter features available for a specific standards configuration" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + } +} diff --git a/static/schemas/source/content-standards/list-content-features-response.json b/static/schemas/source/content-standards/list-content-features-response.json new file mode 100644 index 000000000..ca8596369 --- /dev/null +++ b/static/schemas/source/content-standards/list-content-features-response.json @@ -0,0 +1,106 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/content-standards/list-content-features-response.json", + "title": "List Content Features Response", + "description": "Response payload with features and their types", + "type": "object", + "oneOf": [ + { + "type": "object", + "description": "Success response", + "properties": { + "features": { + "type": "array", + "description": "Available features for content evaluation", + "items": { + "type": "object", + "properties": { + "feature_id": { + "type": "string", + "description": "Unique identifier for this feature" + }, + "name": { + "type": "string", + "description": "Human-readable feature name" + }, + "description": { + "type": "string", + "description": "What this feature evaluates" + }, + "type": { + "type": "string", + "enum": ["binary", "quantitative", "categorical"], + "description": "Feature type" + }, + "range": { + "type": "object", + "description": "For quantitative features, the valid range", + "properties": { + "min": { "type": "number" }, + "max": { "type": "number" } + } + }, + "allowed_values": { + "type": "array", + "description": "For categorical features, the allowed values", + "items": { "type": "string" } + }, + "severity": { + "type": "string", + "enum": ["block", "warn", "info"], + "description": "Default severity when this feature fails" + }, + "coverage": { + "type": "object", + "description": "Geographic coverage for regional features", + "properties": { + "regions": { + "type": "array", + "items": { "type": "string" } + }, + "markets": { + "type": "array", + "items": { "type": "string" } + } + } + } + }, + "required": ["feature_id", "name", "type"] + } + }, + "errors": { + "not": {}, + "description": "Field must not be present in success response" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["features"] + }, + { + "type": "object", + "description": "Error response", + "properties": { + "errors": { + "type": "array", + "items": { "$ref": "/schemas/core/error.json" } + }, + "features": { + "not": {}, + "description": "Field must not be present in error response" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["errors"] + } + ] +} diff --git a/static/schemas/source/content-standards/validate-content-delivery-request.json b/static/schemas/source/content-standards/validate-content-delivery-request.json new file mode 100644 index 000000000..533c0dc1c --- /dev/null +++ b/static/schemas/source/content-standards/validate-content-delivery-request.json @@ -0,0 +1,66 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/content-standards/validate-content-delivery-request.json", + "title": "Validate Content Delivery Request", + "description": "Request parameters for batch validating delivery records against content safety policies", + "type": "object", + "properties": { + "standards_id": { + "type": "string", + "description": "Standards configuration to validate against" + }, + "records": { + "type": "array", + "description": "Delivery records to validate (max 10,000)", + "maxItems": 10000, + "items": { + "type": "object", + "properties": { + "record_id": { + "type": "string", + "description": "Unique identifier for this delivery record" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the delivery occurred" + }, + "content": { + "type": "object", + "description": "Content context where ad was delivered", + "properties": { + "url": { "type": "string", "format": "uri" }, + "title": { "type": "string" }, + "categories": { + "type": "array", + "items": { "type": "string" } + } + } + }, + "creative_id": { + "type": "string", + "description": "Which creative was delivered" + } + }, + "required": ["record_id", "content"] + } + }, + "feature_ids": { + "type": "array", + "items": { "type": "string" }, + "description": "Specific features to evaluate (defaults to all)" + }, + "include_passed": { + "type": "boolean", + "default": true, + "description": "Include passed records in results" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["standards_id", "records"] +} diff --git a/static/schemas/source/content-standards/validate-content-delivery-response.json b/static/schemas/source/content-standards/validate-content-delivery-response.json new file mode 100644 index 000000000..d21c4f0f0 --- /dev/null +++ b/static/schemas/source/content-standards/validate-content-delivery-response.json @@ -0,0 +1,92 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/content-standards/validate-content-delivery-response.json", + "title": "Validate Content Delivery Response", + "description": "Response payload with per-record feature results and summary statistics", + "type": "object", + "oneOf": [ + { + "type": "object", + "description": "Success response", + "properties": { + "summary": { + "type": "object", + "description": "Summary counts across all records", + "properties": { + "total_records": { "type": "integer" }, + "passed_records": { "type": "integer" }, + "failed_records": { "type": "integer" }, + "total_features": { "type": "integer" }, + "passed_features": { "type": "integer" }, + "failed_features": { "type": "integer" } + }, + "required": ["total_records", "passed_records", "failed_records"] + }, + "results": { + "type": "array", + "description": "Per-record evaluation results", + "items": { + "type": "object", + "properties": { + "record_id": { + "type": "string", + "description": "Which delivery record was evaluated" + }, + "features": { + "type": "array", + "description": "Feature results for this record", + "items": { + "type": "object", + "properties": { + "feature_id": { "type": "string" }, + "status": { + "type": "string", + "enum": ["passed", "failed", "warning", "unevaluated"] + }, + "value": {}, + "message": { "type": "string" }, + "suggestion": { "type": "string" } + }, + "required": ["feature_id", "status"] + } + } + }, + "required": ["record_id", "features"] + } + }, + "errors": { + "not": {}, + "description": "Field must not be present in success response" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["summary", "results"] + }, + { + "type": "object", + "description": "Error response", + "properties": { + "errors": { + "type": "array", + "items": { "$ref": "/schemas/core/error.json" } + }, + "summary": { + "not": {}, + "description": "Field must not be present in error response" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["errors"] + } + ] +} diff --git a/static/schemas/source/index.json b/static/schemas/source/index.json index 2056c90ce..dd7525fdb 100644 --- a/static/schemas/source/index.json +++ b/static/schemas/source/index.json @@ -562,6 +562,51 @@ } } }, + "content-standards": { + "description": "Content Standards protocol task request/response schemas for content safety and suitability evaluation", + "tasks": { + "list-content-features": { + "request": { + "$ref": "/schemas/content-standards/list-content-features-request.json", + "description": "Request parameters for discovering available content safety features" + }, + "response": { + "$ref": "/schemas/content-standards/list-content-features-response.json", + "description": "Response payload with available content features" + } + }, + "get-content-standards": { + "request": { + "$ref": "/schemas/content-standards/get-content-standards-request.json", + "description": "Request parameters for retrieving content safety policies" + }, + "response": { + "$ref": "/schemas/content-standards/get-content-standards-response.json", + "description": "Response payload with content safety policies" + } + }, + "check-content": { + "request": { + "$ref": "/schemas/content-standards/check-content-request.json", + "description": "Request parameters for evaluating content context against safety policies" + }, + "response": { + "$ref": "/schemas/content-standards/check-content-response.json", + "description": "Response payload with per-feature evaluation results" + } + }, + "validate-content-delivery": { + "request": { + "$ref": "/schemas/content-standards/validate-content-delivery-request.json", + "description": "Request parameters for batch validating delivery records" + }, + "response": { + "$ref": "/schemas/content-standards/validate-content-delivery-response.json", + "description": "Response payload with batch validation results" + } + } + } + }, "adagents": { "description": "Authorized sales agents file format specification", "$ref": "/schemas/adagents.json", From 43f98e1fecd638128da539caed66967a9921927f Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Mon, 5 Jan 2026 09:22:48 -0500 Subject: [PATCH 02/38] Simplify content standards policy model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove sentiment analysis (subset of suitability) - Clarify brand safety vs suitability distinction: - Safety = safe for ANY brand (universal thresholds) - Suitability = safe for MY brand (brand-specific) - Replace three separate prompts with single policy + examples: - Single policy prompt for natural language guidelines - Examples object with acceptable/unacceptable URLs as training set - Update schema and documentation to match πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/content-standards/index.mdx | 25 +++++++++++-------- .../tasks/get_content_standards.mdx | 19 +++++++++----- .../get-content-standards-response.json | 24 ++++++++++++------ 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index b458cf744..d47c7d0bb 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -9,12 +9,11 @@ The Content Standards Protocol controls *content safety and suitability* - what ## What It Does -Content Standards agents evaluate placement contexts against brand safety policies. This enables: +Content Standards agents evaluate placement contexts against brand policies. This enables: -- **Brand safety verification** - Is this content safe for the brand? -- **Suitability scoring** - How well does this content fit the brand? +- **Brand safety** - Is this content safe for *any* brand? (universal thresholds like hate speech, illegal content) +- **Brand suitability** - Is this content appropriate for *my* brand? (brand-specific preferences and tone) - **Competitive separation** - Avoid appearing near competitor content -- **Sentiment analysis** - Content tone and mood evaluation ## Reference Pattern @@ -43,15 +42,21 @@ Content Standards uses **natural language prompts** rather than rigid keyword li ```json { - "policy": "Avoid content about violence, controversial politics, or adult themes. Sports news is excellent. Entertainment is generally acceptable. Avoid hard news unless positive sentiment.", - - "exclusions_prompt": "Block content containing hate speech, illegal activities, explicit adult content, or ongoing litigation against our company.", - - "context_evaluation_prompt": "Consider the overall tone. Sports and fitness content is ideal. Lifestyle content about health is good. Avoid content portraying sedentary lifestyle positively." + "policy": "Sports and fitness content is ideal. Lifestyle content about health is good. Entertainment is generally acceptable. Avoid content about violence, controversial politics, adult themes, or content portraying sedentary lifestyle positively. Block hate speech, illegal activities, or ongoing litigation against our company.", + "examples": { + "acceptable": [ + "https://espn.com/nba/game-recap", + "https://healthline.com/nutrition/healthy-eating" + ], + "unacceptable": [ + "https://news.example.com/political-controversy", + "https://tabloid.example.com/celebrity-scandal" + ] + } } ``` -This enables AI-powered verification agents to understand context and nuance. +The policy prompt enables AI-powered verification agents to understand context and nuance. Examples provide a training set that helps calibrate the agent's interpretation. ## Scoped Standards diff --git a/docs/governance/content-standards/tasks/get_content_standards.mdx b/docs/governance/content-standards/tasks/get_content_standards.mdx index 2f95cc551..c0a068e29 100644 --- a/docs/governance/content-standards/tasks/get_content_standards.mdx +++ b/docs/governance/content-standards/tasks/get_content_standards.mdx @@ -34,9 +34,17 @@ Retrieve content safety policies for a specific standards configuration. "channels": ["display", "video", "ctv"], "description": "Nike EMEA - all digital channels" }, - "policy": "Avoid content about violence, controversial political topics, or adult themes. Sports news is excellent. Entertainment content is generally acceptable. Avoid hard news unless positive sentiment about athletes or sports.", - "exclusions_prompt": "Block any content containing hate speech, illegal activities, explicit adult content, or content disparaging athletes or athletic achievement.", - "context_evaluation_prompt": "Consider the overall tone. Sports and fitness content is ideal. Lifestyle content about health and wellness is good. Avoid content that portrays sedentary lifestyle positively.", + "policy": "Sports and fitness content is ideal. Lifestyle content about health and wellness is good. Entertainment content is generally acceptable. Avoid content about violence, controversial political topics, adult themes, or content that portrays sedentary lifestyle positively. Block hate speech, illegal activities, or content disparaging athletes.", + "examples": { + "acceptable": [ + "https://espn.com/nba/game-recap", + "https://healthline.com/fitness/running-benefits" + ], + "unacceptable": [ + "https://news.example.com/political-controversy", + "https://tabloid.example.com/athlete-scandal" + ] + }, "competitive_separation": ["adidas", "puma", "under_armour", "reebok"], "floor": "garm_floor" } @@ -46,9 +54,8 @@ Retrieve content safety policies for a specific standards configuration. | Field | Description | |-------|-------------| -| `policy` | Primary natural language safety policy | -| `exclusions_prompt` | Hard exclusions - content that should always be blocked | -| `context_evaluation_prompt` | Guidance for evaluating contextual appropriateness | +| `policy` | Natural language policy describing acceptable and unacceptable content contexts | +| `examples` | Training set of acceptable/unacceptable URLs to calibrate policy interpretation | | `competitive_separation` | Competitor brands to avoid adjacency with | | `floor` | Safety floor baseline (`garm_floor` = GARM Brand Safety Floor) | diff --git a/static/schemas/source/content-standards/get-content-standards-response.json b/static/schemas/source/content-standards/get-content-standards-response.json index d079f35dd..3117e3fa2 100644 --- a/static/schemas/source/content-standards/get-content-standards-response.json +++ b/static/schemas/source/content-standards/get-content-standards-response.json @@ -59,15 +59,23 @@ }, "policy": { "type": "string", - "description": "Primary natural language safety policy" + "description": "Natural language policy describing acceptable and unacceptable content contexts" }, - "exclusions_prompt": { - "type": "string", - "description": "Hard exclusions - content that should always be blocked" - }, - "context_evaluation_prompt": { - "type": "string", - "description": "Guidance for evaluating contextual appropriateness" + "examples": { + "type": "object", + "description": "Training set examples to calibrate policy interpretation", + "properties": { + "acceptable": { + "type": "array", + "items": { "type": "string" }, + "description": "URLs or content descriptions that are acceptable" + }, + "unacceptable": { + "type": "array", + "items": { "type": "string" }, + "description": "URLs or content descriptions that are unacceptable" + } + } }, "competitive_separation": { "type": "array", From 004c6df0a90e63c9d250def4670437d7a42b19b1 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Mon, 5 Jan 2026 09:24:53 -0500 Subject: [PATCH 03/38] Fix broken links in content standards docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove links to Property Governance and Creative Standards which don't exist in this branch yet. πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/content-standards/index.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index d47c7d0bb..b1c001779 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -122,6 +122,4 @@ All verification tasks return **feature-based results** rather than simple pass/ ## Related -- [Property Governance](/docs/governance/property) - Controls *where* ads can run -- [Creative Standards](/docs/governance/creative-standards) - Controls *creative execution* guidelines - [Brand Manifest](/docs/creative/brand-manifest) - Static brand identity that can link to standards agents From 6d21696e7e85250ea3427772698ba8040f99e1c8 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Mon, 5 Jan 2026 09:41:20 -0500 Subject: [PATCH 04/38] Align content standards with property protocol patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review comments: 1. Examples now use typed identifiers with language tags: - Uses identifier schema pattern (type: domain, apple_podcast_id, rss_url, etc.) - Added language field (BCP 47 tags like 'en', 'en-US') - Created content-example.json schema 2. Scope fields aligned with property protocol: - brands β†’ brand_ids (references Brand Manifest identifiers) - countries β†’ countries_all (must apply in ALL listed countries) - channels β†’ channels_any (applies to ANY listed channel) - Removed products field (unclear purpose) πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/content-standards/index.mdx | 26 +++++++++------- .../tasks/get_content_standards.mdx | 24 +++++++-------- .../content-standards/content-example.json | 24 +++++++++++++++ .../get-content-standards-response.json | 30 +++++++------------ static/schemas/source/index.json | 6 ++++ 5 files changed, 66 insertions(+), 44 deletions(-) create mode 100644 static/schemas/source/content-standards/content-example.json diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index b1c001779..3808878ea 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -45,36 +45,40 @@ Content Standards uses **natural language prompts** rather than rigid keyword li "policy": "Sports and fitness content is ideal. Lifestyle content about health is good. Entertainment is generally acceptable. Avoid content about violence, controversial politics, adult themes, or content portraying sedentary lifestyle positively. Block hate speech, illegal activities, or ongoing litigation against our company.", "examples": { "acceptable": [ - "https://espn.com/nba/game-recap", - "https://healthline.com/nutrition/healthy-eating" + { "type": "domain", "value": "espn.com", "language": "en" }, + { "type": "domain", "value": "healthline.com", "language": "en" }, + { "type": "apple_podcast_id", "value": "1234567890", "language": "en" } ], "unacceptable": [ - "https://news.example.com/political-controversy", - "https://tabloid.example.com/celebrity-scandal" + { "type": "domain", "value": "tabloid.example.com", "language": "en" }, + { "type": "rss_url", "value": "https://controversy.example.com/feed.xml", "language": "en" } ] } } ``` -The policy prompt enables AI-powered verification agents to understand context and nuance. Examples provide a training set that helps calibrate the agent's interpretation. +The policy prompt enables AI-powered verification agents to understand context and nuance. Examples provide a training set using typed identifiers (domains, app IDs, podcast IDs, etc.) with language tags that help calibrate the agent's interpretation. ## Scoped Standards -Standards are scoped by country, brand, channel, and product: +Standards are scoped by country, brand, and channel: ```json { "standards_id": "coke_uk_tv_zero", "scope": { - "brands": ["Coca-Cola Zero", "Diet Coke"], - "countries": ["GB"], - "channels": ["ctv", "linear_tv"], - "products": ["zero-calorie beverages"], - "description": "UK TV - zero-calorie beverages" + "brand_ids": ["coke_zero", "diet_coke"], + "countries_all": ["GB"], + "channels_any": ["ctv", "linear_tv"], + "description": "UK TV - Coca-Cola zero-calorie brands" } } ``` +- **brand_ids**: References to brand identifiers defined in the [Brand Manifest](/docs/creative/brand-manifest). Brand names may differ across countries, so we use stable IDs rather than display names. +- **countries_all**: ISO country codes - standards must apply in ALL listed countries (different jurisdictions have different regulations) +- **channels_any**: Advertising channels - standards apply to ANY of the listed channels + This allows different standards for different contexts (e.g., UK TV has different regulations than US display). ## Tasks diff --git a/docs/governance/content-standards/tasks/get_content_standards.mdx b/docs/governance/content-standards/tasks/get_content_standards.mdx index c0a068e29..e90932bd9 100644 --- a/docs/governance/content-standards/tasks/get_content_standards.mdx +++ b/docs/governance/content-standards/tasks/get_content_standards.mdx @@ -29,20 +29,20 @@ Retrieve content safety policies for a specific standards configuration. "version": "1.2.0", "effective_date": "2025-01-01T00:00:00Z", "scope": { - "brands": ["Nike"], - "regions": ["EMEA"], - "channels": ["display", "video", "ctv"], + "brand_ids": ["nike"], + "countries_all": ["GB", "DE", "FR"], + "channels_any": ["display", "video", "ctv"], "description": "Nike EMEA - all digital channels" }, "policy": "Sports and fitness content is ideal. Lifestyle content about health and wellness is good. Entertainment content is generally acceptable. Avoid content about violence, controversial political topics, adult themes, or content that portrays sedentary lifestyle positively. Block hate speech, illegal activities, or content disparaging athletes.", "examples": { "acceptable": [ - "https://espn.com/nba/game-recap", - "https://healthline.com/fitness/running-benefits" + { "type": "domain", "value": "espn.com", "language": "en" }, + { "type": "domain", "value": "healthline.com", "language": "en" } ], "unacceptable": [ - "https://news.example.com/political-controversy", - "https://tabloid.example.com/athlete-scandal" + { "type": "domain", "value": "tabloid.example.com", "language": "en" }, + { "type": "rss_url", "value": "https://controversy.example.com/feed.xml", "language": "en" } ] }, "competitive_separation": ["adidas", "puma", "under_armour", "reebok"], @@ -55,7 +55,7 @@ Retrieve content safety policies for a specific standards configuration. | Field | Description | |-------|-------------| | `policy` | Natural language policy describing acceptable and unacceptable content contexts | -| `examples` | Training set of acceptable/unacceptable URLs to calibrate policy interpretation | +| `examples` | Training set of typed identifiers (domains, app IDs, podcast IDs) to calibrate policy interpretation | | `competitive_separation` | Competitor brands to avoid adjacency with | | `floor` | Safety floor baseline (`garm_floor` = GARM Brand Safety Floor) | @@ -63,11 +63,9 @@ Retrieve content safety policies for a specific standards configuration. | Field | Description | |-------|-------------| -| `brands` | Brand names these standards apply to | -| `regions` | Geographic regions (EMEA, APAC, etc.) | -| `countries` | ISO country codes | -| `channels` | Ad channels (display, video, ctv, audio) | -| `products` | Product lines or categories | +| `brand_ids` | Brand identifiers as defined in the Brand Manifest | +| `countries_all` | ISO country codes - standards must apply in ALL listed countries | +| `channels_any` | Ad channels - standards apply to ANY of the listed channels | | `description` | Human-readable scope description | ### Error Response diff --git a/static/schemas/source/content-standards/content-example.json b/static/schemas/source/content-standards/content-example.json new file mode 100644 index 000000000..dc6ac3d29 --- /dev/null +++ b/static/schemas/source/content-standards/content-example.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/content-standards/content-example.json", + "title": "Content Example", + "description": "A content context example for training policy interpretation. Uses typed identifiers (domains, app IDs, podcast IDs, etc.) with optional language tag.", + "type": "object", + "properties": { + "type": { + "$ref": "/schemas/enums/identifier-types.json", + "description": "Type of content identifier" + }, + "value": { + "type": "string", + "description": "The identifier value (domain, app ID, podcast ID, RSS URL, etc.)" + }, + "language": { + "type": "string", + "description": "BCP 47 language tag (e.g., 'en', 'en-US', 'es-MX')", + "pattern": "^[a-z]{2,3}(-[A-Z]{2})?$" + } + }, + "required": ["type", "value"], + "additionalProperties": false +} diff --git a/static/schemas/source/content-standards/get-content-standards-response.json b/static/schemas/source/content-standards/get-content-standards-response.json index 3117e3fa2..ac27cb64d 100644 --- a/static/schemas/source/content-standards/get-content-standards-response.json +++ b/static/schemas/source/content-standards/get-content-standards-response.json @@ -26,30 +26,20 @@ "type": "object", "description": "What this standards configuration applies to", "properties": { - "brands": { + "brand_ids": { "type": "array", "items": { "type": "string" }, - "description": "Brand names covered" + "description": "Brand identifiers as defined in the Brand Manifest" }, - "countries": { + "countries_all": { "type": "array", "items": { "type": "string" }, - "description": "ISO 3166-1 alpha-2 country codes" + "description": "ISO 3166-1 alpha-2 country codes - standards must apply in ALL listed countries" }, - "regions": { + "channels_any": { "type": "array", "items": { "type": "string" }, - "description": "Named regions (EMEA, APAC, etc.)" - }, - "channels": { - "type": "array", - "items": { "type": "string" }, - "description": "Advertising channels" - }, - "products": { - "type": "array", - "items": { "type": "string" }, - "description": "Product lines or categories" + "description": "Advertising channels - standards apply to ANY of the listed channels" }, "description": { "type": "string", @@ -67,13 +57,13 @@ "properties": { "acceptable": { "type": "array", - "items": { "type": "string" }, - "description": "URLs or content descriptions that are acceptable" + "items": { "$ref": "/schemas/content-standards/content-example.json" }, + "description": "Content contexts that are acceptable" }, "unacceptable": { "type": "array", - "items": { "type": "string" }, - "description": "URLs or content descriptions that are unacceptable" + "items": { "$ref": "/schemas/content-standards/content-example.json" }, + "description": "Content contexts that are unacceptable" } } }, diff --git a/static/schemas/source/index.json b/static/schemas/source/index.json index dd7525fdb..42b14356c 100644 --- a/static/schemas/source/index.json +++ b/static/schemas/source/index.json @@ -564,6 +564,12 @@ }, "content-standards": { "description": "Content Standards protocol task request/response schemas for content safety and suitability evaluation", + "models": { + "content-example": { + "$ref": "/schemas/content-standards/content-example.json", + "description": "A typed content context example for training policy interpretation" + } + }, "tasks": { "list-content-features": { "request": { From 2ddcbac772d5d1442467c42863f606ba17075c7d Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Mon, 5 Jan 2026 09:59:28 -0500 Subject: [PATCH 05/38] Clarify buyer-managed scope selection model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The buyer selects the appropriate standards_id when creating a media buy. The seller receives a reference to resolved standards - they don't need to do scope matching themselves. Scope fields are metadata for the buyer's internal organization, not for seller-side resolution. πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/content-standards/index.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index 3808878ea..708d8ca63 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -61,7 +61,7 @@ The policy prompt enables AI-powered verification agents to understand context a ## Scoped Standards -Standards are scoped by country, brand, and channel: +Buyers typically maintain multiple standards configurations for different contexts - UK TV campaigns have different regulations than US display, and children's brands need stricter safety than adult beverages. ```json { @@ -75,11 +75,11 @@ Standards are scoped by country, brand, and channel: } ``` -- **brand_ids**: References to brand identifiers defined in the [Brand Manifest](/docs/creative/brand-manifest). Brand names may differ across countries, so we use stable IDs rather than display names. -- **countries_all**: ISO country codes - standards must apply in ALL listed countries (different jurisdictions have different regulations) -- **channels_any**: Advertising channels - standards apply to ANY of the listed channels +**The buyer selects the appropriate `standards_id` when creating a media buy.** The seller receives a reference to the resolved standards - they don't need to do scope matching themselves. Scope fields are metadata that help buyers organize and select the right configuration. -This allows different standards for different contexts (e.g., UK TV has different regulations than US display). +- **brand_ids**: References to brand identifiers defined in the [Brand Manifest](/docs/creative/brand-manifest). Brand names may differ across countries, so we use stable IDs rather than display names. +- **countries_all**: ISO country codes - the standards configuration was designed for ALL listed countries +- **channels_any**: Advertising channels - the standards configuration applies to ANY of the listed channels ## Tasks From dc5fbf8bbc8dc9d01b3d4b3493dbfd0d13ed497c Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Mon, 5 Jan 2026 10:45:56 -0500 Subject: [PATCH 06/38] Address review comments on content standards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Scope is targeting, not metadata: - Clarified scope defines where standards apply - Removed "metadata" language 2. Add CRUD tasks for standards management: - Added list_content_standards, create_content_standards, update_content_standards, delete_content_standards - Organized tasks into Discovery, Management, Evaluation sections - Created task documentation pages 3. Clarify evaluation results pattern: - Always returns pass/fail verdict first - Features array is optional breakdown for debugging - Explains use cases: optimization, safety vs suitability, debugging third-party standards (GARM, Scope3) - Updated check-content-response and validate-content-delivery-response schemas to use verdict + optional features pattern πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs.json | 4 + docs/governance/content-standards/index.mdx | 53 ++++++++--- .../content-standards/tasks/check_content.mdx | 95 ++++++------------- .../tasks/create_content_standards.mdx | 76 +++++++++++++++ .../tasks/delete_content_standards.mdx | 53 +++++++++++ .../tasks/list_content_standards.mdx | 67 +++++++++++++ .../tasks/update_content_standards.mdx | 72 ++++++++++++++ .../check-content-response.json | 37 +++----- .../validate-content-delivery-response.json | 21 ++-- 9 files changed, 368 insertions(+), 110 deletions(-) create mode 100644 docs/governance/content-standards/tasks/create_content_standards.mdx create mode 100644 docs/governance/content-standards/tasks/delete_content_standards.mdx create mode 100644 docs/governance/content-standards/tasks/list_content_standards.mdx create mode 100644 docs/governance/content-standards/tasks/update_content_standards.mdx diff --git a/docs.json b/docs.json index c66d89681..96ae63a99 100644 --- a/docs.json +++ b/docs.json @@ -109,7 +109,11 @@ "group": "Tasks", "pages": [ "docs/governance/content-standards/tasks/list_content_features", + "docs/governance/content-standards/tasks/list_content_standards", "docs/governance/content-standards/tasks/get_content_standards", + "docs/governance/content-standards/tasks/create_content_standards", + "docs/governance/content-standards/tasks/update_content_standards", + "docs/governance/content-standards/tasks/delete_content_standards", "docs/governance/content-standards/tasks/check_content", "docs/governance/content-standards/tasks/validate_content_delivery" ] diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index 708d8ca63..d301d51a4 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -75,18 +75,34 @@ Buyers typically maintain multiple standards configurations for different contex } ``` -**The buyer selects the appropriate `standards_id` when creating a media buy.** The seller receives a reference to the resolved standards - they don't need to do scope matching themselves. Scope fields are metadata that help buyers organize and select the right configuration. +**The buyer selects the appropriate `standards_id` when creating a media buy.** The seller receives a reference to the resolved standards - they don't need to do scope matching themselves. Scope defines where this standards configuration applies: - **brand_ids**: References to brand identifiers defined in the [Brand Manifest](/docs/creative/brand-manifest). Brand names may differ across countries, so we use stable IDs rather than display names. -- **countries_all**: ISO country codes - the standards configuration was designed for ALL listed countries -- **channels_any**: Advertising channels - the standards configuration applies to ANY of the listed channels +- **countries_all**: ISO country codes - standards apply in ALL listed countries (different jurisdictions have different regulations) +- **channels_any**: Advertising channels - standards apply to ANY of the listed channels ## Tasks +### Discovery + | Task | Description | |------|-------------| | [list_content_features](/docs/governance/content-standards/tasks/list_content_features) | Discover features the agent can evaluate | -| [get_content_standards](/docs/governance/content-standards/tasks/get_content_standards) | Retrieve content safety policies | +| [list_content_standards](/docs/governance/content-standards/tasks/list_content_standards) | List available standards configurations | +| [get_content_standards](/docs/governance/content-standards/tasks/get_content_standards) | Retrieve a specific standards configuration | + +### Management + +| Task | Description | +|------|-------------| +| [create_content_standards](/docs/governance/content-standards/tasks/create_content_standards) | Create a new standards configuration | +| [update_content_standards](/docs/governance/content-standards/tasks/update_content_standards) | Update an existing standards configuration | +| [delete_content_standards](/docs/governance/content-standards/tasks/delete_content_standards) | Delete a standards configuration | + +### Evaluation + +| Task | Description | +|------|-------------| | [check_content](/docs/governance/content-standards/tasks/check_content) | Evaluate a single content context | | [validate_content_delivery](/docs/governance/content-standards/tasks/validate_content_delivery) | Batch validation of delivery records | @@ -97,33 +113,42 @@ Buyers typically maintain multiple standards configurations for different contex - **Scope3** - Sustainability-focused brand safety with prompt-based policies - **Custom** - Brand-specific implementations -## Feature-Based Results +## Evaluation Results -All verification tasks return **feature-based results** rather than simple pass/fail: +Evaluation tasks always return a **pass/fail verdict** and may optionally include feature-level details: ```json { - "summary": { - "total_features": 5, - "passed_features": 4, - "failed_features": 1 - }, - "results": [ + "verdict": "fail", + "features": [ { "feature_id": "brand_safety", "status": "passed", "value": "safe" }, + { + "feature_id": "brand_suitability", + "status": "failed", + "message": "Content tone doesn't match brand guidelines" + }, { "feature_id": "competitor_adjacency", "status": "failed", - "message": "Content mentions competitor brand", - "suggestion": "Avoid placements on this page" + "message": "Content mentions competitor brand" } ] } ``` +### Why Feature Breakdown Matters + +Feature-level results help buyers understand *why* content was blocked: + +- **Optimization**: Which rules cause the most blocking? Are your suitability criteria too strict? +- **Safety vs Suitability**: Distinguish universal brand safety blocks (hate speech, illegal content) from brand-specific suitability blocks (tone, competitive separation) +- **Debugging**: When using third-party standards like Scope3 Common Sense Brand Standards, identify which specific category caused the issue +- **Reporting**: Track block rates by feature to inform policy refinement + ## Related - [Brand Manifest](/docs/creative/brand-manifest) - Static brand identity that can link to standards agents diff --git a/docs/governance/content-standards/tasks/check_content.mdx b/docs/governance/content-standards/tasks/check_content.mdx index 94f3c4179..bea031dd4 100644 --- a/docs/governance/content-standards/tasks/check_content.mdx +++ b/docs/governance/content-standards/tasks/check_content.mdx @@ -5,7 +5,7 @@ sidebar_position: 3 # check_content -Evaluate a single content context against content safety policies. Returns per-feature evaluation results. +Evaluate a single content context against content safety policies. Returns a pass/fail verdict with optional feature-level breakdown. **Response time**: < 500ms @@ -39,14 +39,8 @@ Evaluate a single content context against content safety policies. Returns per-f ```json { - "summary": { - "total_features": 5, - "passed_features": 5, - "failed_features": 0, - "warning_features": 0, - "unevaluated_features": 0 - }, - "results": [ + "verdict": "pass", + "features": [ { "feature_id": "brand_safety", "status": "passed", @@ -54,26 +48,13 @@ Evaluate a single content context against content safety policies. Returns per-f "message": "Content classified as safe" }, { - "feature_id": "sentiment", + "feature_id": "brand_suitability", "status": "passed", - "value": "positive", - "message": "Positive sports coverage" + "message": "Content matches brand guidelines" }, { "feature_id": "competitor_adjacency", - "status": "passed", - "value": false - }, - { - "feature_id": "garm_category", - "status": "passed", - "value": "safe" - }, - { - "feature_id": "news_quality", - "status": "passed", - "value": 85, - "message": "High-quality sports journalism" + "status": "passed" } ] } @@ -83,59 +64,45 @@ Evaluate a single content context against content safety policies. Returns per-f ```json { - "summary": { - "total_features": 5, - "passed_features": 3, - "failed_features": 2, - "warning_features": 0, - "unevaluated_features": 0 - }, - "results": [ + "verdict": "fail", + "features": [ { "feature_id": "brand_safety", + "status": "passed", + "value": "safe" + }, + { + "feature_id": "brand_suitability", "status": "failed", - "value": "high_risk", - "message": "Content contains controversial political topics", - "rule_id": "safety.political_content", - "suggestion": "Avoid placements on politically charged content" + "message": "Content tone doesn't match brand guidelines", + "rule_id": "suitability.tone" }, { "feature_id": "competitor_adjacency", "status": "failed", - "value": true, "message": "Content mentions competitor brand 'Adidas'", - "rule_id": "competitive.brand_mention", - "suggestion": "Exclude placements where competitors are featured" - }, - { - "feature_id": "sentiment", - "status": "passed", - "value": "neutral" - }, - { - "feature_id": "garm_category", - "status": "passed", - "value": "safe" - }, - { - "feature_id": "news_quality", - "status": "passed", - "value": 72 + "rule_id": "competitive.brand_mention" } ] } ``` -### Result Fields +### Response Fields + +| Field | Description | +|-------|-------------| +| `verdict` | Overall `pass` or `fail` decision | +| `features` | Optional array of per-feature results | + +### Feature Fields | Field | Description | |-------|-------------| -| `feature_id` | Which feature was evaluated | +| `feature_id` | Which feature was evaluated (e.g., `brand_safety`, `brand_suitability`, `competitor_adjacency`) | | `status` | `passed`, `failed`, `warning`, or `unevaluated` | | `value` | The evaluated value (type depends on feature) | | `message` | Human-readable explanation | -| `rule_id` | Which rule triggered this result | -| `suggestion` | Recommended action if failed | +| `rule_id` | Which rule triggered this result (e.g., GARM category, Scope3 standard) | ## Example Usage @@ -150,11 +117,11 @@ result = content_standards_agent.check_content( } ) -if result["summary"]["failed_features"] > 0: - # Content has issues - review or skip - for r in result["results"]: - if r["status"] == "failed": - print(f"Failed: {r['feature_id']} - {r['message']}") +if result["verdict"] == "fail": + # Content blocked - check features to understand why + for feature in result.get("features", []): + if feature["status"] == "failed": + print(f"Blocked by: {feature['feature_id']} - {feature.get('message', '')}") ``` ## Related Tasks diff --git a/docs/governance/content-standards/tasks/create_content_standards.mdx b/docs/governance/content-standards/tasks/create_content_standards.mdx new file mode 100644 index 000000000..d5dc29d64 --- /dev/null +++ b/docs/governance/content-standards/tasks/create_content_standards.mdx @@ -0,0 +1,76 @@ +--- +title: create_content_standards +sidebar_position: 5 +--- + +# create_content_standards + +Create a new content standards configuration. + +**Response time**: < 1s + +## Request + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `scope` | object | Yes | Where this standards configuration applies | +| `policy` | string | Yes | Natural language policy prompt | +| `examples` | object | No | Training set of acceptable/unacceptable content | +| `competitive_separation` | array | No | Competitor brands to avoid | +| `floor` | string | No | Safety floor baseline (`garm_floor` or `custom`) | + +### Example Request + +```json +{ + "scope": { + "brand_ids": ["nike"], + "countries_all": ["GB", "DE", "FR"], + "channels_any": ["display", "video", "ctv"], + "description": "Nike EMEA - all digital channels" + }, + "policy": "Sports and fitness content is ideal. Lifestyle content about health and wellness is good. Entertainment content is generally acceptable. Avoid content about violence, controversial political topics, adult themes, or content that portrays sedentary lifestyle positively.", + "examples": { + "acceptable": [ + { "type": "domain", "value": "espn.com", "language": "en" }, + { "type": "domain", "value": "healthline.com", "language": "en" } + ], + "unacceptable": [ + { "type": "domain", "value": "tabloid.example.com", "language": "en" } + ] + }, + "competitive_separation": ["adidas", "puma", "under_armour"], + "floor": "garm_floor" +} +``` + +## Response + +### Success Response + +```json +{ + "standards_id": "nike_emea_safety", + "version": "1.0.0", + "effective_date": "2025-01-05T00:00:00Z" +} +``` + +### Error Response + +```json +{ + "errors": [ + { + "code": "INVALID_SCOPE", + "message": "At least one brand_id is required" + } + ] +} +``` + +## Related Tasks + +- [list_content_standards](/docs/governance/content-standards/tasks/list_content_standards) - List all configurations +- [update_content_standards](/docs/governance/content-standards/tasks/update_content_standards) - Update a configuration +- [delete_content_standards](/docs/governance/content-standards/tasks/delete_content_standards) - Delete a configuration diff --git a/docs/governance/content-standards/tasks/delete_content_standards.mdx b/docs/governance/content-standards/tasks/delete_content_standards.mdx new file mode 100644 index 000000000..5ac79e644 --- /dev/null +++ b/docs/governance/content-standards/tasks/delete_content_standards.mdx @@ -0,0 +1,53 @@ +--- +title: delete_content_standards +sidebar_position: 7 +--- + +# delete_content_standards + +Delete a content standards configuration. + +**Response time**: < 500ms + +## Request + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `standards_id` | string | Yes | ID of the standards configuration to delete | + +### Example Request + +```json +{ + "standards_id": "nike_emea_safety" +} +``` + +## Response + +### Success Response + +```json +{ + "deleted": true, + "standards_id": "nike_emea_safety" +} +``` + +### Error Response + +```json +{ + "errors": [ + { + "code": "STANDARDS_NOT_FOUND", + "message": "No standards found with ID 'invalid_id'" + } + ] +} +``` + +## Related Tasks + +- [list_content_standards](/docs/governance/content-standards/tasks/list_content_standards) - List all configurations +- [create_content_standards](/docs/governance/content-standards/tasks/create_content_standards) - Create a new configuration diff --git a/docs/governance/content-standards/tasks/list_content_standards.mdx b/docs/governance/content-standards/tasks/list_content_standards.mdx new file mode 100644 index 000000000..6f1d7d1dd --- /dev/null +++ b/docs/governance/content-standards/tasks/list_content_standards.mdx @@ -0,0 +1,67 @@ +--- +title: list_content_standards +sidebar_position: 2 +--- + +# list_content_standards + +List available content standards configurations. + +**Response time**: < 500ms + +## Request + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `brand_ids` | array | No | Filter by brand identifiers | +| `countries` | array | No | Filter by country codes | +| `channels` | array | No | Filter by channels | + +## Response + +### Success Response + +```json +{ + "standards": [ + { + "standards_id": "nike_emea_safety", + "version": "1.2.0", + "scope": { + "brand_ids": ["nike"], + "countries_all": ["GB", "DE", "FR"], + "channels_any": ["display", "video", "ctv"], + "description": "Nike EMEA - all digital channels" + } + }, + { + "standards_id": "nike_us_display", + "version": "1.0.0", + "scope": { + "brand_ids": ["nike"], + "countries_all": ["US"], + "channels_any": ["display"], + "description": "Nike US - display only" + } + } + ] +} +``` + +### Error Response + +```json +{ + "errors": [ + { + "code": "UNAUTHORIZED", + "message": "Invalid or expired token" + } + ] +} +``` + +## Related Tasks + +- [get_content_standards](/docs/governance/content-standards/tasks/get_content_standards) - Get a specific standards configuration +- [create_content_standards](/docs/governance/content-standards/tasks/create_content_standards) - Create a new configuration diff --git a/docs/governance/content-standards/tasks/update_content_standards.mdx b/docs/governance/content-standards/tasks/update_content_standards.mdx new file mode 100644 index 000000000..25c4c28e1 --- /dev/null +++ b/docs/governance/content-standards/tasks/update_content_standards.mdx @@ -0,0 +1,72 @@ +--- +title: update_content_standards +sidebar_position: 6 +--- + +# update_content_standards + +Update an existing content standards configuration. Creates a new version. + +**Response time**: < 1s + +## Request + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `standards_id` | string | Yes | ID of the standards configuration to update | +| `scope` | object | No | Updated scope | +| `policy` | string | No | Updated policy prompt | +| `examples` | object | No | Updated training examples | +| `competitive_separation` | array | No | Updated competitors list | +| `floor` | string | No | Updated safety floor | + +### Example Request + +```json +{ + "standards_id": "nike_emea_safety", + "policy": "Sports and fitness content is ideal. Lifestyle content about health and wellness is good. Entertainment content is generally acceptable. Avoid violence, controversial politics, adult themes. Block hate speech and illegal activities.", + "examples": { + "acceptable": [ + { "type": "domain", "value": "espn.com", "language": "en" }, + { "type": "domain", "value": "healthline.com", "language": "en" }, + { "type": "domain", "value": "runnersworld.com", "language": "en" } + ], + "unacceptable": [ + { "type": "domain", "value": "tabloid.example.com", "language": "en" }, + { "type": "domain", "value": "gambling.example.com", "language": "en" } + ] + } +} +``` + +## Response + +### Success Response + +```json +{ + "standards_id": "nike_emea_safety", + "version": "1.3.0", + "effective_date": "2025-01-05T12:00:00Z" +} +``` + +### Error Response + +```json +{ + "errors": [ + { + "code": "STANDARDS_NOT_FOUND", + "message": "No standards found with ID 'invalid_id'" + } + ] +} +``` + +## Related Tasks + +- [get_content_standards](/docs/governance/content-standards/tasks/get_content_standards) - Get current configuration +- [create_content_standards](/docs/governance/content-standards/tasks/create_content_standards) - Create a new configuration +- [delete_content_standards](/docs/governance/content-standards/tasks/delete_content_standards) - Delete a configuration diff --git a/static/schemas/source/content-standards/check-content-response.json b/static/schemas/source/content-standards/check-content-response.json index a75530519..ca55e0a14 100644 --- a/static/schemas/source/content-standards/check-content-response.json +++ b/static/schemas/source/content-standards/check-content-response.json @@ -2,54 +2,43 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/content-standards/check-content-response.json", "title": "Check Content Response", - "description": "Response payload with per-feature evaluation results", + "description": "Response payload with pass/fail verdict and optional feature-level details", "type": "object", "oneOf": [ { "type": "object", "description": "Success response", "properties": { - "summary": { - "type": "object", - "description": "Summary counts of feature evaluations", - "properties": { - "total_features": { "type": "integer" }, - "passed_features": { "type": "integer" }, - "failed_features": { "type": "integer" }, - "warning_features": { "type": "integer" }, - "unevaluated_features": { "type": "integer" } - }, - "required": ["total_features", "passed_features", "failed_features"] + "verdict": { + "type": "string", + "enum": ["pass", "fail"], + "description": "Overall pass/fail verdict for the content evaluation" }, - "results": { + "features": { "type": "array", - "description": "Per-feature evaluation results", + "description": "Optional feature-level breakdown showing which rules passed/failed", "items": { "type": "object", "properties": { "feature_id": { "type": "string", - "description": "Which feature was evaluated" + "description": "Which feature was evaluated (e.g., brand_safety, brand_suitability, competitor_adjacency)" }, "status": { "type": "string", "enum": ["passed", "failed", "warning", "unevaluated"], - "description": "Evaluation status" + "description": "Evaluation status for this feature" }, "value": { "description": "The evaluated value (type depends on feature)" }, "message": { "type": "string", - "description": "Human-readable explanation" + "description": "Human-readable explanation of the result" }, "rule_id": { "type": "string", - "description": "Which rule triggered this result" - }, - "suggestion": { - "type": "string", - "description": "Recommended action if failed" + "description": "Which rule triggered this result (e.g., GARM category, Scope3 standard)" } }, "required": ["feature_id", "status"] @@ -66,7 +55,7 @@ "$ref": "/schemas/core/ext.json" } }, - "required": ["summary", "results"] + "required": ["verdict"] }, { "type": "object", @@ -76,7 +65,7 @@ "type": "array", "items": { "$ref": "/schemas/core/error.json" } }, - "summary": { + "verdict": { "not": {}, "description": "Field must not be present in error response" }, diff --git a/static/schemas/source/content-standards/validate-content-delivery-response.json b/static/schemas/source/content-standards/validate-content-delivery-response.json index d21c4f0f0..1d1feeb1c 100644 --- a/static/schemas/source/content-standards/validate-content-delivery-response.json +++ b/static/schemas/source/content-standards/validate-content-delivery-response.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/content-standards/validate-content-delivery-response.json", "title": "Validate Content Delivery Response", - "description": "Response payload with per-record feature results and summary statistics", + "description": "Response payload with per-record verdicts and optional feature breakdown", "type": "object", "oneOf": [ { @@ -15,10 +15,7 @@ "properties": { "total_records": { "type": "integer" }, "passed_records": { "type": "integer" }, - "failed_records": { "type": "integer" }, - "total_features": { "type": "integer" }, - "passed_features": { "type": "integer" }, - "failed_features": { "type": "integer" } + "failed_records": { "type": "integer" } }, "required": ["total_records", "passed_records", "failed_records"] }, @@ -32,9 +29,14 @@ "type": "string", "description": "Which delivery record was evaluated" }, + "verdict": { + "type": "string", + "enum": ["pass", "fail"], + "description": "Overall pass/fail verdict for this record" + }, "features": { "type": "array", - "description": "Feature results for this record", + "description": "Optional feature-level breakdown", "items": { "type": "object", "properties": { @@ -45,13 +47,16 @@ }, "value": {}, "message": { "type": "string" }, - "suggestion": { "type": "string" } + "rule_id": { + "type": "string", + "description": "Which rule triggered this result (e.g., GARM category, Scope3 standard)" + } }, "required": ["feature_id", "status"] } } }, - "required": ["record_id", "features"] + "required": ["record_id", "verdict"] } }, "errors": { From 41a1ab0bbba1d4916ea5607acce4fec630e657f2 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Mon, 5 Jan 2026 12:37:24 -0500 Subject: [PATCH 07/38] Refine content standards schema and examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review comments: 1. Rename 'examples' to 'calibration': - Better describes the purpose (training/test set for AI interpretation) - Add support for text snippets, image/video/audio URLs 2. Create unified content-context.json schema: - Used for both calibration examples and check_content requests - Supports: domain, text, image_url, video_url, audio_url, rss_url, apple_podcast_id, spotify_show_id, podcast_guid - Optional metadata: language, title, description, categories, text_content 3. Flatten scope structure: - Move brand_ids, countries_all, channels_any to top level - Aligns with property protocol pattern 4. Remove fixed response time claims: - check_content may be async with webhook callback - Let implementations define their own SLAs πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/content-standards/index.mdx | 30 +++++++---- .../content-standards/tasks/check_content.mdx | 15 ++++-- .../tasks/get_content_standards.mdx | 39 ++++++-------- .../content-standards/content-context.json | 52 +++++++++++++++++++ .../content-standards/content-example.json | 24 --------- .../get-content-standards-response.json | 50 ++++++++---------- static/schemas/source/index.json | 6 +-- 7 files changed, 124 insertions(+), 92 deletions(-) create mode 100644 static/schemas/source/content-standards/content-context.json delete mode 100644 static/schemas/source/content-standards/content-example.json diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index d301d51a4..92c5fa17f 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -43,21 +43,31 @@ Content Standards uses **natural language prompts** rather than rigid keyword li ```json { "policy": "Sports and fitness content is ideal. Lifestyle content about health is good. Entertainment is generally acceptable. Avoid content about violence, controversial politics, adult themes, or content portraying sedentary lifestyle positively. Block hate speech, illegal activities, or ongoing litigation against our company.", - "examples": { + "calibration": { "acceptable": [ { "type": "domain", "value": "espn.com", "language": "en" }, { "type": "domain", "value": "healthline.com", "language": "en" }, - { "type": "apple_podcast_id", "value": "1234567890", "language": "en" } + { "type": "apple_podcast_id", "value": "1234567890", "language": "en" }, + { "type": "text", "value": "Championship game recap: Lakers defeat Celtics in thrilling overtime finish", "language": "en" }, + { "type": "image_url", "value": "https://cdn.example.com/sports/basketball-game.jpg" }, + { "type": "video_url", "value": "https://cdn.example.com/fitness/workout-tutorial.mp4" } ], "unacceptable": [ { "type": "domain", "value": "tabloid.example.com", "language": "en" }, - { "type": "rss_url", "value": "https://controversy.example.com/feed.xml", "language": "en" } + { "type": "rss_url", "value": "https://controversy.example.com/feed.xml", "language": "en" }, + { "type": "text", "value": "Political scandal rocks the nation as allegations surface", "language": "en" }, + { "type": "audio_url", "value": "https://cdn.example.com/news/controversial-podcast.mp3" } ] } } ``` -The policy prompt enables AI-powered verification agents to understand context and nuance. Examples provide a training set using typed identifiers (domains, app IDs, podcast IDs, etc.) with language tags that help calibrate the agent's interpretation. +The policy prompt enables AI-powered verification agents to understand context and nuance. **Calibration** examples provide a training/test set that helps the agent interpret the policy correctly: + +- **Domains** - Website identifiers (evaluated by crawling/classification) +- **Podcast/RSS** - Audio content identifiers +- **Text snippets** - Sample headlines or content for NLP evaluation +- **Media URLs** - Links to images, audio, or video for multimodal evaluation ## Scoped Standards @@ -66,16 +76,14 @@ Buyers typically maintain multiple standards configurations for different contex ```json { "standards_id": "coke_uk_tv_zero", - "scope": { - "brand_ids": ["coke_zero", "diet_coke"], - "countries_all": ["GB"], - "channels_any": ["ctv", "linear_tv"], - "description": "UK TV - Coca-Cola zero-calorie brands" - } + "name": "UK TV - Coca-Cola zero-calorie brands", + "brand_ids": ["coke_zero", "diet_coke"], + "countries_all": ["GB"], + "channels_any": ["ctv", "linear_tv"] } ``` -**The buyer selects the appropriate `standards_id` when creating a media buy.** The seller receives a reference to the resolved standards - they don't need to do scope matching themselves. Scope defines where this standards configuration applies: +**The buyer selects the appropriate `standards_id` when creating a media buy.** The seller receives a reference to the resolved standards - they don't need to do scope matching themselves. These fields define where this standards configuration applies: - **brand_ids**: References to brand identifiers defined in the [Brand Manifest](/docs/creative/brand-manifest). Brand names may differ across countries, so we use stable IDs rather than display names. - **countries_all**: ISO country codes - standards apply in ALL listed countries (different jurisdictions have different regulations) diff --git a/docs/governance/content-standards/tasks/check_content.mdx b/docs/governance/content-standards/tasks/check_content.mdx index bea031dd4..95010f0af 100644 --- a/docs/governance/content-standards/tasks/check_content.mdx +++ b/docs/governance/content-standards/tasks/check_content.mdx @@ -7,7 +7,7 @@ sidebar_position: 3 Evaluate a single content context against content safety policies. Returns a pass/fail verdict with optional feature-level breakdown. -**Response time**: < 500ms +May be synchronous or asynchronous depending on the agent implementation. ## Request @@ -16,14 +16,21 @@ Evaluate a single content context against content safety policies. Returns a pas | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `standards_id` | string | Yes | Standards configuration to evaluate against | -| `content` | object | Yes | Content context to evaluate | +| `content` | content-context | Yes | Content context to evaluate (see below) | | `feature_ids` | array | No | Specific features to check (defaults to all) | +| `webhook_url` | string | No | URL for async callback with results | -### Content Object +### Content Context + +**Schema**: [content-context.json](https://adcontextprotocol.org/schemas/v2/content-standards/content-context.json) + +The content context describes the content being evaluated. This is the same schema used for calibration examples. ```json { - "url": "https://example.com/article/sports-news", + "type": "domain", + "value": "espn.com/nba/game-recap", + "language": "en", "title": "Championship Finals Preview", "description": "Expert analysis of the upcoming championship match", "categories": ["sports", "news"], diff --git a/docs/governance/content-standards/tasks/get_content_standards.mdx b/docs/governance/content-standards/tasks/get_content_standards.mdx index e90932bd9..5c82e8570 100644 --- a/docs/governance/content-standards/tasks/get_content_standards.mdx +++ b/docs/governance/content-standards/tasks/get_content_standards.mdx @@ -7,8 +7,6 @@ sidebar_position: 2 Retrieve content safety policies for a specific standards configuration. -**Response time**: < 500ms - ## Request **Schema**: [get-content-standards-request.json](https://adcontextprotocol.org/schemas/v2/content-standards/get-content-standards-request.json) @@ -28,21 +26,21 @@ Retrieve content safety policies for a specific standards configuration. "standards_id": "nike_emea_safety", "version": "1.2.0", "effective_date": "2025-01-01T00:00:00Z", - "scope": { - "brand_ids": ["nike"], - "countries_all": ["GB", "DE", "FR"], - "channels_any": ["display", "video", "ctv"], - "description": "Nike EMEA - all digital channels" - }, + "name": "Nike EMEA - all digital channels", + "brand_ids": ["nike"], + "countries_all": ["GB", "DE", "FR"], + "channels_any": ["display", "video", "ctv"], "policy": "Sports and fitness content is ideal. Lifestyle content about health and wellness is good. Entertainment content is generally acceptable. Avoid content about violence, controversial political topics, adult themes, or content that portrays sedentary lifestyle positively. Block hate speech, illegal activities, or content disparaging athletes.", - "examples": { + "calibration": { "acceptable": [ { "type": "domain", "value": "espn.com", "language": "en" }, - { "type": "domain", "value": "healthline.com", "language": "en" } + { "type": "domain", "value": "healthline.com", "language": "en" }, + { "type": "text", "value": "Lakers win championship in thrilling overtime finish", "language": "en" } ], "unacceptable": [ { "type": "domain", "value": "tabloid.example.com", "language": "en" }, - { "type": "rss_url", "value": "https://controversy.example.com/feed.xml", "language": "en" } + { "type": "text", "value": "Political scandal rocks the nation", "language": "en" }, + { "type": "audio_url", "value": "https://cdn.example.com/controversial-podcast.mp3" } ] }, "competitive_separation": ["adidas", "puma", "under_armour", "reebok"], @@ -50,24 +48,21 @@ Retrieve content safety policies for a specific standards configuration. } ``` -### Policy Fields +### Fields | Field | Description | |-------|-------------| +| `standards_id` | Unique identifier for this standards configuration | +| `version` | Version of this configuration | +| `name` | Human-readable name | +| `brand_ids` | Brand identifiers as defined in the Brand Manifest | +| `countries_all` | ISO country codes - standards apply in ALL listed countries | +| `channels_any` | Ad channels - standards apply to ANY of the listed channels | | `policy` | Natural language policy describing acceptable and unacceptable content contexts | -| `examples` | Training set of typed identifiers (domains, app IDs, podcast IDs) to calibrate policy interpretation | +| `calibration` | Training/test set of content contexts to calibrate policy interpretation | | `competitive_separation` | Competitor brands to avoid adjacency with | | `floor` | Safety floor baseline (`garm_floor` = GARM Brand Safety Floor) | -### Scope Fields - -| Field | Description | -|-------|-------------| -| `brand_ids` | Brand identifiers as defined in the Brand Manifest | -| `countries_all` | ISO country codes - standards must apply in ALL listed countries | -| `channels_any` | Ad channels - standards apply to ANY of the listed channels | -| `description` | Human-readable scope description | - ### Error Response ```json diff --git a/static/schemas/source/content-standards/content-context.json b/static/schemas/source/content-standards/content-context.json new file mode 100644 index 000000000..ab40277f3 --- /dev/null +++ b/static/schemas/source/content-standards/content-context.json @@ -0,0 +1,52 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/content-standards/content-context.json", + "title": "Content Context", + "description": "A content context for evaluation or calibration. Supports multiple content types: domains, text snippets, media URLs, and podcast identifiers.", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "domain", + "text", + "image_url", + "video_url", + "audio_url", + "rss_url", + "apple_podcast_id", + "spotify_show_id", + "podcast_guid" + ], + "description": "Type of content context" + }, + "value": { + "type": "string", + "description": "The content value - domain name, text snippet, media URL, or podcast identifier" + }, + "language": { + "type": "string", + "description": "BCP 47 language tag (e.g., 'en', 'en-US', 'es-MX')", + "pattern": "^[a-z]{2,3}(-[A-Z]{2})?$" + }, + "title": { + "type": "string", + "description": "Content title or headline" + }, + "description": { + "type": "string", + "description": "Content description or summary" + }, + "categories": { + "type": "array", + "items": { "type": "string" }, + "description": "Content categories or tags" + }, + "text_content": { + "type": "string", + "description": "Full or partial text content for NLP evaluation" + } + }, + "required": ["type", "value"], + "additionalProperties": false +} diff --git a/static/schemas/source/content-standards/content-example.json b/static/schemas/source/content-standards/content-example.json deleted file mode 100644 index dc6ac3d29..000000000 --- a/static/schemas/source/content-standards/content-example.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "/schemas/content-standards/content-example.json", - "title": "Content Example", - "description": "A content context example for training policy interpretation. Uses typed identifiers (domains, app IDs, podcast IDs, etc.) with optional language tag.", - "type": "object", - "properties": { - "type": { - "$ref": "/schemas/enums/identifier-types.json", - "description": "Type of content identifier" - }, - "value": { - "type": "string", - "description": "The identifier value (domain, app ID, podcast ID, RSS URL, etc.)" - }, - "language": { - "type": "string", - "description": "BCP 47 language tag (e.g., 'en', 'en-US', 'es-MX')", - "pattern": "^[a-z]{2,3}(-[A-Z]{2})?$" - } - }, - "required": ["type", "value"], - "additionalProperties": false -} diff --git a/static/schemas/source/content-standards/get-content-standards-response.json b/static/schemas/source/content-standards/get-content-standards-response.json index ac27cb64d..539a599ac 100644 --- a/static/schemas/source/content-standards/get-content-standards-response.json +++ b/static/schemas/source/content-standards/get-content-standards-response.json @@ -22,47 +22,41 @@ "format": "date-time", "description": "When this version became effective" }, - "scope": { - "type": "object", - "description": "What this standards configuration applies to", - "properties": { - "brand_ids": { - "type": "array", - "items": { "type": "string" }, - "description": "Brand identifiers as defined in the Brand Manifest" - }, - "countries_all": { - "type": "array", - "items": { "type": "string" }, - "description": "ISO 3166-1 alpha-2 country codes - standards must apply in ALL listed countries" - }, - "channels_any": { - "type": "array", - "items": { "type": "string" }, - "description": "Advertising channels - standards apply to ANY of the listed channels" - }, - "description": { - "type": "string", - "description": "Human-readable scope description" - } - } + "name": { + "type": "string", + "description": "Human-readable name for this standards configuration" + }, + "brand_ids": { + "type": "array", + "items": { "type": "string" }, + "description": "Brand identifiers as defined in the Brand Manifest" + }, + "countries_all": { + "type": "array", + "items": { "type": "string" }, + "description": "ISO 3166-1 alpha-2 country codes - standards apply in ALL listed countries" + }, + "channels_any": { + "type": "array", + "items": { "type": "string" }, + "description": "Advertising channels - standards apply to ANY of the listed channels" }, "policy": { "type": "string", "description": "Natural language policy describing acceptable and unacceptable content contexts" }, - "examples": { + "calibration": { "type": "object", - "description": "Training set examples to calibrate policy interpretation", + "description": "Training/test set to calibrate policy interpretation", "properties": { "acceptable": { "type": "array", - "items": { "$ref": "/schemas/content-standards/content-example.json" }, + "items": { "$ref": "/schemas/content-standards/content-context.json" }, "description": "Content contexts that are acceptable" }, "unacceptable": { "type": "array", - "items": { "$ref": "/schemas/content-standards/content-example.json" }, + "items": { "$ref": "/schemas/content-standards/content-context.json" }, "description": "Content contexts that are unacceptable" } } diff --git a/static/schemas/source/index.json b/static/schemas/source/index.json index 42b14356c..fcd052c74 100644 --- a/static/schemas/source/index.json +++ b/static/schemas/source/index.json @@ -565,9 +565,9 @@ "content-standards": { "description": "Content Standards protocol task request/response schemas for content safety and suitability evaluation", "models": { - "content-example": { - "$ref": "/schemas/content-standards/content-example.json", - "description": "A typed content context example for training policy interpretation" + "content-context": { + "$ref": "/schemas/content-standards/content-context.json", + "description": "Content context for evaluation or calibration - supports domains, text, media URLs, and podcast identifiers" } }, "tasks": { From 86d79a4c8add1c9040a280193ecb44eb3d9bd9f2 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Mon, 5 Jan 2026 15:52:50 -0800 Subject: [PATCH 08/38] Integrate robust content-context schema from sit/adcp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Integrates comprehensive content-context schema improvements: 1. Signals array (replaces simple categories): - Structured signals with key, value, confidence - Supports version, source, and reasons - Examples: iab_category, violence_score, mpaa_rating 2. Structured text_content (replaces simple string): - Array of typed blocks: paragraph, heading, image, video, audio - Images include alt_text, caption, dimensions, type - Video/audio include duration, transcript, transcript_source 3. Rich metadata object: - Open Graph, Twitter Card, JSON-LD - Author, canonical URL, meta tags 4. Temporal fields: - published_time, last_update_time (ISO 8601) 5. artifact_id for cross-reference tracking 6. additionalProperties: true for extensibility Credit: https://github.com/sit/adcp/commit/ca367da πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/content-standards/index.mdx | 48 ++- .../content-standards/tasks/check_content.mdx | 11 +- .../content-standards/content-context.json | 281 +++++++++++++++++- 3 files changed, 317 insertions(+), 23 deletions(-) diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index 92c5fa17f..a7110270b 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -45,29 +45,53 @@ Content Standards uses **natural language prompts** rather than rigid keyword li "policy": "Sports and fitness content is ideal. Lifestyle content about health is good. Entertainment is generally acceptable. Avoid content about violence, controversial politics, adult themes, or content portraying sedentary lifestyle positively. Block hate speech, illegal activities, or ongoing litigation against our company.", "calibration": { "acceptable": [ - { "type": "domain", "value": "espn.com", "language": "en" }, - { "type": "domain", "value": "healthline.com", "language": "en" }, - { "type": "apple_podcast_id", "value": "1234567890", "language": "en" }, - { "type": "text", "value": "Championship game recap: Lakers defeat Celtics in thrilling overtime finish", "language": "en" }, - { "type": "image_url", "value": "https://cdn.example.com/sports/basketball-game.jpg" }, - { "type": "video_url", "value": "https://cdn.example.com/fitness/workout-tutorial.mp4" } + { + "type": "domain", + "value": "https://espn.com/nba/story/_/championship-recap", + "language": "en", + "title": "Championship Game Recap", + "signals": [ + {"key": "iab_category", "value": "sports/basketball", "confidence": 1.0} + ], + "text_content": [ + {"type": "heading", "heading": {"level": 1, "text": "Lakers Defeat Celtics in Thrilling Overtime"}}, + {"type": "paragraph", "paragraphs": ["In an exciting conclusion to the championship series..."]} + ] + }, + { + "type": "domain", + "value": "healthline.com", + "language": "en", + "signals": [{"key": "iab_category", "value": "health/fitness", "confidence": 0.95}] + }, + {"type": "apple_podcast_id", "value": "1234567890", "language": "en"}, + {"type": "text", "value": "Championship game recap: Lakers defeat Celtics in thrilling overtime finish", "language": "en"}, + {"type": "image_url", "value": "https://cdn.example.com/sports/basketball-game.jpg"}, + {"type": "video_url", "value": "https://cdn.example.com/fitness/workout-tutorial.mp4"} ], "unacceptable": [ - { "type": "domain", "value": "tabloid.example.com", "language": "en" }, - { "type": "rss_url", "value": "https://controversy.example.com/feed.xml", "language": "en" }, - { "type": "text", "value": "Political scandal rocks the nation as allegations surface", "language": "en" }, - { "type": "audio_url", "value": "https://cdn.example.com/news/controversial-podcast.mp3" } + { + "type": "domain", + "value": "tabloid.example.com", + "language": "en", + "signals": [{"key": "brand_safety", "value": "controversial", "confidence": 0.85}] + }, + {"type": "rss_url", "value": "https://controversy.example.com/feed.xml", "language": "en"}, + {"type": "text", "value": "Political scandal rocks the nation as allegations surface", "language": "en"}, + {"type": "audio_url", "value": "https://cdn.example.com/news/controversial-podcast.mp3"} ] } } ``` -The policy prompt enables AI-powered verification agents to understand context and nuance. **Calibration** examples provide a training/test set that helps the agent interpret the policy correctly: +The policy prompt enables AI-powered verification agents to understand context and nuance. **Calibration** examples provide a training/test set that helps the agent interpret the policy correctly. Content contexts can include: -- **Domains** - Website identifiers (evaluated by crawling/classification) +- **Domains** - Website identifiers with optional structured content - **Podcast/RSS** - Audio content identifiers - **Text snippets** - Sample headlines or content for NLP evaluation - **Media URLs** - Links to images, audio, or video for multimodal evaluation +- **Signals** - Pre-computed classifications (IAB category, ratings, scores) with confidence levels +- **Structured content** - Headings, paragraphs, embedded media in document order ## Scoped Standards diff --git a/docs/governance/content-standards/tasks/check_content.mdx b/docs/governance/content-standards/tasks/check_content.mdx index 95010f0af..74fbf4f4b 100644 --- a/docs/governance/content-standards/tasks/check_content.mdx +++ b/docs/governance/content-standards/tasks/check_content.mdx @@ -29,12 +29,17 @@ The content context describes the content being evaluated. This is the same sche ```json { "type": "domain", - "value": "espn.com/nba/game-recap", + "value": "https://espn.com/nba/game-recap", "language": "en", "title": "Championship Finals Preview", "description": "Expert analysis of the upcoming championship match", - "categories": ["sports", "news"], - "text_content": "The championship finals are set to be an exciting..." + "signals": [ + {"key": "iab_category", "value": "sports/basketball", "confidence": 0.98} + ], + "text_content": [ + {"type": "heading", "heading": {"level": 1, "text": "Championship Finals Preview"}}, + {"type": "paragraph", "paragraphs": ["The championship finals are set to be an exciting...", "Both teams have fought hard to reach this point."]} + ] } ``` diff --git a/static/schemas/source/content-standards/content-context.json b/static/schemas/source/content-standards/content-context.json index ab40277f3..73b35752e 100644 --- a/static/schemas/source/content-standards/content-context.json +++ b/static/schemas/source/content-standards/content-context.json @@ -5,6 +5,10 @@ "description": "A content context for evaluation or calibration. Supports multiple content types: domains, text snippets, media URLs, and podcast identifiers.", "type": "object", "properties": { + "artifact_id": { + "type": "string", + "description": "Unique identifier for this artifact" + }, "type": { "type": "string", "enum": [ @@ -26,8 +30,7 @@ }, "language": { "type": "string", - "description": "BCP 47 language tag (e.g., 'en', 'en-US', 'es-MX')", - "pattern": "^[a-z]{2,3}(-[A-Z]{2})?$" + "description": "BCP 47 language tag (e.g., 'en', 'en-US', 'es-MX')" }, "title": { "type": "string", @@ -37,16 +40,278 @@ "type": "string", "description": "Content description or summary" }, - "categories": { + "published_time": { + "type": "string", + "format": "date-time", + "description": "When the content was published (ISO 8601 format)" + }, + "last_update_time": { + "type": "string", + "format": "date-time", + "description": "When the content was last modified (ISO 8601 format)" + }, + "signals": { "type": "array", - "items": { "type": "string" }, - "description": "Content categories or tags" + "description": "Pre-computed signals including categories, classifiers, and ratings", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Signal identifier (e.g., 'iab_category', 'violence_score', 'mpaa_rating')" + }, + "value": { + "description": "Signal value - can be string, number, or boolean", + "oneOf": [ + { "type": "string" }, + { "type": "number" }, + { "type": "boolean" } + ] + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Confidence score for probabilistic signals (0-1)" + }, + "version": { + "type": "string", + "description": "Version of the classifier or model that generated this signal" + }, + "source": { + "type": "string", + "description": "Who generated this signal (e.g., 'publisher_classifier', 'human_moderator')" + }, + "reasons": { + "type": "array", + "items": { "type": "string" }, + "description": "Array of reason codes for ratings or classifications" + } + }, + "required": ["key", "value"], + "additionalProperties": false + } }, "text_content": { - "type": "string", - "description": "Full or partial text content for NLP evaluation" + "type": "array", + "description": "Structured content blocks in document flow order", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["paragraph", "image", "heading", "video", "audio"] + } + }, + "required": ["type"], + "oneOf": [ + { + "properties": { + "type": { "const": "paragraph" }, + "paragraphs": { + "type": "array", + "items": { "type": "string" }, + "description": "Array of paragraph text content" + } + }, + "required": ["type", "paragraphs"] + }, + { + "properties": { + "type": { "const": "heading" }, + "heading": { + "type": "object", + "properties": { + "level": { + "type": "integer", + "minimum": 1, + "maximum": 6, + "description": "Heading level (1-6)" + }, + "text": { + "type": "string", + "description": "Heading text" + } + }, + "required": ["level", "text"], + "additionalProperties": false + } + }, + "required": ["type", "heading"] + }, + { + "properties": { + "type": { "const": "image" }, + "image": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "Image URL" + }, + "alt_text": { + "type": "string", + "description": "Alt text or image description" + }, + "caption": { + "type": "string", + "description": "Image caption" + }, + "width": { + "type": "string", + "description": "Image width in pixels (as string)" + }, + "height": { + "type": "string", + "description": "Image height in pixels (as string)" + }, + "type": { + "type": "string", + "enum": ["content", "advertisement", "logo", "thumbnail"], + "description": "Type of image" + }, + "filepath": { + "type": "string", + "description": "Local filepath if image was downloaded" + } + }, + "required": ["url"], + "additionalProperties": false + } + }, + "required": ["type", "image"] + }, + { + "properties": { + "type": { "const": "video" }, + "video": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "Video URL" + }, + "duration_seconds": { + "type": "number", + "description": "Video duration in seconds" + }, + "transcript": { + "type": "string", + "description": "Video transcript" + }, + "transcript_source": { + "type": "string", + "enum": [ + "original_script", + "subtitles", + "closed_captions", + "dub", + "generated" + ], + "description": "How the transcript was generated" + }, + "thumbnail_url": { + "type": "string", + "format": "uri", + "description": "Video thumbnail URL" + } + }, + "required": ["url"], + "additionalProperties": false + } + }, + "required": ["type", "video"] + }, + { + "properties": { + "type": { "const": "audio" }, + "audio": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "Audio URL" + }, + "duration_seconds": { + "type": "number", + "description": "Audio duration in seconds" + }, + "transcript": { + "type": "string", + "description": "Audio transcript" + }, + "transcript_source": { + "type": "string", + "enum": ["original_script", "closed_captions", "generated"], + "description": "How the transcript was generated" + } + }, + "required": ["url"], + "additionalProperties": false + } + }, + "required": ["type", "audio"] + } + ] + } + }, + "metadata": { + "type": "object", + "description": "Rich metadata extracted from the content", + "properties": { + "title": { + "type": "string", + "description": "Content title from metadata" + }, + "description": { + "type": "string", + "description": "Content description from metadata" + }, + "keywords": { + "type": ["string", "null"], + "description": "Content keywords" + }, + "author": { + "type": ["string", "null"], + "description": "Content author name" + }, + "canonical": { + "type": "string", + "format": "uri", + "description": "Canonical URL" + }, + "metaTags": { + "type": "object", + "description": "All meta tags", + "additionalProperties": { "type": "string" } + }, + "open_graph": { + "type": "object", + "description": "Open Graph protocol metadata", + "additionalProperties": true + }, + "twitter_card": { + "type": "object", + "description": "Twitter Card metadata", + "additionalProperties": true + }, + "json_ld": { + "type": "array", + "description": "JSON-LD structured data (schema.org)", + "items": { "type": "array" } + }, + "microdata": { + "type": "array", + "description": "Microdata structured markup", + "items": {} + } + }, + "additionalProperties": true } }, "required": ["type", "value"], - "additionalProperties": false + "additionalProperties": true } From 212bac58ee3df33957bf5f95a9972de62a306054 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 7 Jan 2026 04:28:01 -0800 Subject: [PATCH 09/38] Update changeset to list all 8 tasks 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/content-standards-protocol.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.changeset/content-standards-protocol.md b/.changeset/content-standards-protocol.md index 4002b20e2..6de7503c2 100644 --- a/.changeset/content-standards-protocol.md +++ b/.changeset/content-standards-protocol.md @@ -4,8 +4,16 @@ Add Content Standards Protocol for content safety and suitability evaluation. -Provides four tasks: +Discovery tasks: - `list_content_features`: Discover available content safety features +- `list_content_standards`: List available standards configurations - `get_content_standards`: Retrieve content safety policies + +Management tasks: +- `create_content_standards`: Create a new standards configuration +- `update_content_standards`: Update an existing configuration +- `delete_content_standards`: Delete a configuration + +Evaluation tasks: - `check_content`: Evaluate content context against safety policies - `validate_content_delivery`: Batch validate delivery records From dee7861cb16e10e44719151363f046b03664fe32 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 7 Jan 2026 04:42:31 -0800 Subject: [PATCH 10/38] Rename check_content to calibrate_content for collaborative calibration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename task to emphasize dialogue-based alignment vs runtime evaluation - Add context_id and feedback parameters for multi-turn conversations - Include verbose explanations with confidence scores and policy alignment - Add workflow diagram showing Setup β†’ Activation β†’ Runtime phases - Update schemas with calibrate-content-request/response - Remove old check-content schemas - Update related task links across all task docs Key insight: calibrate_content is low-volume, verbose, dialogue-based; runtime decisioning happens locally at seller for scale. πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .changeset/content-standards-protocol.md | 4 +- docs.json | 2 +- docs/governance/content-standards/index.mdx | 103 +++++++---- .../tasks/calibrate_content.mdx | 172 ++++++++++++++++++ .../content-standards/tasks/check_content.mdx | 143 --------------- .../tasks/get_content_standards.mdx | 2 +- .../tasks/list_content_features.mdx | 2 +- .../tasks/validate_content_delivery.mdx | 2 +- .../calibrate-content-request.json | 26 +++ .../calibrate-content-response.json | 114 ++++++++++++ .../check-content-request.json | 53 ------ .../check-content-response.json | 82 --------- 12 files changed, 386 insertions(+), 319 deletions(-) create mode 100644 docs/governance/content-standards/tasks/calibrate_content.mdx delete mode 100644 docs/governance/content-standards/tasks/check_content.mdx create mode 100644 static/schemas/source/content-standards/calibrate-content-request.json create mode 100644 static/schemas/source/content-standards/calibrate-content-response.json delete mode 100644 static/schemas/source/content-standards/check-content-request.json delete mode 100644 static/schemas/source/content-standards/check-content-response.json diff --git a/.changeset/content-standards-protocol.md b/.changeset/content-standards-protocol.md index 6de7503c2..2109a9108 100644 --- a/.changeset/content-standards-protocol.md +++ b/.changeset/content-standards-protocol.md @@ -14,6 +14,6 @@ Management tasks: - `update_content_standards`: Update an existing configuration - `delete_content_standards`: Delete a configuration -Evaluation tasks: -- `check_content`: Evaluate content context against safety policies +Calibration & Validation tasks: +- `calibrate_content`: Collaborative dialogue to align on policy interpretation - `validate_content_delivery`: Batch validate delivery records diff --git a/docs.json b/docs.json index 96ae63a99..af80507d9 100644 --- a/docs.json +++ b/docs.json @@ -114,7 +114,7 @@ "docs/governance/content-standards/tasks/create_content_standards", "docs/governance/content-standards/tasks/update_content_standards", "docs/governance/content-standards/tasks/delete_content_standards", - "docs/governance/content-standards/tasks/check_content", + "docs/governance/content-standards/tasks/calibrate_content", "docs/governance/content-standards/tasks/validate_content_delivery" ] } diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index a7110270b..32c4b9868 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -113,6 +113,63 @@ Buyers typically maintain multiple standards configurations for different contex - **countries_all**: ISO country codes - standards apply in ALL listed countries (different jurisdictions have different regulations) - **channels_any**: Advertising channels - standards apply to ANY of the listed channels +## Workflow + +Content Standards involves three phases with different agents: + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 1. SETUP PHASE β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ +β”‚ Brand ──────► Verification Agent (e.g., IAS) β”‚ +β”‚ β”‚ β”‚ +β”‚ β”‚ create_content_standards + calibration examples β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ standards_id β”‚ ← Model trained on brand policy β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 2. ACTIVATION PHASE β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ +β”‚ Brand ──────► Buyer Agent (e.g., Scope3) β”‚ +β”‚ β”‚ β”‚ +β”‚ β”‚ "Buy inventory from Reddit, use standards_id X" β”‚ +β”‚ β–Ό β”‚ +β”‚ Buyer Agent ────► Seller Agent (e.g., Reddit) β”‚ +β”‚ β”‚ β”‚ +β”‚ β”‚ Pass content_standards reference in media buy β”‚ +β”‚ β–Ό β”‚ +β”‚ Seller Agent ───► Verification Agent β”‚ +β”‚ β”‚ β”‚ +β”‚ β”‚ calibrate_content (back-and-forth dialogue) β”‚ +β”‚ β”‚ "Does r/fitness pass?" β†’ "Yes, because..." β”‚ +β”‚ β”‚ "Does r/politics pass?" β†’ "No, because..." β”‚ +β”‚ β–Ό β”‚ +β”‚ Seller says: "I understand the model. Ready to execute." β”‚ +β”‚ β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 3. RUNTIME PHASE β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ +β”‚ Seller runs campaign using local/cached model β”‚ +β”‚ β”‚ β”‚ +β”‚ β”‚ Periodic: validate_content_delivery β”‚ +β”‚ β”‚ (sample delivery reports β†’ Verification Agent) β”‚ +β”‚ β–Ό β”‚ +β”‚ Buyer Agent ────► Verification Agent β”‚ +β”‚ β”‚ β”‚ +β”‚ β”‚ "How aligned is delivery?" β”‚ +β”‚ β–Ό β”‚ +β”‚ Reporting / drift detection β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +**Key insight**: Runtime decisioning happens locally at the seller (for scale), while calibration and validation happen via the verification agent (for alignment). + ## Tasks ### Discovery @@ -131,11 +188,11 @@ Buyers typically maintain multiple standards configurations for different contex | [update_content_standards](/docs/governance/content-standards/tasks/update_content_standards) | Update an existing standards configuration | | [delete_content_standards](/docs/governance/content-standards/tasks/delete_content_standards) | Delete a standards configuration | -### Evaluation +### Calibration & Validation | Task | Description | |------|-------------| -| [check_content](/docs/governance/content-standards/tasks/check_content) | Evaluate a single content context | +| [calibrate_content](/docs/governance/content-standards/tasks/calibrate_content) | Collaborative dialogue to align on policy interpretation | | [validate_content_delivery](/docs/governance/content-standards/tasks/validate_content_delivery) | Batch validation of delivery records | ## Typical Providers @@ -145,41 +202,17 @@ Buyers typically maintain multiple standards configurations for different contex - **Scope3** - Sustainability-focused brand safety with prompt-based policies - **Custom** - Brand-specific implementations -## Evaluation Results - -Evaluation tasks always return a **pass/fail verdict** and may optionally include feature-level details: - -```json -{ - "verdict": "fail", - "features": [ - { - "feature_id": "brand_safety", - "status": "passed", - "value": "safe" - }, - { - "feature_id": "brand_suitability", - "status": "failed", - "message": "Content tone doesn't match brand guidelines" - }, - { - "feature_id": "competitor_adjacency", - "status": "failed", - "message": "Content mentions competitor brand" - } - ] -} -``` - -### Why Feature Breakdown Matters +## Calibration vs Runtime -Feature-level results help buyers understand *why* content was blocked: +| Aspect | calibrate_content | Runtime (seller's local model) | +|--------|-------------------|-------------------------------| +| **Purpose** | Alignment & understanding | High-volume decisioning | +| **Volume** | Low (setup/periodic) | High (every impression) | +| **Response** | Verbose explanations | Pass/fail only | +| **Latency** | Seconds acceptable | Milliseconds required | +| **Dialogue** | Supports back-and-forth | Stateless | -- **Optimization**: Which rules cause the most blocking? Are your suitability criteria too strict? -- **Safety vs Suitability**: Distinguish universal brand safety blocks (hate speech, illegal content) from brand-specific suitability blocks (tone, competitive separation) -- **Debugging**: When using third-party standards like Scope3 Common Sense Brand Standards, identify which specific category caused the issue -- **Reporting**: Track block rates by feature to inform policy refinement +The `calibrate_content` task returns detailed explanations of *why* content passes or fails, mapping decisions back to specific policy criteria. This helps sellers build accurate local models. ## Related diff --git a/docs/governance/content-standards/tasks/calibrate_content.mdx b/docs/governance/content-standards/tasks/calibrate_content.mdx new file mode 100644 index 000000000..e849ac185 --- /dev/null +++ b/docs/governance/content-standards/tasks/calibrate_content.mdx @@ -0,0 +1,172 @@ +--- +title: calibrate_content +sidebar_position: 7 +--- + +# calibrate_content + +Collaborative calibration task for aligning on content standards interpretation. Used during setup to help sellers understand and internalize a buyer's content policies before campaign execution. + +Unlike high-volume runtime evaluation, calibration is a **dialogue-based process** where parties exchange examples and explanations until aligned. + +## When to Use + +- **Seller onboarding**: When a seller first receives content standards from a buyer +- **Policy clarification**: When a seller needs to understand why specific content passes or fails +- **Model training**: When building a local model to run against the standards +- **Drift detection**: Periodic re-calibration to ensure continued alignment + +## Request + +**Schema**: [calibrate-content-request.json](https://adcontextprotocol.org/schemas/v2/content-standards/calibrate-content-request.json) + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `standards_id` | string | Yes | Standards configuration to calibrate against | +| `content` | content-context | Yes | Content context to evaluate | +| `context_id` | string | No | Continue an existing calibration dialogue | +| `feedback` | string | No | Seller's feedback or disagreement with previous response | + +### Content Context + +**Schema**: [content-context.json](https://adcontextprotocol.org/schemas/v2/content-standards/content-context.json) + +```json +{ + "type": "domain", + "value": "https://reddit.com/r/fitness/comments/abc123", + "language": "en", + "title": "Best protein sources for muscle building", + "signals": [ + {"key": "iab_category", "value": "health/fitness", "confidence": 0.92} + ], + "text_content": [ + {"type": "heading", "heading": {"level": 1, "text": "Best protein sources for muscle building"}}, + {"type": "paragraph", "paragraphs": ["Looking for recommendations on high-quality protein sources...", "I've been lifting for 6 months and want to optimize my diet."]} + ] +} +``` + +## Response + +**Schema**: [calibrate-content-response.json](https://adcontextprotocol.org/schemas/v2/content-standards/calibrate-content-response.json) + +### Passing Response + +```json +{ + "context_id": "calib_abc123", + "verdict": "pass", + "confidence": 0.95, + "explanation": "This content aligns well with the brand's fitness-focused positioning. Health and fitness content is explicitly marked as 'ideal' in the policy. The discussion is constructive and educational.", + "policy_alignment": { + "matching_criteria": [ + "Sports and fitness content is ideal", + "Lifestyle content about health is good" + ], + "no_violations": true + }, + "features": [ + { + "feature_id": "brand_safety", + "status": "passed", + "explanation": "No safety concerns. Content is user-generated but constructive fitness discussion." + }, + { + "feature_id": "brand_suitability", + "status": "passed", + "explanation": "Fitness content matches brand's athletic positioning." + } + ] +} +``` + +### Failing Response with Detailed Explanation + +```json +{ + "context_id": "calib_def456", + "verdict": "fail", + "confidence": 0.88, + "explanation": "This content discusses political topics which the policy explicitly excludes. While the article itself is balanced journalism, the brand has requested to avoid all controversial political content regardless of tone.", + "policy_alignment": { + "matching_criteria": [], + "violations": [ + { + "policy_text": "Avoid content about violence, controversial politics, adult themes", + "violation_reason": "Article discusses ongoing political controversy" + } + ] + }, + "features": [ + { + "feature_id": "brand_safety", + "status": "passed", + "explanation": "No hate speech, illegal content, or explicit material." + }, + { + "feature_id": "brand_suitability", + "status": "failed", + "explanation": "Political content is excluded by brand policy, even when balanced.", + "suggestion": "Consider news content focused on sports, entertainment, or lifestyle instead." + } + ] +} +``` + +### Response Fields + +| Field | Description | +|-------|-------------| +| `context_id` | Dialogue identifier for follow-up questions | +| `verdict` | Overall `pass` or `fail` decision | +| `confidence` | Model confidence in the verdict (0-1) | +| `explanation` | Detailed natural language explanation of the decision | +| `policy_alignment` | How the content maps to specific policy criteria | +| `features` | Per-feature breakdown with explanations | + +## Dialogue Flow + +Calibration supports back-and-forth dialogue via `context_id`: + +```python +# Initial calibration request +response = verification_agent.calibrate_content( + standards_id="nike_brand_safety", + content={"type": "domain", "value": "https://reddit.com/r/news/politics"} +) +# Response: verdict=fail, context_id="calib_xyz" + +# Seller disagrees - continue dialogue +response = verification_agent.calibrate_content( + standards_id="nike_brand_safety", + content={"type": "domain", "value": "https://reddit.com/r/news/politics"}, + context_id="calib_xyz", + feedback="This is a factual news article, not opinion. Should balanced journalism be excluded?" +) +# Response: Clarifies that brand policy excludes ALL political content regardless of tone + +# Seller acknowledges and tries different content +response = verification_agent.calibrate_content( + standards_id="nike_brand_safety", + content={"type": "domain", "value": "https://reddit.com/r/running"}, + context_id="calib_xyz" +) +# Response: verdict=pass - now seller understands the boundaries +``` + +## Calibration vs Runtime + +| Aspect | calibrate_content | Runtime (local model) | +|--------|-------------------|----------------------| +| **Purpose** | Alignment & understanding | High-volume decisioning | +| **Volume** | Low (setup/periodic) | High (every impression) | +| **Response** | Verbose explanations | Pass/fail only | +| **Latency** | Seconds acceptable | Milliseconds required | +| **Dialogue** | Supports back-and-forth | Stateless | + +## Related Tasks + +- [get_content_standards](/docs/governance/content-standards/tasks/get_content_standards) - Retrieve the policies being calibrated against +- [list_content_features](/docs/governance/content-standards/tasks/list_content_features) - Discover available features +- [validate_content_delivery](/docs/governance/content-standards/tasks/validate_content_delivery) - Post-campaign delivery validation diff --git a/docs/governance/content-standards/tasks/check_content.mdx b/docs/governance/content-standards/tasks/check_content.mdx deleted file mode 100644 index 74fbf4f4b..000000000 --- a/docs/governance/content-standards/tasks/check_content.mdx +++ /dev/null @@ -1,143 +0,0 @@ ---- -title: check_content -sidebar_position: 3 ---- - -# check_content - -Evaluate a single content context against content safety policies. Returns a pass/fail verdict with optional feature-level breakdown. - -May be synchronous or asynchronous depending on the agent implementation. - -## Request - -**Schema**: [check-content-request.json](https://adcontextprotocol.org/schemas/v2/content-standards/check-content-request.json) - -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `standards_id` | string | Yes | Standards configuration to evaluate against | -| `content` | content-context | Yes | Content context to evaluate (see below) | -| `feature_ids` | array | No | Specific features to check (defaults to all) | -| `webhook_url` | string | No | URL for async callback with results | - -### Content Context - -**Schema**: [content-context.json](https://adcontextprotocol.org/schemas/v2/content-standards/content-context.json) - -The content context describes the content being evaluated. This is the same schema used for calibration examples. - -```json -{ - "type": "domain", - "value": "https://espn.com/nba/game-recap", - "language": "en", - "title": "Championship Finals Preview", - "description": "Expert analysis of the upcoming championship match", - "signals": [ - {"key": "iab_category", "value": "sports/basketball", "confidence": 0.98} - ], - "text_content": [ - {"type": "heading", "heading": {"level": 1, "text": "Championship Finals Preview"}}, - {"type": "paragraph", "paragraphs": ["The championship finals are set to be an exciting...", "Both teams have fought hard to reach this point."]} - ] -} -``` - -## Response - -**Schema**: [check-content-response.json](https://adcontextprotocol.org/schemas/v2/content-standards/check-content-response.json) - -### Passing Response - -```json -{ - "verdict": "pass", - "features": [ - { - "feature_id": "brand_safety", - "status": "passed", - "value": "safe", - "message": "Content classified as safe" - }, - { - "feature_id": "brand_suitability", - "status": "passed", - "message": "Content matches brand guidelines" - }, - { - "feature_id": "competitor_adjacency", - "status": "passed" - } - ] -} -``` - -### Failing Response - -```json -{ - "verdict": "fail", - "features": [ - { - "feature_id": "brand_safety", - "status": "passed", - "value": "safe" - }, - { - "feature_id": "brand_suitability", - "status": "failed", - "message": "Content tone doesn't match brand guidelines", - "rule_id": "suitability.tone" - }, - { - "feature_id": "competitor_adjacency", - "status": "failed", - "message": "Content mentions competitor brand 'Adidas'", - "rule_id": "competitive.brand_mention" - } - ] -} -``` - -### Response Fields - -| Field | Description | -|-------|-------------| -| `verdict` | Overall `pass` or `fail` decision | -| `features` | Optional array of per-feature results | - -### Feature Fields - -| Field | Description | -|-------|-------------| -| `feature_id` | Which feature was evaluated (e.g., `brand_safety`, `brand_suitability`, `competitor_adjacency`) | -| `status` | `passed`, `failed`, `warning`, or `unevaluated` | -| `value` | The evaluated value (type depends on feature) | -| `message` | Human-readable explanation | -| `rule_id` | Which rule triggered this result (e.g., GARM category, Scope3 standard) | - -## Example Usage - -```python -# Check a content URL before placing an ad -result = content_standards_agent.check_content( - standards_id="nike_emea_safety", - content={ - "url": "https://news.example.com/sports/basketball", - "title": "NBA Finals Game 7 Recap", - "categories": ["sports", "basketball"] - } -) - -if result["verdict"] == "fail": - # Content blocked - check features to understand why - for feature in result.get("features", []): - if feature["status"] == "failed": - print(f"Blocked by: {feature['feature_id']} - {feature.get('message', '')}") -``` - -## Related Tasks - -- [list_content_features](/docs/governance/content-standards/tasks/list_content_features) - Discover available features -- [validate_content_delivery](/docs/governance/content-standards/tasks/validate_content_delivery) - Batch validation of delivery records -- [get_content_standards](/docs/governance/content-standards/tasks/get_content_standards) - Retrieve the policies being checked diff --git a/docs/governance/content-standards/tasks/get_content_standards.mdx b/docs/governance/content-standards/tasks/get_content_standards.mdx index 5c82e8570..78479d666 100644 --- a/docs/governance/content-standards/tasks/get_content_standards.mdx +++ b/docs/governance/content-standards/tasks/get_content_standards.mdx @@ -79,4 +79,4 @@ Retrieve content safety policies for a specific standards configuration. ## Related Tasks - [list_content_features](/docs/governance/content-standards/tasks/list_content_features) - Discover available features -- [check_content](/docs/governance/content-standards/tasks/check_content) - Evaluate content against these standards +- [calibrate_content](/docs/governance/content-standards/tasks/calibrate_content) - Collaborative calibration against these standards diff --git a/docs/governance/content-standards/tasks/list_content_features.mdx b/docs/governance/content-standards/tasks/list_content_features.mdx index 2584dd08c..332af17a2 100644 --- a/docs/governance/content-standards/tasks/list_content_features.mdx +++ b/docs/governance/content-standards/tasks/list_content_features.mdx @@ -113,5 +113,5 @@ Some features may only be available in certain regions: ## Related Tasks -- [check_content](/docs/governance/content-standards/tasks/check_content) - Uses these features for evaluation +- [calibrate_content](/docs/governance/content-standards/tasks/calibrate_content) - Uses these features for evaluation - [validate_content_delivery](/docs/governance/content-standards/tasks/validate_content_delivery) - Batch verification with feature results diff --git a/docs/governance/content-standards/tasks/validate_content_delivery.mdx b/docs/governance/content-standards/tasks/validate_content_delivery.mdx index f5a808836..54bae3a14 100644 --- a/docs/governance/content-standards/tasks/validate_content_delivery.mdx +++ b/docs/governance/content-standards/tasks/validate_content_delivery.mdx @@ -147,5 +147,5 @@ for result in response["results"]: ## Related Tasks - [list_content_features](/docs/governance/content-standards/tasks/list_content_features) - Discover available features -- [check_content](/docs/governance/content-standards/tasks/check_content) - Single content check +- [calibrate_content](/docs/governance/content-standards/tasks/calibrate_content) - Collaborative calibration dialogue - [get_content_standards](/docs/governance/content-standards/tasks/get_content_standards) - Retrieve the policies diff --git a/static/schemas/source/content-standards/calibrate-content-request.json b/static/schemas/source/content-standards/calibrate-content-request.json new file mode 100644 index 000000000..b5adaf71b --- /dev/null +++ b/static/schemas/source/content-standards/calibrate-content-request.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/content-standards/calibrate-content-request.json", + "title": "Calibrate Content Request", + "description": "Request parameters for collaborative calibration dialogue to align on content standards interpretation", + "type": "object", + "properties": { + "standards_id": { + "type": "string", + "description": "Standards configuration to calibrate against" + }, + "content": { + "$ref": "/schemas/content-standards/content-context.json", + "description": "Content context to evaluate" + }, + "context_id": { + "type": "string", + "description": "Continue an existing calibration dialogue. Omit to start a new dialogue." + }, + "feedback": { + "type": "string", + "description": "Seller's feedback or disagreement with previous response. Use with context_id to continue dialogue." + } + }, + "required": ["standards_id", "content"] +} diff --git a/static/schemas/source/content-standards/calibrate-content-response.json b/static/schemas/source/content-standards/calibrate-content-response.json new file mode 100644 index 000000000..d401068a7 --- /dev/null +++ b/static/schemas/source/content-standards/calibrate-content-response.json @@ -0,0 +1,114 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/content-standards/calibrate-content-response.json", + "title": "Calibrate Content Response", + "description": "Response payload with verdict, detailed explanations, and dialogue support for collaborative calibration", + "type": "object", + "oneOf": [ + { + "type": "object", + "description": "Success response with detailed calibration feedback", + "properties": { + "context_id": { + "type": "string", + "description": "Dialogue identifier for follow-up questions" + }, + "verdict": { + "type": "string", + "enum": ["pass", "fail"], + "description": "Overall pass/fail verdict for the content evaluation" + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Model confidence in the verdict (0-1)" + }, + "explanation": { + "type": "string", + "description": "Detailed natural language explanation of the decision" + }, + "policy_alignment": { + "type": "object", + "description": "How the content maps to specific policy criteria", + "properties": { + "matching_criteria": { + "type": "array", + "items": { "type": "string" }, + "description": "Policy statements that this content matches positively" + }, + "violations": { + "type": "array", + "description": "Policy violations found in the content", + "items": { + "type": "object", + "properties": { + "policy_text": { + "type": "string", + "description": "The policy text that was violated" + }, + "violation_reason": { + "type": "string", + "description": "Explanation of how the content violates this policy" + } + }, + "required": ["policy_text", "violation_reason"] + } + }, + "no_violations": { + "type": "boolean", + "description": "True if no policy violations were found" + } + } + }, + "features": { + "type": "array", + "description": "Per-feature breakdown with explanations", + "items": { + "type": "object", + "properties": { + "feature_id": { + "type": "string", + "description": "Which feature was evaluated (e.g., brand_safety, brand_suitability, competitor_adjacency)" + }, + "status": { + "type": "string", + "enum": ["passed", "failed", "warning", "unevaluated"], + "description": "Evaluation status for this feature" + }, + "explanation": { + "type": "string", + "description": "Human-readable explanation of why this feature passed or failed" + }, + "suggestion": { + "type": "string", + "description": "Actionable suggestion for content that could meet this feature's requirements" + } + }, + "required": ["feature_id", "status"] + } + }, + "errors": { + "not": {}, + "description": "Field must not be present in success response" + } + }, + "required": ["context_id", "verdict"] + }, + { + "type": "object", + "description": "Error response", + "properties": { + "errors": { + "type": "array", + "items": { "$ref": "/schemas/core/error.json" } + }, + "verdict": { + "not": {}, + "description": "Field must not be present in error response" + } + }, + "required": ["errors"] + } + ] +} diff --git a/static/schemas/source/content-standards/check-content-request.json b/static/schemas/source/content-standards/check-content-request.json deleted file mode 100644 index e4f523af2..000000000 --- a/static/schemas/source/content-standards/check-content-request.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "/schemas/content-standards/check-content-request.json", - "title": "Check Content Request", - "description": "Request parameters for evaluating content context against safety policies", - "type": "object", - "properties": { - "standards_id": { - "type": "string", - "description": "Standards configuration to evaluate against" - }, - "content": { - "type": "object", - "description": "Content context to evaluate", - "properties": { - "url": { - "type": "string", - "format": "uri", - "description": "URL of the content" - }, - "title": { - "type": "string", - "description": "Content title" - }, - "description": { - "type": "string", - "description": "Content description or summary" - }, - "categories": { - "type": "array", - "items": { "type": "string" }, - "description": "Content categories (IAB or custom)" - }, - "text_content": { - "type": "string", - "description": "Full text content for analysis" - } - } - }, - "feature_ids": { - "type": "array", - "items": { "type": "string" }, - "description": "Specific features to check (defaults to all)" - }, - "context": { - "$ref": "/schemas/core/context.json" - }, - "ext": { - "$ref": "/schemas/core/ext.json" - } - }, - "required": ["standards_id", "content"] -} diff --git a/static/schemas/source/content-standards/check-content-response.json b/static/schemas/source/content-standards/check-content-response.json deleted file mode 100644 index ca55e0a14..000000000 --- a/static/schemas/source/content-standards/check-content-response.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "/schemas/content-standards/check-content-response.json", - "title": "Check Content Response", - "description": "Response payload with pass/fail verdict and optional feature-level details", - "type": "object", - "oneOf": [ - { - "type": "object", - "description": "Success response", - "properties": { - "verdict": { - "type": "string", - "enum": ["pass", "fail"], - "description": "Overall pass/fail verdict for the content evaluation" - }, - "features": { - "type": "array", - "description": "Optional feature-level breakdown showing which rules passed/failed", - "items": { - "type": "object", - "properties": { - "feature_id": { - "type": "string", - "description": "Which feature was evaluated (e.g., brand_safety, brand_suitability, competitor_adjacency)" - }, - "status": { - "type": "string", - "enum": ["passed", "failed", "warning", "unevaluated"], - "description": "Evaluation status for this feature" - }, - "value": { - "description": "The evaluated value (type depends on feature)" - }, - "message": { - "type": "string", - "description": "Human-readable explanation of the result" - }, - "rule_id": { - "type": "string", - "description": "Which rule triggered this result (e.g., GARM category, Scope3 standard)" - } - }, - "required": ["feature_id", "status"] - } - }, - "errors": { - "not": {}, - "description": "Field must not be present in success response" - }, - "context": { - "$ref": "/schemas/core/context.json" - }, - "ext": { - "$ref": "/schemas/core/ext.json" - } - }, - "required": ["verdict"] - }, - { - "type": "object", - "description": "Error response", - "properties": { - "errors": { - "type": "array", - "items": { "$ref": "/schemas/core/error.json" } - }, - "verdict": { - "not": {}, - "description": "Field must not be present in error response" - }, - "context": { - "$ref": "/schemas/core/context.json" - }, - "ext": { - "$ref": "/schemas/core/ext.json" - } - }, - "required": ["errors"] - } - ] -} From a0fe8306e304f818a953e92f32ab0600778a00f2 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 7 Jan 2026 04:55:56 -0800 Subject: [PATCH 11/38] Use protocol-level context for calibration dialogue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove context_id and feedback from calibrate_content task - dialogue is handled at the protocol layer via A2A contextId or MCP context_id. Updated documentation to show how multi-turn calibration conversations work using the existing protocol mechanisms for conversation management. πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../tasks/calibrate_content.mdx | 118 +++++++++++++----- .../calibrate-content-request.json | 10 +- .../calibrate-content-response.json | 8 +- 3 files changed, 89 insertions(+), 47 deletions(-) diff --git a/docs/governance/content-standards/tasks/calibrate_content.mdx b/docs/governance/content-standards/tasks/calibrate_content.mdx index e849ac185..225acc1fd 100644 --- a/docs/governance/content-standards/tasks/calibrate_content.mdx +++ b/docs/governance/content-standards/tasks/calibrate_content.mdx @@ -24,8 +24,6 @@ Unlike high-volume runtime evaluation, calibration is a **dialogue-based process |-----------|------|----------|-------------| | `standards_id` | string | Yes | Standards configuration to calibrate against | | `content` | content-context | Yes | Content context to evaluate | -| `context_id` | string | No | Continue an existing calibration dialogue | -| `feedback` | string | No | Seller's feedback or disagreement with previous response | ### Content Context @@ -55,7 +53,6 @@ Unlike high-volume runtime evaluation, calibration is a **dialogue-based process ```json { - "context_id": "calib_abc123", "verdict": "pass", "confidence": 0.95, "explanation": "This content aligns well with the brand's fitness-focused positioning. Health and fitness content is explicitly marked as 'ideal' in the policy. The discussion is constructive and educational.", @@ -85,7 +82,6 @@ Unlike high-volume runtime evaluation, calibration is a **dialogue-based process ```json { - "context_id": "calib_def456", "verdict": "fail", "confidence": 0.88, "explanation": "This content discusses political topics which the policy explicitly excludes. While the article itself is balanced journalism, the brand has requested to avoid all controversial political content regardless of tone.", @@ -118,7 +114,6 @@ Unlike high-volume runtime evaluation, calibration is a **dialogue-based process | Field | Description | |-------|-------------| -| `context_id` | Dialogue identifier for follow-up questions | | `verdict` | Overall `pass` or `fail` decision | | `confidence` | Model confidence in the verdict (0-1) | | `explanation` | Detailed natural language explanation of the decision | @@ -127,34 +122,93 @@ Unlike high-volume runtime evaluation, calibration is a **dialogue-based process ## Dialogue Flow -Calibration supports back-and-forth dialogue via `context_id`: - -```python -# Initial calibration request -response = verification_agent.calibrate_content( - standards_id="nike_brand_safety", - content={"type": "domain", "value": "https://reddit.com/r/news/politics"} -) -# Response: verdict=fail, context_id="calib_xyz" - -# Seller disagrees - continue dialogue -response = verification_agent.calibrate_content( - standards_id="nike_brand_safety", - content={"type": "domain", "value": "https://reddit.com/r/news/politics"}, - context_id="calib_xyz", - feedback="This is a factual news article, not opinion. Should balanced journalism be excluded?" -) -# Response: Clarifies that brand policy excludes ALL political content regardless of tone - -# Seller acknowledges and tries different content -response = verification_agent.calibrate_content( - standards_id="nike_brand_safety", - content={"type": "domain", "value": "https://reddit.com/r/running"}, - context_id="calib_xyz" -) -# Response: verdict=pass - now seller understands the boundaries +Calibration supports back-and-forth dialogue using the protocol's conversation management. The seller sends content, the verification agent responds with an evaluation and explanation, and the seller can respond with questions or try different content - all within the same conversation context. + +### A2A Example + +```javascript +// Seller sends content to evaluate +const response1 = await a2a.send({ + message: { + parts: [ + { kind: "text", text: "Does this content pass our standards?" }, + { + kind: "data", + data: { + skill: "calibrate_content", + parameters: { + standards_id: "nike_brand_safety", + content: { type: "domain", value: "https://reddit.com/r/news/politics" } + } + } + } + ] + } +}); +// Response: verdict=fail with explanation + +// Seller disagrees - continue dialogue in same context +const response2 = await a2a.send({ + contextId: response1.contextId, + message: { + parts: [{ + kind: "text", + text: "This is factual news, not opinion. Should balanced journalism be excluded?" + }] + } +}); +// Verification agent clarifies that brand policy excludes ALL political content + +// Seller tries different content +const response3 = await a2a.send({ + contextId: response1.contextId, + message: { + parts: [ + { kind: "text", text: "What about this fitness content?" }, + { + kind: "data", + data: { + skill: "calibrate_content", + parameters: { + standards_id: "nike_brand_safety", + content: { type: "domain", value: "https://reddit.com/r/running" } + } + } + } + ] + } +}); +// Response: verdict=pass - now seller understands the boundaries ``` +### MCP Example + +```javascript +// Initial calibration request +const response1 = await mcp.call('calibrate_content', { + standards_id: "nike_brand_safety", + content: { type: "domain", value: "https://reddit.com/r/news/politics" } +}); +// Response includes context_id for conversation continuity + +// Continue dialogue with follow-up question +const response2 = await mcp.call('calibrate_content', { + context_id: response1.context_id, + standards_id: "nike_brand_safety", + content: { type: "domain", value: "https://reddit.com/r/news/politics" } +}); +// Include text message in the protocol envelope asking about balanced journalism + +// Try different content in same conversation +const response3 = await mcp.call('calibrate_content', { + context_id: response1.context_id, + standards_id: "nike_brand_safety", + content: { type: "domain", value: "https://reddit.com/r/running" } +}); +``` + +The key insight is that the dialogue happens at the **protocol layer**, not the task layer. The verification agent maintains conversation context and can respond to follow-up questions, disagreements, or requests for clarification - just like any agent-to-agent conversation. + ## Calibration vs Runtime | Aspect | calibrate_content | Runtime (local model) | @@ -163,7 +217,7 @@ response = verification_agent.calibrate_content( | **Volume** | Low (setup/periodic) | High (every impression) | | **Response** | Verbose explanations | Pass/fail only | | **Latency** | Seconds acceptable | Milliseconds required | -| **Dialogue** | Supports back-and-forth | Stateless | +| **Dialogue** | Multi-turn conversation | Stateless | ## Related Tasks diff --git a/static/schemas/source/content-standards/calibrate-content-request.json b/static/schemas/source/content-standards/calibrate-content-request.json index b5adaf71b..67e0b5ad8 100644 --- a/static/schemas/source/content-standards/calibrate-content-request.json +++ b/static/schemas/source/content-standards/calibrate-content-request.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/content-standards/calibrate-content-request.json", "title": "Calibrate Content Request", - "description": "Request parameters for collaborative calibration dialogue to align on content standards interpretation", + "description": "Request parameters for evaluating content context during calibration. Multi-turn dialogue is handled at the protocol layer via contextId.", "type": "object", "properties": { "standards_id": { @@ -12,14 +12,6 @@ "content": { "$ref": "/schemas/content-standards/content-context.json", "description": "Content context to evaluate" - }, - "context_id": { - "type": "string", - "description": "Continue an existing calibration dialogue. Omit to start a new dialogue." - }, - "feedback": { - "type": "string", - "description": "Seller's feedback or disagreement with previous response. Use with context_id to continue dialogue." } }, "required": ["standards_id", "content"] diff --git a/static/schemas/source/content-standards/calibrate-content-response.json b/static/schemas/source/content-standards/calibrate-content-response.json index d401068a7..d5555e3ca 100644 --- a/static/schemas/source/content-standards/calibrate-content-response.json +++ b/static/schemas/source/content-standards/calibrate-content-response.json @@ -2,17 +2,13 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/content-standards/calibrate-content-response.json", "title": "Calibrate Content Response", - "description": "Response payload with verdict, detailed explanations, and dialogue support for collaborative calibration", + "description": "Response payload with verdict and detailed explanations for collaborative calibration", "type": "object", "oneOf": [ { "type": "object", "description": "Success response with detailed calibration feedback", "properties": { - "context_id": { - "type": "string", - "description": "Dialogue identifier for follow-up questions" - }, "verdict": { "type": "string", "enum": ["pass", "fail"], @@ -93,7 +89,7 @@ "description": "Field must not be present in success response" } }, - "required": ["context_id", "verdict"] + "required": ["verdict"] }, { "type": "object", From d6576addeb4ba04343647f4f01d6434f032198a7 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 7 Jan 2026 04:59:55 -0800 Subject: [PATCH 12/38] Rename content-context to content with assets array MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses feedback: 1. Rename "content-context" to "content" - too many "context" terms 2. Restructure to use assets array pattern like creative assets Content is now represented as a collection of assets (text, images, video, audio) plus metadata and signals. This aligns with the creative asset model and avoids overloading "context" terminology. Key changes: - content.json replaces content-context.json - assets array with typed blocks (text, image, video, audio) - identifiers object for platform-specific IDs (apple_podcast_id, etc.) - url is now the primary required field - Updated all docs and schema references πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/content-standards/index.mdx | 52 +-- .../tasks/calibrate_content.mdx | 29 +- .../calibrate-content-request.json | 6 +- .../content-standards/content-context.json | 317 ------------------ .../source/content-standards/content.json | 264 +++++++++++++++ .../get-content-standards-response.json | 8 +- static/schemas/source/index.json | 16 +- 7 files changed, 324 insertions(+), 368 deletions(-) delete mode 100644 static/schemas/source/content-standards/content-context.json create mode 100644 static/schemas/source/content-standards/content.json diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index 32c4b9868..5e6933b37 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -46,52 +46,58 @@ Content Standards uses **natural language prompts** rather than rigid keyword li "calibration": { "acceptable": [ { - "type": "domain", - "value": "https://espn.com/nba/story/_/championship-recap", + "url": "https://espn.com/nba/story/_/championship-recap", "language": "en", "title": "Championship Game Recap", "signals": [ {"key": "iab_category", "value": "sports/basketball", "confidence": 1.0} ], - "text_content": [ - {"type": "heading", "heading": {"level": 1, "text": "Lakers Defeat Celtics in Thrilling Overtime"}}, - {"type": "paragraph", "paragraphs": ["In an exciting conclusion to the championship series..."]} + "assets": [ + {"type": "text", "role": "heading", "content": "Lakers Defeat Celtics in Thrilling Overtime", "heading_level": 1}, + {"type": "text", "role": "paragraph", "content": "In an exciting conclusion to the championship series..."}, + {"type": "image", "url": "https://cdn.espn.com/game-photo.jpg", "alt_text": "Championship celebration"} ] }, { - "type": "domain", - "value": "healthline.com", + "url": "https://healthline.com/fitness", "language": "en", "signals": [{"key": "iab_category", "value": "health/fitness", "confidence": 0.95}] }, - {"type": "apple_podcast_id", "value": "1234567890", "language": "en"}, - {"type": "text", "value": "Championship game recap: Lakers defeat Celtics in thrilling overtime finish", "language": "en"}, - {"type": "image_url", "value": "https://cdn.example.com/sports/basketball-game.jpg"}, - {"type": "video_url", "value": "https://cdn.example.com/fitness/workout-tutorial.mp4"} + { + "url": "https://podcasts.apple.com/podcast/id1234567890", + "identifiers": {"apple_podcast_id": "1234567890"} + }, + { + "url": "https://cdn.example.com/fitness/workout-tutorial.mp4", + "assets": [{"type": "video", "url": "https://cdn.example.com/fitness/workout-tutorial.mp4"}] + } ], "unacceptable": [ { - "type": "domain", - "value": "tabloid.example.com", + "url": "https://tabloid.example.com/scandal", "language": "en", "signals": [{"key": "brand_safety", "value": "controversial", "confidence": 0.85}] }, - {"type": "rss_url", "value": "https://controversy.example.com/feed.xml", "language": "en"}, - {"type": "text", "value": "Political scandal rocks the nation as allegations surface", "language": "en"}, - {"type": "audio_url", "value": "https://cdn.example.com/news/controversial-podcast.mp3"} + { + "url": "https://controversy.example.com", + "identifiers": {"rss_url": "https://controversy.example.com/feed.xml"} + }, + { + "url": "https://cdn.example.com/news/controversial-podcast.mp3", + "assets": [{"type": "audio", "url": "https://cdn.example.com/news/controversial-podcast.mp3"}] + } ] } } ``` -The policy prompt enables AI-powered verification agents to understand context and nuance. **Calibration** examples provide a training/test set that helps the agent interpret the policy correctly. Content contexts can include: +The policy prompt enables AI-powered verification agents to understand context and nuance. **Calibration** examples provide a training/test set that helps the agent interpret the policy correctly. Content is represented as a collection of assets with metadata: -- **Domains** - Website identifiers with optional structured content -- **Podcast/RSS** - Audio content identifiers -- **Text snippets** - Sample headlines or content for NLP evaluation -- **Media URLs** - Links to images, audio, or video for multimodal evaluation -- **Signals** - Pre-computed classifications (IAB category, ratings, scores) with confidence levels -- **Structured content** - Headings, paragraphs, embedded media in document order +- **url** - Primary URL for this content (web page, podcast, video page) +- **assets** - Content assets in document order (text blocks, images, video, audio) +- **signals** - Pre-computed classifications (IAB category, ratings, scores) with confidence levels +- **identifiers** - Platform-specific IDs (apple_podcast_id, spotify_show_id, youtube_video_id) +- **metadata** - Rich metadata (Open Graph, JSON-LD, author info) ## Scoped Standards diff --git a/docs/governance/content-standards/tasks/calibrate_content.mdx b/docs/governance/content-standards/tasks/calibrate_content.mdx index 225acc1fd..833a0cf03 100644 --- a/docs/governance/content-standards/tasks/calibrate_content.mdx +++ b/docs/governance/content-standards/tasks/calibrate_content.mdx @@ -23,24 +23,27 @@ Unlike high-volume runtime evaluation, calibration is a **dialogue-based process | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `standards_id` | string | Yes | Standards configuration to calibrate against | -| `content` | content-context | Yes | Content context to evaluate | +| `content` | content | Yes | Content to evaluate | -### Content Context +### Content -**Schema**: [content-context.json](https://adcontextprotocol.org/schemas/v2/content-standards/content-context.json) +**Schema**: [content.json](https://adcontextprotocol.org/schemas/v2/content-standards/content.json) + +Content is represented as a collection of assets (text, images, video, audio) plus metadata and signals: ```json { - "type": "domain", - "value": "https://reddit.com/r/fitness/comments/abc123", + "url": "https://reddit.com/r/fitness/comments/abc123", "language": "en", "title": "Best protein sources for muscle building", "signals": [ {"key": "iab_category", "value": "health/fitness", "confidence": 0.92} ], - "text_content": [ - {"type": "heading", "heading": {"level": 1, "text": "Best protein sources for muscle building"}}, - {"type": "paragraph", "paragraphs": ["Looking for recommendations on high-quality protein sources...", "I've been lifting for 6 months and want to optimize my diet."]} + "assets": [ + {"type": "text", "role": "heading", "content": "Best protein sources for muscle building", "heading_level": 1}, + {"type": "text", "role": "paragraph", "content": "Looking for recommendations on high-quality protein sources..."}, + {"type": "text", "role": "paragraph", "content": "I've been lifting for 6 months and want to optimize my diet."}, + {"type": "image", "url": "https://cdn.example.com/fitness-image.jpg", "alt_text": "Person lifting weights"} ] } ``` @@ -138,7 +141,7 @@ const response1 = await a2a.send({ skill: "calibrate_content", parameters: { standards_id: "nike_brand_safety", - content: { type: "domain", value: "https://reddit.com/r/news/politics" } + content: { url: "https://reddit.com/r/news/politics", title: "Political News Article" } } } } @@ -171,7 +174,7 @@ const response3 = await a2a.send({ skill: "calibrate_content", parameters: { standards_id: "nike_brand_safety", - content: { type: "domain", value: "https://reddit.com/r/running" } + content: { url: "https://reddit.com/r/running", title: "Running Tips" } } } } @@ -187,7 +190,7 @@ const response3 = await a2a.send({ // Initial calibration request const response1 = await mcp.call('calibrate_content', { standards_id: "nike_brand_safety", - content: { type: "domain", value: "https://reddit.com/r/news/politics" } + content: { url: "https://reddit.com/r/news/politics", title: "Political News Article" } }); // Response includes context_id for conversation continuity @@ -195,7 +198,7 @@ const response1 = await mcp.call('calibrate_content', { const response2 = await mcp.call('calibrate_content', { context_id: response1.context_id, standards_id: "nike_brand_safety", - content: { type: "domain", value: "https://reddit.com/r/news/politics" } + content: { url: "https://reddit.com/r/news/politics", title: "Political News Article" } }); // Include text message in the protocol envelope asking about balanced journalism @@ -203,7 +206,7 @@ const response2 = await mcp.call('calibrate_content', { const response3 = await mcp.call('calibrate_content', { context_id: response1.context_id, standards_id: "nike_brand_safety", - content: { type: "domain", value: "https://reddit.com/r/running" } + content: { url: "https://reddit.com/r/running", title: "Running Tips" } }); ``` diff --git a/static/schemas/source/content-standards/calibrate-content-request.json b/static/schemas/source/content-standards/calibrate-content-request.json index 67e0b5ad8..a0c1984c7 100644 --- a/static/schemas/source/content-standards/calibrate-content-request.json +++ b/static/schemas/source/content-standards/calibrate-content-request.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/content-standards/calibrate-content-request.json", "title": "Calibrate Content Request", - "description": "Request parameters for evaluating content context during calibration. Multi-turn dialogue is handled at the protocol layer via contextId.", + "description": "Request parameters for evaluating content during calibration. Multi-turn dialogue is handled at the protocol layer via contextId.", "type": "object", "properties": { "standards_id": { @@ -10,8 +10,8 @@ "description": "Standards configuration to calibrate against" }, "content": { - "$ref": "/schemas/content-standards/content-context.json", - "description": "Content context to evaluate" + "$ref": "/schemas/content-standards/content.json", + "description": "Content to evaluate" } }, "required": ["standards_id", "content"] diff --git a/static/schemas/source/content-standards/content-context.json b/static/schemas/source/content-standards/content-context.json deleted file mode 100644 index 73b35752e..000000000 --- a/static/schemas/source/content-standards/content-context.json +++ /dev/null @@ -1,317 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "/schemas/content-standards/content-context.json", - "title": "Content Context", - "description": "A content context for evaluation or calibration. Supports multiple content types: domains, text snippets, media URLs, and podcast identifiers.", - "type": "object", - "properties": { - "artifact_id": { - "type": "string", - "description": "Unique identifier for this artifact" - }, - "type": { - "type": "string", - "enum": [ - "domain", - "text", - "image_url", - "video_url", - "audio_url", - "rss_url", - "apple_podcast_id", - "spotify_show_id", - "podcast_guid" - ], - "description": "Type of content context" - }, - "value": { - "type": "string", - "description": "The content value - domain name, text snippet, media URL, or podcast identifier" - }, - "language": { - "type": "string", - "description": "BCP 47 language tag (e.g., 'en', 'en-US', 'es-MX')" - }, - "title": { - "type": "string", - "description": "Content title or headline" - }, - "description": { - "type": "string", - "description": "Content description or summary" - }, - "published_time": { - "type": "string", - "format": "date-time", - "description": "When the content was published (ISO 8601 format)" - }, - "last_update_time": { - "type": "string", - "format": "date-time", - "description": "When the content was last modified (ISO 8601 format)" - }, - "signals": { - "type": "array", - "description": "Pre-computed signals including categories, classifiers, and ratings", - "items": { - "type": "object", - "properties": { - "key": { - "type": "string", - "description": "Signal identifier (e.g., 'iab_category', 'violence_score', 'mpaa_rating')" - }, - "value": { - "description": "Signal value - can be string, number, or boolean", - "oneOf": [ - { "type": "string" }, - { "type": "number" }, - { "type": "boolean" } - ] - }, - "confidence": { - "type": "number", - "minimum": 0, - "maximum": 1, - "description": "Confidence score for probabilistic signals (0-1)" - }, - "version": { - "type": "string", - "description": "Version of the classifier or model that generated this signal" - }, - "source": { - "type": "string", - "description": "Who generated this signal (e.g., 'publisher_classifier', 'human_moderator')" - }, - "reasons": { - "type": "array", - "items": { "type": "string" }, - "description": "Array of reason codes for ratings or classifications" - } - }, - "required": ["key", "value"], - "additionalProperties": false - } - }, - "text_content": { - "type": "array", - "description": "Structured content blocks in document flow order", - "items": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["paragraph", "image", "heading", "video", "audio"] - } - }, - "required": ["type"], - "oneOf": [ - { - "properties": { - "type": { "const": "paragraph" }, - "paragraphs": { - "type": "array", - "items": { "type": "string" }, - "description": "Array of paragraph text content" - } - }, - "required": ["type", "paragraphs"] - }, - { - "properties": { - "type": { "const": "heading" }, - "heading": { - "type": "object", - "properties": { - "level": { - "type": "integer", - "minimum": 1, - "maximum": 6, - "description": "Heading level (1-6)" - }, - "text": { - "type": "string", - "description": "Heading text" - } - }, - "required": ["level", "text"], - "additionalProperties": false - } - }, - "required": ["type", "heading"] - }, - { - "properties": { - "type": { "const": "image" }, - "image": { - "type": "object", - "properties": { - "url": { - "type": "string", - "format": "uri", - "description": "Image URL" - }, - "alt_text": { - "type": "string", - "description": "Alt text or image description" - }, - "caption": { - "type": "string", - "description": "Image caption" - }, - "width": { - "type": "string", - "description": "Image width in pixels (as string)" - }, - "height": { - "type": "string", - "description": "Image height in pixels (as string)" - }, - "type": { - "type": "string", - "enum": ["content", "advertisement", "logo", "thumbnail"], - "description": "Type of image" - }, - "filepath": { - "type": "string", - "description": "Local filepath if image was downloaded" - } - }, - "required": ["url"], - "additionalProperties": false - } - }, - "required": ["type", "image"] - }, - { - "properties": { - "type": { "const": "video" }, - "video": { - "type": "object", - "properties": { - "url": { - "type": "string", - "format": "uri", - "description": "Video URL" - }, - "duration_seconds": { - "type": "number", - "description": "Video duration in seconds" - }, - "transcript": { - "type": "string", - "description": "Video transcript" - }, - "transcript_source": { - "type": "string", - "enum": [ - "original_script", - "subtitles", - "closed_captions", - "dub", - "generated" - ], - "description": "How the transcript was generated" - }, - "thumbnail_url": { - "type": "string", - "format": "uri", - "description": "Video thumbnail URL" - } - }, - "required": ["url"], - "additionalProperties": false - } - }, - "required": ["type", "video"] - }, - { - "properties": { - "type": { "const": "audio" }, - "audio": { - "type": "object", - "properties": { - "url": { - "type": "string", - "format": "uri", - "description": "Audio URL" - }, - "duration_seconds": { - "type": "number", - "description": "Audio duration in seconds" - }, - "transcript": { - "type": "string", - "description": "Audio transcript" - }, - "transcript_source": { - "type": "string", - "enum": ["original_script", "closed_captions", "generated"], - "description": "How the transcript was generated" - } - }, - "required": ["url"], - "additionalProperties": false - } - }, - "required": ["type", "audio"] - } - ] - } - }, - "metadata": { - "type": "object", - "description": "Rich metadata extracted from the content", - "properties": { - "title": { - "type": "string", - "description": "Content title from metadata" - }, - "description": { - "type": "string", - "description": "Content description from metadata" - }, - "keywords": { - "type": ["string", "null"], - "description": "Content keywords" - }, - "author": { - "type": ["string", "null"], - "description": "Content author name" - }, - "canonical": { - "type": "string", - "format": "uri", - "description": "Canonical URL" - }, - "metaTags": { - "type": "object", - "description": "All meta tags", - "additionalProperties": { "type": "string" } - }, - "open_graph": { - "type": "object", - "description": "Open Graph protocol metadata", - "additionalProperties": true - }, - "twitter_card": { - "type": "object", - "description": "Twitter Card metadata", - "additionalProperties": true - }, - "json_ld": { - "type": "array", - "description": "JSON-LD structured data (schema.org)", - "items": { "type": "array" } - }, - "microdata": { - "type": "array", - "description": "Microdata structured markup", - "items": {} - } - }, - "additionalProperties": true - } - }, - "required": ["type", "value"], - "additionalProperties": true -} diff --git a/static/schemas/source/content-standards/content.json b/static/schemas/source/content-standards/content.json new file mode 100644 index 000000000..716ea7e6c --- /dev/null +++ b/static/schemas/source/content-standards/content.json @@ -0,0 +1,264 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/content-standards/content.json", + "title": "Content", + "description": "Content for safety and suitability evaluation. Content is represented as a collection of assets (text, images, video, audio) plus metadata and signals.", + "type": "object", + "properties": { + "content_id": { + "type": "string", + "description": "Unique identifier for this content" + }, + "url": { + "type": "string", + "format": "uri", + "description": "Primary URL for this content (web page, podcast feed, video page, etc.)" + }, + "language": { + "type": "string", + "description": "BCP 47 language tag (e.g., 'en', 'en-US', 'es-MX')" + }, + "title": { + "type": "string", + "description": "Content title or headline" + }, + "description": { + "type": "string", + "description": "Content description or summary" + }, + "published_time": { + "type": "string", + "format": "date-time", + "description": "When the content was published (ISO 8601 format)" + }, + "last_update_time": { + "type": "string", + "format": "date-time", + "description": "When the content was last modified (ISO 8601 format)" + }, + "assets": { + "type": "array", + "description": "Content assets in document flow order - text blocks, images, video, audio", + "items": { + "oneOf": [ + { + "type": "object", + "description": "Text block (paragraph, heading, etc.)", + "properties": { + "type": { "type": "string", "const": "text" }, + "role": { + "type": "string", + "enum": ["paragraph", "heading", "caption", "quote", "list_item"], + "description": "Role of this text in the document" + }, + "content": { + "type": "string", + "description": "Text content" + }, + "heading_level": { + "type": "integer", + "minimum": 1, + "maximum": 6, + "description": "Heading level (1-6), only for role=heading" + } + }, + "required": ["type", "content"] + }, + { + "type": "object", + "description": "Image asset", + "properties": { + "type": { "type": "string", "const": "image" }, + "url": { + "type": "string", + "format": "uri", + "description": "Image URL" + }, + "alt_text": { + "type": "string", + "description": "Alt text or image description" + }, + "caption": { + "type": "string", + "description": "Image caption" + }, + "width": { + "type": "integer", + "description": "Image width in pixels" + }, + "height": { + "type": "integer", + "description": "Image height in pixels" + } + }, + "required": ["type", "url"] + }, + { + "type": "object", + "description": "Video asset", + "properties": { + "type": { "type": "string", "const": "video" }, + "url": { + "type": "string", + "format": "uri", + "description": "Video URL" + }, + "duration_ms": { + "type": "integer", + "description": "Video duration in milliseconds" + }, + "transcript": { + "type": "string", + "description": "Video transcript" + }, + "transcript_source": { + "type": "string", + "enum": ["original_script", "subtitles", "closed_captions", "dub", "generated"], + "description": "How the transcript was generated" + }, + "thumbnail_url": { + "type": "string", + "format": "uri", + "description": "Video thumbnail URL" + } + }, + "required": ["type", "url"] + }, + { + "type": "object", + "description": "Audio asset", + "properties": { + "type": { "type": "string", "const": "audio" }, + "url": { + "type": "string", + "format": "uri", + "description": "Audio URL" + }, + "duration_ms": { + "type": "integer", + "description": "Audio duration in milliseconds" + }, + "transcript": { + "type": "string", + "description": "Audio transcript" + }, + "transcript_source": { + "type": "string", + "enum": ["original_script", "closed_captions", "generated"], + "description": "How the transcript was generated" + } + }, + "required": ["type", "url"] + } + ] + } + }, + "signals": { + "type": "array", + "description": "Pre-computed signals including categories, classifiers, and ratings", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Signal identifier (e.g., 'iab_category', 'violence_score', 'mpaa_rating')" + }, + "value": { + "description": "Signal value - can be string, number, or boolean", + "oneOf": [ + { "type": "string" }, + { "type": "number" }, + { "type": "boolean" } + ] + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Confidence score for probabilistic signals (0-1)" + }, + "version": { + "type": "string", + "description": "Version of the classifier or model that generated this signal" + }, + "source": { + "type": "string", + "description": "Who generated this signal (e.g., 'publisher_classifier', 'human_moderator')" + }, + "reasons": { + "type": "array", + "items": { "type": "string" }, + "description": "Array of reason codes for ratings or classifications" + } + }, + "required": ["key", "value"], + "additionalProperties": false + } + }, + "metadata": { + "type": "object", + "description": "Rich metadata extracted from the content", + "properties": { + "canonical": { + "type": "string", + "format": "uri", + "description": "Canonical URL" + }, + "author": { + "type": "string", + "description": "Content author name" + }, + "keywords": { + "type": "string", + "description": "Content keywords" + }, + "open_graph": { + "type": "object", + "description": "Open Graph protocol metadata", + "additionalProperties": true + }, + "twitter_card": { + "type": "object", + "description": "Twitter Card metadata", + "additionalProperties": true + }, + "json_ld": { + "type": "array", + "description": "JSON-LD structured data (schema.org)", + "items": { "type": "object" } + } + }, + "additionalProperties": true + }, + "identifiers": { + "type": "object", + "description": "Platform-specific identifiers for this content", + "properties": { + "apple_podcast_id": { + "type": "string", + "description": "Apple Podcasts ID" + }, + "spotify_show_id": { + "type": "string", + "description": "Spotify show ID" + }, + "podcast_guid": { + "type": "string", + "description": "Podcast GUID (from RSS feed)" + }, + "youtube_video_id": { + "type": "string", + "description": "YouTube video ID" + }, + "rss_url": { + "type": "string", + "format": "uri", + "description": "RSS feed URL" + } + }, + "additionalProperties": true + } + }, + "required": ["url"], + "additionalProperties": true +} diff --git a/static/schemas/source/content-standards/get-content-standards-response.json b/static/schemas/source/content-standards/get-content-standards-response.json index 539a599ac..6560ac753 100644 --- a/static/schemas/source/content-standards/get-content-standards-response.json +++ b/static/schemas/source/content-standards/get-content-standards-response.json @@ -51,13 +51,13 @@ "properties": { "acceptable": { "type": "array", - "items": { "$ref": "/schemas/content-standards/content-context.json" }, - "description": "Content contexts that are acceptable" + "items": { "$ref": "/schemas/content-standards/content.json" }, + "description": "Content that is acceptable" }, "unacceptable": { "type": "array", - "items": { "$ref": "/schemas/content-standards/content-context.json" }, - "description": "Content contexts that are unacceptable" + "items": { "$ref": "/schemas/content-standards/content.json" }, + "description": "Content that is unacceptable" } } }, diff --git a/static/schemas/source/index.json b/static/schemas/source/index.json index fcd052c74..144d921a2 100644 --- a/static/schemas/source/index.json +++ b/static/schemas/source/index.json @@ -565,9 +565,9 @@ "content-standards": { "description": "Content Standards protocol task request/response schemas for content safety and suitability evaluation", "models": { - "content-context": { - "$ref": "/schemas/content-standards/content-context.json", - "description": "Content context for evaluation or calibration - supports domains, text, media URLs, and podcast identifiers" + "content": { + "$ref": "/schemas/content-standards/content.json", + "description": "Content for evaluation or calibration - a collection of assets (text, images, video, audio) plus metadata and signals" } }, "tasks": { @@ -591,14 +591,14 @@ "description": "Response payload with content safety policies" } }, - "check-content": { + "calibrate-content": { "request": { - "$ref": "/schemas/content-standards/check-content-request.json", - "description": "Request parameters for evaluating content context against safety policies" + "$ref": "/schemas/content-standards/calibrate-content-request.json", + "description": "Request parameters for collaborative calibration dialogue" }, "response": { - "$ref": "/schemas/content-standards/check-content-response.json", - "description": "Response payload with per-feature evaluation results" + "$ref": "/schemas/content-standards/calibrate-content-response.json", + "description": "Response payload with detailed explanations for policy alignment" } }, "validate-content-delivery": { From f12b35c4a80adffec84534430f142252604fd0ec Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 7 Jan 2026 06:30:57 -0800 Subject: [PATCH 13/38] Rename content to artifact with property_id + artifact_id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Content adjacent to ads is now modeled as an "artifact" - identified by property_id (using existing identifier types) plus artifact_id (string defined by property owner). This avoids overloading "content" and enables identification of artifacts that don't have URLs (Instagram, podcasts, TV scenes). Changes: - Rename content.json to artifact.json with new required fields - property_id uses existing identifier type schema (type + value) - artifact_id is a string - property owner defines the scheme - format_id optional - can reference format registry (like creative formats) - url now optional since not all artifacts have URLs - Update all schema references and documentation examples πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/content-standards/index.mdx | 36 +++++----- .../tasks/calibrate_content.mdx | 51 ++++++++++----- .../tasks/validate_content_delivery.mdx | 10 ++- .../{content.json => artifact.json} | 65 ++++++++++++++----- .../calibrate-content-request.json | 8 +-- .../get-content-standards-response.json | 8 +-- .../validate-content-delivery-request.json | 16 ++--- static/schemas/source/index.json | 6 +- 8 files changed, 128 insertions(+), 72 deletions(-) rename static/schemas/source/content-standards/{content.json => artifact.json} (75%) diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index 5e6933b37..32934c473 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -46,7 +46,8 @@ Content Standards uses **natural language prompts** rather than rigid keyword li "calibration": { "acceptable": [ { - "url": "https://espn.com/nba/story/_/championship-recap", + "property_id": {"type": "domain", "value": "espn.com"}, + "artifact_id": "nba_championship_recap_2024", "language": "en", "title": "Championship Game Recap", "signals": [ @@ -59,44 +60,49 @@ Content Standards uses **natural language prompts** rather than rigid keyword li ] }, { - "url": "https://healthline.com/fitness", + "property_id": {"type": "domain", "value": "healthline.com"}, + "artifact_id": "fitness_landing", "language": "en", "signals": [{"key": "iab_category", "value": "health/fitness", "confidence": 0.95}] }, { - "url": "https://podcasts.apple.com/podcast/id1234567890", - "identifiers": {"apple_podcast_id": "1234567890"} + "property_id": {"type": "apple_podcast_id", "value": "1234567890"}, + "artifact_id": "episode_42_segment_2" }, { - "url": "https://cdn.example.com/fitness/workout-tutorial.mp4", - "assets": [{"type": "video", "url": "https://cdn.example.com/fitness/workout-tutorial.mp4"}] + "property_id": {"type": "domain", "value": "fitnesschannel.tv"}, + "artifact_id": "workout_tutorial_ep15", + "assets": [{"type": "video", "url": "https://cdn.fitnesschannel.tv/videos/ep15.mp4"}] } ], "unacceptable": [ { - "url": "https://tabloid.example.com/scandal", + "property_id": {"type": "domain", "value": "tabloid.example.com"}, + "artifact_id": "scandal_story_123", "language": "en", "signals": [{"key": "brand_safety", "value": "controversial", "confidence": 0.85}] }, { - "url": "https://controversy.example.com", - "identifiers": {"rss_url": "https://controversy.example.com/feed.xml"} + "property_id": {"type": "rss_url", "value": "https://controversy.example.com/feed.xml"}, + "artifact_id": "episode_latest" }, { - "url": "https://cdn.example.com/news/controversial-podcast.mp3", - "assets": [{"type": "audio", "url": "https://cdn.example.com/news/controversial-podcast.mp3"}] + "property_id": {"type": "domain", "value": "news.example.com"}, + "artifact_id": "controversial_podcast_ep42", + "assets": [{"type": "audio", "url": "https://cdn.news.example.com/podcasts/ep42.mp3"}] } ] } } ``` -The policy prompt enables AI-powered verification agents to understand context and nuance. **Calibration** examples provide a training/test set that helps the agent interpret the policy correctly. Content is represented as a collection of assets with metadata: +The policy prompt enables AI-powered verification agents to understand context and nuance. **Calibration** examples provide a training/test set that helps the agent interpret the policy correctly. Artifacts are identified by property + artifact_id and represented as collections of assets: -- **url** - Primary URL for this content (web page, podcast, video page) -- **assets** - Content assets in document order (text blocks, images, video, audio) +- **property_id** - Identifies the property where this artifact appears (using standard identifier types) +- **artifact_id** - Unique identifier within the property (property owner defines the scheme) +- **format_id** - Optional reference to format registry (same as creative formats) +- **assets** - Artifact assets in document order (text blocks, images, video, audio) - **signals** - Pre-computed classifications (IAB category, ratings, scores) with confidence levels -- **identifiers** - Platform-specific IDs (apple_podcast_id, spotify_show_id, youtube_video_id) - **metadata** - Rich metadata (Open Graph, JSON-LD, author info) ## Scoped Standards diff --git a/docs/governance/content-standards/tasks/calibrate_content.mdx b/docs/governance/content-standards/tasks/calibrate_content.mdx index 833a0cf03..cbdeb4b4d 100644 --- a/docs/governance/content-standards/tasks/calibrate_content.mdx +++ b/docs/governance/content-standards/tasks/calibrate_content.mdx @@ -23,17 +23,18 @@ Unlike high-volume runtime evaluation, calibration is a **dialogue-based process | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `standards_id` | string | Yes | Standards configuration to calibrate against | -| `content` | content | Yes | Content to evaluate | +| `artifact` | artifact | Yes | Artifact to evaluate | -### Content +### Artifact -**Schema**: [content.json](https://adcontextprotocol.org/schemas/v2/content-standards/content.json) +**Schema**: [artifact.json](https://adcontextprotocol.org/schemas/v2/content-standards/artifact.json) -Content is represented as a collection of assets (text, images, video, audio) plus metadata and signals: +An artifact represents content adjacent to an ad placement - identified by `property_id` + `artifact_id` and represented as a collection of assets: ```json { - "url": "https://reddit.com/r/fitness/comments/abc123", + "property_id": {"type": "domain", "value": "reddit.com"}, + "artifact_id": "r_fitness_abc123", "language": "en", "title": "Best protein sources for muscle building", "signals": [ @@ -43,7 +44,7 @@ Content is represented as a collection of assets (text, images, video, audio) pl {"type": "text", "role": "heading", "content": "Best protein sources for muscle building", "heading_level": 1}, {"type": "text", "role": "paragraph", "content": "Looking for recommendations on high-quality protein sources..."}, {"type": "text", "role": "paragraph", "content": "I've been lifting for 6 months and want to optimize my diet."}, - {"type": "image", "url": "https://cdn.example.com/fitness-image.jpg", "alt_text": "Person lifting weights"} + {"type": "image", "url": "https://cdn.reddit.com/fitness-image.jpg", "alt_text": "Person lifting weights"} ] } ``` @@ -130,18 +131,22 @@ Calibration supports back-and-forth dialogue using the protocol's conversation m ### A2A Example ```javascript -// Seller sends content to evaluate +// Seller sends artifact to evaluate const response1 = await a2a.send({ message: { parts: [ - { kind: "text", text: "Does this content pass our standards?" }, + { kind: "text", text: "Does this artifact pass our standards?" }, { kind: "data", data: { skill: "calibrate_content", parameters: { standards_id: "nike_brand_safety", - content: { url: "https://reddit.com/r/news/politics", title: "Political News Article" } + artifact: { + property_id: { type: "domain", value: "reddit.com" }, + artifact_id: "r_news_politics_123", + title: "Political News Article" + } } } } @@ -162,7 +167,7 @@ const response2 = await a2a.send({ }); // Verification agent clarifies that brand policy excludes ALL political content -// Seller tries different content +// Seller tries different artifact const response3 = await a2a.send({ contextId: response1.contextId, message: { @@ -174,7 +179,11 @@ const response3 = await a2a.send({ skill: "calibrate_content", parameters: { standards_id: "nike_brand_safety", - content: { url: "https://reddit.com/r/running", title: "Running Tips" } + artifact: { + property_id: { type: "domain", value: "reddit.com" }, + artifact_id: "r_running_tips_456", + title: "Running Tips" + } } } } @@ -190,7 +199,11 @@ const response3 = await a2a.send({ // Initial calibration request const response1 = await mcp.call('calibrate_content', { standards_id: "nike_brand_safety", - content: { url: "https://reddit.com/r/news/politics", title: "Political News Article" } + artifact: { + property_id: { type: "domain", value: "reddit.com" }, + artifact_id: "r_news_politics_123", + title: "Political News Article" + } }); // Response includes context_id for conversation continuity @@ -198,15 +211,23 @@ const response1 = await mcp.call('calibrate_content', { const response2 = await mcp.call('calibrate_content', { context_id: response1.context_id, standards_id: "nike_brand_safety", - content: { url: "https://reddit.com/r/news/politics", title: "Political News Article" } + artifact: { + property_id: { type: "domain", value: "reddit.com" }, + artifact_id: "r_news_politics_123", + title: "Political News Article" + } }); // Include text message in the protocol envelope asking about balanced journalism -// Try different content in same conversation +// Try different artifact in same conversation const response3 = await mcp.call('calibrate_content', { context_id: response1.context_id, standards_id: "nike_brand_safety", - content: { url: "https://reddit.com/r/running", title: "Running Tips" } + artifact: { + property_id: { type: "domain", value: "reddit.com" }, + artifact_id: "r_running_tips_456", + title: "Running Tips" + } }); ``` diff --git a/docs/governance/content-standards/tasks/validate_content_delivery.mdx b/docs/governance/content-standards/tasks/validate_content_delivery.mdx index 54bae3a14..2c9942fcf 100644 --- a/docs/governance/content-standards/tasks/validate_content_delivery.mdx +++ b/docs/governance/content-standards/tasks/validate_content_delivery.mdx @@ -26,10 +26,14 @@ Validate delivery records against content safety policies. Designed for batch au { "record_id": "imp_12345", "timestamp": "2025-01-15T10:30:00Z", - "content": { - "url": "https://example.com/article", + "artifact": { + "property_id": {"type": "domain", "value": "example.com"}, + "artifact_id": "article_12345", "title": "Article Title", - "categories": ["news", "sports"] + "signals": [ + {"key": "iab_category", "value": "news", "confidence": 0.9}, + {"key": "iab_category", "value": "sports", "confidence": 0.85} + ] }, "creative_id": "creative_abc" } diff --git a/static/schemas/source/content-standards/content.json b/static/schemas/source/content-standards/artifact.json similarity index 75% rename from static/schemas/source/content-standards/content.json rename to static/schemas/source/content-standards/artifact.json index 716ea7e6c..245c468ce 100644 --- a/static/schemas/source/content-standards/content.json +++ b/static/schemas/source/content-standards/artifact.json @@ -1,18 +1,51 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "/schemas/content-standards/content.json", - "title": "Content", - "description": "Content for safety and suitability evaluation. Content is represented as a collection of assets (text, images, video, audio) plus metadata and signals.", + "$id": "/schemas/content-standards/artifact.json", + "title": "Artifact", + "description": "Content artifact for safety and suitability evaluation. An artifact represents content adjacent to an ad placement - a news article, podcast segment, video chapter, or social post. Artifacts are collections of assets (text, images, video, audio) plus metadata and signals.", "type": "object", "properties": { - "content_id": { + "property_id": { + "type": "object", + "description": "Identifier for the property where this artifact appears", + "properties": { + "type": { + "$ref": "/schemas/enums/identifier-types.json", + "description": "Type of property identifier" + }, + "value": { + "type": "string", + "description": "The identifier value" + } + }, + "required": ["type", "value"], + "additionalProperties": false + }, + "artifact_id": { "type": "string", - "description": "Unique identifier for this content" + "description": "Identifier for this artifact within the property. The property owner defines the scheme (e.g., 'article_12345', 'episode_42_segment_3', 'post_abc123')." + }, + "format_id": { + "type": "object", + "description": "Optional reference to a format definition. Uses the same format registry as creative formats.", + "properties": { + "agent_url": { + "type": "string", + "format": "uri", + "description": "Base URL of the agent that defines this format" + }, + "id": { + "type": "string", + "description": "Format identifier within that agent's registry" + } + }, + "required": ["agent_url", "id"], + "additionalProperties": false }, "url": { "type": "string", "format": "uri", - "description": "Primary URL for this content (web page, podcast feed, video page, etc.)" + "description": "Optional URL for this artifact (web page, podcast feed, video page). Not all artifacts have URLs (e.g., Instagram content, podcast segments, TV scenes)." }, "language": { "type": "string", @@ -20,25 +53,25 @@ }, "title": { "type": "string", - "description": "Content title or headline" + "description": "Artifact title or headline" }, "description": { "type": "string", - "description": "Content description or summary" + "description": "Artifact description or summary" }, "published_time": { "type": "string", "format": "date-time", - "description": "When the content was published (ISO 8601 format)" + "description": "When the artifact was published (ISO 8601 format)" }, "last_update_time": { "type": "string", "format": "date-time", - "description": "When the content was last modified (ISO 8601 format)" + "description": "When the artifact was last modified (ISO 8601 format)" }, "assets": { "type": "array", - "description": "Content assets in document flow order - text blocks, images, video, audio", + "description": "Artifact assets in document flow order - text blocks, images, video, audio", "items": { "oneOf": [ { @@ -197,7 +230,7 @@ }, "metadata": { "type": "object", - "description": "Rich metadata extracted from the content", + "description": "Rich metadata extracted from the artifact", "properties": { "canonical": { "type": "string", @@ -206,11 +239,11 @@ }, "author": { "type": "string", - "description": "Content author name" + "description": "Artifact author name" }, "keywords": { "type": "string", - "description": "Content keywords" + "description": "Artifact keywords" }, "open_graph": { "type": "object", @@ -232,7 +265,7 @@ }, "identifiers": { "type": "object", - "description": "Platform-specific identifiers for this content", + "description": "Platform-specific identifiers for this artifact", "properties": { "apple_podcast_id": { "type": "string", @@ -259,6 +292,6 @@ "additionalProperties": true } }, - "required": ["url"], + "required": ["property_id", "artifact_id"], "additionalProperties": true } diff --git a/static/schemas/source/content-standards/calibrate-content-request.json b/static/schemas/source/content-standards/calibrate-content-request.json index a0c1984c7..b259e500b 100644 --- a/static/schemas/source/content-standards/calibrate-content-request.json +++ b/static/schemas/source/content-standards/calibrate-content-request.json @@ -9,10 +9,10 @@ "type": "string", "description": "Standards configuration to calibrate against" }, - "content": { - "$ref": "/schemas/content-standards/content.json", - "description": "Content to evaluate" + "artifact": { + "$ref": "/schemas/content-standards/artifact.json", + "description": "Artifact to evaluate" } }, - "required": ["standards_id", "content"] + "required": ["standards_id", "artifact"] } diff --git a/static/schemas/source/content-standards/get-content-standards-response.json b/static/schemas/source/content-standards/get-content-standards-response.json index 6560ac753..814d92906 100644 --- a/static/schemas/source/content-standards/get-content-standards-response.json +++ b/static/schemas/source/content-standards/get-content-standards-response.json @@ -51,13 +51,13 @@ "properties": { "acceptable": { "type": "array", - "items": { "$ref": "/schemas/content-standards/content.json" }, - "description": "Content that is acceptable" + "items": { "$ref": "/schemas/content-standards/artifact.json" }, + "description": "Artifacts that are acceptable" }, "unacceptable": { "type": "array", - "items": { "$ref": "/schemas/content-standards/content.json" }, - "description": "Content that is unacceptable" + "items": { "$ref": "/schemas/content-standards/artifact.json" }, + "description": "Artifacts that are unacceptable" } } }, diff --git a/static/schemas/source/content-standards/validate-content-delivery-request.json b/static/schemas/source/content-standards/validate-content-delivery-request.json index 533c0dc1c..8ff37792f 100644 --- a/static/schemas/source/content-standards/validate-content-delivery-request.json +++ b/static/schemas/source/content-standards/validate-content-delivery-request.json @@ -25,24 +25,16 @@ "format": "date-time", "description": "When the delivery occurred" }, - "content": { - "type": "object", - "description": "Content context where ad was delivered", - "properties": { - "url": { "type": "string", "format": "uri" }, - "title": { "type": "string" }, - "categories": { - "type": "array", - "items": { "type": "string" } - } - } + "artifact": { + "$ref": "/schemas/content-standards/artifact.json", + "description": "Artifact where ad was delivered" }, "creative_id": { "type": "string", "description": "Which creative was delivered" } }, - "required": ["record_id", "content"] + "required": ["record_id", "artifact"] } }, "feature_ids": { diff --git a/static/schemas/source/index.json b/static/schemas/source/index.json index 144d921a2..4d911ac79 100644 --- a/static/schemas/source/index.json +++ b/static/schemas/source/index.json @@ -565,9 +565,9 @@ "content-standards": { "description": "Content Standards protocol task request/response schemas for content safety and suitability evaluation", "models": { - "content": { - "$ref": "/schemas/content-standards/content.json", - "description": "Content for evaluation or calibration - a collection of assets (text, images, video, audio) plus metadata and signals" + "artifact": { + "$ref": "/schemas/content-standards/artifact.json", + "description": "Content artifact for evaluation or calibration - represents content adjacent to an ad placement, identified by property_id + artifact_id" } }, "tasks": { From 59cfe75bb2994203fa7298288b3b570894fb55e5 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 7 Jan 2026 06:38:30 -0800 Subject: [PATCH 14/38] Remove signals field, add secured asset access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review feedback: 1. Remove signals from artifact schema and examples - the verification agent evaluates content directly without pre-computed classifications. This simplifies the model: send content, get responses. 2. Add secured URL access pattern for private assets - supports bearer tokens, service accounts, and pre-signed URLs for content that isn't publicly accessible (AI-generated images, private conversations, paywalled content). πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/content-standards/index.mdx | 51 +++++++-- .../tasks/calibrate_content.mdx | 3 - .../tasks/validate_content_delivery.mdx | 6 +- .../source/content-standards/artifact.json | 102 ++++++++++-------- 4 files changed, 101 insertions(+), 61 deletions(-) diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index 32934c473..ce7a648d7 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -50,9 +50,6 @@ Content Standards uses **natural language prompts** rather than rigid keyword li "artifact_id": "nba_championship_recap_2024", "language": "en", "title": "Championship Game Recap", - "signals": [ - {"key": "iab_category", "value": "sports/basketball", "confidence": 1.0} - ], "assets": [ {"type": "text", "role": "heading", "content": "Lakers Defeat Celtics in Thrilling Overtime", "heading_level": 1}, {"type": "text", "role": "paragraph", "content": "In an exciting conclusion to the championship series..."}, @@ -63,11 +60,14 @@ Content Standards uses **natural language prompts** rather than rigid keyword li "property_id": {"type": "domain", "value": "healthline.com"}, "artifact_id": "fitness_landing", "language": "en", - "signals": [{"key": "iab_category", "value": "health/fitness", "confidence": 0.95}] + "title": "Fitness Tips and Workouts" }, { "property_id": {"type": "apple_podcast_id", "value": "1234567890"}, - "artifact_id": "episode_42_segment_2" + "artifact_id": "episode_42_segment_2", + "assets": [ + {"type": "audio", "url": "https://cdn.example.com/podcasts/ep42_seg2.mp3", "transcript": "Today we're discussing marathon training..."} + ] }, { "property_id": {"type": "domain", "value": "fitnesschannel.tv"}, @@ -80,11 +80,15 @@ Content Standards uses **natural language prompts** rather than rigid keyword li "property_id": {"type": "domain", "value": "tabloid.example.com"}, "artifact_id": "scandal_story_123", "language": "en", - "signals": [{"key": "brand_safety", "value": "controversial", "confidence": 0.85}] + "title": "Celebrity Scandal Exposed", + "assets": [ + {"type": "text", "role": "paragraph", "content": "Shocking revelations about..."} + ] }, { "property_id": {"type": "rss_url", "value": "https://controversy.example.com/feed.xml"}, - "artifact_id": "episode_latest" + "artifact_id": "episode_latest", + "title": "Political Hot Takes" }, { "property_id": {"type": "domain", "value": "news.example.com"}, @@ -96,15 +100,42 @@ Content Standards uses **natural language prompts** rather than rigid keyword li } ``` -The policy prompt enables AI-powered verification agents to understand context and nuance. **Calibration** examples provide a training/test set that helps the agent interpret the policy correctly. Artifacts are identified by property + artifact_id and represented as collections of assets: +The policy prompt enables AI-powered verification agents to understand context and nuance. **Calibration** examples provide a training/test set that helps the agent interpret the policy correctly. You send the artifact content, and the verification agent evaluates it - no pre-classification required: - **property_id** - Identifies the property where this artifact appears (using standard identifier types) - **artifact_id** - Unique identifier within the property (property owner defines the scheme) -- **format_id** - Optional reference to format registry (same as creative formats) - **assets** - Artifact assets in document order (text blocks, images, video, audio) -- **signals** - Pre-computed classifications (IAB category, ratings, scores) with confidence levels +- **format_id** - Optional reference to format registry (same as creative formats) - **metadata** - Rich metadata (Open Graph, JSON-LD, author info) +### Secured Asset Access + +Asset URLs may require authentication - for private content like AI-generated images, internal documents, or content behind paywalls. Assets support authenticated access: + +```json +{ + "property_id": {"type": "app_id", "value": "chatgpt"}, + "artifact_id": "conversation_abc123_image_1", + "assets": [ + { + "type": "image", + "url": "https://cdn.openai.com/secured/img_abc123.png", + "access": { + "method": "bearer_token", + "token": "eyJhbGciOiJIUzI1NiIs..." + } + } + ] +} +``` + +Supported access methods: +- **bearer_token** - OAuth2 bearer token in Authorization header +- **service_account** - GCP/AWS service account credentials +- **signed_url** - Pre-signed URL with embedded credentials (URL itself is the credential) + +For large assets or real-time content, the verification agent may need to fetch the content. The caller is responsible for ensuring the agent has access - either through pre-signed URLs, shared service accounts, or token exchange. + ## Scoped Standards Buyers typically maintain multiple standards configurations for different contexts - UK TV campaigns have different regulations than US display, and children's brands need stricter safety than adult beverages. diff --git a/docs/governance/content-standards/tasks/calibrate_content.mdx b/docs/governance/content-standards/tasks/calibrate_content.mdx index cbdeb4b4d..6732b0b3e 100644 --- a/docs/governance/content-standards/tasks/calibrate_content.mdx +++ b/docs/governance/content-standards/tasks/calibrate_content.mdx @@ -37,9 +37,6 @@ An artifact represents content adjacent to an ad placement - identified by `prop "artifact_id": "r_fitness_abc123", "language": "en", "title": "Best protein sources for muscle building", - "signals": [ - {"key": "iab_category", "value": "health/fitness", "confidence": 0.92} - ], "assets": [ {"type": "text", "role": "heading", "content": "Best protein sources for muscle building", "heading_level": 1}, {"type": "text", "role": "paragraph", "content": "Looking for recommendations on high-quality protein sources..."}, diff --git a/docs/governance/content-standards/tasks/validate_content_delivery.mdx b/docs/governance/content-standards/tasks/validate_content_delivery.mdx index 2c9942fcf..185bc3e5e 100644 --- a/docs/governance/content-standards/tasks/validate_content_delivery.mdx +++ b/docs/governance/content-standards/tasks/validate_content_delivery.mdx @@ -29,11 +29,7 @@ Validate delivery records against content safety policies. Designed for batch au "artifact": { "property_id": {"type": "domain", "value": "example.com"}, "artifact_id": "article_12345", - "title": "Article Title", - "signals": [ - {"key": "iab_category", "value": "news", "confidence": 0.9}, - {"key": "iab_category", "value": "sports", "confidence": 0.85} - ] + "title": "Article Title" }, "creative_id": "creative_abc" } diff --git a/static/schemas/source/content-standards/artifact.json b/static/schemas/source/content-standards/artifact.json index 245c468ce..0cf2d2fb3 100644 --- a/static/schemas/source/content-standards/artifact.json +++ b/static/schemas/source/content-standards/artifact.json @@ -107,6 +107,10 @@ "format": "uri", "description": "Image URL" }, + "access": { + "$ref": "#/$defs/asset_access", + "description": "Authentication for secured URLs" + }, "alt_text": { "type": "string", "description": "Alt text or image description" @@ -136,6 +140,10 @@ "format": "uri", "description": "Video URL" }, + "access": { + "$ref": "#/$defs/asset_access", + "description": "Authentication for secured URLs" + }, "duration_ms": { "type": "integer", "description": "Video duration in milliseconds" @@ -167,6 +175,10 @@ "format": "uri", "description": "Audio URL" }, + "access": { + "$ref": "#/$defs/asset_access", + "description": "Authentication for secured URLs" + }, "duration_ms": { "type": "integer", "description": "Audio duration in milliseconds" @@ -186,48 +198,6 @@ ] } }, - "signals": { - "type": "array", - "description": "Pre-computed signals including categories, classifiers, and ratings", - "items": { - "type": "object", - "properties": { - "key": { - "type": "string", - "description": "Signal identifier (e.g., 'iab_category', 'violence_score', 'mpaa_rating')" - }, - "value": { - "description": "Signal value - can be string, number, or boolean", - "oneOf": [ - { "type": "string" }, - { "type": "number" }, - { "type": "boolean" } - ] - }, - "confidence": { - "type": "number", - "minimum": 0, - "maximum": 1, - "description": "Confidence score for probabilistic signals (0-1)" - }, - "version": { - "type": "string", - "description": "Version of the classifier or model that generated this signal" - }, - "source": { - "type": "string", - "description": "Who generated this signal (e.g., 'publisher_classifier', 'human_moderator')" - }, - "reasons": { - "type": "array", - "items": { "type": "string" }, - "description": "Array of reason codes for ratings or classifications" - } - }, - "required": ["key", "value"], - "additionalProperties": false - } - }, "metadata": { "type": "object", "description": "Rich metadata extracted from the artifact", @@ -293,5 +263,51 @@ } }, "required": ["property_id", "artifact_id"], - "additionalProperties": true + "additionalProperties": true, + "$defs": { + "asset_access": { + "type": "object", + "description": "Authentication for accessing secured asset URLs", + "oneOf": [ + { + "type": "object", + "description": "Bearer token authentication", + "properties": { + "method": { "type": "string", "const": "bearer_token" }, + "token": { + "type": "string", + "description": "OAuth2 bearer token for Authorization header" + } + }, + "required": ["method", "token"] + }, + { + "type": "object", + "description": "Service account authentication (GCP, AWS)", + "properties": { + "method": { "type": "string", "const": "service_account" }, + "provider": { + "type": "string", + "enum": ["gcp", "aws"], + "description": "Cloud provider" + }, + "credentials": { + "type": "object", + "description": "Service account credentials", + "additionalProperties": true + } + }, + "required": ["method", "provider"] + }, + { + "type": "object", + "description": "Pre-signed URL (credentials embedded in URL)", + "properties": { + "method": { "type": "string", "const": "signed_url" } + }, + "required": ["method"] + } + ] + } + } } From d993487b33a877ae74f6bc2eda3f757fcb96fc38 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 7 Jan 2026 06:45:45 -0800 Subject: [PATCH 15/38] Add artifacts page, restructure overview with strategic framework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure Content Standards docs around the key strategic questions: 1. What content? β†’ New artifacts.mdx page with full schema details 2. How much adjacency? β†’ Define in products, negotiate in media buys 3. What sampling rate? β†’ Negotiate coverage vs cost tradeoffs 4. How to calibrate? β†’ Dialogue-based alignment process Changes: - Add standalone artifacts.mdx with asset types and secured access - Replace ASCII workflow with mermaid sequence diagram - Add adjacency and sampling_rate sections to overview - Simplify policy examples (remove verbose calibration details) - Move secured URL access documentation to artifacts page πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs.json | 1 + .../content-standards/artifacts.mdx | 223 ++++++++++++++ docs/governance/content-standards/index.mdx | 272 ++++++++---------- 3 files changed, 346 insertions(+), 150 deletions(-) create mode 100644 docs/governance/content-standards/artifacts.mdx diff --git a/docs.json b/docs.json index af80507d9..9bd489fa4 100644 --- a/docs.json +++ b/docs.json @@ -105,6 +105,7 @@ "group": "Content Standards Protocol", "pages": [ "docs/governance/content-standards/index", + "docs/governance/content-standards/artifacts", { "group": "Tasks", "pages": [ diff --git a/docs/governance/content-standards/artifacts.mdx b/docs/governance/content-standards/artifacts.mdx new file mode 100644 index 000000000..1537e88bf --- /dev/null +++ b/docs/governance/content-standards/artifacts.mdx @@ -0,0 +1,223 @@ +--- +title: Artifacts +sidebar_position: 2 +--- + +# Artifacts + +An **artifact** is a unit of content adjacent to an ad placement. When evaluating brand safety and suitability, you're asking: "Is this artifact appropriate for my brand's ads?" + +## What Is an Artifact? + +Artifacts represent the content context where an ad appears: + +- A **news article** on a website +- A **podcast segment** between ad breaks +- A **video chapter** in a YouTube video +- A **social media post** in a feed +- A **scene** in a CTV show +- An **AI-generated image** in a chat conversation + +Artifacts are identified by `property_id` + `artifact_id` - the property defines where the content lives, and the artifact_id is the property owner's identifier for that specific piece of content. + +## Structure + +**Schema**: [artifact.json](https://adcontextprotocol.org/schemas/v2/content-standards/artifact.json) + +```json +{ + "property_id": {"type": "domain", "value": "reddit.com"}, + "artifact_id": "r_fitness_post_abc123", + "language": "en", + "title": "Best protein sources for muscle building", + "assets": [ + {"type": "text", "role": "heading", "content": "Best protein sources for muscle building", "heading_level": 1}, + {"type": "text", "role": "paragraph", "content": "Looking for recommendations on high-quality protein sources..."}, + {"type": "image", "url": "https://cdn.reddit.com/fitness-image.jpg", "alt_text": "Person lifting weights"} + ] +} +``` + +### Required Fields + +| Field | Description | +|-------|-------------| +| `property_id` | Where this artifact lives - uses standard identifier types (`domain`, `app_id`, `apple_podcast_id`, etc.) | +| `artifact_id` | Unique identifier within the property - the property owner defines their scheme | + +### Optional Fields + +| Field | Description | +|-------|-------------| +| `assets` | Content in document order - text blocks, images, video, audio | +| `title` | Artifact title or headline | +| `language` | BCP 47 language tag | +| `format_id` | Reference to format registry (same as creative formats) | +| `metadata` | Rich metadata (Open Graph, JSON-LD, author info) | + +## Asset Types + +Assets represent the actual content within an artifact: + +### Text + +```json +{"type": "text", "role": "heading", "content": "Article Title", "heading_level": 1} +{"type": "text", "role": "paragraph", "content": "The article body text..."} +{"type": "text", "role": "quote", "content": "A quoted statement"} +``` + +Roles: `paragraph`, `heading`, `caption`, `quote`, `list_item` + +### Image + +```json +{ + "type": "image", + "url": "https://cdn.example.com/photo.jpg", + "alt_text": "Description of the image" +} +``` + +### Video + +```json +{ + "type": "video", + "url": "https://cdn.example.com/video.mp4", + "transcript": "Full transcript of the video content...", + "duration_ms": 180000 +} +``` + +### Audio + +```json +{ + "type": "audio", + "url": "https://cdn.example.com/podcast.mp3", + "transcript": "Today we're discussing...", + "duration_ms": 3600000 +} +``` + +## Secured Asset Access + +Many assets aren't publicly accessible - AI-generated images, private conversations, paywalled content. The artifact schema supports authenticated access. + +### Pre-Configuration (Recommended) + +For ongoing partnerships, configure access once during onboarding rather than per-request: + +1. **Service account sharing** - Grant the verification agent access to your cloud storage +2. **OAuth client credentials** - Set up machine-to-machine authentication +3. **API key exchange** - Share long-lived API keys during setup + +This happens during the activation phase when the seller first receives content standards from a buyer. + +### Per-Asset Authentication + +When pre-configuration isn't possible, include access credentials with individual assets: + +```json +{ + "type": "image", + "url": "https://cdn.openai.com/secured/img_abc123.png", + "access": { + "method": "bearer_token", + "token": "eyJhbGciOiJIUzI1NiIs..." + } +} +``` + +### Access Methods + +| Method | Use Case | +|--------|----------| +| `bearer_token` | OAuth2 bearer token in Authorization header | +| `service_account` | GCP/AWS service account credentials | +| `signed_url` | Pre-signed URL with embedded credentials (URL itself is the credential) | + +### Service Account Setup + +For GCP: + +```json +{ + "access": { + "method": "service_account", + "provider": "gcp", + "credentials": { + "type": "service_account", + "project_id": "my-project", + "private_key_id": "...", + "private_key": "-----BEGIN PRIVATE KEY-----\n...", + "client_email": "verification-agent@my-project.iam.gserviceaccount.com" + } + } +} +``` + +For AWS: + +```json +{ + "access": { + "method": "service_account", + "provider": "aws", + "credentials": { + "access_key_id": "AKIAIOSFODNN7EXAMPLE", + "secret_access_key": "...", + "region": "us-east-1" + } + } +} +``` + +### Pre-Signed URLs + +For one-off access without sharing credentials: + +```json +{ + "type": "video", + "url": "https://storage.googleapis.com/bucket/video.mp4?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=...&X-Goog-Signature=...", + "access": { + "method": "signed_url" + } +} +``` + +The URL itself contains the credentials - no additional authentication needed. + +## Property Identifier Types + +The `property_id` uses standard identifier types from the AdCP property schema: + +| Type | Example | Use Case | +|------|---------|----------| +| `domain` | `reddit.com` | Websites | +| `app_id` | `com.spotify.music` | Mobile apps | +| `apple_podcast_id` | `1234567890` | Apple Podcasts | +| `spotify_show_id` | `4rOoJ6Egrf8K2IrywzwOMk` | Spotify podcasts | +| `youtube_channel_id` | `UCddiUEpeqJcYeBxX1IVBKvQ` | YouTube channels | +| `rss_url` | `https://feeds.example.com/podcast.xml` | RSS feeds | + +## Artifact ID Schemes + +The property owner defines their artifact_id scheme. Examples: + +| Property Type | Artifact ID Pattern | Example | +|---------------|---------------------|---------| +| News website | `article_{id}` | `article_12345` | +| Reddit | `r_{subreddit}_{post_id}` | `r_fitness_abc123` | +| Podcast | `episode_{num}_segment_{num}` | `episode_42_segment_2` | +| CTV | `show_{id}_s{season}e{episode}_scene_{num}` | `show_abc_s3e5_scene_12` | +| Social feed | `post_{id}` | `post_xyz789` | + +The verification agent doesn't need to understand the scheme - it's opaque. The property owner uses it to correlate artifacts with their content. + +## Related + +- [Content Standards Overview](/docs/governance/content-standards) - How artifacts fit into the content standards workflow +- [calibrate_content](/docs/governance/content-standards/tasks/calibrate_content) - Sending artifacts for calibration diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index ce7a648d7..09d73ed0a 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -15,28 +15,108 @@ Content Standards agents evaluate placement contexts against brand policies. Thi - **Brand suitability** - Is this content appropriate for *my* brand? (brand-specific preferences and tone) - **Competitive separation** - Avoid appearing near competitor content -## Reference Pattern +## Key Concepts -When placing a media buy, reference your content standards: +Content standards evaluation involves four key questions that buyers and sellers negotiate: + +1. **What content?** - What [artifacts](/docs/governance/content-standards/artifacts) to evaluate (the ad-adjacent content) +2. **How much adjacency?** - How many artifacts around the ad slot to consider +3. **What sampling rate?** - What percentage of traffic to evaluate +4. **How to calibrate?** - How to align on policy interpretation before runtime + +These parameters are negotiated between buyer and seller during product discovery and media buy creation. + +## Workflow + +```mermaid +sequenceDiagram + participant Brand + participant Buyer as Buyer Agent + participant Seller as Seller Agent + participant Verifier as Verification Agent + + Note over Brand,Verifier: 1. SETUP PHASE + Brand->>Verifier: create_content_standards (policy + calibration examples) + Verifier-->>Brand: standards_id + + Note over Brand,Verifier: 2. ACTIVATION PHASE + Brand->>Buyer: "Buy inventory from Reddit, use standards_id X" + Buyer->>Seller: create_media_buy (includes content_standards reference) + + Seller->>Verifier: calibrate_content (sample artifacts) + Verifier-->>Seller: verdict + explanation + Seller->>Verifier: "What about this edge case?" + Verifier-->>Seller: clarification + Note over Seller: Seller builds local model + + Note over Brand,Verifier: 3. RUNTIME PHASE + loop High-volume decisioning + Note over Seller: Local model evaluates artifacts + end + + Seller->>Verifier: validate_content_delivery (sampled records) + Verifier-->>Seller: alignment report + Verifier-->>Buyer: drift detection alerts +``` + +**Key insight**: Runtime decisioning happens locally at the seller (for scale), while calibration and validation happen via the verification agent (for alignment). + +## Adjacency + +How much content around the ad slot should be evaluated? + +| Context | Adjacency Examples | +|---------|-------------------| +| **News article** | The article where the ad appears | +| **Social feed** | 1-2 posts above and below the ad slot | +| **Podcast** | The segment before and after the ad break | +| **CTV** | 1-2 scenes before and after the ad pod | +| **Infinite scroll** | Posts within the visible viewport | + +Adjacency requirements are defined by the seller in their product catalog (`get_products`). The buyer can filter products based on adjacency guarantees: + +```json +{ + "product_id": "reddit_feed_standard", + "content_standards": { + "adjacency": { + "before": 2, + "after": 2, + "unit": "posts" + } + } +} +``` + +Different products may offer different adjacency guarantees at different price points. + +## Sampling Rate + +What percentage of traffic should be evaluated by the verification agent? + +| Rate | Use Case | +|------|----------| +| **100%** | Premium brand safety - every impression validated | +| **10-25%** | Standard monitoring - statistical confidence | +| **1-5%** | Spot checking - drift detection only | + +Sampling rate is negotiated in the media buy: ```json { - "campaign": {...}, "governance": { "content_standards": { "agent_url": "https://safety.ias.com/adcp", "standards_id": "nike_brand_safety", - "token": "..." + "sampling_rate": 0.25 } } } ``` -- **agent_url** - The Content Standards service -- **standards_id** - Which standards configuration to use -- **token** - Access token (may be optional for public standards) +Higher sampling rates typically cost more but provide stronger guarantees. The seller is responsible for implementing the agreed sampling rate and reporting actual coverage. -## Prompt-Based Policies +## Policies Content Standards uses **natural language prompts** rather than rigid keyword lists: @@ -48,93 +128,23 @@ Content Standards uses **natural language prompts** rather than rigid keyword li { "property_id": {"type": "domain", "value": "espn.com"}, "artifact_id": "nba_championship_recap_2024", - "language": "en", - "title": "Championship Game Recap", - "assets": [ - {"type": "text", "role": "heading", "content": "Lakers Defeat Celtics in Thrilling Overtime", "heading_level": 1}, - {"type": "text", "role": "paragraph", "content": "In an exciting conclusion to the championship series..."}, - {"type": "image", "url": "https://cdn.espn.com/game-photo.jpg", "alt_text": "Championship celebration"} - ] - }, - { - "property_id": {"type": "domain", "value": "healthline.com"}, - "artifact_id": "fitness_landing", - "language": "en", - "title": "Fitness Tips and Workouts" - }, - { - "property_id": {"type": "apple_podcast_id", "value": "1234567890"}, - "artifact_id": "episode_42_segment_2", - "assets": [ - {"type": "audio", "url": "https://cdn.example.com/podcasts/ep42_seg2.mp3", "transcript": "Today we're discussing marathon training..."} - ] - }, - { - "property_id": {"type": "domain", "value": "fitnesschannel.tv"}, - "artifact_id": "workout_tutorial_ep15", - "assets": [{"type": "video", "url": "https://cdn.fitnesschannel.tv/videos/ep15.mp4"}] + "title": "Championship Game Recap" } ], "unacceptable": [ { "property_id": {"type": "domain", "value": "tabloid.example.com"}, "artifact_id": "scandal_story_123", - "language": "en", - "title": "Celebrity Scandal Exposed", - "assets": [ - {"type": "text", "role": "paragraph", "content": "Shocking revelations about..."} - ] - }, - { - "property_id": {"type": "rss_url", "value": "https://controversy.example.com/feed.xml"}, - "artifact_id": "episode_latest", - "title": "Political Hot Takes" - }, - { - "property_id": {"type": "domain", "value": "news.example.com"}, - "artifact_id": "controversial_podcast_ep42", - "assets": [{"type": "audio", "url": "https://cdn.news.example.com/podcasts/ep42.mp3"}] + "title": "Celebrity Scandal Exposed" } ] } } ``` -The policy prompt enables AI-powered verification agents to understand context and nuance. **Calibration** examples provide a training/test set that helps the agent interpret the policy correctly. You send the artifact content, and the verification agent evaluates it - no pre-classification required: - -- **property_id** - Identifies the property where this artifact appears (using standard identifier types) -- **artifact_id** - Unique identifier within the property (property owner defines the scheme) -- **assets** - Artifact assets in document order (text blocks, images, video, audio) -- **format_id** - Optional reference to format registry (same as creative formats) -- **metadata** - Rich metadata (Open Graph, JSON-LD, author info) - -### Secured Asset Access - -Asset URLs may require authentication - for private content like AI-generated images, internal documents, or content behind paywalls. Assets support authenticated access: - -```json -{ - "property_id": {"type": "app_id", "value": "chatgpt"}, - "artifact_id": "conversation_abc123_image_1", - "assets": [ - { - "type": "image", - "url": "https://cdn.openai.com/secured/img_abc123.png", - "access": { - "method": "bearer_token", - "token": "eyJhbGciOiJIUzI1NiIs..." - } - } - ] -} -``` - -Supported access methods: -- **bearer_token** - OAuth2 bearer token in Authorization header -- **service_account** - GCP/AWS service account credentials -- **signed_url** - Pre-signed URL with embedded credentials (URL itself is the credential) +The policy prompt enables AI-powered verification agents to understand context and nuance. **Calibration** examples provide a training/test set that helps the agent interpret the policy correctly. -For large assets or real-time content, the verification agent may need to fetch the content. The caller is responsible for ensuring the agent has access - either through pre-signed URLs, shared service accounts, or token exchange. +See [Artifacts](/docs/governance/content-standards/artifacts) for details on artifact structure and secured asset access. ## Scoped Standards @@ -150,68 +160,41 @@ Buyers typically maintain multiple standards configurations for different contex } ``` -**The buyer selects the appropriate `standards_id` when creating a media buy.** The seller receives a reference to the resolved standards - they don't need to do scope matching themselves. These fields define where this standards configuration applies: +**The buyer selects the appropriate `standards_id` when creating a media buy.** The seller receives a reference to the resolved standards - they don't need to do scope matching themselves. -- **brand_ids**: References to brand identifiers defined in the [Brand Manifest](/docs/creative/brand-manifest). Brand names may differ across countries, so we use stable IDs rather than display names. -- **countries_all**: ISO country codes - standards apply in ALL listed countries (different jurisdictions have different regulations) -- **channels_any**: Advertising channels - standards apply to ANY of the listed channels +## Calibration -## Workflow +Before running campaigns, sellers calibrate their local models against the verification agent. This is a **dialogue-based process**: -Content Standards involves three phases with different agents: +1. Seller sends sample artifacts to the verification agent +2. Verification agent returns verdicts with detailed explanations +3. Seller asks follow-up questions about edge cases +4. Process repeats until alignment is achieved -``` -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ 1. SETUP PHASE β”‚ -β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ -β”‚ β”‚ -β”‚ Brand ──────► Verification Agent (e.g., IAS) β”‚ -β”‚ β”‚ β”‚ -β”‚ β”‚ create_content_standards + calibration examples β”‚ -β”‚ β–Ό β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ standards_id β”‚ ← Model trained on brand policy β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β”‚ β”‚ -β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ -β”‚ 2. ACTIVATION PHASE β”‚ -β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ -β”‚ β”‚ -β”‚ Brand ──────► Buyer Agent (e.g., Scope3) β”‚ -β”‚ β”‚ β”‚ -β”‚ β”‚ "Buy inventory from Reddit, use standards_id X" β”‚ -β”‚ β–Ό β”‚ -β”‚ Buyer Agent ────► Seller Agent (e.g., Reddit) β”‚ -β”‚ β”‚ β”‚ -β”‚ β”‚ Pass content_standards reference in media buy β”‚ -β”‚ β–Ό β”‚ -β”‚ Seller Agent ───► Verification Agent β”‚ -β”‚ β”‚ β”‚ -β”‚ β”‚ calibrate_content (back-and-forth dialogue) β”‚ -β”‚ β”‚ "Does r/fitness pass?" β†’ "Yes, because..." β”‚ -β”‚ β”‚ "Does r/politics pass?" β†’ "No, because..." β”‚ -β”‚ β–Ό β”‚ -β”‚ Seller says: "I understand the model. Ready to execute." β”‚ -β”‚ β”‚ -β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ -β”‚ 3. RUNTIME PHASE β”‚ -β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ -β”‚ β”‚ -β”‚ Seller runs campaign using local/cached model β”‚ -β”‚ β”‚ β”‚ -β”‚ β”‚ Periodic: validate_content_delivery β”‚ -β”‚ β”‚ (sample delivery reports β†’ Verification Agent) β”‚ -β”‚ β–Ό β”‚ -β”‚ Buyer Agent ────► Verification Agent β”‚ -β”‚ β”‚ β”‚ -β”‚ β”‚ "How aligned is delivery?" β”‚ -β”‚ β–Ό β”‚ -β”‚ Reporting / drift detection β”‚ -β”‚ β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +```json +// Seller: "Does this pass?" +{ + "artifact": { + "property_id": {"type": "domain", "value": "reddit.com"}, + "artifact_id": "r_news_politics_123", + "title": "Political News Article" + } +} + +// Verification agent: "No, because..." +{ + "verdict": "fail", + "explanation": "Political content is excluded by brand policy, even when balanced.", + "policy_alignment": { + "violations": [{ + "policy_text": "Avoid content about controversial politics", + "violation_reason": "Article discusses ongoing political controversy" + }] + } +} ``` -**Key insight**: Runtime decisioning happens locally at the seller (for scale), while calibration and validation happen via the verification agent (for alignment). +See [calibrate_content](/docs/governance/content-standards/tasks/calibrate_content) for the full task specification. ## Tasks @@ -245,18 +228,7 @@ Content Standards involves three phases with different agents: - **Scope3** - Sustainability-focused brand safety with prompt-based policies - **Custom** - Brand-specific implementations -## Calibration vs Runtime - -| Aspect | calibrate_content | Runtime (seller's local model) | -|--------|-------------------|-------------------------------| -| **Purpose** | Alignment & understanding | High-volume decisioning | -| **Volume** | Low (setup/periodic) | High (every impression) | -| **Response** | Verbose explanations | Pass/fail only | -| **Latency** | Seconds acceptable | Milliseconds required | -| **Dialogue** | Supports back-and-forth | Stateless | - -The `calibrate_content` task returns detailed explanations of *why* content passes or fails, mapping decisions back to specific policy criteria. This helps sellers build accurate local models. - ## Related +- [Artifacts](/docs/governance/content-standards/artifacts) - What artifacts are and how to structure them - [Brand Manifest](/docs/creative/brand-manifest) - Static brand identity that can link to standards agents From 69511fc36980f964fb31f11a837edaf47a4c6295 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 7 Jan 2026 06:58:56 -0800 Subject: [PATCH 16/38] Simplify artifact schema: assets required, title as asset, add variant_id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Make assets array required (artifacts without content are meaningless) - Remove title, language, description top-level fields (they're text assets) - Add variant_id for A/B tests, translations, and temporal versions - Add language field to text assets for mixed-language content - Update all documentation examples to use new pattern - Fix schema validation test to handle internal $defs references πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../content-standards/artifacts.mdx | 97 ++++++++++++++++--- docs/governance/content-standards/index.mdx | 6 +- .../tasks/calibrate_content.mdx | 28 ++++-- .../tasks/validate_content_delivery.mdx | 4 +- .../source/content-standards/artifact.json | 26 +++-- tests/schema-validation.test.cjs | 19 ++-- 6 files changed, 132 insertions(+), 48 deletions(-) diff --git a/docs/governance/content-standards/artifacts.mdx b/docs/governance/content-standards/artifacts.mdx index 1537e88bf..81bf42aaa 100644 --- a/docs/governance/content-standards/artifacts.mdx +++ b/docs/governance/content-standards/artifacts.mdx @@ -28,11 +28,9 @@ Artifacts are identified by `property_id` + `artifact_id` - the property defines { "property_id": {"type": "domain", "value": "reddit.com"}, "artifact_id": "r_fitness_post_abc123", - "language": "en", - "title": "Best protein sources for muscle building", "assets": [ - {"type": "text", "role": "heading", "content": "Best protein sources for muscle building", "heading_level": 1}, - {"type": "text", "role": "paragraph", "content": "Looking for recommendations on high-quality protein sources..."}, + {"type": "text", "role": "title", "content": "Best protein sources for muscle building", "language": "en"}, + {"type": "text", "role": "paragraph", "content": "Looking for recommendations on high-quality protein sources...", "language": "en"}, {"type": "image", "url": "https://cdn.reddit.com/fitness-image.jpg", "alt_text": "Person lifting weights"} ] } @@ -44,30 +42,80 @@ Artifacts are identified by `property_id` + `artifact_id` - the property defines |-------|-------------| | `property_id` | Where this artifact lives - uses standard identifier types (`domain`, `app_id`, `apple_podcast_id`, etc.) | | `artifact_id` | Unique identifier within the property - the property owner defines their scheme | +| `assets` | Content in document order - text blocks, images, video, audio | ### Optional Fields | Field | Description | |-------|-------------| -| `assets` | Content in document order - text blocks, images, video, audio | -| `title` | Artifact title or headline | -| `language` | BCP 47 language tag | +| `variant_id` | Identifies a specific variant (A/B test, translation, temporal version) | | `format_id` | Reference to format registry (same as creative formats) | -| `metadata` | Rich metadata (Open Graph, JSON-LD, author info) | +| `url` | Web URL if the artifact has one | +| `metadata` | Artifact-level metadata (Open Graph, JSON-LD, author info) | +| `published_time` | When the artifact was published | +| `last_update_time` | When the artifact was last modified | + +## Variants + +The same artifact may have multiple variants: + +- **Translations** - English version vs Spanish version +- **A/B tests** - Different headlines being tested +- **Temporal versions** - Content that changed on Wednesday + +Use `variant_id` to distinguish between them: + +```json +// English version +{ + "property_id": {"type": "domain", "value": "nytimes.com"}, + "artifact_id": "article_12345", + "variant_id": "en", + "assets": [ + {"type": "text", "role": "title", "content": "Breaking News Story", "language": "en"} + ] +} + +// Spanish translation +{ + "property_id": {"type": "domain", "value": "nytimes.com"}, + "artifact_id": "article_12345", + "variant_id": "es", + "assets": [ + {"type": "text", "role": "title", "content": "Noticia de ΓΊltima hora", "language": "es"} + ] +} + +// A/B test variant +{ + "property_id": {"type": "domain", "value": "nytimes.com"}, + "artifact_id": "article_12345", + "variant_id": "headline_test_b", + "assets": [ + {"type": "text", "role": "title", "content": "Alternative Headline Being Tested", "language": "en"} + ] +} +``` + +The combination of `artifact_id` + `variant_id` must be unique within a property. This lets you track which variant a user saw and correlate it with delivery reports. ## Asset Types -Assets represent the actual content within an artifact: +Assets are the actual content within an artifact. Everything is an asset - titles, paragraphs, images, videos. ### Text ```json -{"type": "text", "role": "heading", "content": "Article Title", "heading_level": 1} -{"type": "text", "role": "paragraph", "content": "The article body text..."} +{"type": "text", "role": "title", "content": "Article Title", "language": "en"} +{"type": "text", "role": "paragraph", "content": "The article body text...", "language": "en"} +{"type": "text", "role": "description", "content": "A summary of the article", "language": "en"} +{"type": "text", "role": "heading", "content": "Section Header", "heading_level": 2} {"type": "text", "role": "quote", "content": "A quoted statement"} ``` -Roles: `paragraph`, `heading`, `caption`, `quote`, `list_item` +Roles: `title`, `description`, `paragraph`, `heading`, `caption`, `quote`, `list_item` + +Each text asset can have its own `language` tag for mixed-language content. ### Image @@ -101,6 +149,31 @@ Roles: `paragraph`, `heading`, `caption`, `quote`, `list_item` } ``` +## Metadata + +Artifact-level metadata describes the artifact as a whole, not individual assets: + +```json +{ + "metadata": { + "author": "Jane Smith", + "canonical": "https://example.com/article/12345", + "open_graph": { + "og:type": "article", + "og:site_name": "Example News" + }, + "json_ld": [ + { + "@type": "NewsArticle", + "datePublished": "2025-01-15" + } + ] + } +} +``` + +This is separate from assets because it's about the artifact container, not the content itself. + ## Secured Asset Access Many assets aren't publicly accessible - AI-generated images, private conversations, paywalled content. The artifact schema supports authenticated access. diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index 09d73ed0a..1e65c072c 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -128,14 +128,14 @@ Content Standards uses **natural language prompts** rather than rigid keyword li { "property_id": {"type": "domain", "value": "espn.com"}, "artifact_id": "nba_championship_recap_2024", - "title": "Championship Game Recap" + "assets": [{"type": "text", "role": "title", "content": "Championship Game Recap"}] } ], "unacceptable": [ { "property_id": {"type": "domain", "value": "tabloid.example.com"}, "artifact_id": "scandal_story_123", - "title": "Celebrity Scandal Exposed" + "assets": [{"type": "text", "role": "title", "content": "Celebrity Scandal Exposed"}] } ] } @@ -177,7 +177,7 @@ Before running campaigns, sellers calibrate their local models against the verif "artifact": { "property_id": {"type": "domain", "value": "reddit.com"}, "artifact_id": "r_news_politics_123", - "title": "Political News Article" + "assets": [{"type": "text", "role": "title", "content": "Political News Article"}] } } diff --git a/docs/governance/content-standards/tasks/calibrate_content.mdx b/docs/governance/content-standards/tasks/calibrate_content.mdx index 6732b0b3e..77221aaf0 100644 --- a/docs/governance/content-standards/tasks/calibrate_content.mdx +++ b/docs/governance/content-standards/tasks/calibrate_content.mdx @@ -35,12 +35,10 @@ An artifact represents content adjacent to an ad placement - identified by `prop { "property_id": {"type": "domain", "value": "reddit.com"}, "artifact_id": "r_fitness_abc123", - "language": "en", - "title": "Best protein sources for muscle building", "assets": [ - {"type": "text", "role": "heading", "content": "Best protein sources for muscle building", "heading_level": 1}, - {"type": "text", "role": "paragraph", "content": "Looking for recommendations on high-quality protein sources..."}, - {"type": "text", "role": "paragraph", "content": "I've been lifting for 6 months and want to optimize my diet."}, + {"type": "text", "role": "title", "content": "Best protein sources for muscle building", "language": "en"}, + {"type": "text", "role": "paragraph", "content": "Looking for recommendations on high-quality protein sources...", "language": "en"}, + {"type": "text", "role": "paragraph", "content": "I've been lifting for 6 months and want to optimize my diet.", "language": "en"}, {"type": "image", "url": "https://cdn.reddit.com/fitness-image.jpg", "alt_text": "Person lifting weights"} ] } @@ -142,7 +140,9 @@ const response1 = await a2a.send({ artifact: { property_id: { type: "domain", value: "reddit.com" }, artifact_id: "r_news_politics_123", - title: "Political News Article" + assets: [ + { type: "text", role: "title", content: "Political News Article" } + ] } } } @@ -179,7 +179,9 @@ const response3 = await a2a.send({ artifact: { property_id: { type: "domain", value: "reddit.com" }, artifact_id: "r_running_tips_456", - title: "Running Tips" + assets: [ + { type: "text", role: "title", content: "Running Tips" } + ] } } } @@ -199,7 +201,9 @@ const response1 = await mcp.call('calibrate_content', { artifact: { property_id: { type: "domain", value: "reddit.com" }, artifact_id: "r_news_politics_123", - title: "Political News Article" + assets: [ + { type: "text", role: "title", content: "Political News Article" } + ] } }); // Response includes context_id for conversation continuity @@ -211,7 +215,9 @@ const response2 = await mcp.call('calibrate_content', { artifact: { property_id: { type: "domain", value: "reddit.com" }, artifact_id: "r_news_politics_123", - title: "Political News Article" + assets: [ + { type: "text", role: "title", content: "Political News Article" } + ] } }); // Include text message in the protocol envelope asking about balanced journalism @@ -223,7 +229,9 @@ const response3 = await mcp.call('calibrate_content', { artifact: { property_id: { type: "domain", value: "reddit.com" }, artifact_id: "r_running_tips_456", - title: "Running Tips" + assets: [ + { type: "text", role: "title", content: "Running Tips" } + ] } }); ``` diff --git a/docs/governance/content-standards/tasks/validate_content_delivery.mdx b/docs/governance/content-standards/tasks/validate_content_delivery.mdx index 185bc3e5e..f563c1a4d 100644 --- a/docs/governance/content-standards/tasks/validate_content_delivery.mdx +++ b/docs/governance/content-standards/tasks/validate_content_delivery.mdx @@ -29,7 +29,9 @@ Validate delivery records against content safety policies. Designed for batch au "artifact": { "property_id": {"type": "domain", "value": "example.com"}, "artifact_id": "article_12345", - "title": "Article Title" + "assets": [ + {"type": "text", "role": "title", "content": "Article Title"} + ] }, "creative_id": "creative_abc" } diff --git a/static/schemas/source/content-standards/artifact.json b/static/schemas/source/content-standards/artifact.json index 0cf2d2fb3..fda9f2af1 100644 --- a/static/schemas/source/content-standards/artifact.json +++ b/static/schemas/source/content-standards/artifact.json @@ -25,6 +25,10 @@ "type": "string", "description": "Identifier for this artifact within the property. The property owner defines the scheme (e.g., 'article_12345', 'episode_42_segment_3', 'post_abc123')." }, + "variant_id": { + "type": "string", + "description": "Identifies a specific variant of this artifact. Use for A/B tests, translations, or temporal versions. Examples: 'en', 'es-MX', 'v2', 'headline_test_b'. The combination of artifact_id + variant_id must be unique." + }, "format_id": { "type": "object", "description": "Optional reference to a format definition. Uses the same format registry as creative formats.", @@ -47,18 +51,6 @@ "format": "uri", "description": "Optional URL for this artifact (web page, podcast feed, video page). Not all artifacts have URLs (e.g., Instagram content, podcast segments, TV scenes)." }, - "language": { - "type": "string", - "description": "BCP 47 language tag (e.g., 'en', 'en-US', 'es-MX')" - }, - "title": { - "type": "string", - "description": "Artifact title or headline" - }, - "description": { - "type": "string", - "description": "Artifact description or summary" - }, "published_time": { "type": "string", "format": "date-time", @@ -81,13 +73,17 @@ "type": { "type": "string", "const": "text" }, "role": { "type": "string", - "enum": ["paragraph", "heading", "caption", "quote", "list_item"], - "description": "Role of this text in the document" + "enum": ["title", "paragraph", "heading", "caption", "quote", "list_item", "description"], + "description": "Role of this text in the document. Use 'title' for the main artifact title, 'description' for summaries." }, "content": { "type": "string", "description": "Text content" }, + "language": { + "type": "string", + "description": "BCP 47 language tag for this text (e.g., 'en', 'es-MX'). Useful when artifact contains mixed-language content." + }, "heading_level": { "type": "integer", "minimum": 1, @@ -262,7 +258,7 @@ "additionalProperties": true } }, - "required": ["property_id", "artifact_id"], + "required": ["property_id", "artifact_id", "assets"], "additionalProperties": true, "$defs": { "asset_access": { diff --git a/tests/schema-validation.test.cjs b/tests/schema-validation.test.cjs index 2d4b0a71c..40727fc84 100644 --- a/tests/schema-validation.test.cjs +++ b/tests/schema-validation.test.cjs @@ -130,33 +130,38 @@ function validateSchemaStructure(schemaPath, schema) { function validateCrossReferences(schemas) { const schemaIds = new Set(schemas.map(([_, schema]) => schema.$id)); const missingRefs = []; - + for (const [schemaPath, schema] of schemas) { // Find all $ref occurrences const refs = JSON.stringify(schema).match(/"\$ref":\s*"([^"]+)"/g) || []; - + for (const refMatch of refs) { const ref = refMatch.match(/"\$ref":\s*"([^"]+)"/)[1]; - + // Skip external references (http://, https://) if (ref.startsWith('http://') || ref.startsWith('https://')) { continue; } - + + // Skip internal references (#/$defs/..., #/properties/..., etc.) + if (ref.startsWith('#/')) { + continue; + } + // Check if referenced schema exists if (!schemaIds.has(ref)) { missingRefs.push({ schema: schemaPath, ref }); } } } - + if (missingRefs.length > 0) { - const errorMsg = missingRefs.map(({ schema, ref }) => + const errorMsg = missingRefs.map(({ schema, ref }) => `${path.basename(schema)} -> ${ref}` ).join(', '); return `Missing referenced schemas: ${errorMsg}`; } - + return true; } From 2ba4772e75be0a85fef240a15094d23ec516f8db Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 7 Jan 2026 07:07:14 -0800 Subject: [PATCH 17/38] Address review comments on content standards overview MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename content_standards to content_standards_adjacency_definition - Add Adjacency Units table defining: posts, scenes, segments, seconds, viewports, articles - Clarify human-in-the-loop aspect of calibration process πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/content-standards/index.mdx | 25 +++++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index 1e65c072c..20dddefcd 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -78,16 +78,25 @@ Adjacency requirements are defined by the seller in their product catalog (`get_ ```json { "product_id": "reddit_feed_standard", - "content_standards": { - "adjacency": { - "before": 2, - "after": 2, - "unit": "posts" - } + "content_standards_adjacency_definition": { + "before": 2, + "after": 2, + "unit": "posts" } } ``` +### Adjacency Units + +| Unit | Use Case | +|------|----------| +| `posts` | Social feeds, forums, comment threads | +| `scenes` | CTV, streaming video content | +| `segments` | Podcasts, audio content | +| `seconds` | Time-based adjacency in video/audio | +| `viewports` | Infinite scroll contexts | +| `articles` | News sites, content aggregators | + Different products may offer different adjacency guarantees at different price points. ## Sampling Rate @@ -164,13 +173,15 @@ Buyers typically maintain multiple standards configurations for different contex ## Calibration -Before running campaigns, sellers calibrate their local models against the verification agent. This is a **dialogue-based process**: +Before running campaigns, sellers calibrate their local models against the verification agent. This is a **dialogue-based process** that may involve human review on either side: 1. Seller sends sample artifacts to the verification agent 2. Verification agent returns verdicts with detailed explanations 3. Seller asks follow-up questions about edge cases 4. Process repeats until alignment is achieved +**Human-in-the-loop**: Calibration often involves humans on both sides. A brand safety specialist at the buyer might review edge cases flagged by the verification agent. A content operations team at the seller might curate calibration samples and validate the local model's learning. The protocol supports async workflows where either party can pause for human review before responding. + ```json // Seller: "Does this pass?" { From a317d96464e90337434e3693d536084b7b70d3cd Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 7 Jan 2026 07:32:30 -0800 Subject: [PATCH 18/38] Simplify calibrate_content response schema and examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review comments: - Remove suggestion field from features (not appropriate for calibration) - Remove policy_alignment (redundant with features breakdown) - Make confidence explicitly optional in docs - Simplify A2A examples to use data-only parts (no text when calling skill) - Update response field table to show required vs optional πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../tasks/calibrate_content.mdx | 96 +++++++------------ .../calibrate-content-response.json | 37 ------- 2 files changed, 35 insertions(+), 98 deletions(-) diff --git a/docs/governance/content-standards/tasks/calibrate_content.mdx b/docs/governance/content-standards/tasks/calibrate_content.mdx index 77221aaf0..a7e16a59a 100644 --- a/docs/governance/content-standards/tasks/calibrate_content.mdx +++ b/docs/governance/content-standards/tasks/calibrate_content.mdx @@ -53,15 +53,7 @@ An artifact represents content adjacent to an ad placement - identified by `prop ```json { "verdict": "pass", - "confidence": 0.95, "explanation": "This content aligns well with the brand's fitness-focused positioning. Health and fitness content is explicitly marked as 'ideal' in the policy. The discussion is constructive and educational.", - "policy_alignment": { - "matching_criteria": [ - "Sports and fitness content is ideal", - "Lifestyle content about health is good" - ], - "no_violations": true - }, "features": [ { "feature_id": "brand_safety", @@ -82,17 +74,7 @@ An artifact represents content adjacent to an ad placement - identified by `prop ```json { "verdict": "fail", - "confidence": 0.88, "explanation": "This content discusses political topics which the policy explicitly excludes. While the article itself is balanced journalism, the brand has requested to avoid all controversial political content regardless of tone.", - "policy_alignment": { - "matching_criteria": [], - "violations": [ - { - "policy_text": "Avoid content about violence, controversial politics, adult themes", - "violation_reason": "Article discusses ongoing political controversy" - } - ] - }, "features": [ { "feature_id": "brand_safety", @@ -102,8 +84,7 @@ An artifact represents content adjacent to an ad placement - identified by `prop { "feature_id": "brand_suitability", "status": "failed", - "explanation": "Political content is excluded by brand policy, even when balanced.", - "suggestion": "Consider news content focused on sports, entertainment, or lifestyle instead." + "explanation": "Political content is excluded by brand policy, even when balanced." } ] } @@ -111,13 +92,12 @@ An artifact represents content adjacent to an ad placement - identified by `prop ### Response Fields -| Field | Description | -|-------|-------------| -| `verdict` | Overall `pass` or `fail` decision | -| `confidence` | Model confidence in the verdict (0-1) | -| `explanation` | Detailed natural language explanation of the decision | -| `policy_alignment` | How the content maps to specific policy criteria | -| `features` | Per-feature breakdown with explanations | +| Field | Required | Description | +|-------|----------|-------------| +| `verdict` | Yes | Overall `pass` or `fail` decision | +| `explanation` | No | Detailed natural language explanation of the decision | +| `features` | No | Per-feature breakdown with explanations | +| `confidence` | No | Model confidence in the verdict (0-1), when available | ## Dialogue Flow @@ -129,30 +109,27 @@ Calibration supports back-and-forth dialogue using the protocol's conversation m // Seller sends artifact to evaluate const response1 = await a2a.send({ message: { - parts: [ - { kind: "text", text: "Does this artifact pass our standards?" }, - { - kind: "data", - data: { - skill: "calibrate_content", - parameters: { - standards_id: "nike_brand_safety", - artifact: { - property_id: { type: "domain", value: "reddit.com" }, - artifact_id: "r_news_politics_123", - assets: [ - { type: "text", role: "title", content: "Political News Article" } - ] - } + parts: [{ + kind: "data", + data: { + skill: "calibrate_content", + parameters: { + standards_id: "nike_brand_safety", + artifact: { + property_id: { type: "domain", value: "reddit.com" }, + artifact_id: "r_news_politics_123", + assets: [ + { type: "text", role: "title", content: "Political News Article" } + ] } } } - ] + }] } }); -// Response: verdict=fail with explanation +// Response: verdict=fail with feature breakdown -// Seller disagrees - continue dialogue in same context +// Seller asks follow-up question about the decision const response2 = await a2a.send({ contextId: response1.contextId, message: { @@ -168,25 +145,22 @@ const response2 = await a2a.send({ const response3 = await a2a.send({ contextId: response1.contextId, message: { - parts: [ - { kind: "text", text: "What about this fitness content?" }, - { - kind: "data", - data: { - skill: "calibrate_content", - parameters: { - standards_id: "nike_brand_safety", - artifact: { - property_id: { type: "domain", value: "reddit.com" }, - artifact_id: "r_running_tips_456", - assets: [ - { type: "text", role: "title", content: "Running Tips" } - ] - } + parts: [{ + kind: "data", + data: { + skill: "calibrate_content", + parameters: { + standards_id: "nike_brand_safety", + artifact: { + property_id: { type: "domain", value: "reddit.com" }, + artifact_id: "r_running_tips_456", + assets: [ + { type: "text", role: "title", content: "Running Tips" } + ] } } } - ] + }] } }); // Response: verdict=pass - now seller understands the boundaries diff --git a/static/schemas/source/content-standards/calibrate-content-response.json b/static/schemas/source/content-standards/calibrate-content-response.json index d5555e3ca..d6e24a814 100644 --- a/static/schemas/source/content-standards/calibrate-content-response.json +++ b/static/schemas/source/content-standards/calibrate-content-response.json @@ -24,39 +24,6 @@ "type": "string", "description": "Detailed natural language explanation of the decision" }, - "policy_alignment": { - "type": "object", - "description": "How the content maps to specific policy criteria", - "properties": { - "matching_criteria": { - "type": "array", - "items": { "type": "string" }, - "description": "Policy statements that this content matches positively" - }, - "violations": { - "type": "array", - "description": "Policy violations found in the content", - "items": { - "type": "object", - "properties": { - "policy_text": { - "type": "string", - "description": "The policy text that was violated" - }, - "violation_reason": { - "type": "string", - "description": "Explanation of how the content violates this policy" - } - }, - "required": ["policy_text", "violation_reason"] - } - }, - "no_violations": { - "type": "boolean", - "description": "True if no policy violations were found" - } - } - }, "features": { "type": "array", "description": "Per-feature breakdown with explanations", @@ -75,10 +42,6 @@ "explanation": { "type": "string", "description": "Human-readable explanation of why this feature passed or failed" - }, - "suggestion": { - "type": "string", - "description": "Actionable suggestion for content that could meet this feature's requirements" } }, "required": ["feature_id", "status"] From a8bab2729dcec66361fc5c97d97d924fb868eaa7 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 7 Jan 2026 07:42:49 -0800 Subject: [PATCH 19/38] Clarify validate_content_delivery data flow: buyer as intermediary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review comment about who calls validate_content_delivery: - Delivery records flow from seller β†’ buyer β†’ verification agent - Buyer aggregates delivery data and knows which standards_id applies - Verification agent works on behalf of the buyer Changes: - Add Data Flow section with mermaid diagram to validate_content_delivery.mdx - Add media_buy_id field to request schema and delivery records - Update overview mermaid diagram to show correct flow - Remove suggestion field from response examples πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/content-standards/index.mdx | 8 ++--- .../tasks/validate_content_delivery.mdx | 31 +++++++++++++++++-- .../validate-content-delivery-request.json | 8 +++++ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index 20dddefcd..3628a59dc 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -54,12 +54,12 @@ sequenceDiagram Note over Seller: Local model evaluates artifacts end - Seller->>Verifier: validate_content_delivery (sampled records) - Verifier-->>Seller: alignment report - Verifier-->>Buyer: drift detection alerts + Seller->>Buyer: Delivery records (get_media_buy_delivery) + Buyer->>Verifier: validate_content_delivery (sampled) + Verifier-->>Buyer: Validation results ``` -**Key insight**: Runtime decisioning happens locally at the seller (for scale), while calibration and validation happen via the verification agent (for alignment). +**Key insight**: Runtime decisioning happens locally at the seller (for scale). Delivery records flow through the buyer, who validates samples against the verification agent. ## Adjacency diff --git a/docs/governance/content-standards/tasks/validate_content_delivery.mdx b/docs/governance/content-standards/tasks/validate_content_delivery.mdx index f563c1a4d..802c9eb0e 100644 --- a/docs/governance/content-standards/tasks/validate_content_delivery.mdx +++ b/docs/governance/content-standards/tasks/validate_content_delivery.mdx @@ -9,6 +9,32 @@ Validate delivery records against content safety policies. Designed for batch au **Response time**: < 10s (batch of 10,000 records) +## Data Flow + +Delivery validation follows the same path as delivery reporting: + +```mermaid +sequenceDiagram + participant Seller as Seller Agent + participant Buyer as Buyer Agent + participant Verifier as Verification Agent + + Seller->>Buyer: Delivery records (via get_media_buy_delivery) + Note over Buyer: Buyer aggregates delivery data + Buyer->>Verifier: validate_content_delivery (sampled records) + Verifier-->>Buyer: Validation results + Note over Buyer: Buyer tracks compliance +``` + +**Why through the buyer?** + +- The **buyer** owns the media buy and knows which `standards_id` applies +- The **buyer** aggregates delivery data from multiple sellers +- The **buyer** is accountable for brand safety compliance +- The **verification agent** works on behalf of the buyer + +Sellers send delivery records to buyers via `get_media_buy_delivery`. The buyer then samples and validates against the verification agent. This keeps the seller's responsibility simple: report what was delivered. + ## Request **Schema**: [validate-content-delivery-request.json](https://adcontextprotocol.org/schemas/v2/content-standards/validate-content-delivery-request.json) @@ -16,6 +42,7 @@ Validate delivery records against content safety policies. Designed for batch au | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `standards_id` | string | Yes | Standards configuration to validate against | +| `media_buy_id` | string | No | Media buy these records belong to (for context) | | `records` | array | Yes | Delivery records to validate (max 10,000) | | `feature_ids` | array | No | Specific features to evaluate (defaults to all) | | `include_passed` | boolean | No | Include passed records in results (default: true) | @@ -26,6 +53,7 @@ Validate delivery records against content safety policies. Designed for batch au { "record_id": "imp_12345", "timestamp": "2025-01-15T10:30:00Z", + "media_buy_id": "mb_nike_reddit_q1", "artifact": { "property_id": {"type": "domain", "value": "example.com"}, "artifact_id": "article_12345", @@ -76,8 +104,7 @@ Validate delivery records against content safety policies. Designed for batch au "feature_id": "brand_safety", "status": "failed", "value": "high_risk", - "message": "Content contains violence", - "suggestion": "Review placement policies" + "message": "Content contains violence" }, { "feature_id": "competitor_adjacency", diff --git a/static/schemas/source/content-standards/validate-content-delivery-request.json b/static/schemas/source/content-standards/validate-content-delivery-request.json index 8ff37792f..e071fe31c 100644 --- a/static/schemas/source/content-standards/validate-content-delivery-request.json +++ b/static/schemas/source/content-standards/validate-content-delivery-request.json @@ -9,6 +9,10 @@ "type": "string", "description": "Standards configuration to validate against" }, + "media_buy_id": { + "type": "string", + "description": "Media buy these records belong to. Provides context for the verification agent." + }, "records": { "type": "array", "description": "Delivery records to validate (max 10,000)", @@ -20,6 +24,10 @@ "type": "string", "description": "Unique identifier for this delivery record" }, + "media_buy_id": { + "type": "string", + "description": "Media buy this record belongs to (when batching across multiple buys)" + }, "timestamp": { "type": "string", "format": "date-time", From be23208ea5873f437d24ba93bcf9778ac151ada9 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 7 Jan 2026 07:50:02 -0800 Subject: [PATCH 20/38] Add get_media_buy_artifacts task for separate content retrieval MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Content artifacts (text, images, video) are separate from delivery metrics. This new task allows buyers to request content samples from sellers for validation, keeping the data flow clean: - Buyer calls get_media_buy_artifacts to get content samples - Buyer validates samples with verification agent - Delivery metrics stay in get_media_buy_delivery Updates validate_content_delivery flow to reference new task. πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs.json | 1 + docs/governance/content-standards/index.mdx | 10 +- .../tasks/get_media_buy_artifacts.mdx | 197 ++++++++++++++++++ .../tasks/validate_content_delivery.mdx | 18 +- .../get-media-buy-artifacts-request.json | 69 ++++++ .../get-media-buy-artifacts-response.json | 124 +++++++++++ static/schemas/source/index.json | 10 + 7 files changed, 416 insertions(+), 13 deletions(-) create mode 100644 docs/governance/content-standards/tasks/get_media_buy_artifacts.mdx create mode 100644 static/schemas/source/content-standards/get-media-buy-artifacts-request.json create mode 100644 static/schemas/source/content-standards/get-media-buy-artifacts-response.json diff --git a/docs.json b/docs.json index 9bd489fa4..b2fa46f5a 100644 --- a/docs.json +++ b/docs.json @@ -116,6 +116,7 @@ "docs/governance/content-standards/tasks/update_content_standards", "docs/governance/content-standards/tasks/delete_content_standards", "docs/governance/content-standards/tasks/calibrate_content", + "docs/governance/content-standards/tasks/get_media_buy_artifacts", "docs/governance/content-standards/tasks/validate_content_delivery" ] } diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index 3628a59dc..feb13b6ef 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -54,12 +54,13 @@ sequenceDiagram Note over Seller: Local model evaluates artifacts end - Seller->>Buyer: Delivery records (get_media_buy_delivery) - Buyer->>Verifier: validate_content_delivery (sampled) + Buyer->>Seller: get_media_buy_artifacts (sampled) + Seller-->>Buyer: Content artifacts + Buyer->>Verifier: validate_content_delivery Verifier-->>Buyer: Validation results ``` -**Key insight**: Runtime decisioning happens locally at the seller (for scale). Delivery records flow through the buyer, who validates samples against the verification agent. +**Key insight**: Runtime decisioning happens locally at the seller (for scale). Buyers pull content samples from sellers and validate against the verification agent. ## Adjacency @@ -230,7 +231,8 @@ See [calibrate_content](/docs/governance/content-standards/tasks/calibrate_conte | Task | Description | |------|-------------| | [calibrate_content](/docs/governance/content-standards/tasks/calibrate_content) | Collaborative dialogue to align on policy interpretation | -| [validate_content_delivery](/docs/governance/content-standards/tasks/validate_content_delivery) | Batch validation of delivery records | +| [get_media_buy_artifacts](/docs/governance/content-standards/tasks/get_media_buy_artifacts) | Retrieve content artifacts from a media buy | +| [validate_content_delivery](/docs/governance/content-standards/tasks/validate_content_delivery) | Batch validation of content artifacts | ## Typical Providers diff --git a/docs/governance/content-standards/tasks/get_media_buy_artifacts.mdx b/docs/governance/content-standards/tasks/get_media_buy_artifacts.mdx new file mode 100644 index 000000000..59e29c905 --- /dev/null +++ b/docs/governance/content-standards/tasks/get_media_buy_artifacts.mdx @@ -0,0 +1,197 @@ +--- +title: get_media_buy_artifacts +sidebar_position: 8 +--- + +# get_media_buy_artifacts + +Retrieve content artifacts from a media buy for validation. This is separate from `get_media_buy_delivery` which returns performance metrics - artifacts contain the actual content (text, images, video) where ads were placed. + +**Response time**: < 5s (batch of 1,000 artifacts) + +## Data Flow + +```mermaid +sequenceDiagram + participant Buyer as Buyer Agent + participant Seller as Seller Agent + participant Verifier as Verification Agent + + Buyer->>Seller: get_media_buy_artifacts (sampled or full) + Seller-->>Buyer: Artifacts with content + Buyer->>Verifier: validate_content_delivery + Verifier-->>Buyer: Validation results +``` + +The buyer requests artifacts from the seller using the same media buy parameters. The seller returns content samples based on the agreed sampling rate. The buyer then validates these against the verification agent. + +## Request + +**Schema**: [get-media-buy-artifacts-request.json](https://adcontextprotocol.org/schemas/v2/content-standards/get-media-buy-artifacts-request.json) + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `media_buy_id` | string | Yes | Media buy to get artifacts from | +| `package_ids` | array | No | Filter to specific packages | +| `sampling` | object | No | Sampling parameters (defaults to media buy agreement) | +| `time_range` | object | No | Filter to specific time period | +| `limit` | integer | No | Maximum artifacts to return (default: 1000) | +| `cursor` | string | No | Pagination cursor for large result sets | + +### Sampling Options + +```json +{ + "sampling": { + "rate": 0.25, + "method": "random" + } +} +``` + +| Method | Description | +|--------|-------------| +| `random` | Random sample across all deliveries | +| `stratified` | Sample proportionally across packages/properties | +| `recent` | Most recent deliveries first | +| `failures_only` | Only artifacts that failed local evaluation | + +## Response + +**Schema**: [get-media-buy-artifacts-response.json](https://adcontextprotocol.org/schemas/v2/content-standards/get-media-buy-artifacts-response.json) + +### Success Response + +```json +{ + "media_buy_id": "mb_nike_reddit_q1", + "artifacts": [ + { + "record_id": "imp_12345", + "timestamp": "2025-01-15T10:30:00Z", + "package_id": "pkg_feed_standard", + "artifact": { + "property_id": {"type": "domain", "value": "reddit.com"}, + "artifact_id": "r_fitness_abc123", + "assets": [ + {"type": "text", "role": "title", "content": "Best protein sources for muscle building", "language": "en"}, + {"type": "text", "role": "paragraph", "content": "Looking for recommendations on high-quality protein sources...", "language": "en"}, + {"type": "image", "url": "https://cdn.reddit.com/fitness-image.jpg", "alt_text": "Person lifting weights"} + ] + }, + "creative_id": "creative_abc", + "local_verdict": "pass" + }, + { + "record_id": "imp_12346", + "timestamp": "2025-01-15T10:35:00Z", + "package_id": "pkg_feed_standard", + "artifact": { + "property_id": {"type": "domain", "value": "reddit.com"}, + "artifact_id": "r_news_politics_456", + "assets": [ + {"type": "text", "role": "title", "content": "Election Results Analysis", "language": "en"}, + {"type": "text", "role": "paragraph", "content": "The latest polling data shows...", "language": "en"} + ] + }, + "creative_id": "creative_abc", + "local_verdict": "fail" + } + ], + "sampling_info": { + "total_deliveries": 100000, + "sampled_count": 1000, + "effective_rate": 0.01, + "method": "random" + }, + "pagination": { + "cursor": "eyJvZmZzZXQiOjEwMDB9", + "has_more": true + } +} +``` + +### Response Fields + +| Field | Description | +|-------|-------------| +| `artifacts` | Array of delivery records with full artifact content | +| `artifacts[].local_verdict` | Seller's local model verdict (pass/fail/unevaluated) | +| `sampling_info` | How the sample was generated | +| `pagination` | Cursor for fetching more results | + +## Use Cases + +### Validate Sample Against Standards + +```python +# Get artifacts from seller +artifacts_response = seller_agent.get_media_buy_artifacts( + media_buy_id="mb_nike_reddit_q1", + sampling={"rate": 0.25, "method": "random"} +) + +# Convert to validation records +records = [ + { + "record_id": a["record_id"], + "timestamp": a["timestamp"], + "media_buy_id": artifacts_response["media_buy_id"], + "artifact": a["artifact"], + "creative_id": a["creative_id"] + } + for a in artifacts_response["artifacts"] +] + +# Validate against verification agent +validation = verification_agent.validate_content_delivery( + standards_id="nike_brand_safety", + media_buy_id="mb_nike_reddit_q1", + records=records +) + +# Check for drift between local and verified verdicts +for i, result in enumerate(validation["results"]): + local = artifacts_response["artifacts"][i]["local_verdict"] + verified = result["verdict"] + if local != verified: + print(f"Drift detected: {result['record_id']} - local={local}, verified={verified}") +``` + +### Focus on Local Failures + +```python +# Get only artifacts that failed local evaluation +failures = seller_agent.get_media_buy_artifacts( + media_buy_id="mb_nike_reddit_q1", + sampling={"method": "failures_only"}, + limit=100 +) + +# Verify these were correctly flagged +validation = verification_agent.validate_content_delivery( + standards_id="nike_brand_safety", + records=[{"record_id": a["record_id"], "artifact": a["artifact"]} + for a in failures["artifacts"]] +) + +# Check false positive rate +false_positives = sum(1 for r in validation["results"] if r["verdict"] == "pass") +print(f"False positive rate: {false_positives / len(failures['artifacts']):.1%}") +``` + +## Delivery vs Artifacts + +| Aspect | get_media_buy_delivery | get_media_buy_artifacts | +|--------|------------------------|-------------------------| +| **Purpose** | Performance reporting | Content validation | +| **Data size** | Small (metrics) | Large (full content) | +| **Frequency** | Regular reporting | Sampled validation | +| **Contains** | Impressions, clicks, spend | Text, images, video | +| **Consumer** | Buyer for optimization | Verification agent | + +## Related Tasks + +- [validate_content_delivery](/docs/governance/content-standards/tasks/validate_content_delivery) - Validate the artifacts +- [calibrate_content](/docs/governance/content-standards/tasks/calibrate_content) - Understand why artifacts pass/fail +- [get_media_buy_delivery](/docs/media-buy/task-reference/get_media_buy_delivery) - Get performance metrics diff --git a/docs/governance/content-standards/tasks/validate_content_delivery.mdx b/docs/governance/content-standards/tasks/validate_content_delivery.mdx index 802c9eb0e..72c858256 100644 --- a/docs/governance/content-standards/tasks/validate_content_delivery.mdx +++ b/docs/governance/content-standards/tasks/validate_content_delivery.mdx @@ -11,29 +11,28 @@ Validate delivery records against content safety policies. Designed for batch au ## Data Flow -Delivery validation follows the same path as delivery reporting: +Content artifacts are separate from delivery metrics. Use `get_media_buy_artifacts` to retrieve content for validation: ```mermaid sequenceDiagram - participant Seller as Seller Agent participant Buyer as Buyer Agent + participant Seller as Seller Agent participant Verifier as Verification Agent - Seller->>Buyer: Delivery records (via get_media_buy_delivery) - Note over Buyer: Buyer aggregates delivery data - Buyer->>Verifier: validate_content_delivery (sampled records) + Buyer->>Seller: get_media_buy_artifacts (sampled) + Seller-->>Buyer: Artifacts with content + Buyer->>Verifier: validate_content_delivery Verifier-->>Buyer: Validation results - Note over Buyer: Buyer tracks compliance ``` **Why through the buyer?** - The **buyer** owns the media buy and knows which `standards_id` applies -- The **buyer** aggregates delivery data from multiple sellers +- The **buyer** requests artifacts from sellers (separate from performance metrics) - The **buyer** is accountable for brand safety compliance - The **verification agent** works on behalf of the buyer -Sellers send delivery records to buyers via `get_media_buy_delivery`. The buyer then samples and validates against the verification agent. This keeps the seller's responsibility simple: report what was delivered. +This keeps responsibilities clear: sellers provide content samples via `get_media_buy_artifacts`, buyers validate samples against the verification agent. ## Request @@ -175,6 +174,7 @@ for result in response["results"]: ## Related Tasks +- [get_media_buy_artifacts](/docs/governance/content-standards/tasks/get_media_buy_artifacts) - Get content artifacts from seller +- [calibrate_content](/docs/governance/content-standards/tasks/calibrate_content) - Understand why artifacts pass/fail - [list_content_features](/docs/governance/content-standards/tasks/list_content_features) - Discover available features -- [calibrate_content](/docs/governance/content-standards/tasks/calibrate_content) - Collaborative calibration dialogue - [get_content_standards](/docs/governance/content-standards/tasks/get_content_standards) - Retrieve the policies diff --git a/static/schemas/source/content-standards/get-media-buy-artifacts-request.json b/static/schemas/source/content-standards/get-media-buy-artifacts-request.json new file mode 100644 index 000000000..90a33cbb7 --- /dev/null +++ b/static/schemas/source/content-standards/get-media-buy-artifacts-request.json @@ -0,0 +1,69 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/content-standards/get-media-buy-artifacts-request.json", + "title": "Get Media Buy Artifacts Request", + "description": "Request parameters for retrieving content artifacts from a media buy for validation", + "type": "object", + "properties": { + "media_buy_id": { + "type": "string", + "description": "Media buy to get artifacts from" + }, + "package_ids": { + "type": "array", + "items": { "type": "string" }, + "description": "Filter to specific packages within the media buy" + }, + "sampling": { + "type": "object", + "description": "Sampling parameters. Defaults to the sampling rate agreed in the media buy.", + "properties": { + "rate": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Sampling rate (0-1). 1.0 = all deliveries, 0.25 = 25% sample." + }, + "method": { + "type": "string", + "enum": ["random", "stratified", "recent", "failures_only"], + "description": "How to select the sample" + } + } + }, + "time_range": { + "type": "object", + "description": "Filter to specific time period", + "properties": { + "start": { + "type": "string", + "format": "date-time", + "description": "Start of time range (inclusive)" + }, + "end": { + "type": "string", + "format": "date-time", + "description": "End of time range (exclusive)" + } + } + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 10000, + "default": 1000, + "description": "Maximum artifacts to return per request" + }, + "cursor": { + "type": "string", + "description": "Pagination cursor for fetching subsequent pages" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["media_buy_id"] +} diff --git a/static/schemas/source/content-standards/get-media-buy-artifacts-response.json b/static/schemas/source/content-standards/get-media-buy-artifacts-response.json new file mode 100644 index 000000000..cb3e3bf3b --- /dev/null +++ b/static/schemas/source/content-standards/get-media-buy-artifacts-response.json @@ -0,0 +1,124 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/content-standards/get-media-buy-artifacts-response.json", + "title": "Get Media Buy Artifacts Response", + "description": "Response containing content artifacts from a media buy for validation", + "type": "object", + "oneOf": [ + { + "type": "object", + "description": "Success response with artifacts", + "properties": { + "media_buy_id": { + "type": "string", + "description": "Media buy these artifacts belong to" + }, + "artifacts": { + "type": "array", + "description": "Delivery records with full artifact content", + "items": { + "type": "object", + "properties": { + "record_id": { + "type": "string", + "description": "Unique identifier for this delivery record" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the delivery occurred" + }, + "package_id": { + "type": "string", + "description": "Which package this delivery belongs to" + }, + "artifact": { + "$ref": "/schemas/content-standards/artifact.json", + "description": "Full artifact with content assets" + }, + "creative_id": { + "type": "string", + "description": "Which creative was delivered" + }, + "local_verdict": { + "type": "string", + "enum": ["pass", "fail", "unevaluated"], + "description": "Seller's local model verdict for this artifact" + } + }, + "required": ["record_id", "artifact"] + } + }, + "sampling_info": { + "type": "object", + "description": "Information about how the sample was generated", + "properties": { + "total_deliveries": { + "type": "integer", + "description": "Total deliveries in the time range" + }, + "sampled_count": { + "type": "integer", + "description": "Number of artifacts in this response" + }, + "effective_rate": { + "type": "number", + "description": "Actual sampling rate achieved" + }, + "method": { + "type": "string", + "enum": ["random", "stratified", "recent", "failures_only"], + "description": "Sampling method used" + } + } + }, + "pagination": { + "type": "object", + "description": "Pagination information for large result sets", + "properties": { + "cursor": { + "type": "string", + "description": "Cursor for fetching the next page" + }, + "has_more": { + "type": "boolean", + "description": "Whether more results are available" + } + } + }, + "errors": { + "not": {}, + "description": "Field must not be present in success response" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["media_buy_id", "artifacts"] + }, + { + "type": "object", + "description": "Error response", + "properties": { + "errors": { + "type": "array", + "items": { "$ref": "/schemas/core/error.json" } + }, + "media_buy_id": { + "not": {}, + "description": "Field must not be present in error response" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["errors"] + } + ] +} diff --git a/static/schemas/source/index.json b/static/schemas/source/index.json index 4d911ac79..e5ace9529 100644 --- a/static/schemas/source/index.json +++ b/static/schemas/source/index.json @@ -610,6 +610,16 @@ "$ref": "/schemas/content-standards/validate-content-delivery-response.json", "description": "Response payload with batch validation results" } + }, + "get-media-buy-artifacts": { + "request": { + "$ref": "/schemas/content-standards/get-media-buy-artifacts-request.json", + "description": "Request parameters for retrieving content artifacts from a media buy" + }, + "response": { + "$ref": "/schemas/content-standards/get-media-buy-artifacts-response.json", + "description": "Response payload with content artifacts for validation" + } } } }, From a920df10895cd81fd05e87f87c53d16e65129b50 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Thu, 8 Jan 2026 06:29:32 -0800 Subject: [PATCH 21/38] Address review comments on validate_content_delivery schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Remove redundant top-level media_buy_id - keep at record level since records can come from multiple media buys 2. Add country and channel fields to delivery records for targeting context validation 3. Replace creative_id with brand_context placeholder object - the governance agent needs brand/SKU info, not opaque creative IDs. Schema marked as TBD pending brand identifier standardization. πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../tasks/get_media_buy_artifacts.mdx | 16 +++++++++--- .../tasks/validate_content_delivery.mdx | 18 +++++++++++-- .../get-media-buy-artifacts-response.json | 22 ++++++++++++++-- .../validate-content-delivery-request.json | 26 ++++++++++++++----- 4 files changed, 68 insertions(+), 14 deletions(-) diff --git a/docs/governance/content-standards/tasks/get_media_buy_artifacts.mdx b/docs/governance/content-standards/tasks/get_media_buy_artifacts.mdx index 59e29c905..7b430da99 100644 --- a/docs/governance/content-standards/tasks/get_media_buy_artifacts.mdx +++ b/docs/governance/content-standards/tasks/get_media_buy_artifacts.mdx @@ -79,7 +79,9 @@ The buyer requests artifacts from the seller using the same media buy parameters {"type": "image", "url": "https://cdn.reddit.com/fitness-image.jpg", "alt_text": "Person lifting weights"} ] }, - "creative_id": "creative_abc", + "country": "US", + "channel": "social", + "brand_context": {"brand_id": "nike_global", "sku_id": "air_max_2025"}, "local_verdict": "pass" }, { @@ -94,7 +96,9 @@ The buyer requests artifacts from the seller using the same media buy parameters {"type": "text", "role": "paragraph", "content": "The latest polling data shows...", "language": "en"} ] }, - "creative_id": "creative_abc", + "country": "US", + "channel": "social", + "brand_context": {"brand_id": "nike_global", "sku_id": "air_max_2025"}, "local_verdict": "fail" } ], @@ -116,6 +120,9 @@ The buyer requests artifacts from the seller using the same media buy parameters | Field | Description | |-------|-------------| | `artifacts` | Array of delivery records with full artifact content | +| `artifacts[].country` | ISO 3166-1 alpha-2 country code where delivery occurred | +| `artifacts[].channel` | Channel type (display, video, audio, social) | +| `artifacts[].brand_context` | Brand/SKU information for policy evaluation (schema TBD) | | `artifacts[].local_verdict` | Seller's local model verdict (pass/fail/unevaluated) | | `sampling_info` | How the sample was generated | | `pagination` | Cursor for fetching more results | @@ -138,7 +145,9 @@ records = [ "timestamp": a["timestamp"], "media_buy_id": artifacts_response["media_buy_id"], "artifact": a["artifact"], - "creative_id": a["creative_id"] + "country": a.get("country"), + "channel": a.get("channel"), + "brand_context": a.get("brand_context") } for a in artifacts_response["artifacts"] ] @@ -146,7 +155,6 @@ records = [ # Validate against verification agent validation = verification_agent.validate_content_delivery( standards_id="nike_brand_safety", - media_buy_id="mb_nike_reddit_q1", records=records ) diff --git a/docs/governance/content-standards/tasks/validate_content_delivery.mdx b/docs/governance/content-standards/tasks/validate_content_delivery.mdx index 72c858256..06c974d2e 100644 --- a/docs/governance/content-standards/tasks/validate_content_delivery.mdx +++ b/docs/governance/content-standards/tasks/validate_content_delivery.mdx @@ -41,7 +41,6 @@ This keeps responsibilities clear: sellers provide content samples via `get_medi | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `standards_id` | string | Yes | Standards configuration to validate against | -| `media_buy_id` | string | No | Media buy these records belong to (for context) | | `records` | array | Yes | Delivery records to validate (max 10,000) | | `feature_ids` | array | No | Specific features to evaluate (defaults to all) | | `include_passed` | boolean | No | Include passed records in results (default: true) | @@ -60,10 +59,25 @@ This keeps responsibilities clear: sellers provide content samples via `get_medi {"type": "text", "role": "title", "content": "Article Title"} ] }, - "creative_id": "creative_abc" + "country": "US", + "channel": "display", + "brand_context": { + "brand_id": "nike_global", + "sku_id": "air_max_2025" + } } ``` +| Field | Required | Description | +|-------|----------|-------------| +| `record_id` | Yes | Unique identifier for this delivery record | +| `artifact` | Yes | Content artifact where ad was delivered | +| `media_buy_id` | No | Media buy this record belongs to (for multi-buy batches) | +| `timestamp` | No | When the delivery occurred | +| `country` | No | ISO 3166-1 alpha-2 country code for targeting context | +| `channel` | No | Channel type (display, video, audio, social) | +| `brand_context` | No | Brand/SKU information for policy evaluation (schema TBD) | + ## Response **Schema**: [validate-content-delivery-response.json](https://adcontextprotocol.org/schemas/v2/content-standards/validate-content-delivery-response.json) diff --git a/static/schemas/source/content-standards/get-media-buy-artifacts-response.json b/static/schemas/source/content-standards/get-media-buy-artifacts-response.json index cb3e3bf3b..fc1035b41 100644 --- a/static/schemas/source/content-standards/get-media-buy-artifacts-response.json +++ b/static/schemas/source/content-standards/get-media-buy-artifacts-response.json @@ -36,9 +36,27 @@ "$ref": "/schemas/content-standards/artifact.json", "description": "Full artifact with content assets" }, - "creative_id": { + "country": { "type": "string", - "description": "Which creative was delivered" + "description": "ISO 3166-1 alpha-2 country code where delivery occurred" + }, + "channel": { + "type": "string", + "description": "Channel type (e.g., display, video, audio, social)" + }, + "brand_context": { + "type": "object", + "description": "Brand information for policy evaluation. Schema TBD - placeholder for brand identifiers.", + "properties": { + "brand_id": { + "type": "string", + "description": "Brand identifier" + }, + "sku_id": { + "type": "string", + "description": "Product/SKU identifier if applicable" + } + } }, "local_verdict": { "type": "string", diff --git a/static/schemas/source/content-standards/validate-content-delivery-request.json b/static/schemas/source/content-standards/validate-content-delivery-request.json index e071fe31c..4e35137b0 100644 --- a/static/schemas/source/content-standards/validate-content-delivery-request.json +++ b/static/schemas/source/content-standards/validate-content-delivery-request.json @@ -9,10 +9,6 @@ "type": "string", "description": "Standards configuration to validate against" }, - "media_buy_id": { - "type": "string", - "description": "Media buy these records belong to. Provides context for the verification agent." - }, "records": { "type": "array", "description": "Delivery records to validate (max 10,000)", @@ -37,9 +33,27 @@ "$ref": "/schemas/content-standards/artifact.json", "description": "Artifact where ad was delivered" }, - "creative_id": { + "country": { "type": "string", - "description": "Which creative was delivered" + "description": "ISO 3166-1 alpha-2 country code where delivery occurred" + }, + "channel": { + "type": "string", + "description": "Channel type (e.g., display, video, audio, social)" + }, + "brand_context": { + "type": "object", + "description": "Brand information for policy evaluation. Schema TBD - placeholder for brand identifiers.", + "properties": { + "brand_id": { + "type": "string", + "description": "Brand identifier" + }, + "sku_id": { + "type": "string", + "description": "Product/SKU identifier if applicable" + } + } } }, "required": ["record_id", "artifact"] From d7a85a4d0dcdb7e36e4abeb0eb2aca18959eca84 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Thu, 8 Jan 2026 18:48:35 -0800 Subject: [PATCH 22/38] Address PR review comments on Content Standards Protocol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #1/#16: Update artifact description to "content context where placements occur" - #2: Add STANDARDS_IN_USE error code to delete_content_standards - #4: Clarify effective_date semantics (null = not yet effective, multiple allowed) - #5: Add note about calibration data size concerns - #7: Document scope conflict handling in create_content_standards - #8/#9: Rename examples to calibration for consistency - #10: Increase validate_content_delivery response time to 60s - #11: Clarify artifact_id is opaque/flexible - #12/#13: Add notes about auth mechanism extraction for large artifacts - #14/#15: Add effective_date filter and abbreviated response note to list πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../content-standards/artifacts.mdx | 10 ++++++- .../tasks/calibrate_content.mdx | 2 +- .../tasks/create_content_standards.mdx | 30 +++++++++++++++++-- .../tasks/delete_content_standards.mdx | 19 +++++++++++- .../tasks/list_content_standards.mdx | 6 ++++ .../tasks/validate_content_delivery.mdx | 2 +- .../get-content-standards-response.json | 4 +-- 7 files changed, 64 insertions(+), 9 deletions(-) diff --git a/docs/governance/content-standards/artifacts.mdx b/docs/governance/content-standards/artifacts.mdx index 81bf42aaa..7d2f834a2 100644 --- a/docs/governance/content-standards/artifacts.mdx +++ b/docs/governance/content-standards/artifacts.mdx @@ -18,7 +18,7 @@ Artifacts represent the content context where an ad appears: - A **scene** in a CTV show - An **AI-generated image** in a chat conversation -Artifacts are identified by `property_id` + `artifact_id` - the property defines where the content lives, and the artifact_id is the property owner's identifier for that specific piece of content. +Artifacts are identified by `property_id` + `artifact_id` - the property defines where the content lives, and the artifact_id is an opaque identifier for that specific piece of content. The artifact_id scheme is flexible - it could be a URL path, a platform-specific ID, or any consistent identifier the property owner uses internally. ## Structure @@ -203,6 +203,14 @@ When pre-configuration isn't possible, include access credentials with individua } ``` +**Note on token size**: For artifacts with many assets, per-asset tokens can significantly increase payload size. Consider: + +1. **Pre-configured access** - Set up service account access once during onboarding +2. **Shared token reference** - Define tokens at the artifact level and reference by ID +3. **Signed URLs** - Use pre-signed URLs where the URL itself is the credential + +The `url` field is the access URL - it may differ from the artifact's canonical/published URL. For example, a published article at `https://news.example.com/article/123` might have assets served from `https://cdn.example.com/secured/...`. + ### Access Methods | Method | Use Case | diff --git a/docs/governance/content-standards/tasks/calibrate_content.mdx b/docs/governance/content-standards/tasks/calibrate_content.mdx index a7e16a59a..e65a71699 100644 --- a/docs/governance/content-standards/tasks/calibrate_content.mdx +++ b/docs/governance/content-standards/tasks/calibrate_content.mdx @@ -29,7 +29,7 @@ Unlike high-volume runtime evaluation, calibration is a **dialogue-based process **Schema**: [artifact.json](https://adcontextprotocol.org/schemas/v2/content-standards/artifact.json) -An artifact represents content adjacent to an ad placement - identified by `property_id` + `artifact_id` and represented as a collection of assets: +An artifact represents content context where ad placements occur - identified by `property_id` + `artifact_id` and represented as a collection of assets: ```json { diff --git a/docs/governance/content-standards/tasks/create_content_standards.mdx b/docs/governance/content-standards/tasks/create_content_standards.mdx index d5dc29d64..b62f742f6 100644 --- a/docs/governance/content-standards/tasks/create_content_standards.mdx +++ b/docs/governance/content-standards/tasks/create_content_standards.mdx @@ -15,7 +15,7 @@ Create a new content standards configuration. |-----------|------|----------|-------------| | `scope` | object | Yes | Where this standards configuration applies | | `policy` | string | Yes | Natural language policy prompt | -| `examples` | object | No | Training set of acceptable/unacceptable content | +| `calibration` | object | No | Training set of acceptable/unacceptable artifacts | | `competitive_separation` | array | No | Competitor brands to avoid | | `floor` | string | No | Safety floor baseline (`garm_floor` or `custom`) | @@ -30,7 +30,7 @@ Create a new content standards configuration. "description": "Nike EMEA - all digital channels" }, "policy": "Sports and fitness content is ideal. Lifestyle content about health and wellness is good. Entertainment content is generally acceptable. Avoid content about violence, controversial political topics, adult themes, or content that portrays sedentary lifestyle positively.", - "examples": { + "calibration": { "acceptable": [ { "type": "domain", "value": "espn.com", "language": "en" }, { "type": "domain", "value": "healthline.com", "language": "en" } @@ -56,7 +56,9 @@ Create a new content standards configuration. } ``` -### Error Response +### Error Responses + +**Invalid Scope:** ```json { @@ -69,6 +71,28 @@ Create a new content standards configuration. } ``` +**Scope Conflict:** + +```json +{ + "errors": [ + { + "code": "SCOPE_CONFLICT", + "message": "Standards already exist for brand 'nike' in country 'DE' on channel 'display'", + "conflicting_standards_id": "nike_emea_safety" + } + ] +} +``` + +## Scope Conflict Handling + +Multiple standards cannot have overlapping scopes for the same brand/country/channel combination. When creating standards that would conflict: + +1. **Check existing standards** - Use [list_content_standards](/docs/governance/content-standards/tasks/list_content_standards) filtered by your scope +2. **Update rather than create** - If standards already exist, use [update_content_standards](/docs/governance/content-standards/tasks/update_content_standards) +3. **Narrow the scope** - Adjust countries or channels to avoid overlap + ## Related Tasks - [list_content_standards](/docs/governance/content-standards/tasks/list_content_standards) - List all configurations diff --git a/docs/governance/content-standards/tasks/delete_content_standards.mdx b/docs/governance/content-standards/tasks/delete_content_standards.mdx index 5ac79e644..2f38244c5 100644 --- a/docs/governance/content-standards/tasks/delete_content_standards.mdx +++ b/docs/governance/content-standards/tasks/delete_content_standards.mdx @@ -34,7 +34,9 @@ Delete a content standards configuration. } ``` -### Error Response +### Error Responses + +**Not Found:** ```json { @@ -47,6 +49,21 @@ Delete a content standards configuration. } ``` +**Standards In Use:** + +```json +{ + "errors": [ + { + "code": "STANDARDS_IN_USE", + "message": "Cannot delete standards 'nike_emea_safety' - currently referenced by active media buys" + } + ] +} +``` + +Standards cannot be deleted while they are referenced by active media buys. Use [list_content_standards](/docs/governance/content-standards/tasks/list_content_standards) to identify usage, or archive standards by setting an expiration date rather than deleting. + ## Related Tasks - [list_content_standards](/docs/governance/content-standards/tasks/list_content_standards) - List all configurations diff --git a/docs/governance/content-standards/tasks/list_content_standards.mdx b/docs/governance/content-standards/tasks/list_content_standards.mdx index 6f1d7d1dd..a136a0f3f 100644 --- a/docs/governance/content-standards/tasks/list_content_standards.mdx +++ b/docs/governance/content-standards/tasks/list_content_standards.mdx @@ -16,9 +16,13 @@ List available content standards configurations. | `brand_ids` | array | No | Filter by brand identifiers | | `countries` | array | No | Filter by country codes | | `channels` | array | No | Filter by channels | +| `effective_before` | string | No | Filter to standards effective before this date (ISO 8601) | +| `effective_after` | string | No | Filter to standards effective after this date (ISO 8601) | ## Response +Returns an abbreviated list of standards configurations. Use [get_content_standards](/docs/governance/content-standards/tasks/get_content_standards) to retrieve full details including policy text and calibration data. + ### Success Response ```json @@ -27,6 +31,7 @@ List available content standards configurations. { "standards_id": "nike_emea_safety", "version": "1.2.0", + "effective_date": "2025-01-05T00:00:00Z", "scope": { "brand_ids": ["nike"], "countries_all": ["GB", "DE", "FR"], @@ -37,6 +42,7 @@ List available content standards configurations. { "standards_id": "nike_us_display", "version": "1.0.0", + "effective_date": "2025-01-10T00:00:00Z", "scope": { "brand_ids": ["nike"], "countries_all": ["US"], diff --git a/docs/governance/content-standards/tasks/validate_content_delivery.mdx b/docs/governance/content-standards/tasks/validate_content_delivery.mdx index 06c974d2e..f2d71e354 100644 --- a/docs/governance/content-standards/tasks/validate_content_delivery.mdx +++ b/docs/governance/content-standards/tasks/validate_content_delivery.mdx @@ -7,7 +7,7 @@ sidebar_position: 4 Validate delivery records against content safety policies. Designed for batch auditing of where ads were actually delivered. -**Response time**: < 10s (batch of 10,000 records) +**Response time**: < 60s (batch of 10,000 records) ## Data Flow diff --git a/static/schemas/source/content-standards/get-content-standards-response.json b/static/schemas/source/content-standards/get-content-standards-response.json index 814d92906..79439c3b7 100644 --- a/static/schemas/source/content-standards/get-content-standards-response.json +++ b/static/schemas/source/content-standards/get-content-standards-response.json @@ -20,7 +20,7 @@ "effective_date": { "type": "string", "format": "date-time", - "description": "When this version became effective" + "description": "When this standards version became effective. Null or absent means not yet effective. Multiple standards may be effective simultaneously for different scopes." }, "name": { "type": "string", @@ -47,7 +47,7 @@ }, "calibration": { "type": "object", - "description": "Training/test set to calibrate policy interpretation", + "description": "Training/test set to calibrate policy interpretation. Note: Large calibration sets may impact response size - consider using pagination or separate retrieval for large artifact collections.", "properties": { "acceptable": { "type": "array", From 94f67812f5ee55af413f7eff257026fbb08c3796 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Fri, 16 Jan 2026 15:31:51 -0500 Subject: [PATCH 23/38] Add Content Standards implementation guide Documents implementation patterns for three roles: - Sales agents: accepting standards, calibration, validation - Buyer agents: proxy pattern, orchestrating across publishers - Web publishers: URL-based content with scraper/cache patterns Also documents the content_access pattern for authenticated URL namespace access. Co-Authored-By: Claude Opus 4.5 --- docs.json | 1 + .../implementation-guide.mdx | 351 ++++++++++++++++++ 2 files changed, 352 insertions(+) create mode 100644 docs/governance/content-standards/implementation-guide.mdx diff --git a/docs.json b/docs.json index a4221e992..d3969354c 100644 --- a/docs.json +++ b/docs.json @@ -127,6 +127,7 @@ "pages": [ "docs/governance/content-standards/index", "docs/governance/content-standards/artifacts", + "docs/governance/content-standards/implementation-guide", { "group": "Tasks", "pages": [ diff --git a/docs/governance/content-standards/implementation-guide.mdx b/docs/governance/content-standards/implementation-guide.mdx new file mode 100644 index 000000000..01e295f09 --- /dev/null +++ b/docs/governance/content-standards/implementation-guide.mdx @@ -0,0 +1,351 @@ +--- +title: Implementation Guide +description: How to implement the Content Standards Protocol as a sales agent, buyer agent, or web publisher +--- + +This guide covers implementation patterns for the Content Standards Protocol from three perspectives: + +1. **Sales agents** accepting and enforcing brand safety standards +2. **Buyer agents** orchestrating content validation across publishers +3. **Web publishers** without direct content access + +## For Sales Agents + +If you're a sales agent (publisher ad server, SSP, or platform), implementing Content Standards means accepting buyer policies and enforcing them during delivery. + +### What You Need to Implement + +**1. Accept content standards references on `get_products` and `create_media_buy`** + +Buyers pass their standards via reference: + +```json +{ + "content_standards_ref": { + "standards_id": "nike_emea_brand_safety", + "agent_url": "https://brandsafety.nike.com" + } +} +``` + +When you receive this: +- Fetch the standards from the buyer's agent (cache aggressively - these don't change often) +- Validate you can support the requested features via `list_content_features` +- Store the association between the media buy and the standards + +**2. Build your evaluation capability** + +The standards document contains: +- Rules (what categories to allow/block) +- Calibration examples (how to interpret edge cases) +- Feature requirements (what signals matter to this buyer) + +Use this to train or configure your content evaluation system. This could be: +- An LLM with the rules as system prompt +- A classifier trained on the calibration examples +- A rules engine for deterministic evaluation +- A third-party brand safety vendor + +The protocol doesn't prescribe your implementation - just that you honor the standards. + +**3. Participate in calibration (optional but recommended)** + +If the buyer initiates `calibrate_content`, engage in the dialogue: + +```json +// Buyer sends ambiguous examples +{ + "standards_id": "nike_emea_brand_safety", + "artifacts": [ + { + "property_id": { "type": "domain", "value": "espn.com" }, + "artifact_id": "article_123", + "assets": [{ "type": "text", "role": "title", "content": "Marathon Runner Collapses at Finish Line" }] + } + ] +} + +// You respond with your interpretation +{ + "evaluations": [{ + "artifact_id": "article_123", + "suitable": true, + "confidence": 0.7, + "explanation": "Sports injury coverage in athletic context - aligns with brand's sports marketing positioning" + }] +} +``` + +Calibration aligns your interpretation with the buyer's expectations before delivery starts. + +**4. Support `validate_content_delivery` calls** + +After delivery, buyers may request validation of where their ads appeared: + +```json +{ + "standards_id": "nike_emea_brand_safety", + "records": [ + { "artifact_id": "article_456", "impression_count": 15000 }, + { "artifact_id": "article_789", "impression_count": 8500 } + ] +} +``` + +Return your evaluation for each: + +```json +{ + "results": [ + { "artifact_id": "article_456", "suitable": true, "categories": ["sports", "lifestyle"] }, + { "artifact_id": "article_789", "suitable": false, "categories": ["violence"], "reason": "Graphic injury imagery" } + ] +} +``` + +### Implementation Checklist + +- [ ] Parse `content_standards_ref` in `get_products` and `create_media_buy` +- [ ] Fetch and cache standards documents from buyer agents +- [ ] Implement `list_content_features` to advertise your capabilities +- [ ] Build content evaluation against buyer rules +- [ ] Implement `calibrate_content` for alignment dialogue +- [ ] Implement `validate_content_delivery` for post-delivery audit +- [ ] Return `get_media_buy_artifacts` so buyers can retrieve content for their own validation + +--- + +## For Buyer Agents + +If you're a buyer agent (DSP, agency platform, or brand safety service), you orchestrate content standards across multiple publishers. + +### The Proxy Pattern + +Buyer agents act as intermediaries between brands and publishers: + +``` +Brand β†’ Buyer Agent β†’ Sales Agent (Publisher) + ↓ + Content Standards + (hosted by buyer) +``` + +**1. Host the brand's standards** + +Create and store content standards for each brand: + +```json +{ + "standards_id": "nike_emea_brand_safety", + "name": "Nike EMEA Brand Safety Policy", + "brand_id": "nike", + "rules": { + "blocked_categories": ["violence", "adult", "drugs"], + "required_categories": [], + "competitive_separation": ["adidas", "puma", "reebok"] + }, + "calibration": [ + { + "artifact": { ... }, + "expected_result": { "suitable": true }, + "rationale": "Sports injury in athletic context is acceptable" + } + ] +} +``` + +Expose these via your agent's API so publishers can fetch them. + +**2. Pass standards references when buying** + +When creating media buys, include the reference: + +```json +{ + "product_id": "espn_sports_display", + "packages": [...], + "content_standards_ref": { + "standards_id": "nike_emea_brand_safety", + "agent_url": "https://buying.agency.com" + } +} +``` + +**3. Orchestrate calibration** + +Before campaigns launch, run calibration with each publisher: + +```python +# Pseudo-code for calibration orchestration +for publisher in campaign.publishers: + # Get sample content from publisher + artifacts = publisher.get_media_buy_artifacts(media_buy_id) + + # Run through your evaluation + our_results = evaluate(artifacts, standards) + + # Send to publisher for their evaluation + their_results = publisher.calibrate_content(standards_id, artifacts) + + # Compare and resolve discrepancies + discrepancies = find_discrepancies(our_results, their_results) + if discrepancies: + # Iterate with more examples until aligned + resolve_calibration(publisher, standards, discrepancies) +``` + +**4. Validate delivery** + +After campaign delivery: + +```python +# Get delivery records from publisher +artifacts = publisher.get_media_buy_artifacts(media_buy_id, include_delivery=True) + +# Option A: Trust publisher's evaluation +results = publisher.validate_content_delivery(standards_id, artifacts) + +# Option B: Validate yourself (fetch content, run your own model) +for artifact in artifacts: + content = fetch_content(artifact.url) # or use content_access + our_result = evaluate(content, standards) + compare_with_publisher(our_result, publisher_result) +``` + +**5. Report discrepancies** + +Surface any misalignment to the brand: +- Publisher marked suitable, you disagree β†’ potential brand safety incident +- Publisher marked unsuitable, you disagree β†’ potential missed inventory + +### Implementation Checklist + +- [ ] Store and serve content standards documents +- [ ] Include `content_standards_ref` in media buy requests +- [ ] Implement calibration orchestration workflow +- [ ] Fetch artifacts via `get_media_buy_artifacts` +- [ ] Validate delivery via `validate_content_delivery` or your own evaluation +- [ ] Build discrepancy reporting for brands + +--- + +## For Web Publishers (URL-Based Content) + +If you're a web publisher without direct programmatic access to your content (e.g., your CMS doesn't expose an API), you can use a service provider pattern. + +### The Problem + +A buyer sends you artifact IDs (URLs) for validation: + +```json +{ + "records": [ + { "artifact_id": "https://yoursite.com/article/12345" }, + { "artifact_id": "https://yoursite.com/article/67890" } + ] +} +``` + +But your ad server doesn't have the article content - it just knows the URL. + +### Solution: Content Service Provider + +Use a scraping/content service to resolve URLs to content: + +``` +Buyer β†’ Your Ad Server β†’ Content Service β†’ Your Website + ↓ + Evaluation +``` + +**Option A: Integrate a content service** + +```python +async def validate_content_delivery(standards_id, records): + standards = fetch_standards(standards_id) + results = [] + + for record in records: + # Use content service to fetch and parse + content = await content_service.fetch(record.artifact_id) + + # Evaluate against standards + result = evaluate(content, standards) + results.append(result) + + return results +``` + +**Option B: Pre-index your content** + +Build a content index that maps URLs to extracted content: + +```json +{ + "content_access": { + "url_pattern": "https://content-cache.yoursite.com/*", + "auth": { "type": "bearer", "token": "..." } + } +} +``` + +When buyers request validation, they can fetch content directly from your cache. + +**Option C: Provide artifact URLs in responses** + +In `get_media_buy_artifacts`, include URLs that resolve to content: + +```json +{ + "artifacts": [ + { + "property_id": { "type": "domain", "value": "yoursite.com" }, + "artifact_id": "article_12345", + "url": "https://yoursite.com/article/12345", + "assets": [] // Empty - buyer fetches via URL + } + ], + "content_access": { + "url_pattern": "https://yoursite.com/*", + "auth": null // Public content + } +} +``` + +The buyer then fetches and evaluates the content themselves. + +### Implementation Checklist + +- [ ] Decide on content access pattern (service, cache, or buyer-fetch) +- [ ] Implement content fetching/caching if needed +- [ ] Expose `content_access` configuration for authenticated content +- [ ] Return resolvable URLs in `get_media_buy_artifacts` + +--- + +## Content Access Pattern + +All three roles may need to exchange content securely. The `content_access` pattern provides authenticated access to a URL namespace: + +```json +{ + "content_access": { + "url_pattern": "https://cache.example.com/*", + "auth": { + "type": "bearer", + "token": "eyJ..." + } + } +} +``` + +- **url_pattern**: URLs matching this pattern use this auth +- **auth.type**: Authentication method (`bearer`, `api_key`, `signed_url`) +- **auth.token**: The credential + +Include this in: +- `create_content_standards` (buyer β†’ seller: "fetch my standards here") +- `get_media_buy_artifacts` response (seller β†’ buyer: "fetch content here") +- `validate_content_delivery` request (buyer β†’ seller: "fetch content here if you need it") + +This avoids per-asset tokens and keeps payloads small while enabling secure content exchange. From a7fa1713c7fed2a3c461e79a1e6d83c6c9539629 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sat, 17 Jan 2026 06:34:51 -0500 Subject: [PATCH 24/38] Fix implementation guide flow for list_content_features Clarifies that: - Buyers call list_content_features to discover publisher capabilities BEFORE creating standards - Sellers don't need to call back - the standards document contains all required features/rules - Updated step numbering for buyer agent section Co-Authored-By: Claude Opus 4.5 --- .../implementation-guide.mdx | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/docs/governance/content-standards/implementation-guide.mdx b/docs/governance/content-standards/implementation-guide.mdx index 01e295f09..7bc51c24e 100644 --- a/docs/governance/content-standards/implementation-guide.mdx +++ b/docs/governance/content-standards/implementation-guide.mdx @@ -30,15 +30,15 @@ Buyers pass their standards via reference: When you receive this: - Fetch the standards from the buyer's agent (cache aggressively - these don't change often) -- Validate you can support the requested features via `list_content_features` +- The standards document tells you exactly what to enforce - no need to call back - Store the association between the media buy and the standards **2. Build your evaluation capability** -The standards document contains: +The standards document contains everything you need: - Rules (what categories to allow/block) - Calibration examples (how to interpret edge cases) -- Feature requirements (what signals matter to this buyer) +- Required features (what signals this buyer cares about) Use this to train or configure your content evaluation system. This could be: - An LLM with the rules as system prompt @@ -105,13 +105,13 @@ Return your evaluation for each: ### Implementation Checklist +- [ ] Implement `list_content_features` so buyers can discover your capabilities before creating standards - [ ] Parse `content_standards_ref` in `get_products` and `create_media_buy` - [ ] Fetch and cache standards documents from buyer agents -- [ ] Implement `list_content_features` to advertise your capabilities -- [ ] Build content evaluation against buyer rules +- [ ] Build content evaluation against the rules in the standards document - [ ] Implement `calibrate_content` for alignment dialogue - [ ] Implement `validate_content_delivery` for post-delivery audit -- [ ] Return `get_media_buy_artifacts` so buyers can retrieve content for their own validation +- [ ] Implement `get_media_buy_artifacts` so buyers can retrieve content for their own validation --- @@ -130,7 +130,24 @@ Brand β†’ Buyer Agent β†’ Sales Agent (Publisher) (hosted by buyer) ``` -**1. Host the brand's standards** +**1. Discover publisher capabilities** + +Before creating standards, call `list_content_features` on each publisher to understand what they can evaluate: + +```json +// Response from publisher +{ + "features": [ + { "id": "garm_brand_safety", "name": "GARM Brand Safety Categories" }, + { "id": "sentiment", "name": "Content Sentiment Analysis" }, + { "id": "competitive_separation", "name": "Competitor Blocking" } + ] +} +``` + +Use this to build standards that reference features the publisher actually supports. + +**2. Host the brand's standards** Create and store content standards for each brand: @@ -156,7 +173,7 @@ Create and store content standards for each brand: Expose these via your agent's API so publishers can fetch them. -**2. Pass standards references when buying** +**3. Pass standards references when buying** When creating media buys, include the reference: @@ -171,7 +188,7 @@ When creating media buys, include the reference: } ``` -**3. Orchestrate calibration** +**4. Orchestrate calibration** Before campaigns launch, run calibration with each publisher: @@ -194,7 +211,7 @@ for publisher in campaign.publishers: resolve_calibration(publisher, standards, discrepancies) ``` -**4. Validate delivery** +**5. Validate delivery** After campaign delivery: @@ -212,7 +229,7 @@ for artifact in artifacts: compare_with_publisher(our_result, publisher_result) ``` -**5. Report discrepancies** +**6. Report discrepancies** Surface any misalignment to the brand: - Publisher marked suitable, you disagree β†’ potential brand safety incident @@ -220,6 +237,7 @@ Surface any misalignment to the brand: ### Implementation Checklist +- [ ] Call `list_content_features` to discover publisher capabilities - [ ] Store and serve content standards documents - [ ] Include `content_standards_ref` in media buy requests - [ ] Implement calibration orchestration workflow From 5da8bbd07238b2fe472f65379e5676d03742956a Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sat, 17 Jan 2026 07:02:36 -0500 Subject: [PATCH 25/38] Remove list_content_features - simplify to accept-or-reject model Content Standards Protocol should be binary: sellers either accept the standards they can fulfill or reject what they cannot. Feature discovery adds unnecessary complexity when we don't yet know how adjacency will play out across different publishers. - Remove list_content_features task and schemas - Simplify implementation guide to focus on accept-or-reject model - Update navigation and schema index Co-Authored-By: Claude Opus 4.5 --- docs.json | 1 - .../implementation-guide.mdx | 100 +++++++-------- docs/governance/content-standards/index.mdx | 1 - .../tasks/calibrate_content.mdx | 1 - .../tasks/get_content_standards.mdx | 2 +- .../tasks/list_content_features.mdx | 117 ------------------ .../tasks/validate_content_delivery.mdx | 1 - .../list-content-features-request.json | 19 --- .../list-content-features-response.json | 106 ---------------- static/schemas/source/index.json | 10 -- 10 files changed, 43 insertions(+), 315 deletions(-) delete mode 100644 docs/governance/content-standards/tasks/list_content_features.mdx delete mode 100644 static/schemas/source/content-standards/list-content-features-request.json delete mode 100644 static/schemas/source/content-standards/list-content-features-response.json diff --git a/docs.json b/docs.json index d3969354c..3384c0e78 100644 --- a/docs.json +++ b/docs.json @@ -131,7 +131,6 @@ { "group": "Tasks", "pages": [ - "docs/governance/content-standards/tasks/list_content_features", "docs/governance/content-standards/tasks/list_content_standards", "docs/governance/content-standards/tasks/get_content_standards", "docs/governance/content-standards/tasks/create_content_standards", diff --git a/docs/governance/content-standards/implementation-guide.mdx b/docs/governance/content-standards/implementation-guide.mdx index 7bc51c24e..d0538c81b 100644 --- a/docs/governance/content-standards/implementation-guide.mdx +++ b/docs/governance/content-standards/implementation-guide.mdx @@ -13,6 +13,18 @@ This guide covers implementation patterns for the Content Standards Protocol fro If you're a sales agent (publisher ad server, SSP, or platform), implementing Content Standards means accepting buyer policies and enforcing them during delivery. +### The Core Model + +When a buyer includes a `content_standards_ref` in their request, you must: + +1. **Fetch the standards** and evaluate if you can fulfill them +2. **Accept or reject** the buy based on your capabilities +3. **Calibrate** your evaluation model against the buyer's expectations +4. **Enforce** the standards during delivery +5. **Report** validation results after delivery + +If you cannot fulfill the content standards requirements, **reject the buy**. Don't accept a campaign you can't properly enforce. + ### What You Need to Implement **1. Accept content standards references on `get_products` and `create_media_buy`** @@ -29,18 +41,26 @@ Buyers pass their standards via reference: ``` When you receive this: -- Fetch the standards from the buyer's agent (cache aggressively - these don't change often) -- The standards document tells you exactly what to enforce - no need to call back -- Store the association between the media buy and the standards +- Fetch the standards document from the buyer's agent +- Evaluate whether you can enforce these requirements +- If you cannot meet the standards, reject the request +- If you can, accept and store the association with the media buy -**2. Build your evaluation capability** +**2. Decide: Can you fulfill this?** -The standards document contains everything you need: +The standards document contains: - Rules (what categories to allow/block) - Calibration examples (how to interpret edge cases) -- Required features (what signals this buyer cares about) +- Competitive separation requirements +- Any other buyer-specific policies -Use this to train or configure your content evaluation system. This could be: +Review these requirements against your capabilities. Different publishers have different definitions of "adjacency" - Reddit might include comments, YouTube might include related videos, a news site might mean the article body. That's fine - as long as you can meaningfully enforce the buyer's intent, accept the buy. + +If you can't - for example, the buyer requires competitive separation but you don't support it, or they need adjacency data for a channel where it doesn't apply (like billboards) - reject the buy. + +**3. Build your evaluation capability** + +Use the standards document to train or configure your content evaluation system. This could be: - An LLM with the rules as system prompt - A classifier trained on the calibration examples - A rules engine for deterministic evaluation @@ -48,12 +68,12 @@ Use this to train or configure your content evaluation system. This could be: The protocol doesn't prescribe your implementation - just that you honor the standards. -**3. Participate in calibration (optional but recommended)** +**4. Participate in calibration** If the buyer initiates `calibrate_content`, engage in the dialogue: ```json -// Buyer sends ambiguous examples +// Buyer sends examples for evaluation { "standards_id": "nike_emea_brand_safety", "artifacts": [ @@ -78,37 +98,16 @@ If the buyer initiates `calibrate_content`, engage in the dialogue: Calibration aligns your interpretation with the buyer's expectations before delivery starts. -**4. Support `validate_content_delivery` calls** - -After delivery, buyers may request validation of where their ads appeared: - -```json -{ - "standards_id": "nike_emea_brand_safety", - "records": [ - { "artifact_id": "article_456", "impression_count": 15000 }, - { "artifact_id": "article_789", "impression_count": 8500 } - ] -} -``` - -Return your evaluation for each: +**5. Support post-delivery validation** -```json -{ - "results": [ - { "artifact_id": "article_456", "suitable": true, "categories": ["sports", "lifestyle"] }, - { "artifact_id": "article_789", "suitable": false, "categories": ["violence"], "reason": "Graphic injury imagery" } - ] -} -``` +After delivery, support `validate_content_delivery` calls and `get_media_buy_artifacts` so buyers can audit where their ads appeared. ### Implementation Checklist -- [ ] Implement `list_content_features` so buyers can discover your capabilities before creating standards - [ ] Parse `content_standards_ref` in `get_products` and `create_media_buy` -- [ ] Fetch and cache standards documents from buyer agents -- [ ] Build content evaluation against the rules in the standards document +- [ ] Fetch and evaluate standards documents from buyer agents +- [ ] Reject buys you cannot fulfill - don't accept campaigns you can't enforce +- [ ] Build content evaluation against the standards document - [ ] Implement `calibrate_content` for alignment dialogue - [ ] Implement `validate_content_delivery` for post-delivery audit - [ ] Implement `get_media_buy_artifacts` so buyers can retrieve content for their own validation @@ -130,24 +129,7 @@ Brand β†’ Buyer Agent β†’ Sales Agent (Publisher) (hosted by buyer) ``` -**1. Discover publisher capabilities** - -Before creating standards, call `list_content_features` on each publisher to understand what they can evaluate: - -```json -// Response from publisher -{ - "features": [ - { "id": "garm_brand_safety", "name": "GARM Brand Safety Categories" }, - { "id": "sentiment", "name": "Content Sentiment Analysis" }, - { "id": "competitive_separation", "name": "Competitor Blocking" } - ] -} -``` - -Use this to build standards that reference features the publisher actually supports. - -**2. Host the brand's standards** +**1. Host the brand's standards** Create and store content standards for each brand: @@ -173,7 +155,7 @@ Create and store content standards for each brand: Expose these via your agent's API so publishers can fetch them. -**3. Pass standards references when buying** +**2. Pass standards references when buying** When creating media buys, include the reference: @@ -188,7 +170,9 @@ When creating media buys, include the reference: } ``` -**4. Orchestrate calibration** +If the publisher cannot fulfill the standards, they should reject the buy. Handle rejections gracefully and find alternative inventory. + +**3. Orchestrate calibration** Before campaigns launch, run calibration with each publisher: @@ -211,7 +195,7 @@ for publisher in campaign.publishers: resolve_calibration(publisher, standards, discrepancies) ``` -**5. Validate delivery** +**4. Validate delivery** After campaign delivery: @@ -229,7 +213,7 @@ for artifact in artifacts: compare_with_publisher(our_result, publisher_result) ``` -**6. Report discrepancies** +**5. Report discrepancies** Surface any misalignment to the brand: - Publisher marked suitable, you disagree β†’ potential brand safety incident @@ -237,9 +221,9 @@ Surface any misalignment to the brand: ### Implementation Checklist -- [ ] Call `list_content_features` to discover publisher capabilities - [ ] Store and serve content standards documents - [ ] Include `content_standards_ref` in media buy requests +- [ ] Handle rejections from publishers who can't fulfill standards - [ ] Implement calibration orchestration workflow - [ ] Fetch artifacts via `get_media_buy_artifacts` - [ ] Validate delivery via `validate_content_delivery` or your own evaluation diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index feb13b6ef..745aa632a 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -214,7 +214,6 @@ See [calibrate_content](/docs/governance/content-standards/tasks/calibrate_conte | Task | Description | |------|-------------| -| [list_content_features](/docs/governance/content-standards/tasks/list_content_features) | Discover features the agent can evaluate | | [list_content_standards](/docs/governance/content-standards/tasks/list_content_standards) | List available standards configurations | | [get_content_standards](/docs/governance/content-standards/tasks/get_content_standards) | Retrieve a specific standards configuration | diff --git a/docs/governance/content-standards/tasks/calibrate_content.mdx b/docs/governance/content-standards/tasks/calibrate_content.mdx index e65a71699..801ee3bb5 100644 --- a/docs/governance/content-standards/tasks/calibrate_content.mdx +++ b/docs/governance/content-standards/tasks/calibrate_content.mdx @@ -225,5 +225,4 @@ The key insight is that the dialogue happens at the **protocol layer**, not the ## Related Tasks - [get_content_standards](/docs/governance/content-standards/tasks/get_content_standards) - Retrieve the policies being calibrated against -- [list_content_features](/docs/governance/content-standards/tasks/list_content_features) - Discover available features - [validate_content_delivery](/docs/governance/content-standards/tasks/validate_content_delivery) - Post-campaign delivery validation diff --git a/docs/governance/content-standards/tasks/get_content_standards.mdx b/docs/governance/content-standards/tasks/get_content_standards.mdx index 78479d666..b01177fcd 100644 --- a/docs/governance/content-standards/tasks/get_content_standards.mdx +++ b/docs/governance/content-standards/tasks/get_content_standards.mdx @@ -78,5 +78,5 @@ Retrieve content safety policies for a specific standards configuration. ## Related Tasks -- [list_content_features](/docs/governance/content-standards/tasks/list_content_features) - Discover available features - [calibrate_content](/docs/governance/content-standards/tasks/calibrate_content) - Collaborative calibration against these standards +- [list_content_standards](/docs/governance/content-standards/tasks/list_content_standards) - List available standards configurations diff --git a/docs/governance/content-standards/tasks/list_content_features.mdx b/docs/governance/content-standards/tasks/list_content_features.mdx deleted file mode 100644 index 332af17a2..000000000 --- a/docs/governance/content-standards/tasks/list_content_features.mdx +++ /dev/null @@ -1,117 +0,0 @@ ---- -title: list_content_features -sidebar_position: 1 ---- - -# list_content_features - -Discover what features a Content Standards agent can evaluate. Features define the verification capabilities available. - -**Response time**: < 500ms - -## Request - -**Schema**: [list-content-features-request.json](https://adcontextprotocol.org/schemas/v2/content-standards/list-content-features-request.json) - -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `standards_id` | string | No | Filter features available for a specific standards configuration | - -## Response - -**Schema**: [list-content-features-response.json](https://adcontextprotocol.org/schemas/v2/content-standards/list-content-features-response.json) - -### Success Response - -```json -{ - "features": [ - { - "feature_id": "brand_safety", - "name": "Brand Safety", - "description": "Overall brand safety classification", - "type": "categorical", - "allowed_values": ["safe", "low_risk", "medium_risk", "high_risk", "blocked"], - "severity": "block" - }, - { - "feature_id": "sentiment", - "name": "Content Sentiment", - "description": "Overall sentiment of the content", - "type": "categorical", - "allowed_values": ["positive", "neutral", "negative"], - "severity": "warn" - }, - { - "feature_id": "sentiment_score", - "name": "Sentiment Score", - "description": "Numerical sentiment score", - "type": "quantitative", - "range": { "min": -1.0, "max": 1.0 }, - "severity": "info" - }, - { - "feature_id": "competitor_adjacency", - "name": "Competitor Adjacency", - "description": "Whether content mentions competitor brands", - "type": "binary", - "severity": "block" - }, - { - "feature_id": "garm_category", - "name": "GARM Category", - "description": "GARM Brand Safety Floor category", - "type": "categorical", - "allowed_values": ["adult_explicit", "arms_ammunition", "crime_harmful", "death_injury", "online_piracy", "hate_speech", "terrorism", "spam", "safe"], - "severity": "block" - }, - { - "feature_id": "news_quality", - "name": "News Quality Score", - "description": "Quality assessment for news content", - "type": "quantitative", - "range": { "min": 0, "max": 100 }, - "severity": "warn" - } - ] -} -``` - -### Feature Types - -| Type | Description | Example | -|------|-------------|---------| -| `binary` | True/false evaluation | `competitor_adjacency` | -| `quantitative` | Numeric value in a range | `sentiment_score` (-1.0 to 1.0) | -| `categorical` | One of allowed values | `brand_safety` (safe, low_risk, etc.) | - -### Severity Levels - -| Severity | Meaning | -|----------|---------| -| `block` | Failed features with this severity should block the placement | -| `warn` | Advisory warning, may not block | -| `info` | Informational, for reporting only | - -### Regional Features - -Some features may only be available in certain regions: - -```json -{ - "feature_id": "imagery_policy", - "name": "Imagery Policy Compliance", - "description": "Compliance with regional imagery policies", - "type": "binary", - "severity": "block", - "coverage": { - "regions": ["EMEA", "APAC"], - "markets": ["SA", "AE", "KW"] - } -} -``` - -## Related Tasks - -- [calibrate_content](/docs/governance/content-standards/tasks/calibrate_content) - Uses these features for evaluation -- [validate_content_delivery](/docs/governance/content-standards/tasks/validate_content_delivery) - Batch verification with feature results diff --git a/docs/governance/content-standards/tasks/validate_content_delivery.mdx b/docs/governance/content-standards/tasks/validate_content_delivery.mdx index f2d71e354..6d55bd4c1 100644 --- a/docs/governance/content-standards/tasks/validate_content_delivery.mdx +++ b/docs/governance/content-standards/tasks/validate_content_delivery.mdx @@ -190,5 +190,4 @@ for result in response["results"]: - [get_media_buy_artifacts](/docs/governance/content-standards/tasks/get_media_buy_artifacts) - Get content artifacts from seller - [calibrate_content](/docs/governance/content-standards/tasks/calibrate_content) - Understand why artifacts pass/fail -- [list_content_features](/docs/governance/content-standards/tasks/list_content_features) - Discover available features - [get_content_standards](/docs/governance/content-standards/tasks/get_content_standards) - Retrieve the policies diff --git a/static/schemas/source/content-standards/list-content-features-request.json b/static/schemas/source/content-standards/list-content-features-request.json deleted file mode 100644 index 07d4b67f2..000000000 --- a/static/schemas/source/content-standards/list-content-features-request.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "/schemas/content-standards/list-content-features-request.json", - "title": "List Content Features Request", - "description": "Request parameters for discovering features the agent can evaluate", - "type": "object", - "properties": { - "standards_id": { - "type": "string", - "description": "Filter features available for a specific standards configuration" - }, - "context": { - "$ref": "/schemas/core/context.json" - }, - "ext": { - "$ref": "/schemas/core/ext.json" - } - } -} diff --git a/static/schemas/source/content-standards/list-content-features-response.json b/static/schemas/source/content-standards/list-content-features-response.json deleted file mode 100644 index ca8596369..000000000 --- a/static/schemas/source/content-standards/list-content-features-response.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "/schemas/content-standards/list-content-features-response.json", - "title": "List Content Features Response", - "description": "Response payload with features and their types", - "type": "object", - "oneOf": [ - { - "type": "object", - "description": "Success response", - "properties": { - "features": { - "type": "array", - "description": "Available features for content evaluation", - "items": { - "type": "object", - "properties": { - "feature_id": { - "type": "string", - "description": "Unique identifier for this feature" - }, - "name": { - "type": "string", - "description": "Human-readable feature name" - }, - "description": { - "type": "string", - "description": "What this feature evaluates" - }, - "type": { - "type": "string", - "enum": ["binary", "quantitative", "categorical"], - "description": "Feature type" - }, - "range": { - "type": "object", - "description": "For quantitative features, the valid range", - "properties": { - "min": { "type": "number" }, - "max": { "type": "number" } - } - }, - "allowed_values": { - "type": "array", - "description": "For categorical features, the allowed values", - "items": { "type": "string" } - }, - "severity": { - "type": "string", - "enum": ["block", "warn", "info"], - "description": "Default severity when this feature fails" - }, - "coverage": { - "type": "object", - "description": "Geographic coverage for regional features", - "properties": { - "regions": { - "type": "array", - "items": { "type": "string" } - }, - "markets": { - "type": "array", - "items": { "type": "string" } - } - } - } - }, - "required": ["feature_id", "name", "type"] - } - }, - "errors": { - "not": {}, - "description": "Field must not be present in success response" - }, - "context": { - "$ref": "/schemas/core/context.json" - }, - "ext": { - "$ref": "/schemas/core/ext.json" - } - }, - "required": ["features"] - }, - { - "type": "object", - "description": "Error response", - "properties": { - "errors": { - "type": "array", - "items": { "$ref": "/schemas/core/error.json" } - }, - "features": { - "not": {}, - "description": "Field must not be present in error response" - }, - "context": { - "$ref": "/schemas/core/context.json" - }, - "ext": { - "$ref": "/schemas/core/ext.json" - } - }, - "required": ["errors"] - } - ] -} diff --git a/static/schemas/source/index.json b/static/schemas/source/index.json index ef6fb30f1..af633d5c2 100644 --- a/static/schemas/source/index.json +++ b/static/schemas/source/index.json @@ -671,16 +671,6 @@ "description": "Response payload for delete_property_list task" } }, - "list-content-features": { - "request": { - "$ref": "/schemas/content-standards/list-content-features-request.json", - "description": "Request parameters for discovering available content safety features" - }, - "response": { - "$ref": "/schemas/content-standards/list-content-features-response.json", - "description": "Response payload with available content features" - } - }, "get-content-standards": { "request": { "$ref": "/schemas/content-standards/get-content-standards-request.json", From 14acdf38a48f6195151a4128bc4399c4732c7152 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sat, 17 Jan 2026 11:37:27 -0500 Subject: [PATCH 26/38] Fix calibration and validation flows in implementation guide - Calibration: Seller sends samples TO buyer's governance agent (not reverse) - Validation: Governance agent implements validate_content_delivery (not seller) - Seller's job: Implement get_media_buy_artifacts and support webhooks - Added artifact webhook and reporting webhook requirements for sellers Co-Authored-By: Claude Opus 4.5 --- .../implementation-guide.mdx | 112 +++++++++++------- 1 file changed, 68 insertions(+), 44 deletions(-) diff --git a/docs/governance/content-standards/implementation-guide.mdx b/docs/governance/content-standards/implementation-guide.mdx index d0538c81b..eca57d4fb 100644 --- a/docs/governance/content-standards/implementation-guide.mdx +++ b/docs/governance/content-standards/implementation-guide.mdx @@ -70,10 +70,10 @@ The protocol doesn't prescribe your implementation - just that you honor the sta **4. Participate in calibration** -If the buyer initiates `calibrate_content`, engage in the dialogue: +Before campaigns launch, calibrate your interpretation against the buyer's governance agent. You send sample artifacts, they tell you how they would rate them: ```json -// Buyer sends examples for evaluation +// You send examples from your inventory { "standards_id": "nike_emea_brand_safety", "artifacts": [ @@ -85,22 +85,43 @@ If the buyer initiates `calibrate_content`, engage in the dialogue: ] } -// You respond with your interpretation +// Buyer's governance agent responds with their interpretation { "evaluations": [{ "artifact_id": "article_123", "suitable": true, - "confidence": 0.7, + "confidence": 0.9, "explanation": "Sports injury coverage in athletic context - aligns with brand's sports marketing positioning" }] } ``` -Calibration aligns your interpretation with the buyer's expectations before delivery starts. +Use these responses to train your local model. If you disagree with a rating, ask follow-up questions to understand the buyer's reasoning. -**5. Support post-delivery validation** +**5. Support artifact retrieval and webhooks** -After delivery, support `validate_content_delivery` calls and `get_media_buy_artifacts` so buyers can audit where their ads appeared. +After delivery, buyers need to audit where their ads appeared. Your responsibilities: + +- **Implement `get_media_buy_artifacts`** - Return content artifacts for delivered impressions so buyers can validate against their governance agent +- **Support artifact webhooks** - Push artifacts to buyer-specified endpoints in real-time or batched +- **Support reporting webhooks** - Push delivery reports to buyer-specified endpoints + +The **governance agent** (not you) implements `validate_content_delivery`. Buyers fetch artifacts from you, then send them to their governance agent for validation. + +```json +// Artifact webhook payload +{ + "media_buy_id": "mb_nike_reddit_q1", + "artifacts": [ + { + "property_id": { "type": "domain", "value": "reddit.com" }, + "artifact_id": "r_fitness_abc123", + "assets": [{ "type": "text", "role": "title", "content": "Best protein sources" }], + "delivered_at": "2025-01-15T10:30:00Z" + } + ] +} +``` ### Implementation Checklist @@ -108,9 +129,10 @@ After delivery, support `validate_content_delivery` calls and `get_media_buy_art - [ ] Fetch and evaluate standards documents from buyer agents - [ ] Reject buys you cannot fulfill - don't accept campaigns you can't enforce - [ ] Build content evaluation against the standards document -- [ ] Implement `calibrate_content` for alignment dialogue -- [ ] Implement `validate_content_delivery` for post-delivery audit -- [ ] Implement `get_media_buy_artifacts` so buyers can retrieve content for their own validation +- [ ] Call `calibrate_content` on buyer's governance agent to align interpretation +- [ ] Implement `get_media_buy_artifacts` so buyers can retrieve content for validation +- [ ] Support artifact webhooks for real-time/batched artifact delivery +- [ ] Support reporting webhooks for delivery metrics --- @@ -172,62 +194,64 @@ When creating media buys, include the reference: If the publisher cannot fulfill the standards, they should reject the buy. Handle rejections gracefully and find alternative inventory. -**3. Orchestrate calibration** +**3. Implement `calibrate_content`** -Before campaigns launch, run calibration with each publisher: +Publishers call your `calibrate_content` endpoint to calibrate their local models. They send sample artifacts, you respond with how the brand would rate them: ```python -# Pseudo-code for calibration orchestration -for publisher in campaign.publishers: - # Get sample content from publisher - artifacts = publisher.get_media_buy_artifacts(media_buy_id) - - # Run through your evaluation - our_results = evaluate(artifacts, standards) - - # Send to publisher for their evaluation - their_results = publisher.calibrate_content(standards_id, artifacts) - - # Compare and resolve discrepancies - discrepancies = find_discrepancies(our_results, their_results) - if discrepancies: - # Iterate with more examples until aligned - resolve_calibration(publisher, standards, discrepancies) +# Your calibrate_content implementation +def calibrate_content(standards_id, artifacts): + standards = get_standards(standards_id) + evaluations = [] + + for artifact in artifacts: + # Evaluate against brand's policy + result = evaluate_against_policy(artifact, standards) + evaluations.append({ + "artifact_id": artifact["artifact_id"], + "suitable": result.suitable, + "confidence": result.confidence, + "explanation": result.explanation + }) + + return {"evaluations": evaluations} ``` +This helps publishers understand your interpretation before they start enforcing. + **4. Validate delivery** -After campaign delivery: +After campaign delivery, fetch artifacts from publishers and validate against the standards: ```python # Get delivery records from publisher artifacts = publisher.get_media_buy_artifacts(media_buy_id, include_delivery=True) -# Option A: Trust publisher's evaluation -results = publisher.validate_content_delivery(standards_id, artifacts) - -# Option B: Validate yourself (fetch content, run your own model) +# Validate using your governance logic for artifact in artifacts: - content = fetch_content(artifact.url) # or use content_access - our_result = evaluate(content, standards) - compare_with_publisher(our_result, publisher_result) + result = evaluate_against_policy(artifact, standards) + if not result.suitable: + log_brand_safety_incident(artifact, result) ``` -**5. Report discrepancies** +You can also receive artifacts via webhook instead of polling. + +**5. Report to brands** -Surface any misalignment to the brand: -- Publisher marked suitable, you disagree β†’ potential brand safety incident -- Publisher marked unsuitable, you disagree β†’ potential missed inventory +Surface validation results to the brand: +- **Incidents**: Content that didn't meet standards +- **Coverage**: What percentage of delivery was validated +- **Trends**: Changes in content safety over time ### Implementation Checklist - [ ] Store and serve content standards documents - [ ] Include `content_standards_ref` in media buy requests - [ ] Handle rejections from publishers who can't fulfill standards -- [ ] Implement calibration orchestration workflow -- [ ] Fetch artifacts via `get_media_buy_artifacts` -- [ ] Validate delivery via `validate_content_delivery` or your own evaluation -- [ ] Build discrepancy reporting for brands +- [ ] Implement `calibrate_content` so publishers can align their models +- [ ] Implement `validate_content_delivery` to evaluate artifacts against standards +- [ ] Fetch artifacts via `get_media_buy_artifacts` or receive via webhook +- [ ] Build reporting for brands --- From dc19a74c27708727bb44bc3978098e7cb2e97060 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 18 Jan 2026 04:58:33 -0500 Subject: [PATCH 27/38] Add artifact webhook spec and clarify role terminology MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Artifact Webhooks: - Add artifact_webhook config to create-media-buy-request.json - Add artifact-webhook-payload.json schema for push notifications - Supports realtime and batched delivery modes with sampling Role Terminology: - Orchestrator (not "buyer agent") - DSP, trading desk, agency platform - Sales Agent - publisher ad server, SSP - Governance Agent - IAS, DoubleVerify, brand safety service Implementation Guide: - Clear roles table showing who does what - Correct flow: brand β†’ orchestrator β†’ governance agent (setup) β†’ sales agent - Sales agent calibrates against governance agent, not orchestrator - Sales agent pushes artifacts to orchestrator, orchestrator forwards to governance agent Co-Authored-By: Claude Opus 4.5 --- .../implementation-guide.mdx | 334 +++++++++--------- .../artifact-webhook-payload.json | 72 ++++ static/schemas/source/index.json | 4 + .../media-buy/create-media-buy-request.json | 70 ++++ 4 files changed, 318 insertions(+), 162 deletions(-) create mode 100644 static/schemas/source/content-standards/artifact-webhook-payload.json diff --git a/docs/governance/content-standards/implementation-guide.mdx b/docs/governance/content-standards/implementation-guide.mdx index eca57d4fb..ce8399542 100644 --- a/docs/governance/content-standards/implementation-guide.mdx +++ b/docs/governance/content-standards/implementation-guide.mdx @@ -1,27 +1,51 @@ --- title: Implementation Guide -description: How to implement the Content Standards Protocol as a sales agent, buyer agent, or web publisher +description: How to implement the Content Standards Protocol as a sales agent, orchestrator, or governance agent --- This guide covers implementation patterns for the Content Standards Protocol from three perspectives: 1. **Sales agents** accepting and enforcing brand safety standards -2. **Buyer agents** orchestrating content validation across publishers -3. **Web publishers** without direct content access +2. **Orchestrators** coordinating content standards across publishers +3. **Governance agents** providing content evaluation services + +## Roles Overview + +Before diving in, understand who does what: + +| Role | Examples | Responsibilities | +|------|----------|-----------------| +| **Orchestrator** | DSP, trading desk, agency platform | Coordinates media buying; passes standards refs to sellers; receives artifacts for validation | +| **Sales Agent** | Publisher ad server, SSP | Accepts standards; calibrates local model; enforces during delivery; pushes artifacts | +| **Governance Agent** | IAS, DoubleVerify, brand safety service | Hosts standards; implements `calibrate_content` and `validate_content_delivery` | + +The typical flow: + +``` +1. Brand sets up standards with governance agent (via orchestrator) +2. Orchestrator sends standards_ref with get_products/create_media_buy +3. Sales agent accepts or rejects based on capability +4. Sales agent calibrates against governance agent +5. Sales agent enforces during delivery +6. Sales agent pushes artifacts via webhook (or orchestrator polls) +7. Orchestrator forwards artifacts to governance agent for validation +``` + +--- ## For Sales Agents -If you're a sales agent (publisher ad server, SSP, or platform), implementing Content Standards means accepting buyer policies and enforcing them during delivery. +If you're a sales agent (publisher ad server, SSP, or platform), implementing Content Standards means accepting orchestrator policies and enforcing them during delivery. ### The Core Model -When a buyer includes a `content_standards_ref` in their request, you must: +When an orchestrator includes a `content_standards_ref` in their request, you must: -1. **Fetch the standards** and evaluate if you can fulfill them +1. **Fetch the standards** from the governance agent and evaluate if you can fulfill them 2. **Accept or reject** the buy based on your capabilities -3. **Calibrate** your evaluation model against the buyer's expectations +3. **Calibrate** your evaluation model against the governance agent's expectations 4. **Enforce** the standards during delivery -5. **Report** validation results after delivery +5. **Push artifacts** to the orchestrator for validation If you cannot fulfill the content standards requirements, **reject the buy**. Don't accept a campaign you can't properly enforce. @@ -29,19 +53,19 @@ If you cannot fulfill the content standards requirements, **reject the buy**. Do **1. Accept content standards references on `get_products` and `create_media_buy`** -Buyers pass their standards via reference: +Orchestrators pass their standards via reference: ```json { "content_standards_ref": { "standards_id": "nike_emea_brand_safety", - "agent_url": "https://brandsafety.nike.com" + "agent_url": "https://brandsafety.ias.com" } } ``` When you receive this: -- Fetch the standards document from the buyer's agent +- Fetch the standards document from the governance agent at `agent_url` - Evaluate whether you can enforce these requirements - If you cannot meet the standards, reject the request - If you can, accept and store the association with the media buy @@ -54,9 +78,9 @@ The standards document contains: - Competitive separation requirements - Any other buyer-specific policies -Review these requirements against your capabilities. Different publishers have different definitions of "adjacency" - Reddit might include comments, YouTube might include related videos, a news site might mean the article body. That's fine - as long as you can meaningfully enforce the buyer's intent, accept the buy. +Review these requirements against your capabilities. Different publishers have different definitions of "adjacency" - Reddit might include comments, YouTube might include related videos, a news site might mean the article body. That's fine - as long as you can meaningfully enforce the brand's intent, accept the buy. -If you can't - for example, the buyer requires competitive separation but you don't support it, or they need adjacency data for a channel where it doesn't apply (like billboards) - reject the buy. +If you can't - for example, the brand requires competitive separation but you don't support it, or they need adjacency data for a channel where it doesn't apply (like billboards) - reject the buy. **3. Build your evaluation capability** @@ -68,12 +92,12 @@ Use the standards document to train or configure your content evaluation system. The protocol doesn't prescribe your implementation - just that you honor the standards. -**4. Participate in calibration** +**4. Calibrate against the governance agent** -Before campaigns launch, calibrate your interpretation against the buyer's governance agent. You send sample artifacts, they tell you how they would rate them: +After accepting the buy, calibrate your local model by calling `calibrate_content` on the governance agent. You send sample artifacts from your inventory, they tell you how they would rate them: ```json -// You send examples from your inventory +// You send examples from your inventory to the governance agent { "standards_id": "nike_emea_brand_safety", "artifacts": [ @@ -85,7 +109,7 @@ Before campaigns launch, calibrate your interpretation against the buyer's gover ] } -// Buyer's governance agent responds with their interpretation +// Governance agent responds with their interpretation { "evaluations": [{ "artifact_id": "article_123", @@ -96,66 +120,67 @@ Before campaigns launch, calibrate your interpretation against the buyer's gover } ``` -Use these responses to train your local model. If you disagree with a rating, ask follow-up questions to understand the buyer's reasoning. - -**5. Support artifact retrieval and webhooks** +Use these responses to train your local model. If you disagree with a rating, ask follow-up questions to understand the governance agent's reasoning. -After delivery, buyers need to audit where their ads appeared. Your responsibilities: +**5. Push artifacts to the orchestrator** -- **Implement `get_media_buy_artifacts`** - Return content artifacts for delivered impressions so buyers can validate against their governance agent -- **Support artifact webhooks** - Push artifacts to buyer-specified endpoints in real-time or batched -- **Support reporting webhooks** - Push delivery reports to buyer-specified endpoints - -The **governance agent** (not you) implements `validate_content_delivery`. Buyers fetch artifacts from you, then send them to their governance agent for validation. +After delivery, push artifacts to the orchestrator so they can validate against the governance agent. Configure via `artifact_webhook` in the media buy: ```json -// Artifact webhook payload +// Artifact webhook payload (you send this to the orchestrator) { "media_buy_id": "mb_nike_reddit_q1", + "batch_id": "batch_20250115_001", + "timestamp": "2025-01-15T11:00:00Z", "artifacts": [ { - "property_id": { "type": "domain", "value": "reddit.com" }, - "artifact_id": "r_fitness_abc123", - "assets": [{ "type": "text", "role": "title", "content": "Best protein sources" }], - "delivered_at": "2025-01-15T10:30:00Z" + "artifact": { + "property_id": { "type": "domain", "value": "reddit.com" }, + "artifact_id": "r_fitness_abc123", + "assets": [{ "type": "text", "role": "title", "content": "Best protein sources" }] + }, + "delivered_at": "2025-01-15T10:30:00Z", + "impression_id": "imp_abc123" } ] } ``` +Also support `get_media_buy_artifacts` for orchestrators who prefer to poll. + ### Implementation Checklist - [ ] Parse `content_standards_ref` in `get_products` and `create_media_buy` -- [ ] Fetch and evaluate standards documents from buyer agents +- [ ] Fetch and evaluate standards documents from governance agents - [ ] Reject buys you cannot fulfill - don't accept campaigns you can't enforce - [ ] Build content evaluation against the standards document -- [ ] Call `calibrate_content` on buyer's governance agent to align interpretation -- [ ] Implement `get_media_buy_artifacts` so buyers can retrieve content for validation -- [ ] Support artifact webhooks for real-time/batched artifact delivery -- [ ] Support reporting webhooks for delivery metrics +- [ ] Call `calibrate_content` on the governance agent to align interpretation +- [ ] Implement `get_media_buy_artifacts` so orchestrators can retrieve content for validation +- [ ] Support `artifact_webhook` for push-based artifact delivery +- [ ] Support `reporting_webhook` for delivery metrics --- -## For Buyer Agents - -If you're a buyer agent (DSP, agency platform, or brand safety service), you orchestrate content standards across multiple publishers. +## For Orchestrators -### The Proxy Pattern +If you're an orchestrator (DSP, trading desk, or agency platform), you coordinate content standards between brands, governance agents, and publishers. -Buyer agents act as intermediaries between brands and publishers: +### The Orchestration Pattern ``` -Brand β†’ Buyer Agent β†’ Sales Agent (Publisher) - ↓ - Content Standards - (hosted by buyer) +Brand β†’ Orchestrator β†’ Governance Agent (setup) + β†’ Sales Agent (buying) + ← Sales Agent (artifacts) + β†’ Governance Agent (validation) + β†’ Brand (reporting) ``` -**1. Host the brand's standards** +**1. Help brands set up standards with governance agents** -Create and store content standards for each brand: +Brands create content standards through a governance agent. You might facilitate this or the brand may do it directly: ```json +// Standards stored at the governance agent { "standards_id": "nike_emea_brand_safety", "name": "Nike EMEA Brand Safety Policy", @@ -175,11 +200,9 @@ Create and store content standards for each brand: } ``` -Expose these via your agent's API so publishers can fetch them. - **2. Pass standards references when buying** -When creating media buys, include the reference: +When discovering products or creating media buys, include the governance agent reference: ```json { @@ -187,56 +210,49 @@ When creating media buys, include the reference: "packages": [...], "content_standards_ref": { "standards_id": "nike_emea_brand_safety", - "agent_url": "https://buying.agency.com" + "agent_url": "https://brandsafety.ias.com" + }, + "artifact_webhook": { + "url": "https://your-platform.com/webhooks/artifacts", + "authentication": { + "schemes": ["HMAC-SHA256"], + "credentials": "your-shared-secret-min-32-chars" + }, + "delivery_mode": "batched", + "batch_frequency": "hourly", + "sampling_rate": 0.25 } } ``` If the publisher cannot fulfill the standards, they should reject the buy. Handle rejections gracefully and find alternative inventory. -**3. Implement `calibrate_content`** +**3. Receive artifacts from sales agents** -Publishers call your `calibrate_content` endpoint to calibrate their local models. They send sample artifacts, you respond with how the brand would rate them: +Sales agents push artifacts to your `artifact_webhook` endpoint. Forward them to the governance agent for validation: ```python -# Your calibrate_content implementation -def calibrate_content(standards_id, artifacts): - standards = get_standards(standards_id) - evaluations = [] - - for artifact in artifacts: - # Evaluate against brand's policy - result = evaluate_against_policy(artifact, standards) - evaluations.append({ - "artifact_id": artifact["artifact_id"], - "suitable": result.suitable, - "confidence": result.confidence, - "explanation": result.explanation - }) - - return {"evaluations": evaluations} +# Receive artifact webhook from sales agent +@app.post("/webhooks/artifacts") +async def receive_artifacts(payload: ArtifactWebhookPayload): + # Forward to governance agent for validation + validation_result = await governance_agent.validate_content_delivery( + standards_id=get_standards_id(payload.media_buy_id), + records=[ + {"artifact": a.artifact, "record_id": a.impression_id} + for a in payload.artifacts + ] + ) + + # Log any failures + for result in validation_result.results: + if any(f.status == "failed" for f in result.features): + log_brand_safety_incident(payload.media_buy_id, result) + + return {"status": "received", "batch_id": payload.batch_id} ``` -This helps publishers understand your interpretation before they start enforcing. - -**4. Validate delivery** - -After campaign delivery, fetch artifacts from publishers and validate against the standards: - -```python -# Get delivery records from publisher -artifacts = publisher.get_media_buy_artifacts(media_buy_id, include_delivery=True) - -# Validate using your governance logic -for artifact in artifacts: - result = evaluate_against_policy(artifact, standards) - if not result.suitable: - log_brand_safety_incident(artifact, result) -``` - -You can also receive artifacts via webhook instead of polling. - -**5. Report to brands** +**4. Report to brands** Surface validation results to the brand: - **Incidents**: Content that didn't meet standards @@ -245,107 +261,102 @@ Surface validation results to the brand: ### Implementation Checklist -- [ ] Store and serve content standards documents -- [ ] Include `content_standards_ref` in media buy requests +- [ ] Facilitate brand setup with governance agents +- [ ] Include `content_standards_ref` in `get_products` and `create_media_buy` requests +- [ ] Configure `artifact_webhook` to receive artifacts from sales agents - [ ] Handle rejections from publishers who can't fulfill standards -- [ ] Implement `calibrate_content` so publishers can align their models -- [ ] Implement `validate_content_delivery` to evaluate artifacts against standards -- [ ] Fetch artifacts via `get_media_buy_artifacts` or receive via webhook +- [ ] Forward artifacts to governance agent via `validate_content_delivery` - [ ] Build reporting for brands --- -## For Web Publishers (URL-Based Content) +## For Governance Agents -If you're a web publisher without direct programmatic access to your content (e.g., your CMS doesn't expose an API), you can use a service provider pattern. +If you're a governance agent (IAS, DoubleVerify, or brand safety service), you provide content evaluation as a service. -### The Problem +### What You Implement -A buyer sends you artifact IDs (URLs) for validation: +**1. Host and serve content standards** + +Store standards configurations and expose them via `get_content_standards`: ```json +// Response to get_content_standards { - "records": [ - { "artifact_id": "https://yoursite.com/article/12345" }, - { "artifact_id": "https://yoursite.com/article/67890" } - ] + "standards_id": "nike_emea_brand_safety", + "version": "1.2.0", + "effective_date": "2025-01-01T00:00:00Z", + "name": "Nike EMEA - all digital channels", + "policy": "Sports and fitness content is ideal. Lifestyle content about health is good...", + "calibration": { + "acceptable": [...], + "unacceptable": [...] + }, + "competitive_separation": ["adidas", "puma", "under_armour"] } ``` -But your ad server doesn't have the article content - it just knows the URL. +**2. Implement `calibrate_content`** -### Solution: Content Service Provider - -Use a scraping/content service to resolve URLs to content: - -``` -Buyer β†’ Your Ad Server β†’ Content Service β†’ Your Website - ↓ - Evaluation -``` - -**Option A: Integrate a content service** +Sales agents call this to align their local models before campaign execution. They send sample artifacts, you respond with how the brand would rate them: ```python -async def validate_content_delivery(standards_id, records): - standards = fetch_standards(standards_id) - results = [] - - for record in records: - # Use content service to fetch and parse - content = await content_service.fetch(record.artifact_id) +def calibrate_content(standards_id: str, artifacts: list) -> dict: + standards = get_standards(standards_id) + evaluations = [] - # Evaluate against standards - result = evaluate(content, standards) - results.append(result) + for artifact in artifacts: + # Evaluate against brand's policy + result = evaluate_against_policy(artifact, standards) + evaluations.append({ + "artifact_id": artifact["artifact_id"], + "suitable": result.suitable, + "confidence": result.confidence, + "explanation": result.explanation # Help them understand your reasoning + }) - return results + return {"evaluations": evaluations} ``` -**Option B: Pre-index your content** +Calibration is a dialogue - be prepared for follow-up questions and edge cases. -Build a content index that maps URLs to extracted content: +**3. Implement `validate_content_delivery`** -```json -{ - "content_access": { - "url_pattern": "https://content-cache.yoursite.com/*", - "auth": { "type": "bearer", "token": "..." } - } -} -``` +Orchestrators call this to validate artifacts after delivery. Batch evaluation at scale: -When buyers request validation, they can fetch content directly from your cache. - -**Option C: Provide artifact URLs in responses** +```python +def validate_content_delivery(standards_id: str, records: list) -> dict: + standards = get_standards(standards_id) + results = [] -In `get_media_buy_artifacts`, include URLs that resolve to content: + for record in records: + features = [] + for feature in ["brand_safety", "brand_suitability", "competitive_separation"]: + evaluation = evaluate_feature(record["artifact"], standards, feature) + features.append({ + "feature_id": feature, + "status": "passed" if evaluation.passed else "failed", + "value": evaluation.value, + "message": evaluation.message if not evaluation.passed else None + }) + results.append({ + "record_id": record["record_id"], + "features": features + }) -```json -{ - "artifacts": [ - { - "property_id": { "type": "domain", "value": "yoursite.com" }, - "artifact_id": "article_12345", - "url": "https://yoursite.com/article/12345", - "assets": [] // Empty - buyer fetches via URL + return { + "summary": compute_summary(results), + "results": results } - ], - "content_access": { - "url_pattern": "https://yoursite.com/*", - "auth": null // Public content - } -} ``` -The buyer then fetches and evaluates the content themselves. - ### Implementation Checklist -- [ ] Decide on content access pattern (service, cache, or buyer-fetch) -- [ ] Implement content fetching/caching if needed -- [ ] Expose `content_access` configuration for authenticated content -- [ ] Return resolvable URLs in `get_media_buy_artifacts` +- [ ] Implement `create_content_standards` for brands to set up policies +- [ ] Implement `get_content_standards` for sales agents to fetch policies +- [ ] Implement `calibrate_content` for sales agents to align their models +- [ ] Implement `validate_content_delivery` for orchestrators to validate delivery +- [ ] Support dialogue in calibration (follow-up questions, edge cases) --- @@ -370,8 +381,7 @@ All three roles may need to exchange content securely. The `content_access` patt - **auth.token**: The credential Include this in: -- `create_content_standards` (buyer β†’ seller: "fetch my standards here") -- `get_media_buy_artifacts` response (seller β†’ buyer: "fetch content here") -- `validate_content_delivery` request (buyer β†’ seller: "fetch content here if you need it") +- `get_content_standards` response (governance agent β†’ sales agent: "fetch examples here") +- `get_media_buy_artifacts` response (sales agent β†’ orchestrator: "fetch content here") This avoids per-asset tokens and keeps payloads small while enabling secure content exchange. diff --git a/static/schemas/source/content-standards/artifact-webhook-payload.json b/static/schemas/source/content-standards/artifact-webhook-payload.json new file mode 100644 index 000000000..63132ff51 --- /dev/null +++ b/static/schemas/source/content-standards/artifact-webhook-payload.json @@ -0,0 +1,72 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/content-standards/artifact-webhook-payload.json", + "title": "Artifact Webhook Payload", + "description": "Payload sent by sales agents to orchestrators when pushing content artifacts for governance validation. Complements get_media_buy_artifacts for push-based artifact delivery.", + "type": "object", + "properties": { + "media_buy_id": { + "type": "string", + "description": "Media buy identifier these artifacts belong to" + }, + "batch_id": { + "type": "string", + "description": "Unique identifier for this batch of artifacts. Use for deduplication and acknowledgment." + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When this batch was generated (ISO 8601)" + }, + "artifacts": { + "type": "array", + "description": "Content artifacts from delivered impressions", + "items": { + "type": "object", + "properties": { + "artifact": { + "$ref": "/schemas/content-standards/artifact.json", + "description": "The content artifact" + }, + "delivered_at": { + "type": "string", + "format": "date-time", + "description": "When the impression was delivered (ISO 8601)" + }, + "impression_id": { + "type": "string", + "description": "Optional impression identifier for correlation with delivery reports" + }, + "package_id": { + "type": "string", + "description": "Package within the media buy this artifact relates to" + } + }, + "required": ["artifact", "delivered_at"] + } + }, + "pagination": { + "type": "object", + "description": "Pagination info when batching large artifact sets", + "properties": { + "total_artifacts": { + "type": "integer", + "description": "Total artifacts in the delivery period" + }, + "batch_number": { + "type": "integer", + "description": "Current batch number (1-indexed)" + }, + "total_batches": { + "type": "integer", + "description": "Total batches for this delivery period" + } + } + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["media_buy_id", "batch_id", "timestamp", "artifacts"], + "additionalProperties": true +} diff --git a/static/schemas/source/index.json b/static/schemas/source/index.json index af633d5c2..e0e218117 100644 --- a/static/schemas/source/index.json +++ b/static/schemas/source/index.json @@ -608,6 +608,10 @@ "content-standards-artifact": { "$ref": "/schemas/content-standards/artifact.json", "description": "Content artifact for evaluation or calibration - represents content context where ad placements occur, identified by property_id + artifact_id" + }, + "artifact-webhook-payload": { + "$ref": "/schemas/content-standards/artifact-webhook-payload.json", + "description": "Webhook payload for content artifact delivery from sales agents to orchestrators" } }, "tasks": { diff --git a/static/schemas/source/media-buy/create-media-buy-request.json b/static/schemas/source/media-buy/create-media-buy-request.json index 3e83f44ba..0e5fb8413 100644 --- a/static/schemas/source/media-buy/create-media-buy-request.json +++ b/static/schemas/source/media-buy/create-media-buy-request.json @@ -108,6 +108,76 @@ ], "additionalProperties": true }, + "artifact_webhook": { + "$comment": "Webhook configuration for content artifact delivery - enables governance validation. Same authentication structure as reporting_webhook.", + "type": "object", + "description": "Optional webhook configuration for content artifact delivery. Used by governance agents to validate content adjacency. Seller pushes artifacts to this endpoint; orchestrator forwards to governance agent for validation.", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "Webhook endpoint URL for artifact delivery" + }, + "token": { + "type": "string", + "description": "Optional client-provided token for webhook validation. Echoed back in webhook payload to validate request authenticity.", + "minLength": 16 + }, + "authentication": { + "type": "object", + "description": "Authentication configuration for webhook delivery (A2A-compatible)", + "properties": { + "schemes": { + "type": "array", + "description": "Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for signature verification (recommended for production)", + "items": { + "$ref": "/schemas/enums/auth-scheme.json" + }, + "minItems": 1, + "maxItems": 1 + }, + "credentials": { + "type": "string", + "description": "Credentials for authentication. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.", + "minLength": 32 + } + }, + "required": [ + "schemes", + "credentials" + ], + "additionalProperties": false + }, + "delivery_mode": { + "type": "string", + "enum": [ + "realtime", + "batched" + ], + "description": "How artifacts are delivered. 'realtime' pushes artifacts as impressions occur. 'batched' aggregates artifacts and pushes periodically (see batch_frequency)." + }, + "batch_frequency": { + "type": "string", + "enum": [ + "hourly", + "daily" + ], + "description": "For batched delivery, how often to push artifacts. Required when delivery_mode is 'batched'." + }, + "sampling_rate": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Fraction of impressions to include (0-1). 1.0 = all impressions, 0.1 = 10% sample. Default: 1.0" + } + }, + "required": [ + "url", + "authentication", + "delivery_mode" + ], + "additionalProperties": true + }, "context": { "$ref": "/schemas/core/context.json" }, From d1084a911ad38e6440d9760d401db1e42c3cee96 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 18 Jan 2026 08:56:00 -0500 Subject: [PATCH 28/38] Fix terminology: artifacts can be push or pull Changed "push artifacts" to "provide artifacts" since sales agents can either push via artifact_webhook or respond to get_media_buy_artifacts (pull). Co-Authored-By: Claude Opus 4.5 --- docs/governance/content-standards/implementation-guide.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/governance/content-standards/implementation-guide.mdx b/docs/governance/content-standards/implementation-guide.mdx index ce8399542..419fcfe7f 100644 --- a/docs/governance/content-standards/implementation-guide.mdx +++ b/docs/governance/content-standards/implementation-guide.mdx @@ -27,7 +27,7 @@ The typical flow: 3. Sales agent accepts or rejects based on capability 4. Sales agent calibrates against governance agent 5. Sales agent enforces during delivery -6. Sales agent pushes artifacts via webhook (or orchestrator polls) +6. Sales agent provides artifacts (push via webhook or pull via get_media_buy_artifacts) 7. Orchestrator forwards artifacts to governance agent for validation ``` @@ -45,7 +45,7 @@ When an orchestrator includes a `content_standards_ref` in their request, you mu 2. **Accept or reject** the buy based on your capabilities 3. **Calibrate** your evaluation model against the governance agent's expectations 4. **Enforce** the standards during delivery -5. **Push artifacts** to the orchestrator for validation +5. **Provide artifacts** to the orchestrator for validation If you cannot fulfill the content standards requirements, **reject the buy**. Don't accept a campaign you can't properly enforce. From 8787b92cb0935aa6c326aa79c1b9bff9f3ad8d58 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 18 Jan 2026 09:01:33 -0500 Subject: [PATCH 29/38] Simplify content standards: async validation, rename fields, remove competitive separation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. validate_content_delivery is now async - Accept batch immediately, process in background - Returns validation_id for status polling 2. Rename calibration β†’ calibration_exemplars - More descriptive name for training set - Change acceptable/unacceptable β†’ pass/fail for consistency 3. Remove competitive_separation - Out of scope for content standards - Competitive separation is about ad co-occurrence, not content adjacency - Requires knowledge of other ads on page, which content standards doesn't have Co-Authored-By: Claude Opus 4.5 --- .../implementation-guide.mdx | 39 +++++++++---------- docs/governance/content-standards/index.mdx | 1 - .../tasks/create_content_standards.mdx | 10 ++--- .../tasks/get_content_standards.mdx | 10 ++--- .../tasks/update_content_standards.mdx | 9 ++--- .../tasks/validate_content_delivery.mdx | 12 +----- .../get-content-standards-response.json | 15 +++---- 7 files changed, 36 insertions(+), 60 deletions(-) diff --git a/docs/governance/content-standards/implementation-guide.mdx b/docs/governance/content-standards/implementation-guide.mdx index 419fcfe7f..2c3e33bc9 100644 --- a/docs/governance/content-standards/implementation-guide.mdx +++ b/docs/governance/content-standards/implementation-guide.mdx @@ -73,14 +73,13 @@ When you receive this: **2. Decide: Can you fulfill this?** The standards document contains: -- Rules (what categories to allow/block) -- Calibration examples (how to interpret edge cases) -- Competitive separation requirements -- Any other buyer-specific policies +- Policy (natural language description of acceptable/unacceptable content) +- Calibration exemplars (pass/fail examples to interpret edge cases) +- Floor (baseline safety standards like GARM) Review these requirements against your capabilities. Different publishers have different definitions of "adjacency" - Reddit might include comments, YouTube might include related videos, a news site might mean the article body. That's fine - as long as you can meaningfully enforce the brand's intent, accept the buy. -If you can't - for example, the brand requires competitive separation but you don't support it, or they need adjacency data for a channel where it doesn't apply (like billboards) - reject the buy. +If you can't - for example, they need adjacency data for a channel where it doesn't apply (like billboards) - reject the buy. **3. Build your evaluation capability** @@ -185,18 +184,16 @@ Brands create content standards through a governance agent. You might facilitate "standards_id": "nike_emea_brand_safety", "name": "Nike EMEA Brand Safety Policy", "brand_id": "nike", - "rules": { - "blocked_categories": ["violence", "adult", "drugs"], - "required_categories": [], - "competitive_separation": ["adidas", "puma", "reebok"] + "policy": "Sports and fitness content is ideal. Avoid violence, adult themes, drugs.", + "calibration_exemplars": { + "pass": [ + { "type": "domain", "value": "espn.com" } + ], + "fail": [ + { "type": "domain", "value": "tabloid.example.com" } + ] }, - "calibration": [ - { - "artifact": { ... }, - "expected_result": { "suitable": true }, - "rationale": "Sports injury in athletic context is acceptable" - } - ] + "floor": "garm_floor" } ``` @@ -288,11 +285,11 @@ Store standards configurations and expose them via `get_content_standards`: "effective_date": "2025-01-01T00:00:00Z", "name": "Nike EMEA - all digital channels", "policy": "Sports and fitness content is ideal. Lifestyle content about health is good...", - "calibration": { - "acceptable": [...], - "unacceptable": [...] + "calibration_exemplars": { + "pass": [...], + "fail": [...] }, - "competitive_separation": ["adidas", "puma", "under_armour"] + "floor": "garm_floor" } ``` @@ -331,7 +328,7 @@ def validate_content_delivery(standards_id: str, records: list) -> dict: for record in records: features = [] - for feature in ["brand_safety", "brand_suitability", "competitive_separation"]: + for feature in ["brand_safety", "brand_suitability"]: evaluation = evaluate_feature(record["artifact"], standards, feature) features.append({ "feature_id": feature, diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index 745aa632a..6ac64800b 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -13,7 +13,6 @@ Content Standards agents evaluate placement contexts against brand policies. Thi - **Brand safety** - Is this content safe for *any* brand? (universal thresholds like hate speech, illegal content) - **Brand suitability** - Is this content appropriate for *my* brand? (brand-specific preferences and tone) -- **Competitive separation** - Avoid appearing near competitor content ## Key Concepts diff --git a/docs/governance/content-standards/tasks/create_content_standards.mdx b/docs/governance/content-standards/tasks/create_content_standards.mdx index b62f742f6..3bddbd9af 100644 --- a/docs/governance/content-standards/tasks/create_content_standards.mdx +++ b/docs/governance/content-standards/tasks/create_content_standards.mdx @@ -15,8 +15,7 @@ Create a new content standards configuration. |-----------|------|----------|-------------| | `scope` | object | Yes | Where this standards configuration applies | | `policy` | string | Yes | Natural language policy prompt | -| `calibration` | object | No | Training set of acceptable/unacceptable artifacts | -| `competitive_separation` | array | No | Competitor brands to avoid | +| `calibration_exemplars` | object | No | Training set of pass/fail artifacts for calibration | | `floor` | string | No | Safety floor baseline (`garm_floor` or `custom`) | ### Example Request @@ -30,16 +29,15 @@ Create a new content standards configuration. "description": "Nike EMEA - all digital channels" }, "policy": "Sports and fitness content is ideal. Lifestyle content about health and wellness is good. Entertainment content is generally acceptable. Avoid content about violence, controversial political topics, adult themes, or content that portrays sedentary lifestyle positively.", - "calibration": { - "acceptable": [ + "calibration_exemplars": { + "pass": [ { "type": "domain", "value": "espn.com", "language": "en" }, { "type": "domain", "value": "healthline.com", "language": "en" } ], - "unacceptable": [ + "fail": [ { "type": "domain", "value": "tabloid.example.com", "language": "en" } ] }, - "competitive_separation": ["adidas", "puma", "under_armour"], "floor": "garm_floor" } ``` diff --git a/docs/governance/content-standards/tasks/get_content_standards.mdx b/docs/governance/content-standards/tasks/get_content_standards.mdx index b01177fcd..e1222e0d9 100644 --- a/docs/governance/content-standards/tasks/get_content_standards.mdx +++ b/docs/governance/content-standards/tasks/get_content_standards.mdx @@ -31,19 +31,18 @@ Retrieve content safety policies for a specific standards configuration. "countries_all": ["GB", "DE", "FR"], "channels_any": ["display", "video", "ctv"], "policy": "Sports and fitness content is ideal. Lifestyle content about health and wellness is good. Entertainment content is generally acceptable. Avoid content about violence, controversial political topics, adult themes, or content that portrays sedentary lifestyle positively. Block hate speech, illegal activities, or content disparaging athletes.", - "calibration": { - "acceptable": [ + "calibration_exemplars": { + "pass": [ { "type": "domain", "value": "espn.com", "language": "en" }, { "type": "domain", "value": "healthline.com", "language": "en" }, { "type": "text", "value": "Lakers win championship in thrilling overtime finish", "language": "en" } ], - "unacceptable": [ + "fail": [ { "type": "domain", "value": "tabloid.example.com", "language": "en" }, { "type": "text", "value": "Political scandal rocks the nation", "language": "en" }, { "type": "audio_url", "value": "https://cdn.example.com/controversial-podcast.mp3" } ] }, - "competitive_separation": ["adidas", "puma", "under_armour", "reebok"], "floor": "garm_floor" } ``` @@ -59,8 +58,7 @@ Retrieve content safety policies for a specific standards configuration. | `countries_all` | ISO country codes - standards apply in ALL listed countries | | `channels_any` | Ad channels - standards apply to ANY of the listed channels | | `policy` | Natural language policy describing acceptable and unacceptable content contexts | -| `calibration` | Training/test set of content contexts to calibrate policy interpretation | -| `competitive_separation` | Competitor brands to avoid adjacency with | +| `calibration_exemplars` | Training/test set of content contexts (pass/fail) to calibrate policy interpretation | | `floor` | Safety floor baseline (`garm_floor` = GARM Brand Safety Floor) | ### Error Response diff --git a/docs/governance/content-standards/tasks/update_content_standards.mdx b/docs/governance/content-standards/tasks/update_content_standards.mdx index 25c4c28e1..c5fe71a9a 100644 --- a/docs/governance/content-standards/tasks/update_content_standards.mdx +++ b/docs/governance/content-standards/tasks/update_content_standards.mdx @@ -16,8 +16,7 @@ Update an existing content standards configuration. Creates a new version. | `standards_id` | string | Yes | ID of the standards configuration to update | | `scope` | object | No | Updated scope | | `policy` | string | No | Updated policy prompt | -| `examples` | object | No | Updated training examples | -| `competitive_separation` | array | No | Updated competitors list | +| `calibration_exemplars` | object | No | Updated training exemplars (pass/fail) | | `floor` | string | No | Updated safety floor | ### Example Request @@ -26,13 +25,13 @@ Update an existing content standards configuration. Creates a new version. { "standards_id": "nike_emea_safety", "policy": "Sports and fitness content is ideal. Lifestyle content about health and wellness is good. Entertainment content is generally acceptable. Avoid violence, controversial politics, adult themes. Block hate speech and illegal activities.", - "examples": { - "acceptable": [ + "calibration_exemplars": { + "pass": [ { "type": "domain", "value": "espn.com", "language": "en" }, { "type": "domain", "value": "healthline.com", "language": "en" }, { "type": "domain", "value": "runnersworld.com", "language": "en" } ], - "unacceptable": [ + "fail": [ { "type": "domain", "value": "tabloid.example.com", "language": "en" }, { "type": "domain", "value": "gambling.example.com", "language": "en" } ] diff --git a/docs/governance/content-standards/tasks/validate_content_delivery.mdx b/docs/governance/content-standards/tasks/validate_content_delivery.mdx index 6d55bd4c1..97f36c2b7 100644 --- a/docs/governance/content-standards/tasks/validate_content_delivery.mdx +++ b/docs/governance/content-standards/tasks/validate_content_delivery.mdx @@ -7,7 +7,7 @@ sidebar_position: 4 Validate delivery records against content safety policies. Designed for batch auditing of where ads were actually delivered. -**Response time**: < 60s (batch of 10,000 records) +**Asynchronous**: Accept immediately, process in background. Returns a `validation_id` for status polling. ## Data Flow @@ -102,11 +102,6 @@ This keeps responsibilities clear: sellers provide content samples via `get_medi "feature_id": "brand_safety", "status": "passed", "value": "safe" - }, - { - "feature_id": "competitor_adjacency", - "status": "passed", - "value": false } ] }, @@ -118,11 +113,6 @@ This keeps responsibilities clear: sellers provide content samples via `get_medi "status": "failed", "value": "high_risk", "message": "Content contains violence" - }, - { - "feature_id": "competitor_adjacency", - "status": "passed", - "value": false } ] } diff --git a/static/schemas/source/content-standards/get-content-standards-response.json b/static/schemas/source/content-standards/get-content-standards-response.json index 79439c3b7..be5b17f57 100644 --- a/static/schemas/source/content-standards/get-content-standards-response.json +++ b/static/schemas/source/content-standards/get-content-standards-response.json @@ -45,27 +45,22 @@ "type": "string", "description": "Natural language policy describing acceptable and unacceptable content contexts" }, - "calibration": { + "calibration_exemplars": { "type": "object", "description": "Training/test set to calibrate policy interpretation. Note: Large calibration sets may impact response size - consider using pagination or separate retrieval for large artifact collections.", "properties": { - "acceptable": { + "pass": { "type": "array", "items": { "$ref": "/schemas/content-standards/artifact.json" }, - "description": "Artifacts that are acceptable" + "description": "Artifacts that pass the content standards" }, - "unacceptable": { + "fail": { "type": "array", "items": { "$ref": "/schemas/content-standards/artifact.json" }, - "description": "Artifacts that are unacceptable" + "description": "Artifacts that fail the content standards" } } }, - "competitive_separation": { - "type": "array", - "items": { "type": "string" }, - "description": "Competitor brands to avoid adjacency with" - }, "floor": { "type": "string", "enum": ["garm_floor", "custom"], From 4a69d9ff71e4fbae2ac469b0215ca6b5a977b222 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 18 Jan 2026 09:13:08 -0500 Subject: [PATCH 30/38] Extract content-standards.json and add lifecycle dates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Extract reusable content-standards.json schema - Defines brand safety/suitability policies - Can be referenced by get/list/create responses - Includes scope, policy, calibration_exemplars, floor 2. Add termination_date for lifecycle management - effective_date: when standard becomes active (null = draft) - termination_date: when standard was archived (null = active) - Lifecycle states: Draft β†’ Active β†’ Terminated 3. Document lifecycle states in get_content_standards docs - Explains how to identify unused standards for cleanup Co-Authored-By: Claude Opus 4.5 --- .../tasks/get_content_standards.mdx | 16 +++- .../content-standards/content-standards.json | 75 +++++++++++++++++++ .../get-content-standards-response.json | 68 ++--------------- static/schemas/source/index.json | 4 + 4 files changed, 99 insertions(+), 64 deletions(-) create mode 100644 static/schemas/source/content-standards/content-standards.json diff --git a/docs/governance/content-standards/tasks/get_content_standards.mdx b/docs/governance/content-standards/tasks/get_content_standards.mdx index e1222e0d9..9ebb129cf 100644 --- a/docs/governance/content-standards/tasks/get_content_standards.mdx +++ b/docs/governance/content-standards/tasks/get_content_standards.mdx @@ -52,7 +52,9 @@ Retrieve content safety policies for a specific standards configuration. | Field | Description | |-------|-------------| | `standards_id` | Unique identifier for this standards configuration | -| `version` | Version of this configuration | +| `version` | Version of this configuration (semver recommended) | +| `effective_date` | When this version becomes/became effective. Null = draft/not yet active | +| `termination_date` | When this version was terminated. Null = currently active (if effective) or never activated | | `name` | Human-readable name | | `brand_ids` | Brand identifiers as defined in the Brand Manifest | | `countries_all` | ISO country codes - standards apply in ALL listed countries | @@ -61,6 +63,18 @@ Retrieve content safety policies for a specific standards configuration. | `calibration_exemplars` | Training/test set of content contexts (pass/fail) to calibrate policy interpretation | | `floor` | Safety floor baseline (`garm_floor` = GARM Brand Safety Floor) | +### Lifecycle States + +Standards go through a simple lifecycle: + +| State | effective_date | termination_date | Meaning | +|-------|----------------|------------------|---------| +| Draft | null | null | Being configured, not yet active | +| Active | set | null | Currently in use | +| Terminated | set | set | Superseded or archived | + +To find unused standards for cleanup, query `list_content_standards` with `effective_date=null` (never activated drafts) or cross-reference with active media buys. + ### Error Response ```json diff --git a/static/schemas/source/content-standards/content-standards.json b/static/schemas/source/content-standards/content-standards.json new file mode 100644 index 000000000..eb91aa195 --- /dev/null +++ b/static/schemas/source/content-standards/content-standards.json @@ -0,0 +1,75 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/content-standards/content-standards.json", + "title": "Content Standards", + "description": "A content standards configuration defining brand safety and suitability policies. Standards are scoped by brand, geography, and channel. Multiple standards can be active simultaneously for different scopes.", + "type": "object", + "properties": { + "standards_id": { + "type": "string", + "description": "Unique identifier for this standards configuration" + }, + "version": { + "type": "string", + "description": "Version of this standards configuration (semver recommended)" + }, + "effective_date": { + "type": ["string", "null"], + "format": "date-time", + "description": "When this standards version becomes/became effective (ISO 8601). Null means draft/not yet active. Standards remain effective until superseded by a newer version or explicitly terminated." + }, + "termination_date": { + "type": ["string", "null"], + "format": "date-time", + "description": "When this standards version was terminated (ISO 8601). Null means currently active (if effective_date is set) or never activated (if effective_date is null). Set when archiving or replacing a standard." + }, + "name": { + "type": "string", + "description": "Human-readable name for this standards configuration" + }, + "brand_ids": { + "type": "array", + "items": { "type": "string" }, + "description": "Brand identifiers as defined in the Brand Manifest. Standards apply to all listed brands." + }, + "countries_all": { + "type": "array", + "items": { "type": "string" }, + "description": "ISO 3166-1 alpha-2 country codes. Standards apply in ALL listed countries (AND logic)." + }, + "channels_any": { + "type": "array", + "items": { "type": "string" }, + "description": "Advertising channels (display, video, audio, ctv, etc.). Standards apply to ANY of the listed channels (OR logic)." + }, + "policy": { + "type": "string", + "description": "Natural language policy describing acceptable and unacceptable content contexts. Used by LLMs and human reviewers to make judgments." + }, + "calibration_exemplars": { + "type": "object", + "description": "Training/test set to calibrate policy interpretation. Provides concrete examples of pass/fail decisions.", + "properties": { + "pass": { + "type": "array", + "items": { "$ref": "/schemas/content-standards/artifact.json" }, + "description": "Artifacts that pass the content standards" + }, + "fail": { + "type": "array", + "items": { "$ref": "/schemas/content-standards/artifact.json" }, + "description": "Artifacts that fail the content standards" + } + } + }, + "floor": { + "type": "string", + "enum": ["garm_floor", "custom"], + "description": "Safety floor baseline. 'garm_floor' applies GARM Brand Safety Floor categories. 'custom' means floor is defined entirely by policy." + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["standards_id", "version"] +} diff --git a/static/schemas/source/content-standards/get-content-standards-response.json b/static/schemas/source/content-standards/get-content-standards-response.json index be5b17f57..cea2869f0 100644 --- a/static/schemas/source/content-standards/get-content-standards-response.json +++ b/static/schemas/source/content-standards/get-content-standards-response.json @@ -7,77 +7,19 @@ "oneOf": [ { "type": "object", - "description": "Success response", + "description": "Success response - returns the content standards configuration", + "allOf": [ + { "$ref": "/schemas/content-standards/content-standards.json" } + ], "properties": { - "standards_id": { - "type": "string", - "description": "Identifier for this standards configuration" - }, - "version": { - "type": "string", - "description": "Version of this standards configuration" - }, - "effective_date": { - "type": "string", - "format": "date-time", - "description": "When this standards version became effective. Null or absent means not yet effective. Multiple standards may be effective simultaneously for different scopes." - }, - "name": { - "type": "string", - "description": "Human-readable name for this standards configuration" - }, - "brand_ids": { - "type": "array", - "items": { "type": "string" }, - "description": "Brand identifiers as defined in the Brand Manifest" - }, - "countries_all": { - "type": "array", - "items": { "type": "string" }, - "description": "ISO 3166-1 alpha-2 country codes - standards apply in ALL listed countries" - }, - "channels_any": { - "type": "array", - "items": { "type": "string" }, - "description": "Advertising channels - standards apply to ANY of the listed channels" - }, - "policy": { - "type": "string", - "description": "Natural language policy describing acceptable and unacceptable content contexts" - }, - "calibration_exemplars": { - "type": "object", - "description": "Training/test set to calibrate policy interpretation. Note: Large calibration sets may impact response size - consider using pagination or separate retrieval for large artifact collections.", - "properties": { - "pass": { - "type": "array", - "items": { "$ref": "/schemas/content-standards/artifact.json" }, - "description": "Artifacts that pass the content standards" - }, - "fail": { - "type": "array", - "items": { "$ref": "/schemas/content-standards/artifact.json" }, - "description": "Artifacts that fail the content standards" - } - } - }, - "floor": { - "type": "string", - "enum": ["garm_floor", "custom"], - "description": "Safety floor baseline" - }, "errors": { "not": {}, "description": "Field must not be present in success response" }, "context": { "$ref": "/schemas/core/context.json" - }, - "ext": { - "$ref": "/schemas/core/ext.json" } - }, - "required": ["standards_id", "version"] + } }, { "type": "object", diff --git a/static/schemas/source/index.json b/static/schemas/source/index.json index e0e218117..da760d3eb 100644 --- a/static/schemas/source/index.json +++ b/static/schemas/source/index.json @@ -605,6 +605,10 @@ "$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" }, + "content-standards": { + "$ref": "/schemas/content-standards/content-standards.json", + "description": "Reusable content standards configuration - defines brand safety/suitability policies with scope, policy, calibration exemplars, and lifecycle dates" + }, "content-standards-artifact": { "$ref": "/schemas/content-standards/artifact.json", "description": "Content artifact for evaluation or calibration - represents content context where ad placements occur, identified by property_id + artifact_id" From 695b9cfb1d6f682ac72dad4269b2c633b566e352 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 18 Jan 2026 09:24:08 -0500 Subject: [PATCH 31/38] Add list_content_standards schemas using reusable content-standards.json - list-content-standards-request.json: filters by brand, channel, country - list-content-standards-response.json: returns array of content standards - Response references content-standards.json via $ref, demonstrating reuse - Supports include_terminated and include_drafts filters for lifecycle queries Co-Authored-By: Claude Opus 4.5 --- .../list-content-standards-request.json | 41 +++++++++++++++ .../list-content-standards-response.json | 52 +++++++++++++++++++ static/schemas/source/index.json | 10 ++++ 3 files changed, 103 insertions(+) create mode 100644 static/schemas/source/content-standards/list-content-standards-request.json create mode 100644 static/schemas/source/content-standards/list-content-standards-response.json diff --git a/static/schemas/source/content-standards/list-content-standards-request.json b/static/schemas/source/content-standards/list-content-standards-request.json new file mode 100644 index 000000000..8c03a6cfb --- /dev/null +++ b/static/schemas/source/content-standards/list-content-standards-request.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/content-standards/list-content-standards-request.json", + "title": "List Content Standards Request", + "description": "Request parameters for listing content standards configurations", + "type": "object", + "properties": { + "brand_ids": { + "type": "array", + "items": { "type": "string" }, + "description": "Filter by brand identifiers" + }, + "channels": { + "type": "array", + "items": { "type": "string" }, + "description": "Filter by channel (display, video, audio, ctv, etc.)" + }, + "countries": { + "type": "array", + "items": { "type": "string" }, + "description": "Filter by ISO 3166-1 alpha-2 country codes" + }, + "include_terminated": { + "type": "boolean", + "description": "Include terminated standards (default: false)", + "default": false + }, + "include_drafts": { + "type": "boolean", + "description": "Include draft standards with no effective_date (default: false)", + "default": false + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "additionalProperties": true +} diff --git a/static/schemas/source/content-standards/list-content-standards-response.json b/static/schemas/source/content-standards/list-content-standards-response.json new file mode 100644 index 000000000..b436bc3f4 --- /dev/null +++ b/static/schemas/source/content-standards/list-content-standards-response.json @@ -0,0 +1,52 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/content-standards/list-content-standards-response.json", + "title": "List Content Standards Response", + "description": "Response payload with list of content standards configurations", + "type": "object", + "oneOf": [ + { + "type": "object", + "description": "Success response - returns array of content standards", + "properties": { + "standards": { + "type": "array", + "items": { "$ref": "/schemas/content-standards/content-standards.json" }, + "description": "Array of content standards configurations matching the filter criteria" + }, + "errors": { + "not": {}, + "description": "Field must not be present in success response" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["standards"] + }, + { + "type": "object", + "description": "Error response", + "properties": { + "errors": { + "type": "array", + "items": { "$ref": "/schemas/core/error.json" } + }, + "standards": { + "not": {}, + "description": "Field must not be present in error response" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["errors"] + } + ] +} diff --git a/static/schemas/source/index.json b/static/schemas/source/index.json index da760d3eb..05ffdaaff 100644 --- a/static/schemas/source/index.json +++ b/static/schemas/source/index.json @@ -679,6 +679,16 @@ "description": "Response payload for delete_property_list task" } }, + "list-content-standards": { + "request": { + "$ref": "/schemas/content-standards/list-content-standards-request.json", + "description": "Request parameters for listing content standards configurations" + }, + "response": { + "$ref": "/schemas/content-standards/list-content-standards-response.json", + "description": "Response payload with list of content standards configurations" + } + }, "get-content-standards": { "request": { "$ref": "/schemas/content-standards/get-content-standards-request.json", From 18632b2832dc946460e536244876c2d1468a91ff Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 18 Jan 2026 09:29:09 -0500 Subject: [PATCH 32/38] Add effective_date filters and document Content Standards errors - Add effective_before/effective_after filters to list-content-standards-request schema - Add include_terminated/include_drafts to list_content_standards docs - Add schema reference links to list_content_standards docs - Document Content Standards error codes (STANDARDS_NOT_FOUND, STANDARDS_IN_USE, STANDARDS_SCOPE_CONFLICT) Co-Authored-By: Claude Opus 4.5 --- .../tasks/list_content_standards.mdx | 6 ++ docs/reference/error-codes.mdx | 60 ++++++++++++++++++- .../list-content-standards-request.json | 10 ++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/docs/governance/content-standards/tasks/list_content_standards.mdx b/docs/governance/content-standards/tasks/list_content_standards.mdx index a136a0f3f..34db9f1ee 100644 --- a/docs/governance/content-standards/tasks/list_content_standards.mdx +++ b/docs/governance/content-standards/tasks/list_content_standards.mdx @@ -11,6 +11,8 @@ List available content standards configurations. ## Request +**Schema**: [list-content-standards-request.json](https://adcontextprotocol.org/schemas/v2/content-standards/list-content-standards-request.json) + | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `brand_ids` | array | No | Filter by brand identifiers | @@ -18,9 +20,13 @@ List available content standards configurations. | `channels` | array | No | Filter by channels | | `effective_before` | string | No | Filter to standards effective before this date (ISO 8601) | | `effective_after` | string | No | Filter to standards effective after this date (ISO 8601) | +| `include_terminated` | boolean | No | Include terminated standards (default: false) | +| `include_drafts` | boolean | No | Include draft standards with no effective_date (default: false) | ## Response +**Schema**: [list-content-standards-response.json](https://adcontextprotocol.org/schemas/v2/content-standards/list-content-standards-response.json) + Returns an abbreviated list of standards configurations. Use [get_content_standards](/docs/governance/content-standards/tasks/get_content_standards) to retrieve full details including policy text and calibration data. ### Success Response diff --git a/docs/reference/error-codes.mdx b/docs/reference/error-codes.mdx index 0132e64cd..840798c9b 100644 --- a/docs/reference/error-codes.mdx +++ b/docs/reference/error-codes.mdx @@ -425,6 +425,61 @@ Request exceeded maximum processing time. **Resolution**: Refine request parameters or retry. +## Content Standards Errors + +### STANDARDS_NOT_FOUND +Specified standards ID doesn't exist. + +**Example**: +```json +{ + "$schema": "https://adcontextprotocol.org/schemas/v2/core/error.json", + "code": "STANDARDS_NOT_FOUND", + "message": "No standards found with ID 'invalid_id'", + "details": { + "standards_id": "invalid_id" + } +} +``` + +**Resolution**: Use `list_content_standards` to find valid standards IDs. + +### STANDARDS_IN_USE +Cannot delete standards that are referenced by active media buys. + +**Example**: +```json +{ + "$schema": "https://adcontextprotocol.org/schemas/v2/core/error.json", + "code": "STANDARDS_IN_USE", + "message": "Cannot delete standards 'nike_emea_safety' - currently referenced by active media buys", + "details": { + "standards_id": "nike_emea_safety", + "active_media_buy_count": 3 + } +} +``` + +**Resolution**: Wait for media buys to complete, or set a termination_date to archive rather than delete. + +### STANDARDS_SCOPE_CONFLICT +New standards configuration conflicts with existing standards for the same scope. + +**Example**: +```json +{ + "$schema": "https://adcontextprotocol.org/schemas/v2/core/error.json", + "code": "STANDARDS_SCOPE_CONFLICT", + "message": "Standards already exist for brand 'nike' in countries ['GB', 'DE']", + "details": { + "conflicting_standards_id": "nike_emea_safety", + "overlapping_countries": ["GB", "DE"] + } +} +``` + +**Resolution**: Update existing standards or narrow scope to avoid overlap. + ## Data Errors ### DATA_QUALITY_ISSUE @@ -506,7 +561,10 @@ const PERMANENT_ERRORS = [ 'INSUFFICIENT_PERMISSIONS', 'SEGMENT_NOT_FOUND', 'PLATFORM_UNAUTHORIZED', - 'UNSUPPORTED_VERSION' + 'UNSUPPORTED_VERSION', + 'STANDARDS_NOT_FOUND', + 'STANDARDS_IN_USE', + 'STANDARDS_SCOPE_CONFLICT' ]; function isRetryable(errorCode: string): boolean { diff --git a/static/schemas/source/content-standards/list-content-standards-request.json b/static/schemas/source/content-standards/list-content-standards-request.json index 8c03a6cfb..2c30867b6 100644 --- a/static/schemas/source/content-standards/list-content-standards-request.json +++ b/static/schemas/source/content-standards/list-content-standards-request.json @@ -30,6 +30,16 @@ "description": "Include draft standards with no effective_date (default: false)", "default": false }, + "effective_before": { + "type": "string", + "format": "date-time", + "description": "Filter to standards effective before this date (ISO 8601)" + }, + "effective_after": { + "type": "string", + "format": "date-time", + "description": "Filter to standards effective after this date (ISO 8601)" + }, "context": { "$ref": "/schemas/core/context.json" }, From addb7af1400b4c3a876e09bd58670d39d20f8cfa Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 18 Jan 2026 09:37:17 -0500 Subject: [PATCH 33/38] Remove lifecycle complexity from Content Standards Simplify by removing effective_date/termination_date lifecycle management: - These are implementation details, not protocol concerns - Governance agent returns current active standards via get_content_standards - Cache-Control headers handle freshness, not effective_date filtering Removed: - effective_date, termination_date from content-standards.json schema - effective_before, effective_after, include_terminated, include_drafts filters - Lifecycle states documentation The protocol should be simple: request standards, get current standards. Co-Authored-By: Claude Opus 4.5 --- .../implementation-guide.mdx | 1 - .../tasks/create_content_standards.mdx | 3 +-- .../tasks/get_content_standards.mdx | 15 ----------- .../tasks/list_content_standards.mdx | 26 ++++++------------- .../tasks/update_content_standards.mdx | 3 +-- docs/reference/error-codes.mdx | 2 +- .../content-standards/content-standards.json | 10 ------- .../list-content-standards-request.json | 20 -------------- 8 files changed, 11 insertions(+), 69 deletions(-) diff --git a/docs/governance/content-standards/implementation-guide.mdx b/docs/governance/content-standards/implementation-guide.mdx index 2c3e33bc9..d4ec1f27b 100644 --- a/docs/governance/content-standards/implementation-guide.mdx +++ b/docs/governance/content-standards/implementation-guide.mdx @@ -282,7 +282,6 @@ Store standards configurations and expose them via `get_content_standards`: { "standards_id": "nike_emea_brand_safety", "version": "1.2.0", - "effective_date": "2025-01-01T00:00:00Z", "name": "Nike EMEA - all digital channels", "policy": "Sports and fitness content is ideal. Lifestyle content about health is good...", "calibration_exemplars": { diff --git a/docs/governance/content-standards/tasks/create_content_standards.mdx b/docs/governance/content-standards/tasks/create_content_standards.mdx index 3bddbd9af..488a3ca22 100644 --- a/docs/governance/content-standards/tasks/create_content_standards.mdx +++ b/docs/governance/content-standards/tasks/create_content_standards.mdx @@ -49,8 +49,7 @@ Create a new content standards configuration. ```json { "standards_id": "nike_emea_safety", - "version": "1.0.0", - "effective_date": "2025-01-05T00:00:00Z" + "version": "1.0.0" } ``` diff --git a/docs/governance/content-standards/tasks/get_content_standards.mdx b/docs/governance/content-standards/tasks/get_content_standards.mdx index 9ebb129cf..6f0a028c7 100644 --- a/docs/governance/content-standards/tasks/get_content_standards.mdx +++ b/docs/governance/content-standards/tasks/get_content_standards.mdx @@ -25,7 +25,6 @@ Retrieve content safety policies for a specific standards configuration. { "standards_id": "nike_emea_safety", "version": "1.2.0", - "effective_date": "2025-01-01T00:00:00Z", "name": "Nike EMEA - all digital channels", "brand_ids": ["nike"], "countries_all": ["GB", "DE", "FR"], @@ -53,8 +52,6 @@ Retrieve content safety policies for a specific standards configuration. |-------|-------------| | `standards_id` | Unique identifier for this standards configuration | | `version` | Version of this configuration (semver recommended) | -| `effective_date` | When this version becomes/became effective. Null = draft/not yet active | -| `termination_date` | When this version was terminated. Null = currently active (if effective) or never activated | | `name` | Human-readable name | | `brand_ids` | Brand identifiers as defined in the Brand Manifest | | `countries_all` | ISO country codes - standards apply in ALL listed countries | @@ -63,18 +60,6 @@ Retrieve content safety policies for a specific standards configuration. | `calibration_exemplars` | Training/test set of content contexts (pass/fail) to calibrate policy interpretation | | `floor` | Safety floor baseline (`garm_floor` = GARM Brand Safety Floor) | -### Lifecycle States - -Standards go through a simple lifecycle: - -| State | effective_date | termination_date | Meaning | -|-------|----------------|------------------|---------| -| Draft | null | null | Being configured, not yet active | -| Active | set | null | Currently in use | -| Terminated | set | set | Superseded or archived | - -To find unused standards for cleanup, query `list_content_standards` with `effective_date=null` (never activated drafts) or cross-reference with active media buys. - ### Error Response ```json diff --git a/docs/governance/content-standards/tasks/list_content_standards.mdx b/docs/governance/content-standards/tasks/list_content_standards.mdx index 34db9f1ee..bb7c4d6d0 100644 --- a/docs/governance/content-standards/tasks/list_content_standards.mdx +++ b/docs/governance/content-standards/tasks/list_content_standards.mdx @@ -18,10 +18,6 @@ List available content standards configurations. | `brand_ids` | array | No | Filter by brand identifiers | | `countries` | array | No | Filter by country codes | | `channels` | array | No | Filter by channels | -| `effective_before` | string | No | Filter to standards effective before this date (ISO 8601) | -| `effective_after` | string | No | Filter to standards effective after this date (ISO 8601) | -| `include_terminated` | boolean | No | Include terminated standards (default: false) | -| `include_drafts` | boolean | No | Include draft standards with no effective_date (default: false) | ## Response @@ -37,24 +33,18 @@ Returns an abbreviated list of standards configurations. Use [get_content_standa { "standards_id": "nike_emea_safety", "version": "1.2.0", - "effective_date": "2025-01-05T00:00:00Z", - "scope": { - "brand_ids": ["nike"], - "countries_all": ["GB", "DE", "FR"], - "channels_any": ["display", "video", "ctv"], - "description": "Nike EMEA - all digital channels" - } + "name": "Nike EMEA - all digital channels", + "brand_ids": ["nike"], + "countries_all": ["GB", "DE", "FR"], + "channels_any": ["display", "video", "ctv"] }, { "standards_id": "nike_us_display", "version": "1.0.0", - "effective_date": "2025-01-10T00:00:00Z", - "scope": { - "brand_ids": ["nike"], - "countries_all": ["US"], - "channels_any": ["display"], - "description": "Nike US - display only" - } + "name": "Nike US - display only", + "brand_ids": ["nike"], + "countries_all": ["US"], + "channels_any": ["display"] } ] } diff --git a/docs/governance/content-standards/tasks/update_content_standards.mdx b/docs/governance/content-standards/tasks/update_content_standards.mdx index c5fe71a9a..b26faa007 100644 --- a/docs/governance/content-standards/tasks/update_content_standards.mdx +++ b/docs/governance/content-standards/tasks/update_content_standards.mdx @@ -46,8 +46,7 @@ Update an existing content standards configuration. Creates a new version. ```json { "standards_id": "nike_emea_safety", - "version": "1.3.0", - "effective_date": "2025-01-05T12:00:00Z" + "version": "1.3.0" } ``` diff --git a/docs/reference/error-codes.mdx b/docs/reference/error-codes.mdx index 840798c9b..0eac0041c 100644 --- a/docs/reference/error-codes.mdx +++ b/docs/reference/error-codes.mdx @@ -460,7 +460,7 @@ Cannot delete standards that are referenced by active media buys. } ``` -**Resolution**: Wait for media buys to complete, or set a termination_date to archive rather than delete. +**Resolution**: Wait for media buys to complete before deleting. ### STANDARDS_SCOPE_CONFLICT New standards configuration conflicts with existing standards for the same scope. diff --git a/static/schemas/source/content-standards/content-standards.json b/static/schemas/source/content-standards/content-standards.json index eb91aa195..a59d75b2b 100644 --- a/static/schemas/source/content-standards/content-standards.json +++ b/static/schemas/source/content-standards/content-standards.json @@ -13,16 +13,6 @@ "type": "string", "description": "Version of this standards configuration (semver recommended)" }, - "effective_date": { - "type": ["string", "null"], - "format": "date-time", - "description": "When this standards version becomes/became effective (ISO 8601). Null means draft/not yet active. Standards remain effective until superseded by a newer version or explicitly terminated." - }, - "termination_date": { - "type": ["string", "null"], - "format": "date-time", - "description": "When this standards version was terminated (ISO 8601). Null means currently active (if effective_date is set) or never activated (if effective_date is null). Set when archiving or replacing a standard." - }, "name": { "type": "string", "description": "Human-readable name for this standards configuration" diff --git a/static/schemas/source/content-standards/list-content-standards-request.json b/static/schemas/source/content-standards/list-content-standards-request.json index 2c30867b6..42530fc69 100644 --- a/static/schemas/source/content-standards/list-content-standards-request.json +++ b/static/schemas/source/content-standards/list-content-standards-request.json @@ -20,26 +20,6 @@ "items": { "type": "string" }, "description": "Filter by ISO 3166-1 alpha-2 country codes" }, - "include_terminated": { - "type": "boolean", - "description": "Include terminated standards (default: false)", - "default": false - }, - "include_drafts": { - "type": "boolean", - "description": "Include draft standards with no effective_date (default: false)", - "default": false - }, - "effective_before": { - "type": "string", - "format": "date-time", - "description": "Filter to standards effective before this date (ISO 8601)" - }, - "effective_after": { - "type": "string", - "format": "date-time", - "description": "Filter to standards effective after this date (ISO 8601)" - }, "context": { "$ref": "/schemas/core/context.json" }, From c32fa9848e7b6cf4e8e066925211e09c8db0f96d Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 18 Jan 2026 10:03:53 -0500 Subject: [PATCH 34/38] Refine Content Standards: URI-based floors and validation thresholds - Change floor from enum to URI-based reference (GARM is defunct) - floor now has url, name, and version fields - Supports external floor definitions (e.g., Scope3 brand safety) - Fix calibration_exemplars terminology (pass/fail, not acceptable/unacceptable) - Add validation threshold concept to product catalog - Sellers advertise their calibration accuracy (e.g., 95% aligned) - Acts as contractual guarantee for brand safety compliance Co-Authored-By: Claude Opus 4.5 --- .../implementation-guide.mdx | 12 +++- docs/governance/content-standards/index.mdx | 57 +++++++++++++++++-- .../tasks/create_content_standards.mdx | 7 ++- .../tasks/get_content_standards.mdx | 7 ++- .../content-standards/content-standards.json | 21 ++++++- 5 files changed, 88 insertions(+), 16 deletions(-) diff --git a/docs/governance/content-standards/implementation-guide.mdx b/docs/governance/content-standards/implementation-guide.mdx index d4ec1f27b..18a22e0db 100644 --- a/docs/governance/content-standards/implementation-guide.mdx +++ b/docs/governance/content-standards/implementation-guide.mdx @@ -75,7 +75,7 @@ When you receive this: The standards document contains: - Policy (natural language description of acceptable/unacceptable content) - Calibration exemplars (pass/fail examples to interpret edge cases) -- Floor (baseline safety standards like GARM) +- Floor (reference to external baseline safety standards) Review these requirements against your capabilities. Different publishers have different definitions of "adjacency" - Reddit might include comments, YouTube might include related videos, a news site might mean the article body. That's fine - as long as you can meaningfully enforce the brand's intent, accept the buy. @@ -193,7 +193,10 @@ Brands create content standards through a governance agent. You might facilitate { "type": "domain", "value": "tabloid.example.com" } ] }, - "floor": "garm_floor" + "floor": { + "url": "https://scope3.com/brand-safety-floor", + "name": "Scope3 Common Sense Brand Safety" + } } ``` @@ -288,7 +291,10 @@ Store standards configurations and expose them via `get_content_standards`: "pass": [...], "fail": [...] }, - "floor": "garm_floor" + "floor": { + "url": "https://scope3.com/brand-safety-floor", + "name": "Scope3 Common Sense Brand Safety" + } } ``` diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index 6ac64800b..1a05eac9a 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -5,11 +5,30 @@ sidebar_position: 1 # Content Standards Protocol -The Content Standards Protocol controls *content safety and suitability* - what content contexts are appropriate for a brand's ads. +The Content Standards Protocol enables **privacy-preserving brand safety** for ephemeral and sensitive content that cannot leave a publisher's infrastructure. -## What It Does +## The Problem -Content Standards agents evaluate placement contexts against brand policies. This enables: +Traditional brand safety relies on third-party verification: send your content to IAS or DoubleVerify, they evaluate it, return a verdict. This works for static web pages. It doesn't work for: + +- **AI-generated content** - ChatGPT responses, DALL-E images that exist only in a user session +- **Private conversations** - Content in messaging apps, private social feeds +- **Ephemeral content** - Stories, live streams, real-time feeds that disappear +- **Privacy-regulated content** - GDPR-protected data that cannot be exported + +These platforms can't send content to external verification services. But brands still need safety guarantees. + +## The Solution: Calibration-Based Alignment + +Content Standards solves this with a three-phase model where **no sensitive content leaves the publisher's walls**: + +1. **Calibration** - Publisher and verification agent align on policy interpretation using synthetic or sample data +2. **Local Execution** - Publisher runs evaluation locally on every impression (content never leaves) +3. **Validation** - Statistical sampling detects drift; verification agent audits a sample + +This inverts the traditional model. Instead of "send us your content, we'll evaluate it," it's "we'll teach you our standards, you evaluate locally, we'll audit statistically." + +## What It Covers - **Brand safety** - Is this content safe for *any* brand? (universal thresholds like hate speech, illegal content) - **Brand suitability** - Is this content appropriate for *my* brand? (brand-specific preferences and tone) @@ -125,6 +144,32 @@ Sampling rate is negotiated in the media buy: Higher sampling rates typically cost more but provide stronger guarantees. The seller is responsible for implementing the agreed sampling rate and reporting actual coverage. +## Validation Thresholds + +When a seller calibrates their local model against a verification agent, there's an expected drift - the local model won't match the verification agent 100% of the time. **Validation thresholds** define acceptable drift between local execution and validation samples. + +Sellers advertise their content safety capabilities in their product catalog: + +```json +{ + "product_id": "reddit_feed_premium", + "content_standards": { + "validation_threshold": 0.95, + "validation_threshold_description": "Local model matches verification agent 95% of the time" + } +} +``` + +| Threshold | Meaning | +|-----------|---------| +| **0.99** | Premium - local model is 99% aligned with verification agent | +| **0.95** | Standard - local model is 95% aligned | +| **0.90** | Budget - local model is 90% aligned | + +**This is a contractual guarantee.** If the seller's validation results show more drift than the advertised threshold, buyers can expect remediation (makegoods, refunds, etc.) just like any other delivery discrepancy. + +The threshold answers the key buyer question: "If I accept your local model, how confident can I be that you're enforcing my standards correctly?" + ## Policies Content Standards uses **natural language prompts** rather than rigid keyword lists: @@ -132,15 +177,15 @@ Content Standards uses **natural language prompts** rather than rigid keyword li ```json { "policy": "Sports and fitness content is ideal. Lifestyle content about health is good. Entertainment is generally acceptable. Avoid content about violence, controversial politics, adult themes, or content portraying sedentary lifestyle positively. Block hate speech, illegal activities, or ongoing litigation against our company.", - "calibration": { - "acceptable": [ + "calibration_exemplars": { + "pass": [ { "property_id": {"type": "domain", "value": "espn.com"}, "artifact_id": "nba_championship_recap_2024", "assets": [{"type": "text", "role": "title", "content": "Championship Game Recap"}] } ], - "unacceptable": [ + "fail": [ { "property_id": {"type": "domain", "value": "tabloid.example.com"}, "artifact_id": "scandal_story_123", diff --git a/docs/governance/content-standards/tasks/create_content_standards.mdx b/docs/governance/content-standards/tasks/create_content_standards.mdx index 488a3ca22..7c73c577c 100644 --- a/docs/governance/content-standards/tasks/create_content_standards.mdx +++ b/docs/governance/content-standards/tasks/create_content_standards.mdx @@ -16,7 +16,7 @@ Create a new content standards configuration. | `scope` | object | Yes | Where this standards configuration applies | | `policy` | string | Yes | Natural language policy prompt | | `calibration_exemplars` | object | No | Training set of pass/fail artifacts for calibration | -| `floor` | string | No | Safety floor baseline (`garm_floor` or `custom`) | +| `floor` | object | No | Safety floor baseline - reference to external floor definition | ### Example Request @@ -38,7 +38,10 @@ Create a new content standards configuration. { "type": "domain", "value": "tabloid.example.com", "language": "en" } ] }, - "floor": "garm_floor" + "floor": { + "url": "https://scope3.com/brand-safety-floor", + "name": "Scope3 Common Sense Brand Safety" + } } ``` diff --git a/docs/governance/content-standards/tasks/get_content_standards.mdx b/docs/governance/content-standards/tasks/get_content_standards.mdx index 6f0a028c7..74229890e 100644 --- a/docs/governance/content-standards/tasks/get_content_standards.mdx +++ b/docs/governance/content-standards/tasks/get_content_standards.mdx @@ -42,7 +42,10 @@ Retrieve content safety policies for a specific standards configuration. { "type": "audio_url", "value": "https://cdn.example.com/controversial-podcast.mp3" } ] }, - "floor": "garm_floor" + "floor": { + "url": "https://scope3.com/brand-safety-floor", + "name": "Scope3 Common Sense Brand Safety" + } } ``` @@ -58,7 +61,7 @@ Retrieve content safety policies for a specific standards configuration. | `channels_any` | Ad channels - standards apply to ANY of the listed channels | | `policy` | Natural language policy describing acceptable and unacceptable content contexts | | `calibration_exemplars` | Training/test set of content contexts (pass/fail) to calibrate policy interpretation | -| `floor` | Safety floor baseline (`garm_floor` = GARM Brand Safety Floor) | +| `floor` | Reference to external safety floor definition (URL + name) | ### Error Response diff --git a/static/schemas/source/content-standards/content-standards.json b/static/schemas/source/content-standards/content-standards.json index a59d75b2b..a88fb8135 100644 --- a/static/schemas/source/content-standards/content-standards.json +++ b/static/schemas/source/content-standards/content-standards.json @@ -53,9 +53,24 @@ } }, "floor": { - "type": "string", - "enum": ["garm_floor", "custom"], - "description": "Safety floor baseline. 'garm_floor' applies GARM Brand Safety Floor categories. 'custom' means floor is defined entirely by policy." + "type": "object", + "description": "Safety floor baseline - a reference to an external floor definition. The floor defines universal content exclusions (hate speech, illegal content, etc.) that apply regardless of brand-specific policy.", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "URL to the floor definition document (e.g., 'https://scope3.com/brand-safety-floor', 'https://example-publisher.com/content-guidelines')" + }, + "name": { + "type": "string", + "description": "Human-readable name for the floor (e.g., 'Scope3 Common Sense Brand Safety')" + }, + "version": { + "type": "string", + "description": "Version of the floor definition being referenced" + } + }, + "required": ["url"] }, "ext": { "$ref": "/schemas/core/ext.json" From 46fb80992855599b50fc9b76e557769daff32e00 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 18 Jan 2026 10:18:20 -0500 Subject: [PATCH 35/38] Strengthen Content Standards privacy-preserving value proposition - Make clear this is the ONLY solution for ephemeral/sensitive content - Add "using agents to protect privacy" framing - Clarify where each phase runs (calibration, local execution, validation) - Add table showing phase locations and what happens - Add "Future: Secure Enclaves" section with TEE/containerized governance vision - Reference IAB Tech Lab Authentic framework for real-time verification The key insight: the execution engine runs entirely inside the publisher's infrastructure - content never leaves. OpenAI conversations stay within their firewall. This is the fundamental value proposition for AI-generated and privacy-regulated content. Co-Authored-By: Claude Opus 4.5 --- docs/governance/content-standards/index.mdx | 52 ++++++++++++++++++--- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index 1a05eac9a..4856ebd16 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -9,25 +9,31 @@ The Content Standards Protocol enables **privacy-preserving brand safety** for e ## The Problem -Traditional brand safety relies on third-party verification: send your content to IAS or DoubleVerify, they evaluate it, return a verdict. This works for static web pages. It doesn't work for: +Traditional brand safety relies on third-party verification: send your content to IAS or DoubleVerify, they evaluate it, return a verdict. This works for static web pages. It fundamentally cannot work for: - **AI-generated content** - ChatGPT responses, DALL-E images that exist only in a user session - **Private conversations** - Content in messaging apps, private social feeds - **Ephemeral content** - Stories, live streams, real-time feeds that disappear - **Privacy-regulated content** - GDPR-protected data that cannot be exported -These platforms can't send content to external verification services. But brands still need safety guarantees. +For these platforms, **there is no traditional verification option**. The content simply cannot leave. OpenAI cannot send user conversations to an external service. A messaging app cannot export private chats. A streaming platform cannot share real-time content before it disappears. + +Yet these are exactly the environments where advertising is growing fastest - and where brands most need safety guarantees. Without a privacy-preserving approach, brands either avoid these channels entirely or accept unknown risk. ## The Solution: Calibration-Based Alignment -Content Standards solves this with a three-phase model where **no sensitive content leaves the publisher's walls**: +Content Standards solves this by **using agents to protect privacy**. It's a three-phase model where no sensitive content ever leaves the publisher's infrastructure: -1. **Calibration** - Publisher and verification agent align on policy interpretation using synthetic or sample data -2. **Local Execution** - Publisher runs evaluation locally on every impression (content never leaves) -3. **Validation** - Statistical sampling detects drift; verification agent audits a sample +| Phase | Where It Runs | What Happens | +|-------|---------------|--------------| +| **1. Calibration** | External (safe data only) | Publisher and verification agent align on policy interpretation using synthetic examples or public samples - no PII, no sensitive content | +| **2. Local Execution** | Inside publisher's walls | Publisher runs evaluation on every impression using a local model trained during calibration - content never leaves | +| **3. Validation** | Statistical sampling | Verification agent audits a sample to detect drift - both parties can verify the system is working without exposing PII | This inverts the traditional model. Instead of "send us your content, we'll evaluate it," it's "we'll teach you our standards, you evaluate locally, we'll audit statistically." +**The key insight**: The execution engine runs entirely inside the publisher's infrastructure. For OpenAI, that means brand safety evaluation happens within their firewall - user conversations never leave. For a messaging app, it means private content stays private. The calibration and validation phases provide confidence that the local model is working correctly, without ever requiring access to sensitive data. + ## What It Covers - **Brand safety** - Is this content safe for *any* brand? (universal thresholds like hate speech, illegal content) @@ -284,6 +290,40 @@ See [calibrate_content](/docs/governance/content-standards/tasks/calibrate_conte - **Scope3** - Sustainability-focused brand safety with prompt-based policies - **Custom** - Brand-specific implementations +## Future: Secure Enclaves + +The current model trusts the publisher to faithfully implement the calibrated standards. A future evolution uses **secure enclaves** (Trusted Execution Environments / TEEs) to provide cryptographic guarantees: + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Publisher Infrastructure β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Secure Enclave (TEE) β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ Containerized Governance Agent β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ (provided by verification service) β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β€’ Receives content for evaluation β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β€’ Runs brand safety model β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β€’ Returns pass/fail verdict β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β€’ Content never leaves enclave β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β€’ Code is attestable and auditable β”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +In this model: + +- **Publisher** hosts a secure enclave inside their infrastructure +- **Governance agent** (from IAS, DoubleVerify, etc.) runs as a container within the enclave +- **Content** flows into the enclave for evaluation but never leaves the publisher's walls +- **Both parties** can verify the governance code is running unmodified via attestation + +This provides the same privacy guarantees as local execution, but with cryptographic proof that the correct algorithm is running. The brand knows their standards are being enforced faithfully. The publisher proves compliance without exposing content. + +This architecture aligns with emerging industry frameworks like the [IAB Tech Lab Authentic](https://iabtechlab.com/authentic/) initiative for real-time content verification. + ## Related - [Artifacts](/docs/governance/content-standards/artifacts) - What artifacts are and how to structure them From cc86fd15aaafefa8c133dc4154cffc90a0593fdd Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 18 Jan 2026 10:29:11 -0500 Subject: [PATCH 36/38] Fix IAB Tech Lab reference to ARTF (Agentic RTB Framework) Update the future vision section to reference the correct IAB Tech Lab specification - ARTF defines how service providers package containers deployed into host infrastructure, which is exactly the model Content Standards uses for privacy-preserving brand safety. Co-Authored-By: Claude Opus 4.5 --- docs/governance/content-standards/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index 4856ebd16..8163d552e 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -322,7 +322,7 @@ In this model: This provides the same privacy guarantees as local execution, but with cryptographic proof that the correct algorithm is running. The brand knows their standards are being enforced faithfully. The publisher proves compliance without exposing content. -This architecture aligns with emerging industry frameworks like the [IAB Tech Lab Authentic](https://iabtechlab.com/authentic/) initiative for real-time content verification. +This architecture aligns with the [IAB Tech Lab ARTF (Agentic RTB Framework)](https://iabtechlab.com/standards/artf/), which defines how service providers can package offerings as containers deployed into host infrastructure. ARTF enables hosts to "provide greater access to data and more interaction opportunities to service agents without concerns about leakage, misappropriation or latency" - exactly the model Content Standards requires for privacy-preserving brand safety. ## Related From 4037c85ca1c85a6f4f33f63a2315b28acdff8df6 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 18 Jan 2026 10:30:43 -0500 Subject: [PATCH 37/38] Add pinhole interface specification for secure enclaves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The containerized governance agent needs a well-defined narrow interface: Inbound (verification service β†’ enclave): - Updated models - Policy changes and calibration exemplars - Configuration updates Outbound (enclave β†’ verification service): - Aggregated results (pass rates, drift metrics) - Statistical summaries - Attestation proofs Never crosses the boundary: - Raw content artifacts - User data / PII - Individual impressions This pinhole is the interface that needs standardization for the reference implementation. Co-Authored-By: Claude Opus 4.5 --- docs/governance/content-standards/index.mdx | 72 ++++++++++++++++----- 1 file changed, 55 insertions(+), 17 deletions(-) diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index 8163d552e..d645a7530 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -295,30 +295,68 @@ See [calibrate_content](/docs/governance/content-standards/tasks/calibrate_conte The current model trusts the publisher to faithfully implement the calibrated standards. A future evolution uses **secure enclaves** (Trusted Execution Environments / TEEs) to provide cryptographic guarantees: ``` -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ Publisher Infrastructure β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ Secure Enclave (TEE) β”‚ β”‚ -β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ -β”‚ β”‚ β”‚ Containerized Governance Agent β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ (provided by verification service) β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ β€’ Receives content for evaluation β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ β€’ Runs brand safety model β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ β€’ Returns pass/fail verdict β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ β€’ Content never leaves enclave β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ β€’ Code is attestable and auditable β”‚ β”‚ β”‚ -β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + Verification Service + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ "Pinhole" β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ + β”‚ β”‚ Models β”‚ β”‚ Aggregateβ”‚ β”‚ + β”‚ β”‚ Examples β”‚ β”‚ Results β”‚ β”‚ + β”‚ β”‚ ↓ β”‚ β”‚ ↑ β”‚ β”‚ + β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Publisher Infrastructure β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Secure Enclave (TEE) β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ Containerized Governance Agent β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ IN: β”‚ OUT: β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β€’ Updated models β”‚ β€’ Pass/fail β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β€’ Policy changes β”‚ β€’ Drift metrics β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β€’ Calibration data β”‚ β€’ Aggregates β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ NEVER OUT: β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β€’ Content artifacts β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β€’ User data / PII β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β€’ Individual impressions β”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ Content β†’ [Enclave] β†’ Pass/Fail verdict (content never leaves) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` -In this model: +### The Pinhole Interface + +The enclave maintains a narrow, well-defined interface to the verification service: + +**Inbound (verification service β†’ enclave):** +- Updated brand safety models +- Policy changes and calibration exemplars +- Configuration updates + +**Outbound (enclave β†’ verification service):** +- Aggregated validation results (pass rates, drift metrics) +- Statistical summaries +- Attestation proofs + +**Never crosses the boundary:** +- Raw content artifacts +- User data or PII +- Individual impression-level data + +This pinhole is the interface that needs standardization - it defines exactly what flows in and out while keeping sensitive content locked inside the publisher's walls. + +### Why This Matters - **Publisher** hosts a secure enclave inside their infrastructure - **Governance agent** (from IAS, DoubleVerify, etc.) runs as a container within the enclave - **Content** flows into the enclave for evaluation but never leaves the publisher's walls - **Both parties** can verify the governance code is running unmodified via attestation +- **Models stay current** - the enclave can receive updates without exposing content This provides the same privacy guarantees as local execution, but with cryptographic proof that the correct algorithm is running. The brand knows their standards are being enforced faithfully. The publisher proves compliance without exposing content. From 80e8dafd2e7a320f72506dd6153760b1e26d2038 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 18 Jan 2026 10:35:06 -0500 Subject: [PATCH 38/38] Replace ASCII art with mermaid diagram for secure enclaves ASCII art doesn't render reliably across different fonts and screen sizes. Mermaid diagrams are supported by Mintlify and render consistently. Co-Authored-By: Claude Opus 4.5 --- docs/governance/content-standards/index.mdx | 57 +++++++++------------ 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/docs/governance/content-standards/index.mdx b/docs/governance/content-standards/index.mdx index d645a7530..5328d7a0f 100644 --- a/docs/governance/content-standards/index.mdx +++ b/docs/governance/content-standards/index.mdx @@ -294,40 +294,31 @@ See [calibrate_content](/docs/governance/content-standards/tasks/calibrate_conte The current model trusts the publisher to faithfully implement the calibrated standards. A future evolution uses **secure enclaves** (Trusted Execution Environments / TEEs) to provide cryptographic guarantees: +```mermaid +flowchart TB + subgraph VS["Verification Service"] + Models["Models & Calibration Data"] + Results["Aggregate Results"] + end + + subgraph PUB["Publisher Infrastructure"] + subgraph TEE["Secure Enclave (TEE)"] + Agent["Containerized
Governance Agent"] + end + Content["Content Artifacts"] + end + + Models -->|"Pinhole IN:
models, policy, examples"| Agent + Agent -->|"Pinhole OUT:
pass rates, drift metrics"| Results + Content -->|"evaluate"| Agent + Agent -->|"pass/fail verdict"| Content + + style TEE fill:#e8f5e9,stroke:#4caf50 + style Agent fill:#c8e6c9,stroke:#388e3c + style PUB fill:#fafafa,stroke:#9e9e9e ``` - Verification Service - β”‚ - β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” - β”‚ "Pinhole" β”‚ - β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ - β”‚ β”‚ Models β”‚ β”‚ Aggregateβ”‚ β”‚ - β”‚ β”‚ Examples β”‚ β”‚ Results β”‚ β”‚ - β”‚ β”‚ ↓ β”‚ β”‚ ↑ β”‚ β”‚ - β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ - β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - β”‚ -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ Publisher Infrastructure β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ Secure Enclave (TEE) β”‚ β”‚ -β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ -β”‚ β”‚ β”‚ Containerized Governance Agent β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ IN: β”‚ OUT: β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ β€’ Updated models β”‚ β€’ Pass/fail β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ β€’ Policy changes β”‚ β€’ Drift metrics β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ β€’ Calibration data β”‚ β€’ Aggregates β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ NEVER OUT: β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ β€’ Content artifacts β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ β€’ User data / PII β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ β€’ Individual impressions β”‚ β”‚ β”‚ -β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β”‚ β”‚ -β”‚ Content β†’ [Enclave] β†’ Pass/Fail verdict (content never leaves) β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ -``` + +**Content never crosses the pinhole** - only models flow in, only aggregates flow out. ### The Pinhole Interface