From a8723be98e4b9f57f3d1dc26a5a44fd93799ba29 Mon Sep 17 00:00:00 2001 From: BaiyuScope3 Date: Wed, 24 Dec 2025 17:02:14 -0500 Subject: [PATCH 01/49] feat: add unified assets field for better asset discovery - Add new 'assets' array to format schema with 'required' boolean per asset - Deprecate 'assets_required' (still supported for backward compatibility) - Enables full asset discovery for buyers and AI agents - Update server to include both fields in format responses - Update documentation across creative and media-buy docs - Bump version to 2.6.0 --- CHANGELOG.md | 39 +++++++ docs/creative/asset-types.mdx | 20 +++- docs/creative/formats.mdx | 51 ++++++++- .../creative/implementing-creative-agents.mdx | 4 +- .../task-reference/list_creative_formats.mdx | 4 +- package-lock.json | 4 +- package.json | 2 +- server/src/http.ts | 29 +++-- static/schemas/source/core/format.json | 107 +++++++++++++++++- static/schemas/source/index.json | 2 +- 10 files changed, 237 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ac14daf4f..156ba3729d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,44 @@ # Changelog +## 2.6.0 + +### Minor Changes + +- Add unified `assets` field to format schema for better asset discovery + + **Schema Changes:** + + - **format.json**: Add new `assets` array field that includes both required and optional assets + - **format.json**: Deprecate `assets_required` (still supported for backward compatibility) + + **Rationale:** + + Previously, buyers and AI agents could only see required assets via `assets_required`. There was no way to discover optional assets that enhance creatives (companion banners, third-party tracking pixels, etc.). + + Since each asset already has a `required` boolean field, we introduced a unified `assets` array where: + - `required: true` - Asset MUST be provided for a valid creative + - `required: false` - Asset is optional, enhances the creative when provided + + This enables: + - **Full asset discovery**: Buyers and AI agents can see ALL assets a format supports + - **Richer creatives**: Optional assets like impression trackers can now be discovered and used + - **Cleaner schema**: Single array instead of two separate arrays + + **Example:** + + ```json + { + "format_id": { "agent_url": "https://creative.adcontextprotocol.org", "id": "video_30s" }, + "assets": [ + { "item_type": "individual", "asset_id": "video_file", "asset_type": "video", "required": true }, + { "item_type": "individual", "asset_id": "end_card", "asset_type": "image", "required": false }, + { "item_type": "individual", "asset_id": "impression_tracker", "asset_type": "tracking_pixel", "required": false } + ] + } + ``` + + **Migration:** Non-breaking change. `assets_required` is deprecated but still supported. New implementations should use `assets`. + ## 2.5.1 ### Patch Changes diff --git a/docs/creative/asset-types.mdx b/docs/creative/asset-types.mdx index c3d58aa685..7d669e1a2d 100644 --- a/docs/creative/asset-types.mdx +++ b/docs/creative/asset-types.mdx @@ -338,7 +338,9 @@ The keys in the assets object correspond to the `asset_id` values defined in the ## Usage in Creative Formats -Creative formats specify their required assets using `assets_required` (an array): +Creative formats specify their assets using the `assets` array. Each asset has a `required` boolean: +- **`required: true`** - Asset MUST be provided for a valid creative +- **`required: false`** - Asset is optional, enhances the creative (e.g., companion banners, third-party tracking pixels) ```json { @@ -346,7 +348,7 @@ Creative formats specify their required assets using `assets_required` (an array "agent_url": "https://creative.adcontextprotocol.org", "id": "video_15s_hosted" }, - "assets_required": [ + "assets": [ { "item_type": "individual", "asset_id": "video_file", @@ -359,11 +361,25 @@ Creative formats specify their required assets using `assets_required` (an array "acceptable_resolutions": ["1920x1080", "1280x720"], "max_file_size_mb": 30 } + }, + { + "item_type": "individual", + "asset_id": "impression_tracker", + "asset_type": "tracking_pixel", + "required": false, + "requirements": { + "format": ["url"], + "description": "Third-party impression tracking pixel URL" + } } ] } ``` + +**Backward Compatibility**: The deprecated `assets_required` field is still supported. New implementations should use `assets`. + + ## Repeatable Asset Groups For formats with asset sequences (like carousels, slideshows, stories), see the [Carousel & Multi-Asset Formats guide](/docs/creative/channels/carousels) for complete documentation on repeatable asset group patterns. diff --git a/docs/creative/formats.mdx b/docs/creative/formats.mdx index 882fe96257..be40371c0d 100644 --- a/docs/creative/formats.mdx +++ b/docs/creative/formats.mdx @@ -216,8 +216,9 @@ Formats are JSON objects with the following key fields: }, "name": "30-Second Hosted Video", "type": "video", - "assets_required": [ + "assets": [ { + "item_type": "individual", "asset_id": "video_file", "asset_type": "video", "asset_role": "hero_video", @@ -227,6 +228,39 @@ Formats are JSON objects with the following key fields: "format": ["MP4"], "resolution": ["1920x1080", "1280x720"] } + }, + { + "item_type": "individual", + "asset_id": "end_card_image", + "asset_type": "image", + "asset_role": "end_card", + "required": false, + "requirements": { + "dimensions": "1920x1080", + "format": ["PNG", "JPG"] + } + }, + { + "item_type": "individual", + "asset_id": "companion_banner", + "asset_type": "image", + "asset_role": "companion", + "required": false, + "requirements": { + "dimensions": "300x250", + "format": ["PNG", "JPG", "GIF"] + } + }, + { + "item_type": "individual", + "asset_id": "impression_tracker", + "asset_type": "tracking_pixel", + "asset_role": "third_party_tracking", + "required": false, + "requirements": { + "format": ["url"], + "description": "Third-party impression tracking pixel URL" + } } ] } @@ -236,10 +270,23 @@ Formats are JSON objects with the following key fields: - **format_id**: Unique identifier (may be namespaced with `domain:id`) - **agent_url**: The creative agent authoritative for this format - **type**: Category (video, display, audio, native, dooh, rich_media) -- **assets_required**: Array of asset specifications +- **assets**: Array of all asset specifications with `required` boolean indicating mandatory vs optional - **asset_role**: Identifies asset purpose (hero_image, logo, cta_button, etc.) - **renders**: Array of rendered outputs with dimensions - see below +### Asset Discovery + +The `assets` array enables comprehensive asset discovery. Each asset has a `required` boolean: + +- **`required: true`** - Asset MUST be provided for a valid creative +- **`required: false`** - Asset is optional, enhances the creative when provided (e.g., companion banners, third-party tracking pixels) + +This unified approach helps creative tools and AI agents understand the full capabilities of a format, enabling richer creative experiences when optional assets are available while clearly indicating minimum requirements. + + +**Backward Compatibility**: The deprecated `assets_required` field is still supported for existing implementations. New implementations should use `assets` with the `required` boolean on each asset. + + ### Rendered Outputs and Dimensions Formats specify their rendered outputs via the `renders` array. Most formats produce a single render, but some (companion ads, adaptive formats, multi-placement) produce multiple renders: diff --git a/docs/creative/implementing-creative-agents.mdx b/docs/creative/implementing-creative-agents.mdx index 98671f1eb2..dea73b1478 100644 --- a/docs/creative/implementing-creative-agents.mdx +++ b/docs/creative/implementing-creative-agents.mdx @@ -100,7 +100,7 @@ Creative agents must implement these two tasks: Return all formats your agent defines. This is how buyers discover what creative formats you support. **Key responsibilities:** -- Return complete format definitions with all `assets_required` +- Return complete format definitions with all `assets` (both required and optional) - Include your `agent_url` in each format - Use proper namespacing for `format_id` values @@ -169,7 +169,7 @@ When validating manifests: When updating format definitions: -- **Additive changes** (new optional assets) are safe +- **Additive changes** (new optional assets in `assets_optional`) are safe - **Breaking changes** (removing assets, changing requirements) require new format_id - Consider versioning: `youragency.com:format_name_v2` - Maintain backward compatibility when possible diff --git a/docs/media-buy/task-reference/list_creative_formats.mdx b/docs/media-buy/task-reference/list_creative_formats.mdx index f5f7ed11f3..30c4ba1b58 100644 --- a/docs/media-buy/task-reference/list_creative_formats.mdx +++ b/docs/media-buy/task-reference/list_creative_formats.mdx @@ -40,7 +40,7 @@ Formats may produce multiple rendered pieces (e.g., video + companion banner). D | Field | Description | |-------|-------------| -| `formats` | Array of full format definitions (format_id, name, type, requirements, assets_required, renders) | +| `formats` | Array of full format definitions (format_id, name, type, requirements, assets, renders) | | `creative_agents` | Optional array of other creative agents providing additional formats | See [Format schema](https://adcontextprotocol.org/schemas/v2/core/format.json) for complete format object structure. @@ -405,7 +405,7 @@ Each format includes: | `name` | Human-readable format name | | `type` | Format type (audio, video, display, dooh) | | `requirements` | Technical requirements (duration, file types, bitrate, etc.) | -| `assets_required` | Array of required assets with specifications | +| `assets` | Array of all assets with `required` boolean indicating mandatory vs optional | | `renders` | Array of rendered output pieces (dimensions, role) | ### Asset Roles diff --git a/package-lock.json b/package-lock.json index db95e22227..31742dbfb8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "adcontextprotocol", - "version": "2.5.0", + "version": "2.5.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "adcontextprotocol", - "version": "2.5.0", + "version": "2.5.2", "dependencies": { "@adcp/client": "^3.4.0", "@modelcontextprotocol/sdk": "^1.24.3", diff --git a/package.json b/package.json index 0ec58a9540..91ded87e56 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adcontextprotocol", - "version": "2.5.1", + "version": "2.6.0", "private": true, "scripts": { "start": "bash -c 'source <(grep CONDUCTOR_PORT .env.local | sed \"s/^/export /\") && DOTENV_CONFIG_PATH=.env.local PORT=${CONDUCTOR_PORT:-3000} tsx watch server/src/index.ts'", diff --git a/server/src/http.ts b/server/src/http.ts index 3b9718d96d..fbcbe426cd 100644 --- a/server/src/http.ts +++ b/server/src/http.ts @@ -8334,18 +8334,23 @@ Disallow: /api/admin/ return res.json({ success: true, - formats: formats.map(format => ({ - format_id: format.format_id, - name: format.name, - type: format.type, - description: format.description, - preview_image: format.preview_image, - example_url: format.example_url, - renders: format.renders, - assets_required: format.assets_required, - output_format_ids: format.output_format_ids, - agent_url: format.agent_url, - })), + formats: formats.map(format => { + // Cast to allow 'assets' field (added in schema v2.5.2, @adcp/client may not have it yet) + const formatWithAssets = format as typeof format & { assets?: unknown }; + return { + format_id: format.format_id, + name: format.name, + type: format.type, + description: format.description, + preview_image: format.preview_image, + example_url: format.example_url, + renders: format.renders, + assets_required: format.assets_required, // deprecated but kept for backward compatibility + assets: formatWithAssets.assets, // new unified field + output_format_ids: format.output_format_ids, + agent_url: format.agent_url, + }; + }), }); } catch (error) { logger.error({ err: error, url }, 'Agent formats fetch error'); diff --git a/static/schemas/source/core/format.json b/static/schemas/source/core/format.json index d778d5de8c..b399d24cef 100644 --- a/static/schemas/source/core/format.json +++ b/static/schemas/source/core/format.json @@ -121,7 +121,7 @@ }, "assets_required": { "type": "array", - "description": "Array of required assets or asset groups for this format. Each asset is identified by its asset_id, which must be used as the key in creative manifests. Can contain individual assets or repeatable asset sequences (e.g., carousel products, slideshow frames).", + "description": "DEPRECATED: Use 'assets' instead. Array of required assets or asset groups for this format. Each asset is identified by its asset_id, which must be used as the key in creative manifests. Can contain individual assets or repeatable asset sequences (e.g., carousel products, slideshow frames). This field is maintained for backward compatibility; new implementations should use 'assets' with the 'required' boolean on each asset.", "items": { "oneOf": [ { @@ -217,6 +217,111 @@ ] } }, + "assets": { + "type": "array", + "description": "Array of all assets supported for this format. Each asset is identified by its asset_id, which must be used as the key in creative manifests. Use the 'required' boolean on each asset to indicate whether it's mandatory. This field replaces the deprecated 'assets_required' and enables full asset discovery for buyers and AI agents.", + "items": { + "oneOf": [ + { + "description": "Individual asset specification", + "type": "object", + "properties": { + "item_type": { + "type": "string", + "const": "individual", + "description": "Discriminator indicating this is an individual asset" + }, + "asset_id": { + "type": "string", + "description": "Unique identifier for this asset. Creative manifests MUST use this exact value as the key in the assets object." + }, + "asset_type": { + "$ref": "/schemas/enums/asset-content-type.json", + "description": "Type of asset" + }, + "asset_role": { + "type": "string", + "description": "Optional descriptive label for this asset's purpose (e.g., 'hero_image', 'logo', 'third_party_tracking'). Not used for referencing assets in manifests—use asset_id instead. This field is for human-readable documentation and UI display only." + }, + "required": { + "type": "boolean", + "description": "Whether this asset is required (true) or optional (false). Required assets must be provided for a valid creative. Optional assets enhance the creative but are not mandatory.", + "default": false + }, + "requirements": { + "type": "object", + "description": "Technical requirements for this asset (dimensions, file size, duration, etc.). For template formats, use parameters_from_format_id: true to indicate asset parameters must match the format_id parameters (width/height/unit and/or duration_ms).", + "additionalProperties": true + } + }, + "required": ["item_type", "asset_id", "asset_type"] + }, + { + "description": "Repeatable asset group (for carousels, slideshows, playlists, etc.)", + "type": "object", + "properties": { + "item_type": { + "type": "string", + "const": "repeatable_group", + "description": "Discriminator indicating this is a repeatable asset group" + }, + "asset_group_id": { + "type": "string", + "description": "Identifier for this asset group (e.g., 'product', 'slide', 'card')" + }, + "required": { + "type": "boolean", + "description": "Whether this asset group is required. If true, at least min_count repetitions must be provided.", + "default": false + }, + "min_count": { + "type": "integer", + "description": "Minimum number of repetitions required (if group is required) or allowed (if optional)", + "minimum": 0 + }, + "max_count": { + "type": "integer", + "description": "Maximum number of repetitions allowed", + "minimum": 1 + }, + "assets": { + "type": "array", + "description": "Assets within each repetition of this group", + "items": { + "type": "object", + "properties": { + "asset_id": { + "type": "string", + "description": "Identifier for this asset within the group" + }, + "asset_type": { + "$ref": "/schemas/enums/asset-content-type.json", + "description": "Type of asset" + }, + "asset_role": { + "type": "string", + "description": "Optional descriptive label for this asset's purpose. Not used for referencing assets in manifests—use asset_id instead. This field is for human-readable documentation and UI display only." + }, + "required": { + "type": "boolean", + "description": "Whether this asset is required within each repetition of the group", + "default": false + }, + "requirements": { + "type": "object", + "description": "Technical requirements for this asset. For template formats, use parameters_from_format_id: true to indicate asset parameters must match the format_id parameters (width/height/unit and/or duration_ms).", + "additionalProperties": true + } + }, + "required": ["asset_id", "asset_type"] + } + } + }, + "required": ["item_type", "asset_group_id", "min_count", "max_count", "assets"] + } + ] + } + }, "delivery": { "type": "object", "description": "Delivery method specifications (e.g., hosted, VAST, third-party tags)", diff --git a/static/schemas/source/index.json b/static/schemas/source/index.json index 2056c90cee..6ea9e42a77 100644 --- a/static/schemas/source/index.json +++ b/static/schemas/source/index.json @@ -4,7 +4,7 @@ "title": "AdCP Schema Registry v1", "version": "1.0.0", "description": "Registry of all AdCP JSON schemas for validation and discovery", - "adcp_version": "2.5.1", + "adcp_version": "2.6.0", "standard_formats_version": "2.0.0", "versioning": { "note": "AdCP uses path-based versioning. The schema URL path (/schemas/) indicates the version. Individual request/response schemas do NOT include adcp_version fields. Compatibility follows semantic versioning rules." From e2b0b62364f4fd9dfc7894d14cf7bb52eeb74243 Mon Sep 17 00:00:00 2001 From: BaiyuScope3 Date: Fri, 2 Jan 2026 14:57:17 -0500 Subject: [PATCH 02/49] Add changeset for assets field feature --- .changeset/assets-discovery.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .changeset/assets-discovery.md diff --git a/.changeset/assets-discovery.md b/.changeset/assets-discovery.md new file mode 100644 index 0000000000..43d94fd6e9 --- /dev/null +++ b/.changeset/assets-discovery.md @@ -0,0 +1,11 @@ +--- +"adcontextprotocol": minor +--- + +Add unified `assets` field to format schema for better asset discovery + +- Add new `assets` array to format schema with `required` boolean per asset +- Deprecate `assets_required` (still supported for backward compatibility) +- Enables full asset discovery for buyers and AI agents to see all supported assets +- Optional assets like impression trackers can now be discovered and used + From e4ed83965e62060ed3231f2f6cfa2e88061fa373 Mon Sep 17 00:00:00 2001 From: BaiyuScope3 Date: Fri, 2 Jan 2026 15:02:11 -0500 Subject: [PATCH 03/49] fix: update CI to use .cjs extension for check-testable-snippets --- .github/workflows/check-testable-snippets.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-testable-snippets.yml b/.github/workflows/check-testable-snippets.yml index 7dea6c929c..e3ef8c2785 100644 --- a/.github/workflows/check-testable-snippets.yml +++ b/.github/workflows/check-testable-snippets.yml @@ -26,7 +26,7 @@ jobs: if [ -s changed_files.txt ]; then echo "📋 Checking documentation changes for testable snippets..." - node scripts/check-testable-snippets.js + node scripts/check-testable-snippets.cjs else echo "✓ No documentation files changed" fi From e7bbaac17bdf2813a028b89c776e2b99bbbd8e02 Mon Sep 17 00:00:00 2001 From: BaiyuScope3 Date: Fri, 2 Jan 2026 15:15:54 -0500 Subject: [PATCH 04/49] fix: update docs to reference assets field instead of assets_optional --- docs/creative/implementing-creative-agents.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/creative/implementing-creative-agents.mdx b/docs/creative/implementing-creative-agents.mdx index dea73b1478..abb86bb5d0 100644 --- a/docs/creative/implementing-creative-agents.mdx +++ b/docs/creative/implementing-creative-agents.mdx @@ -169,7 +169,7 @@ When validating manifests: When updating format definitions: -- **Additive changes** (new optional assets in `assets_optional`) are safe +- **Additive changes** (new optional assets with `required: false` in `assets`) are safe - **Breaking changes** (removing assets, changing requirements) require new format_id - Consider versioning: `youragency.com:format_name_v2` - Maintain backward compatibility when possible From 619ca3e7f3492a55189e7ca49a6602973db5130b Mon Sep 17 00:00:00 2001 From: BaiyuScope3 Date: Fri, 2 Jan 2026 16:36:52 -0500 Subject: [PATCH 05/49] docs: keep assets_required as deprecated alongside new assets field --- docs/creative/asset-types.mdx | 2 +- docs/creative/formats.mdx | 3 ++- docs/creative/task-reference/list_creative_formats.mdx | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/creative/asset-types.mdx b/docs/creative/asset-types.mdx index b4a4cdbac4..32ba29a231 100644 --- a/docs/creative/asset-types.mdx +++ b/docs/creative/asset-types.mdx @@ -377,7 +377,7 @@ Creative formats specify their assets using the `assets` array. Each asset has a ``` -**Backward Compatibility**: The deprecated `assets_required` field is still supported. New implementations should use `assets`. +**Backward Compatibility**: The `assets_required` field is deprecated but still supported. Existing implementations can continue using `assets_required` for required assets only. New implementations should use `assets` with the `required` boolean for full asset discovery. ## Repeatable Asset Groups diff --git a/docs/creative/formats.mdx b/docs/creative/formats.mdx index 15470c3b6f..d3fb20715e 100644 --- a/docs/creative/formats.mdx +++ b/docs/creative/formats.mdx @@ -276,6 +276,7 @@ Formats are JSON objects with the following key fields: - **agent_url**: The creative agent authoritative for this format - **type**: Category (video, display, audio, native, dooh, rich_media) - **assets**: Array of all asset specifications with `required` boolean indicating mandatory vs optional +- **assets_required**: *(Deprecated)* Array of required asset specifications - use `assets` instead - **asset_role**: Identifies asset purpose (hero_image, logo, cta_button, etc.) - **renders**: Array of rendered outputs with dimensions - see below @@ -289,7 +290,7 @@ The `assets` array enables comprehensive asset discovery. Each asset has a `requ This unified approach helps creative tools and AI agents understand the full capabilities of a format, enabling richer creative experiences when optional assets are available while clearly indicating minimum requirements. -**Backward Compatibility**: The deprecated `assets_required` field is still supported for existing implementations. New implementations should use `assets` with the `required` boolean on each asset. +**Backward Compatibility**: The `assets_required` field is deprecated but still supported. Existing implementations can continue using `assets_required` for required assets only. New implementations should use `assets` with the `required` boolean on each asset for full asset discovery. ### Rendered Outputs and Dimensions diff --git a/docs/creative/task-reference/list_creative_formats.mdx b/docs/creative/task-reference/list_creative_formats.mdx index cc22e2b296..af0348f718 100644 --- a/docs/creative/task-reference/list_creative_formats.mdx +++ b/docs/creative/task-reference/list_creative_formats.mdx @@ -56,7 +56,7 @@ Formats may produce multiple rendered pieces (e.g., video + companion banner). D | Field | Description | |-------|-------------| -| `formats` | Array of full format definitions (format_id, name, type, requirements, assets, renders) | +| `formats` | Array of full format definitions (format_id, name, type, requirements, assets, assets_required, renders) | | `creative_agents` | Optional array of other creative agents providing additional formats | See [Format schema](https://adcontextprotocol.org/schemas/v2/core/format.json) for complete format object structure. @@ -422,6 +422,7 @@ Each format includes: | `type` | Format type (audio, video, display, dooh) | | `requirements` | Technical requirements (duration, file types, bitrate, etc.) | | `assets` | Array of all assets with `required` boolean indicating mandatory vs optional | +| `assets_required` | *(Deprecated)* Array of required assets - use `assets` instead | | `renders` | Array of rendered output pieces (dimensions, role) | ### Asset Roles From 7681bfb4982908d97ed44ad4ec48133503371f1c Mon Sep 17 00:00:00 2001 From: BaiyuScope3 Date: Fri, 2 Jan 2026 16:38:27 -0500 Subject: [PATCH 06/49] schema: add formal deprecated: true to assets_required field --- static/schemas/source/core/format.json | 1 + 1 file changed, 1 insertion(+) diff --git a/static/schemas/source/core/format.json b/static/schemas/source/core/format.json index b399d24cef..9ab38f749c 100644 --- a/static/schemas/source/core/format.json +++ b/static/schemas/source/core/format.json @@ -121,6 +121,7 @@ }, "assets_required": { "type": "array", + "deprecated": true, "description": "DEPRECATED: Use 'assets' instead. Array of required assets or asset groups for this format. Each asset is identified by its asset_id, which must be used as the key in creative manifests. Can contain individual assets or repeatable asset sequences (e.g., carousel products, slideshow frames). This field is maintained for backward compatibility; new implementations should use 'assets' with the 'required' boolean on each asset.", "items": { "oneOf": [ From 0bbdf287021976be9ea577d1d28683529ee504c8 Mon Sep 17 00:00:00 2001 From: BaiyuScope3 Date: Fri, 2 Jan 2026 16:42:08 -0500 Subject: [PATCH 07/49] schema: make 'required' field mandatory in assets array --- static/schemas/source/core/format.json | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/static/schemas/source/core/format.json b/static/schemas/source/core/format.json index 9ab38f749c..5e6330740c 100644 --- a/static/schemas/source/core/format.json +++ b/static/schemas/source/core/format.json @@ -246,8 +246,7 @@ }, "required": { "type": "boolean", - "description": "Whether this asset is required (true) or optional (false). Required assets must be provided for a valid creative. Optional assets enhance the creative but are not mandatory.", - "default": false + "description": "Whether this asset is required (true) or optional (false). Required assets must be provided for a valid creative. Optional assets enhance the creative but are not mandatory." }, "requirements": { "type": "object", @@ -255,7 +254,7 @@ "additionalProperties": true } }, - "required": ["item_type", "asset_id", "asset_type"] + "required": ["item_type", "asset_id", "asset_type", "required"] }, { "description": "Repeatable asset group (for carousels, slideshows, playlists, etc.)", @@ -272,8 +271,7 @@ }, "required": { "type": "boolean", - "description": "Whether this asset group is required. If true, at least min_count repetitions must be provided.", - "default": false + "description": "Whether this asset group is required. If true, at least min_count repetitions must be provided." }, "min_count": { "type": "integer", @@ -318,7 +316,7 @@ } } }, - "required": ["item_type", "asset_group_id", "min_count", "max_count", "assets"] + "required": ["item_type", "asset_group_id", "required", "min_count", "max_count", "assets"] } ] } From c8a4487b1af8b8e4162cd35d8e33857c778cb50c Mon Sep 17 00:00:00 2001 From: BaiyuScope3 Date: Fri, 2 Jan 2026 16:45:36 -0500 Subject: [PATCH 08/49] schema: make 'required' field mandatory in nested repeatable_group assets --- static/schemas/source/core/format.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/schemas/source/core/format.json b/static/schemas/source/core/format.json index 5e6330740c..a018252ddc 100644 --- a/static/schemas/source/core/format.json +++ b/static/schemas/source/core/format.json @@ -312,7 +312,7 @@ "additionalProperties": true } }, - "required": ["asset_id", "asset_type"] + "required": ["asset_id", "asset_type", "required"] } } }, From 8261125d9e20c954c24c26b451f5fb8cf6d92f89 Mon Sep 17 00:00:00 2001 From: BaiyuScope3 Date: Fri, 2 Jan 2026 17:24:34 -0500 Subject: [PATCH 09/49] docs: add deprecated assets_required examples with deprecation comments --- docs/creative/asset-types.mdx | 16 ++++++++++++++++ docs/creative/formats.mdx | 15 +++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/docs/creative/asset-types.mdx b/docs/creative/asset-types.mdx index 32ba29a231..927fbedf1f 100644 --- a/docs/creative/asset-types.mdx +++ b/docs/creative/asset-types.mdx @@ -372,6 +372,22 @@ Creative formats specify their assets using the `assets` array. Each asset has a "description": "Third-party impression tracking pixel URL" } } + ], + + // DEPRECATED: Use "assets" above instead. Kept for backward compatibility. + "assets_required": [ + { + "item_type": "individual", + "asset_id": "video_file", + "asset_type": "video", + "requirements": { + "duration_seconds": 15, + "acceptable_formats": ["mp4"], + "acceptable_codecs": ["h264"], + "acceptable_resolutions": ["1920x1080", "1280x720"], + "max_file_size_mb": 30 + } + } ] } ``` diff --git a/docs/creative/formats.mdx b/docs/creative/formats.mdx index d3fb20715e..ba6b100d4f 100644 --- a/docs/creative/formats.mdx +++ b/docs/creative/formats.mdx @@ -267,6 +267,21 @@ Formats are JSON objects with the following key fields: "description": "Third-party impression tracking pixel URL" } } + ], + + // DEPRECATED: Use "assets" above instead. Kept for backward compatibility. + "assets_required": [ + { + "item_type": "individual", + "asset_id": "video_file", + "asset_type": "video", + "asset_role": "hero_video", + "requirements": { + "duration": "30s", + "format": ["MP4"], + "resolution": ["1920x1080", "1280x720"] + } + } ] } ``` From 7c9c9f9b5ac55b9116e9de525dfaf9427472d682 Mon Sep 17 00:00:00 2001 From: BaiyuScope3 Date: Fri, 2 Jan 2026 17:30:32 -0500 Subject: [PATCH 10/49] ci: add 2.6.x branch to all workflows --- .github/workflows/broken-links.yml | 4 ++-- .github/workflows/changeset-check.yml | 2 +- .github/workflows/check-testable-snippets.yml | 1 + .github/workflows/release.yml | 1 + .github/workflows/schema-validation.yml | 4 ++-- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/broken-links.yml b/.github/workflows/broken-links.yml index b8b2bce77e..8b3045af7e 100644 --- a/.github/workflows/broken-links.yml +++ b/.github/workflows/broken-links.yml @@ -2,9 +2,9 @@ name: Check for Broken Links on: push: - branches: [ main, develop ] + branches: [ main, develop, '2.6.x' ] pull_request: - branches: [ main, develop ] + branches: [ main, develop, '2.6.x' ] jobs: broken-links: diff --git a/.github/workflows/changeset-check.yml b/.github/workflows/changeset-check.yml index 346ed6202c..267de5f1f6 100644 --- a/.github/workflows/changeset-check.yml +++ b/.github/workflows/changeset-check.yml @@ -2,7 +2,7 @@ name: Changeset Check on: pull_request: - branches: [main] + branches: [main, '2.6.x'] jobs: check: diff --git a/.github/workflows/check-testable-snippets.yml b/.github/workflows/check-testable-snippets.yml index e3ef8c2785..f28614b7da 100644 --- a/.github/workflows/check-testable-snippets.yml +++ b/.github/workflows/check-testable-snippets.yml @@ -2,6 +2,7 @@ name: Check Testable Snippets on: pull_request: + branches: [main, '2.6.x'] paths: - 'docs/**/*.md' - 'docs/**/*.mdx' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c5665ce6d3..176da7a247 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - '2.6.x' concurrency: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/schema-validation.yml b/.github/workflows/schema-validation.yml index 7d772503ff..a599e1adb5 100644 --- a/.github/workflows/schema-validation.yml +++ b/.github/workflows/schema-validation.yml @@ -2,9 +2,9 @@ name: JSON Schema Validation on: push: - branches: [ main, develop ] + branches: [ main, develop, '2.6.x' ] pull_request: - branches: [ main, develop ] + branches: [ main, develop, '2.6.x' ] jobs: schema-validation: From dba91d112e64119ea02a616ab80f7873f901ceff Mon Sep 17 00:00:00 2001 From: BaiyuScope3 Date: Mon, 5 Jan 2026 14:36:25 -0500 Subject: [PATCH 11/49] docs: update intro and schema-versioning for 2.6.0 --- docs/intro.mdx | 4 ++-- docs/reference/schema-versioning.mdx | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/intro.mdx b/docs/intro.mdx index 7ec71cf192..fa5dd7389a 100644 --- a/docs/intro.mdx +++ b/docs/intro.mdx @@ -7,9 +7,9 @@ keywords: [advertising automation protocol, programmatic advertising API, MCP ad -**🎉 AdCP v2.5.0 Released** +**🎉 AdCP v2.6.0 Released** -Developer experience and API refinement release featuring type safety improvements, batch creative previews (5-10x faster), schema versioning (`/schemas/v2.5/`), template formats with dynamic sizing, enhanced product filtering, and inline creative updates. [See what's new →](/docs/reference/release-notes) +Asset discovery release featuring unified `assets` field in format schema for discovering both required and optional assets. Buyers and AI agents can now see ALL assets a format supports. [See what's new →](/docs/reference/release-notes) Welcome to the Ad Context Protocol (AdCP) documentation. AdCP is an **open standard for advertising automation** that enables AI assistants to interact with advertising platforms through unified, standardized interfaces. diff --git a/docs/reference/schema-versioning.mdx b/docs/reference/schema-versioning.mdx index 8c11d4606e..7b979d3ffd 100644 --- a/docs/reference/schema-versioning.mdx +++ b/docs/reference/schema-versioning.mdx @@ -21,7 +21,7 @@ All AdCP schemas are available at multiple paths: ### Exact Version (Recommended for Production) ``` -/schemas/2.5.0/core/product.json +/schemas/2.6.0/core/product.json ``` - **Pin to specific release** - URL never changes - **Guaranteed stability** - Schema won't update unexpectedly @@ -69,8 +69,8 @@ Bundled schemas are experimental and may change or be removed in future releases For tools that don't support `$ref` resolution (some API clients, code generators, or desktop applications), AdCP provides **bundled schemas** with all references resolved inline: ``` -/schemas/2.5.0/bundled/media-buy/create-media-buy-request.json -/schemas/2.5.0/bundled/media-buy/get-products-response.json +/schemas/2.6.0/bundled/media-buy/create-media-buy-request.json +/schemas/2.6.0/bundled/media-buy/get-products-response.json ``` ### What's Bundled @@ -91,11 +91,11 @@ Bundled schemas are generated for all request/response schemas (`*-request.json` ```javascript // Bundled - self-contained, no ref resolution needed -const schema = await fetch('https://adcp.org/schemas/2.5.0/bundled/media-buy/create-media-buy-request.json'); +const schema = await fetch('https://adcp.org/schemas/2.6.0/bundled/media-buy/create-media-buy-request.json'); const validate = ajv.compile(await schema.json()); // Modular - requires $ref resolution -const schema = await fetch('https://adcp.org/schemas/2.5.0/media-buy/create-media-buy-request.json'); +const schema = await fetch('https://adcp.org/schemas/2.6.0/media-buy/create-media-buy-request.json'); // This schema contains $ref to other schemas that must be resolved ``` @@ -105,7 +105,7 @@ Bundled schemas include a `_bundled` field with generation metadata: ```json { - "$id": "/schemas/2.5.0/bundled/core/product.json", + "$id": "/schemas/2.6.0/bundled/core/product.json", "title": "Product", "_bundled": { "generatedAt": "2024-12-17T10:30:00.000Z", @@ -120,7 +120,7 @@ Bundled schemas include a `_bundled` field with generation metadata: ```javascript // Production - pin exact version -const SCHEMA_VERSION = '2.5.0'; +const SCHEMA_VERSION = '2.6.0'; const schema = await fetch(`https://adcp.org/schemas/${SCHEMA_VERSION}/core/product.json`); // Stable development - track minor version (patch updates only) @@ -137,12 +137,12 @@ Always use exact versions to ensure generated types match your target protocol v ```bash # TypeScript SDK generation npx json-schema-to-typescript \ - https://adcp.org/schemas/2.5.0/core/product.json \ + https://adcp.org/schemas/2.6.0/core/product.json \ --output types/product.d.ts # Python SDK generation datamodel-codegen \ - --url https://adcp.org/schemas/2.5.0/core/product.json \ + --url https://adcp.org/schemas/2.6.0/core/product.json \ --output models/product.py ``` @@ -153,7 +153,7 @@ datamodel-codegen \ import Ajv from 'ajv'; const ajv = new Ajv(); -const schema = await fetch('https://adcp.org/schemas/2.5.0/core/product.json'); +const schema = await fetch('https://adcp.org/schemas/2.6.0/core/product.json'); const validate = ajv.compile(await schema.json()); if (!validate(data)) { @@ -227,7 +227,7 @@ If you're currently using `/schemas/v2/`: ``` 3. **Consider pinning** for production stability: ```javascript - const schema = await fetch('/schemas/2.5.0/core/product.json'); + const schema = await fetch('/schemas/2.6.0/core/product.json'); ``` ### From Exact Version to Major Alias @@ -270,11 +270,11 @@ If you want to track the latest 2.x: ```bash # Get schema registry with version info curl https://adcp.org/schemas/v2/index.json | jq '.adcp_version' -# Output: "2.5.0" +# Output: "2.6.0" # Get version from specific release -curl https://adcp.org/schemas/2.5.0/index.json | jq '.adcp_version' -# Output: "2.5.0" +curl https://adcp.org/schemas/2.6.0/index.json | jq '.adcp_version' +# Output: "2.6.0" ``` ### List Available Versions From 8a3e2c4987c147770490c0f02cbc5b23aff27c74 Mon Sep 17 00:00:00 2001 From: BaiyuScope3 Date: Mon, 5 Jan 2026 14:51:39 -0500 Subject: [PATCH 12/49] fix: detect untracked files in sync-versioned-docs workflow git diff only detects tracked files. Use git add + git diff --cached to properly detect new v2.6-rc/ folder on first sync. --- .github/workflows/sync-versioned-docs.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sync-versioned-docs.yml b/.github/workflows/sync-versioned-docs.yml index 62819dc076..3a15e4bec3 100644 --- a/.github/workflows/sync-versioned-docs.yml +++ b/.github/workflows/sync-versioned-docs.yml @@ -38,8 +38,9 @@ jobs: # Extract docs folder from 2.6.x branch git archive origin/2.6.x -- docs | tar -x -C v2.6-rc/ - # Check if there are changes - if git diff --quiet v2.6-rc/; then + # Check if there are changes (including untracked files) + git add v2.6-rc/ + if git diff --cached --quiet; then echo "No changes to sync" echo "CHANGES_DETECTED=false" >> $GITHUB_ENV else From d7677307248d2bb41d70dace9ffcf0087f9ebc82 Mon Sep 17 00:00:00 2001 From: BaiyuScope3 Date: Mon, 5 Jan 2026 14:57:40 -0500 Subject: [PATCH 13/49] fix: create PR instead of pushing directly to main Main branch is protected. Use peter-evans/create-pull-request action to create a PR for the synced docs instead of direct push. --- .github/workflows/sync-versioned-docs.yml | 36 +++++++++-------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/.github/workflows/sync-versioned-docs.yml b/.github/workflows/sync-versioned-docs.yml index 3a15e4bec3..e22cd7dfe7 100644 --- a/.github/workflows/sync-versioned-docs.yml +++ b/.github/workflows/sync-versioned-docs.yml @@ -21,11 +21,6 @@ jobs: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - - name: Configure Git - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - name: Fetch 2.6.x branch run: git fetch origin 2.6.x @@ -38,20 +33,17 @@ jobs: # Extract docs folder from 2.6.x branch git archive origin/2.6.x -- docs | tar -x -C v2.6-rc/ - # Check if there are changes (including untracked files) - git add v2.6-rc/ - if git diff --cached --quiet; then - echo "No changes to sync" - echo "CHANGES_DETECTED=false" >> $GITHUB_ENV - else - echo "Changes detected in v2.6-rc docs" - echo "CHANGES_DETECTED=true" >> $GITHUB_ENV - fi - - - name: Commit and push changes - if: env.CHANGES_DETECTED == 'true' - run: | - git add v2.6-rc/ - git commit -m "chore: sync v2.6-rc docs from 2.6.x branch [skip ci]" - git push origin main - + - name: Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "chore: sync v2.6-rc docs from 2.6.x branch" + title: "chore: sync v2.6-rc docs from 2.6.x branch" + body: | + This PR syncs the v2.6-rc documentation from the 2.6.x branch. + + Auto-generated by the sync-versioned-docs workflow. + branch: auto/sync-v2.6-rc-docs + delete-branch: true + add-paths: | + v2.6-rc/ From fcb13f2b740749df1ebf656068935620f84e1c56 Mon Sep 17 00:00:00 2001 From: BaiyuScope3 Date: Wed, 7 Jan 2026 14:58:15 -0500 Subject: [PATCH 14/49] chore: regenerate dist/schemas/2.6.0 with additionalProperties: true Rebuilds 2.6.0 schemas to include the relaxed validation from #646. All schema objects now allow unknown fields for forward compatibility. --- dist/schemas/2.6.0/adagents.json | 480 ++++++++++-------- .../media-buy/build-creative-request.json | 54 +- .../media-buy/build-creative-response.json | 60 +-- .../media-buy/create-media-buy-request.json | 68 +-- .../media-buy/create-media-buy-response.json | 24 +- .../get-media-buy-delivery-request.json | 6 +- .../get-media-buy-delivery-response.json | 28 +- .../media-buy/get-products-request.json | 18 +- .../media-buy/get-products-response.json | 66 +-- .../list-authorized-properties-request.json | 6 +- .../list-authorized-properties-response.json | 12 +- .../list-creative-formats-request.json | 8 +- .../list-creative-formats-response.json | 26 +- .../media-buy/list-creatives-request.json | 12 +- .../media-buy/list-creatives-response.json | 66 +-- .../bundled/media-buy/package-request.json | 58 +-- .../provide-performance-feedback-request.json | 8 +- ...provide-performance-feedback-response.json | 14 +- .../media-buy/sync-creatives-request.json | 54 +- .../media-buy/sync-creatives-response.json | 18 +- .../media-buy/update-media-buy-request.json | 60 +-- .../media-buy/update-media-buy-response.json | 24 +- .../signals/activate-signal-request.json | 10 +- .../signals/activate-signal-response.json | 26 +- .../bundled/signals/get-signals-request.json | 14 +- .../bundled/signals/get-signals-response.json | 28 +- dist/schemas/2.6.0/core/activation-key.json | 15 +- .../2.6.0/core/assets/audio-asset.json | 6 +- dist/schemas/2.6.0/core/assets/css-asset.json | 6 +- .../2.6.0/core/assets/daast-asset.json | 14 +- .../schemas/2.6.0/core/assets/html-asset.json | 6 +- .../2.6.0/core/assets/image-asset.json | 8 +- .../2.6.0/core/assets/javascript-asset.json | 6 +- .../2.6.0/core/assets/markdown-asset.json | 6 +- .../schemas/2.6.0/core/assets/text-asset.json | 6 +- dist/schemas/2.6.0/core/assets/url-asset.json | 6 +- .../schemas/2.6.0/core/assets/vast-asset.json | 14 +- .../2.6.0/core/assets/video-asset.json | 8 +- .../2.6.0/core/assets/webhook-asset.json | 12 +- .../2.6.0/core/async-response-data.json | 1 - dist/schemas/2.6.0/core/brand-manifest.json | 185 ++++--- dist/schemas/2.6.0/core/creative-asset.json | 67 ++- .../2.6.0/core/creative-assignment.json | 8 +- dist/schemas/2.6.0/core/creative-filters.json | 2 +- .../schemas/2.6.0/core/creative-manifest.json | 4 +- dist/schemas/2.6.0/core/creative-policy.json | 10 +- dist/schemas/2.6.0/core/delivery-metrics.json | 9 +- dist/schemas/2.6.0/core/deployment.json | 16 +- dist/schemas/2.6.0/core/destination.json | 14 +- dist/schemas/2.6.0/core/error.json | 13 +- dist/schemas/2.6.0/core/format-id.json | 15 +- dist/schemas/2.6.0/core/format.json | 90 +++- dist/schemas/2.6.0/core/frequency-cap.json | 8 +- .../2.6.0/core/mcp-webhook-payload.json | 7 +- dist/schemas/2.6.0/core/measurement.json | 31 +- dist/schemas/2.6.0/core/media-buy.json | 2 +- dist/schemas/2.6.0/core/package.json | 2 +- .../2.6.0/core/performance-feedback.json | 18 +- dist/schemas/2.6.0/core/placement.json | 7 +- dist/schemas/2.6.0/core/product-filters.json | 20 +- dist/schemas/2.6.0/core/product.json | 6 +- .../2.6.0/core/promoted-offerings.json | 42 +- .../schemas/2.6.0/core/promoted-products.json | 16 +- dist/schemas/2.6.0/core/property-id.json | 7 +- dist/schemas/2.6.0/core/property-tag.json | 9 +- dist/schemas/2.6.0/core/property.json | 17 +- .../schemas/2.6.0/core/protocol-envelope.json | 8 +- .../core/publisher-property-selector.json | 23 +- .../2.6.0/core/push-notification-config.json | 10 +- .../2.6.0/core/reporting-capabilities.json | 38 +- dist/schemas/2.6.0/core/response.json | 8 +- dist/schemas/2.6.0/core/signal-filters.json | 2 +- dist/schemas/2.6.0/core/sub-asset.json | 18 +- dist/schemas/2.6.0/core/targeting.json | 4 +- .../schemas/2.6.0/core/tasks-get-request.json | 2 +- .../2.6.0/core/tasks-get-response.json | 8 +- .../2.6.0/core/tasks-list-request.json | 8 +- .../2.6.0/core/tasks-list-response.json | 12 +- .../2.6.0/creative/asset-types/index.json | 7 +- .../list-creative-formats-request.json | 2 +- .../list-creative-formats-response.json | 2 +- .../creative/preview-creative-request.json | 12 +- .../creative/preview-creative-response.json | 4 +- .../2.6.0/creative/preview-render.json | 43 +- dist/schemas/2.6.0/enums/adcp-domain.json | 5 +- dist/schemas/2.6.0/enums/auth-scheme.json | 5 +- .../2.6.0/enums/co-branding-requirement.json | 6 +- dist/schemas/2.6.0/enums/creative-action.json | 8 +- .../enums/creative-agent-capability.json | 7 +- dist/schemas/2.6.0/enums/creative-status.json | 9 +- dist/schemas/2.6.0/enums/daast-version.json | 5 +- dist/schemas/2.6.0/enums/delivery-type.json | 7 +- dist/schemas/2.6.0/enums/dimension-unit.json | 7 +- dist/schemas/2.6.0/enums/feed-format.json | 6 +- .../2.6.0/enums/format-id-parameter.json | 5 +- .../2.6.0/enums/frequency-cap-scope.json | 6 +- .../2.6.0/enums/history-entry-type.json | 5 +- dist/schemas/2.6.0/enums/http-method.json | 5 +- .../schemas/2.6.0/enums/identifier-types.json | 6 +- .../2.6.0/enums/javascript-module-type.json | 6 +- .../2.6.0/enums/landing-page-requirement.json | 6 +- dist/schemas/2.6.0/enums/markdown-flavor.json | 5 +- .../schemas/2.6.0/enums/media-buy-status.json | 9 +- .../2.6.0/enums/notification-type.json | 7 +- dist/schemas/2.6.0/enums/pacing.json | 8 +- .../2.6.0/enums/preview-output-format.json | 5 +- .../2.6.0/enums/reporting-frequency.json | 6 +- .../2.6.0/enums/signal-catalog-type.json | 6 +- dist/schemas/2.6.0/enums/sort-direction.json | 5 +- .../2.6.0/enums/standard-format-ids.json | 2 +- dist/schemas/2.6.0/enums/task-status.json | 6 +- .../schemas/2.6.0/enums/update-frequency.json | 7 +- dist/schemas/2.6.0/enums/url-asset-type.json | 6 +- dist/schemas/2.6.0/enums/validation-mode.json | 5 +- dist/schemas/2.6.0/enums/vast-version.json | 8 +- .../2.6.0/enums/webhook-response-type.json | 7 +- .../2.6.0/enums/webhook-security-method.json | 6 +- dist/schemas/2.6.0/index.json | 4 +- .../media-buy/build-creative-request.json | 2 +- .../media-buy/build-creative-response.json | 4 +- ...dia-buy-async-response-input-required.json | 2 +- ...te-media-buy-async-response-submitted.json | 3 +- ...eate-media-buy-async-response-working.json | 3 +- .../media-buy/create-media-buy-request.json | 15 +- .../media-buy/create-media-buy-response.json | 4 +- .../get-media-buy-delivery-request.json | 2 +- .../get-media-buy-delivery-response.json | 10 +- ...roducts-async-response-input-required.json | 3 +- ...get-products-async-response-submitted.json | 3 +- .../get-products-async-response-working.json | 3 +- .../2.6.0/media-buy/get-products-request.json | 2 +- .../media-buy/get-products-response.json | 2 +- .../list-authorized-properties-request.json | 2 +- .../list-authorized-properties-response.json | 2 +- .../list-creative-formats-request.json | 2 +- .../list-creative-formats-response.json | 2 +- .../media-buy/list-creatives-request.json | 6 +- .../media-buy/list-creatives-response.json | 18 +- .../2.6.0/media-buy/package-request.json | 2 +- .../provide-performance-feedback-request.json | 4 +- ...provide-performance-feedback-response.json | 4 +- ...eatives-async-response-input-required.json | 2 +- ...nc-creatives-async-response-submitted.json | 3 +- ...sync-creatives-async-response-working.json | 3 +- .../media-buy/sync-creatives-request.json | 9 +- .../media-buy/sync-creatives-response.json | 8 +- ...dia-buy-async-response-input-required.json | 3 +- ...te-media-buy-async-response-submitted.json | 3 +- ...date-media-buy-async-response-working.json | 3 +- .../media-buy/update-media-buy-request.json | 4 +- .../media-buy/update-media-buy-response.json | 4 +- .../2.6.0/pricing-options/cpc-option.json | 17 +- .../2.6.0/pricing-options/cpcv-option.json | 17 +- .../pricing-options/cpm-auction-option.json | 21 +- .../pricing-options/cpm-fixed-option.json | 17 +- .../2.6.0/pricing-options/cpp-option.json | 24 +- .../2.6.0/pricing-options/cpv-option.json | 30 +- .../pricing-options/flat-rate-option.json | 19 +- .../pricing-options/vcpm-auction-option.json | 21 +- .../pricing-options/vcpm-fixed-option.json | 17 +- .../2.6.0/protocols/adcp-extension.json | 13 +- .../signals/activate-signal-request.json | 2 +- .../signals/activate-signal-response.json | 4 +- .../2.6.0/signals/get-signals-request.json | 4 +- .../2.6.0/signals/get-signals-response.json | 6 +- static/schemas/source/index.json | 2 +- 166 files changed, 1736 insertions(+), 1024 deletions(-) diff --git a/dist/schemas/2.6.0/adagents.json b/dist/schemas/2.6.0/adagents.json index dd9ad29277..4fafdaa844 100644 --- a/dist/schemas/2.6.0/adagents.json +++ b/dist/schemas/2.6.0/adagents.json @@ -24,8 +24,10 @@ "description": "ISO 8601 timestamp indicating when this reference was last updated" } }, - "required": ["authoritative_location"], - "additionalProperties": false + "required": [ + "authoritative_location" + ], + "additionalProperties": true }, { "type": "object", @@ -36,211 +38,238 @@ "description": "JSON Schema identifier for this adagents.json file" }, "contact": { - "type": "object", - "description": "Contact information for the entity managing this adagents.json file (may be publisher or third-party operator)", - "properties": { - "name": { - "type": "string", - "description": "Name of the entity managing this file (e.g., 'Meta Advertising Operations', 'Clear Channel Digital')", - "minLength": 1, - "maxLength": 255 - }, - "email": { - "type": "string", - "format": "email", - "description": "Contact email for questions or issues with this authorization file", - "minLength": 1, - "maxLength": 255 - }, - "domain": { - "type": "string", - "description": "Primary domain of the entity managing this file", - "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$" - }, - "seller_id": { - "type": "string", - "description": "Seller ID from IAB Tech Lab sellers.json (if applicable)", - "minLength": 1, - "maxLength": 255 + "type": "object", + "description": "Contact information for the entity managing this adagents.json file (may be publisher or third-party operator)", + "properties": { + "name": { + "type": "string", + "description": "Name of the entity managing this file (e.g., 'Meta Advertising Operations', 'Clear Channel Digital')", + "minLength": 1, + "maxLength": 255 + }, + "email": { + "type": "string", + "format": "email", + "description": "Contact email for questions or issues with this authorization file", + "minLength": 1, + "maxLength": 255 + }, + "domain": { + "type": "string", + "description": "Primary domain of the entity managing this file", + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$" + }, + "seller_id": { + "type": "string", + "description": "Seller ID from IAB Tech Lab sellers.json (if applicable)", + "minLength": 1, + "maxLength": 255 + }, + "tag_id": { + "type": "string", + "description": "TAG Certified Against Fraud ID for verification (if applicable)", + "minLength": 1, + "maxLength": 100 + } + }, + "required": [ + "name" + ], + "additionalProperties": true }, - "tag_id": { - "type": "string", - "description": "TAG Certified Against Fraud ID for verification (if applicable)", - "minLength": 1, - "maxLength": 100 - } - }, - "required": ["name"], - "additionalProperties": false - }, - "properties": { - "type": "array", - "description": "Array of all properties covered by this adagents.json file. Defines the canonical property list that authorized agents reference.", - "items": { - "$ref": "/schemas/2.6.0/core/property.json" - }, - "minItems": 1 - }, - "tags": { - "type": "object", - "description": "Metadata for each tag referenced by properties. Provides human-readable context for property tag values.", - "additionalProperties": { - "type": "object", "properties": { - "name": { - "type": "string", - "description": "Human-readable name for this tag" + "type": "array", + "description": "Array of all properties covered by this adagents.json file. Defines the canonical property list that authorized agents reference.", + "items": { + "$ref": "/schemas/2.6.0/core/property.json" }, - "description": { - "type": "string", - "description": "Description of what this tag represents" - } + "minItems": 1 }, - "required": ["name", "description"], - "additionalProperties": false - } - }, - "authorized_agents": { - "type": "array", - "description": "Array of sales agents authorized to sell inventory for properties in this file", - "items": { - "oneOf": [ - { + "tags": { + "type": "object", + "description": "Metadata for each tag referenced by properties. Provides human-readable context for property tag values.", + "additionalProperties": { "type": "object", "properties": { - "url": { + "name": { "type": "string", - "format": "uri", - "description": "The authorized agent's API endpoint URL" + "description": "Human-readable name for this tag" }, - "authorized_for": { + "description": { "type": "string", - "description": "Human-readable description of what this agent is authorized to sell", - "minLength": 1, - "maxLength": 500 - }, - "authorization_type": { - "type": "string", - "const": "property_ids", - "description": "Discriminator indicating authorization by specific property IDs" - }, - "property_ids": { - "type": "array", - "description": "Property IDs this agent is authorized for. Resolved against the top-level properties array in this file", - "items": { - "$ref": "/schemas/2.6.0/core/property-id.json" - }, - "minItems": 1 + "description": "Description of what this tag represents" } }, - "required": ["url", "authorized_for", "authorization_type", "property_ids"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "url": { - "type": "string", - "format": "uri", - "description": "The authorized agent's API endpoint URL" - }, - "authorized_for": { - "type": "string", - "description": "Human-readable description of what this agent is authorized to sell", - "minLength": 1, - "maxLength": 500 - }, - "authorization_type": { - "type": "string", - "const": "property_tags", - "description": "Discriminator indicating authorization by property tags" - }, - "property_tags": { - "type": "array", - "description": "Tags identifying which properties this agent is authorized for. Resolved against the top-level properties array in this file using tag matching", - "items": { - "$ref": "/schemas/2.6.0/core/property-tag.json" + "required": [ + "name", + "description" + ], + "additionalProperties": true + } + }, + "authorized_agents": { + "type": "array", + "description": "Array of sales agents authorized to sell inventory for properties in this file", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "The authorized agent's API endpoint URL" + }, + "authorized_for": { + "type": "string", + "description": "Human-readable description of what this agent is authorized to sell", + "minLength": 1, + "maxLength": 500 + }, + "authorization_type": { + "type": "string", + "const": "property_ids", + "description": "Discriminator indicating authorization by specific property IDs" + }, + "property_ids": { + "type": "array", + "description": "Property IDs this agent is authorized for. Resolved against the top-level properties array in this file", + "items": { + "$ref": "/schemas/2.6.0/core/property-id.json" + }, + "minItems": 1 + } }, - "minItems": 1 - } - }, - "required": ["url", "authorized_for", "authorization_type", "property_tags"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "url": { - "type": "string", - "format": "uri", - "description": "The authorized agent's API endpoint URL" - }, - "authorized_for": { - "type": "string", - "description": "Human-readable description of what this agent is authorized to sell", - "minLength": 1, - "maxLength": 500 + "required": [ + "url", + "authorized_for", + "authorization_type", + "property_ids" + ], + "additionalProperties": true }, - "authorization_type": { - "type": "string", - "const": "inline_properties", - "description": "Discriminator indicating authorization by inline property definitions" - }, - "properties": { - "type": "array", - "description": "Specific properties this agent is authorized for (alternative to property_ids/property_tags)", - "items": { - "$ref": "/schemas/2.6.0/core/property.json" + { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "The authorized agent's API endpoint URL" + }, + "authorized_for": { + "type": "string", + "description": "Human-readable description of what this agent is authorized to sell", + "minLength": 1, + "maxLength": 500 + }, + "authorization_type": { + "type": "string", + "const": "property_tags", + "description": "Discriminator indicating authorization by property tags" + }, + "property_tags": { + "type": "array", + "description": "Tags identifying which properties this agent is authorized for. Resolved against the top-level properties array in this file using tag matching", + "items": { + "$ref": "/schemas/2.6.0/core/property-tag.json" + }, + "minItems": 1 + } }, - "minItems": 1 - } - }, - "required": ["url", "authorized_for", "authorization_type", "properties"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "url": { - "type": "string", - "format": "uri", - "description": "The authorized agent's API endpoint URL" + "required": [ + "url", + "authorized_for", + "authorization_type", + "property_tags" + ], + "additionalProperties": true }, - "authorized_for": { - "type": "string", - "description": "Human-readable description of what this agent is authorized to sell", - "minLength": 1, - "maxLength": 500 - }, - "authorization_type": { - "type": "string", - "const": "publisher_properties", - "description": "Discriminator indicating authorization for properties from other publisher domains" + { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "The authorized agent's API endpoint URL" + }, + "authorized_for": { + "type": "string", + "description": "Human-readable description of what this agent is authorized to sell", + "minLength": 1, + "maxLength": 500 + }, + "authorization_type": { + "type": "string", + "const": "inline_properties", + "description": "Discriminator indicating authorization by inline property definitions" + }, + "properties": { + "type": "array", + "description": "Specific properties this agent is authorized for (alternative to property_ids/property_tags)", + "items": { + "$ref": "/schemas/2.6.0/core/property.json" + }, + "minItems": 1 + } + }, + "required": [ + "url", + "authorized_for", + "authorization_type", + "properties" + ], + "additionalProperties": true }, - "publisher_properties": { - "type": "array", - "description": "Properties from other publisher domains this agent is authorized for. Each entry specifies a publisher domain and which of their properties this agent can sell", - "items": { - "$ref": "/schemas/2.6.0/core/publisher-property-selector.json" + { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "The authorized agent's API endpoint URL" + }, + "authorized_for": { + "type": "string", + "description": "Human-readable description of what this agent is authorized to sell", + "minLength": 1, + "maxLength": 500 + }, + "authorization_type": { + "type": "string", + "const": "publisher_properties", + "description": "Discriminator indicating authorization for properties from other publisher domains" + }, + "publisher_properties": { + "type": "array", + "description": "Properties from other publisher domains this agent is authorized for. Each entry specifies a publisher domain and which of their properties this agent can sell", + "items": { + "$ref": "/schemas/2.6.0/core/publisher-property-selector.json" + }, + "minItems": 1 + } }, - "minItems": 1 + "required": [ + "url", + "authorized_for", + "authorization_type", + "publisher_properties" + ], + "additionalProperties": true } - }, - "required": ["url", "authorized_for", "authorization_type", "publisher_properties"], - "additionalProperties": false - } - ] - }, - "minItems": 1 - }, + ] + }, + "minItems": 1 + }, "last_updated": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp indicating when this file was last updated" } }, - "required": ["authorized_agents"], - "additionalProperties": false + "required": [ + "authorized_agents" + ], + "additionalProperties": true } ], "examples": [ @@ -256,7 +285,10 @@ "property_type": "website", "name": "Example Site", "identifiers": [ - {"type": "domain", "value": "example.com"} + { + "type": "domain", + "value": "example.com" + } ], "publisher_domain": "example.com" } @@ -266,7 +298,9 @@ "url": "https://agent.example.com", "authorized_for": "Official sales agent", "authorization_type": "property_tags", - "property_tags": ["all"] + "property_tags": [ + "all" + ] } ], "tags": { @@ -291,30 +325,57 @@ "property_type": "mobile_app", "name": "Instagram", "identifiers": [ - {"type": "ios_bundle", "value": "com.burbn.instagram"}, - {"type": "android_package", "value": "com.instagram.android"} + { + "type": "ios_bundle", + "value": "com.burbn.instagram" + }, + { + "type": "android_package", + "value": "com.instagram.android" + } + ], + "tags": [ + "meta_network", + "social_media" ], - "tags": ["meta_network", "social_media"], "publisher_domain": "instagram.com" }, { "property_type": "mobile_app", "name": "Facebook", "identifiers": [ - {"type": "ios_bundle", "value": "com.facebook.Facebook"}, - {"type": "android_package", "value": "com.facebook.katana"} + { + "type": "ios_bundle", + "value": "com.facebook.Facebook" + }, + { + "type": "android_package", + "value": "com.facebook.katana" + } + ], + "tags": [ + "meta_network", + "social_media" ], - "tags": ["meta_network", "social_media"], "publisher_domain": "facebook.com" }, { "property_type": "mobile_app", "name": "WhatsApp", "identifiers": [ - {"type": "ios_bundle", "value": "net.whatsapp.WhatsApp"}, - {"type": "android_package", "value": "com.whatsapp"} + { + "type": "ios_bundle", + "value": "net.whatsapp.WhatsApp" + }, + { + "type": "android_package", + "value": "com.whatsapp" + } + ], + "tags": [ + "meta_network", + "messaging" ], - "tags": ["meta_network", "messaging"], "publisher_domain": "whatsapp.com" } ], @@ -337,7 +398,9 @@ "url": "https://meta-ads.com", "authorized_for": "All Meta properties", "authorization_type": "property_tags", - "property_tags": ["meta_network"] + "property_tags": [ + "meta_network" + ] } ], "last_updated": "2025-01-10T15:30:00Z" @@ -352,9 +415,14 @@ "property_type": "website", "name": "Tumblr Corporate", "identifiers": [ - {"type": "domain", "value": "tumblr.com"} + { + "type": "domain", + "value": "tumblr.com" + } + ], + "tags": [ + "corporate" ], - "tags": ["corporate"], "publisher_domain": "tumblr.com" } ], @@ -369,7 +437,9 @@ "url": "https://tumblr-sales.com", "authorized_for": "Tumblr corporate properties only", "authorization_type": "property_tags", - "property_tags": ["corporate"] + "property_tags": [ + "corporate" + ] } ], "last_updated": "2025-01-10T16:00:00Z" @@ -390,7 +460,9 @@ { "publisher_domain": "cnn.com", "selection_type": "by_id", - "property_ids": ["cnn_ctv_app"] + "property_ids": [ + "cnn_ctv_app" + ] } ] }, @@ -402,12 +474,16 @@ { "publisher_domain": "cnn.com", "selection_type": "by_tag", - "property_tags": ["ctv"] + "property_tags": [ + "ctv" + ] }, { "publisher_domain": "espn.com", "selection_type": "by_tag", - "property_tags": ["ctv"] + "property_tags": [ + "ctv" + ] } ] } @@ -415,4 +491,4 @@ "last_updated": "2025-01-10T17:00:00Z" } ] -} \ No newline at end of file +} diff --git a/dist/schemas/2.6.0/bundled/media-buy/build-creative-request.json b/dist/schemas/2.6.0/bundled/media-buy/build-creative-request.json index 96138d0284..add3c1ba85 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/build-creative-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/build-creative-request.json @@ -49,7 +49,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -103,7 +103,7 @@ "width", "height" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Video Asset", @@ -145,7 +145,7 @@ "width", "height" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Audio Asset", @@ -175,7 +175,7 @@ "required": [ "url" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "VAST Asset", @@ -247,7 +247,7 @@ "delivery_type", "url" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -314,7 +314,7 @@ "delivery_type", "content" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -335,7 +335,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "URL Asset", @@ -365,7 +365,7 @@ "required": [ "url" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "HTML Asset", @@ -384,7 +384,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "JavaScript Asset", @@ -409,7 +409,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Webhook Asset", @@ -496,7 +496,7 @@ "response_type", "security" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "CSS Asset", @@ -515,7 +515,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "DAAST Asset", @@ -579,7 +579,7 @@ "delivery_type", "url" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -638,7 +638,7 @@ "delivery_type", "content" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -839,7 +839,7 @@ "asset_type", "url" ], - "additionalProperties": false + "additionalProperties": true } }, "product_catalog": { @@ -887,7 +887,7 @@ "required": [ "feed_url" ], - "additionalProperties": false + "additionalProperties": true }, "disclaimers": { "type": "array", @@ -961,7 +961,7 @@ "required": [ "name" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Example with both URL and name", @@ -1125,7 +1125,7 @@ "description": "Natural language query to select products from the brand manifest (e.g., 'all Kraft Heinz pasta sauces', 'organic products under $20')" } }, - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Direct SKU selection for specific products from brand manifest", @@ -1189,7 +1189,7 @@ "required": [ "name" ], - "additionalProperties": false + "additionalProperties": true } }, "asset_selectors": { @@ -1231,13 +1231,13 @@ } } }, - "additionalProperties": false + "additionalProperties": true } }, "required": [ "brand_manifest" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "brand_manifest": { @@ -1265,7 +1265,7 @@ ] } }, - "additionalProperties": false + "additionalProperties": true }, "ext": { "title": "Extension Object", @@ -1278,7 +1278,7 @@ "format_id", "assets" ], - "additionalProperties": false + "additionalProperties": true }, "target_format_id": { "title": "Format ID", @@ -1315,7 +1315,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -1341,9 +1341,9 @@ "required": [ "target_format_id" ], - "additionalProperties": false, + "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-05T18:19:47.402Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.823Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/build-creative-response.json b/dist/schemas/2.6.0/bundled/media-buy/build-creative-response.json index 5b0748d268..d3b8342b62 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/build-creative-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/build-creative-response.json @@ -50,7 +50,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -104,7 +104,7 @@ "width", "height" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Video Asset", @@ -146,7 +146,7 @@ "width", "height" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Audio Asset", @@ -176,7 +176,7 @@ "required": [ "url" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "VAST Asset", @@ -248,7 +248,7 @@ "delivery_type", "url" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -315,7 +315,7 @@ "delivery_type", "content" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -336,7 +336,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "URL Asset", @@ -366,7 +366,7 @@ "required": [ "url" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "HTML Asset", @@ -385,7 +385,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "JavaScript Asset", @@ -410,7 +410,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Webhook Asset", @@ -497,7 +497,7 @@ "response_type", "security" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "CSS Asset", @@ -516,7 +516,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "DAAST Asset", @@ -580,7 +580,7 @@ "delivery_type", "url" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -639,7 +639,7 @@ "delivery_type", "content" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -840,7 +840,7 @@ "asset_type", "url" ], - "additionalProperties": false + "additionalProperties": true } }, "product_catalog": { @@ -888,7 +888,7 @@ "required": [ "feed_url" ], - "additionalProperties": false + "additionalProperties": true }, "disclaimers": { "type": "array", @@ -962,7 +962,7 @@ "required": [ "name" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Example with both URL and name", @@ -1126,7 +1126,7 @@ "description": "Natural language query to select products from the brand manifest (e.g., 'all Kraft Heinz pasta sauces', 'organic products under $20')" } }, - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Direct SKU selection for specific products from brand manifest", @@ -1190,7 +1190,7 @@ "required": [ "name" ], - "additionalProperties": false + "additionalProperties": true } }, "asset_selectors": { @@ -1232,13 +1232,13 @@ } } }, - "additionalProperties": false + "additionalProperties": true } }, "required": [ "brand_manifest" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "brand_manifest": { @@ -1266,7 +1266,7 @@ ] } }, - "additionalProperties": false + "additionalProperties": true }, "ext": { "title": "Extension Object", @@ -1279,7 +1279,7 @@ "format_id", "assets" ], - "additionalProperties": false + "additionalProperties": true }, "context": { "title": "Context Object", @@ -1297,7 +1297,7 @@ "required": [ "creative_manifest" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "required": [ "errors" @@ -1339,14 +1339,16 @@ "minimum": 0 }, "details": { - "description": "Additional task-specific error details" + "type": "object", + "description": "Additional task-specific error details", + "additionalProperties": true } }, "required": [ "code", "message" ], - "additionalProperties": false + "additionalProperties": true }, "minItems": 1 }, @@ -1366,7 +1368,7 @@ "required": [ "errors" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "required": [ "creative_manifest" @@ -1375,7 +1377,7 @@ } ], "_bundled": { - "generatedAt": "2026-01-05T18:19:47.404Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.825Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/create-media-buy-request.json b/dist/schemas/2.6.0/bundled/media-buy/create-media-buy-request.json index b48889e787..9b5f26e79c 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/create-media-buy-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/create-media-buy-request.json @@ -63,7 +63,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -160,10 +160,10 @@ "required": [ "suppress_minutes" ], - "additionalProperties": false + "additionalProperties": true } }, - "additionalProperties": false + "additionalProperties": true }, "creative_ids": { "type": "array", @@ -223,7 +223,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -273,7 +273,7 @@ "width", "height" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Video Asset", @@ -315,7 +315,7 @@ "width", "height" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Audio Asset", @@ -345,7 +345,7 @@ "required": [ "url" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Text Asset", @@ -364,7 +364,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "HTML Asset", @@ -383,7 +383,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "CSS Asset", @@ -402,7 +402,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "JavaScript Asset", @@ -427,7 +427,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "VAST Asset", @@ -499,7 +499,7 @@ "delivery_type", "url" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -566,7 +566,7 @@ "delivery_type", "content" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -632,7 +632,7 @@ "delivery_type", "url" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -691,7 +691,7 @@ "delivery_type", "content" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -892,7 +892,7 @@ "asset_type", "url" ], - "additionalProperties": false + "additionalProperties": true } }, "product_catalog": { @@ -940,7 +940,7 @@ "required": [ "feed_url" ], - "additionalProperties": false + "additionalProperties": true }, "disclaimers": { "type": "array", @@ -1014,7 +1014,7 @@ "required": [ "name" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Example with both URL and name", @@ -1178,7 +1178,7 @@ "description": "Natural language query to select products from the brand manifest (e.g., 'all Kraft Heinz pasta sauces', 'organic products under $20')" } }, - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Direct SKU selection for specific products from brand manifest", @@ -1242,7 +1242,7 @@ "required": [ "name" ], - "additionalProperties": false + "additionalProperties": true } }, "asset_selectors": { @@ -1284,13 +1284,13 @@ } } }, - "additionalProperties": false + "additionalProperties": true } }, "required": [ "brand_manifest" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "brand_manifest": { @@ -1343,12 +1343,12 @@ "required": [ "url" ], - "additionalProperties": false + "additionalProperties": true } ] } }, - "additionalProperties": false + "additionalProperties": true }, "inputs": { "type": "array", @@ -1375,7 +1375,7 @@ "required": [ "name" ], - "additionalProperties": false + "additionalProperties": true } }, "tags": { @@ -1410,7 +1410,7 @@ "format_id", "assets" ], - "additionalProperties": false + "additionalProperties": true }, "maxItems": 100 }, @@ -1427,7 +1427,7 @@ "budget", "pricing_option_id" ], - "additionalProperties": false + "additionalProperties": true } }, "brand_manifest": { @@ -1622,7 +1622,7 @@ "asset_type", "url" ], - "additionalProperties": false + "additionalProperties": true } }, "product_catalog": { @@ -1670,7 +1670,7 @@ "required": [ "feed_url" ], - "additionalProperties": false + "additionalProperties": true }, "disclaimers": { "type": "array", @@ -1744,7 +1744,7 @@ "required": [ "name" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Example with both URL and name", @@ -1985,7 +1985,7 @@ "authentication", "reporting_frequency" ], - "additionalProperties": false + "additionalProperties": true }, "context": { "title": "Context Object", @@ -2007,9 +2007,9 @@ "start_time", "end_time" ], - "additionalProperties": false, + "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-05T18:19:47.417Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.826Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/create-media-buy-response.json b/dist/schemas/2.6.0/bundled/media-buy/create-media-buy-response.json index 8e78ef504d..8d99b96e93 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/create-media-buy-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/create-media-buy-response.json @@ -133,10 +133,10 @@ "required": [ "suppress_minutes" ], - "additionalProperties": false + "additionalProperties": true } }, - "additionalProperties": false + "additionalProperties": true }, "creative_assignments": { "type": "array", @@ -168,7 +168,7 @@ "required": [ "creative_id" ], - "additionalProperties": false + "additionalProperties": true } }, "format_ids_to_provide": { @@ -209,7 +209,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -235,7 +235,7 @@ "required": [ "package_id" ], - "additionalProperties": false + "additionalProperties": true } }, "context": { @@ -256,7 +256,7 @@ "buyer_ref", "packages" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "required": [ "errors" @@ -298,14 +298,16 @@ "minimum": 0 }, "details": { - "description": "Additional task-specific error details" + "type": "object", + "description": "Additional task-specific error details", + "additionalProperties": true } }, "required": [ "code", "message" ], - "additionalProperties": false + "additionalProperties": true }, "minItems": 1 }, @@ -325,7 +327,7 @@ "required": [ "errors" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "anyOf": [ { @@ -348,7 +350,7 @@ } ], "_bundled": { - "generatedAt": "2026-01-05T18:19:47.418Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.827Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/get-media-buy-delivery-request.json b/dist/schemas/2.6.0/bundled/media-buy/get-media-buy-delivery-request.json index 2b3bd94030..26de332bc0 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/get-media-buy-delivery-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/get-media-buy-delivery-request.json @@ -84,9 +84,9 @@ "additionalProperties": true } }, - "additionalProperties": false, + "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-05T18:19:47.418Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.827Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/get-media-buy-delivery-response.json b/dist/schemas/2.6.0/bundled/media-buy/get-media-buy-delivery-response.json index 5c2b87edaa..e0053adb76 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/get-media-buy-delivery-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/get-media-buy-delivery-response.json @@ -53,7 +53,7 @@ "start", "end" ], - "additionalProperties": false + "additionalProperties": true }, "currency": { "type": "string", @@ -95,7 +95,7 @@ "spend", "media_buy_count" ], - "additionalProperties": false + "additionalProperties": true }, "media_buy_deliveries": { "type": "array", @@ -317,11 +317,11 @@ "venue_id", "impressions" ], - "additionalProperties": false + "additionalProperties": true } } }, - "additionalProperties": false + "additionalProperties": true } }, "additionalProperties": true @@ -507,11 +507,11 @@ "venue_id", "impressions" ], - "additionalProperties": false + "additionalProperties": true } } }, - "additionalProperties": false + "additionalProperties": true } }, "additionalProperties": true @@ -619,7 +619,7 @@ "impressions", "spend" ], - "additionalProperties": false + "additionalProperties": true } } }, @@ -629,7 +629,7 @@ "totals", "by_package" ], - "additionalProperties": false + "additionalProperties": true } }, "errors": { @@ -662,14 +662,16 @@ "minimum": 0 }, "details": { - "description": "Additional task-specific error details" + "type": "object", + "description": "Additional task-specific error details", + "additionalProperties": true } }, "required": [ "code", "message" ], - "additionalProperties": false + "additionalProperties": true } }, "context": { @@ -690,9 +692,9 @@ "currency", "media_buy_deliveries" ], - "additionalProperties": false, + "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-05T18:19:47.419Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.828Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/get-products-request.json b/dist/schemas/2.6.0/bundled/media-buy/get-products-request.json index d47e750e82..63d7d0ab1f 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/get-products-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/get-products-request.json @@ -201,7 +201,7 @@ "asset_type", "url" ], - "additionalProperties": false + "additionalProperties": true } }, "product_catalog": { @@ -249,7 +249,7 @@ "required": [ "feed_url" ], - "additionalProperties": false + "additionalProperties": true }, "disclaimers": { "type": "array", @@ -323,7 +323,7 @@ "required": [ "name" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Example with both URL and name", @@ -537,7 +537,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -602,7 +602,7 @@ ] } ], - "additionalProperties": false + "additionalProperties": true }, "countries": { "type": "array", @@ -633,7 +633,7 @@ } } }, - "additionalProperties": false + "additionalProperties": true }, "context": { "title": "Context Object", @@ -649,9 +649,9 @@ } }, "required": [], - "additionalProperties": false, + "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-05T18:19:47.420Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.829Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/get-products-response.json b/dist/schemas/2.6.0/bundled/media-buy/get-products-response.json index 69d49af26d..b0599f53d8 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/get-products-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/get-products-response.json @@ -54,7 +54,7 @@ "publisher_domain", "selection_type" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -93,7 +93,7 @@ "selection_type", "property_ids" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -134,7 +134,7 @@ "selection_type", "property_tags" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -178,7 +178,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -247,7 +247,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -264,7 +264,7 @@ "placement_id", "name" ], - "additionalProperties": false + "additionalProperties": true }, "minItems": 1 }, @@ -336,7 +336,7 @@ "currency", "is_fixed" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "CPM Auction Pricing Option", @@ -415,7 +415,7 @@ "currency", "is_fixed" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "vCPM Fixed Rate Pricing Option", @@ -465,7 +465,7 @@ "currency", "is_fixed" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "vCPM Auction Pricing Option", @@ -544,7 +544,7 @@ "price_guidance", "is_fixed" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "CPC Pricing Option", @@ -594,7 +594,7 @@ "currency", "is_fixed" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "CPCV Pricing Option", @@ -644,7 +644,7 @@ "currency", "is_fixed" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "CPV Pricing Option", @@ -706,7 +706,7 @@ "required": [ "duration_seconds" ], - "additionalProperties": false + "additionalProperties": true } ] } @@ -714,7 +714,7 @@ "required": [ "view_threshold" ], - "additionalProperties": false + "additionalProperties": true }, "min_spend_per_package": { "type": "number", @@ -730,7 +730,7 @@ "is_fixed", "parameters" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "CPP Pricing Option", @@ -785,7 +785,7 @@ "required": [ "demographic" ], - "additionalProperties": false + "additionalProperties": true }, "min_spend_per_package": { "type": "number", @@ -801,7 +801,7 @@ "is_fixed", "parameters" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Flat Rate Pricing Option", @@ -877,7 +877,7 @@ "description": "Specific daypart for time-based pricing (e.g., 'morning_commute', 'evening_prime', 'overnight')" } }, - "additionalProperties": false + "additionalProperties": true }, "min_spend_per_package": { "type": "number", @@ -892,7 +892,7 @@ "is_fixed", "rate" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -947,7 +947,7 @@ "attribution", "reporting" ], - "additionalProperties": false + "additionalProperties": true }, "delivery_measurement": { "type": "object", @@ -1053,7 +1053,7 @@ "supports_webhooks", "available_metrics" ], - "additionalProperties": false + "additionalProperties": true }, "creative_policy": { "title": "Creative Policy", @@ -1090,7 +1090,7 @@ "landing_page", "templates_available" ], - "additionalProperties": false + "additionalProperties": true }, "is_custom": { "type": "boolean", @@ -1144,7 +1144,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -1164,7 +1164,7 @@ "format_id", "manifest" ], - "additionalProperties": false + "additionalProperties": true }, "product_card_detailed": { "type": "object", @@ -1205,7 +1205,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -1225,7 +1225,7 @@ "format_id", "manifest" ], - "additionalProperties": false + "additionalProperties": true }, "ext": { "title": "Extension Object", @@ -1244,7 +1244,7 @@ "delivery_measurement", "pricing_options" ], - "additionalProperties": false + "additionalProperties": true } }, "errors": { @@ -1277,14 +1277,16 @@ "minimum": 0 }, "details": { - "description": "Additional task-specific error details" + "type": "object", + "description": "Additional task-specific error details", + "additionalProperties": true } }, "required": [ "code", "message" ], - "additionalProperties": false + "additionalProperties": true } }, "context": { @@ -1303,9 +1305,9 @@ "required": [ "products" ], - "additionalProperties": false, + "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-05T18:19:47.422Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.830Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/list-authorized-properties-request.json b/dist/schemas/2.6.0/bundled/media-buy/list-authorized-properties-request.json index 27c3a5d01b..7e4e17f7cf 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/list-authorized-properties-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/list-authorized-properties-request.json @@ -28,9 +28,9 @@ "additionalProperties": true } }, - "additionalProperties": false, + "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-05T18:19:47.423Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.830Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/list-authorized-properties-response.json b/dist/schemas/2.6.0/bundled/media-buy/list-authorized-properties-response.json index 91f7fbaee1..6ae624ec4f 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/list-authorized-properties-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/list-authorized-properties-response.json @@ -92,14 +92,16 @@ "minimum": 0 }, "details": { - "description": "Additional task-specific error details" + "type": "object", + "description": "Additional task-specific error details", + "additionalProperties": true } }, "required": [ "code", "message" ], - "additionalProperties": false + "additionalProperties": true } }, "context": { @@ -118,9 +120,9 @@ "required": [ "publisher_domains" ], - "additionalProperties": false, + "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-05T18:19:47.423Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.831Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/list-creative-formats-request.json b/dist/schemas/2.6.0/bundled/media-buy/list-creative-formats-request.json index d414941c5c..d02fc863f7 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/list-creative-formats-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/list-creative-formats-request.json @@ -43,7 +43,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -129,9 +129,9 @@ "additionalProperties": true } }, - "additionalProperties": false, + "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-05T18:19:47.423Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.831Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/list-creative-formats-response.json b/dist/schemas/2.6.0/bundled/media-buy/list-creative-formats-response.json index e326584e67..ed691f6fa6 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/list-creative-formats-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/list-creative-formats-response.json @@ -48,7 +48,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -554,7 +554,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -604,7 +604,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -624,7 +624,7 @@ "format_id", "manifest" ], - "additionalProperties": false + "additionalProperties": true }, "format_card_detailed": { "type": "object", @@ -665,7 +665,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -685,7 +685,7 @@ "format_id", "manifest" ], - "additionalProperties": false + "additionalProperties": true } }, "required": [ @@ -693,7 +693,7 @@ "name", "type" ], - "additionalProperties": false + "additionalProperties": true } }, "creative_agents": { @@ -762,14 +762,16 @@ "minimum": 0 }, "details": { - "description": "Additional task-specific error details" + "type": "object", + "description": "Additional task-specific error details", + "additionalProperties": true } }, "required": [ "code", "message" ], - "additionalProperties": false + "additionalProperties": true } }, "context": { @@ -788,9 +790,9 @@ "required": [ "formats" ], - "additionalProperties": false, + "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-05T18:19:47.426Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.832Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/list-creatives-request.json b/dist/schemas/2.6.0/bundled/media-buy/list-creatives-request.json index 0263af51d4..319341183d 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/list-creatives-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/list-creatives-request.json @@ -139,7 +139,7 @@ "description": "Filter creatives that have performance data when true" } }, - "additionalProperties": false + "additionalProperties": true }, "sort": { "type": "object", @@ -170,7 +170,7 @@ "default": "desc" } }, - "additionalProperties": false + "additionalProperties": true }, "pagination": { "type": "object", @@ -190,7 +190,7 @@ "description": "Number of creatives to skip" } }, - "additionalProperties": false + "additionalProperties": true }, "include_assignments": { "type": "boolean", @@ -239,7 +239,7 @@ "additionalProperties": true } }, - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "List all approved video creatives", @@ -288,7 +288,7 @@ } ], "_bundled": { - "generatedAt": "2026-01-05T18:19:47.426Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.834Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/list-creatives-response.json b/dist/schemas/2.6.0/bundled/media-buy/list-creatives-response.json index 2730ab2038..ab7c867de7 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/list-creatives-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/list-creatives-response.json @@ -49,7 +49,7 @@ "total_matching", "returned" ], - "additionalProperties": false + "additionalProperties": true }, "pagination": { "type": "object", @@ -85,7 +85,7 @@ "offset", "has_more" ], - "additionalProperties": false + "additionalProperties": true }, "creatives": { "type": "array", @@ -136,7 +136,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -213,7 +213,7 @@ "width", "height" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Video Asset", @@ -255,7 +255,7 @@ "width", "height" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Audio Asset", @@ -285,7 +285,7 @@ "required": [ "url" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Text Asset", @@ -304,7 +304,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "HTML Asset", @@ -323,7 +323,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "CSS Asset", @@ -342,7 +342,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "JavaScript Asset", @@ -367,7 +367,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "VAST Asset", @@ -439,7 +439,7 @@ "delivery_type", "url" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -506,7 +506,7 @@ "delivery_type", "content" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -572,7 +572,7 @@ "delivery_type", "url" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -631,7 +631,7 @@ "delivery_type", "content" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -832,7 +832,7 @@ "asset_type", "url" ], - "additionalProperties": false + "additionalProperties": true } }, "product_catalog": { @@ -880,7 +880,7 @@ "required": [ "feed_url" ], - "additionalProperties": false + "additionalProperties": true }, "disclaimers": { "type": "array", @@ -954,7 +954,7 @@ "required": [ "name" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Example with both URL and name", @@ -1118,7 +1118,7 @@ "description": "Natural language query to select products from the brand manifest (e.g., 'all Kraft Heinz pasta sauces', 'organic products under $20')" } }, - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Direct SKU selection for specific products from brand manifest", @@ -1182,7 +1182,7 @@ "required": [ "name" ], - "additionalProperties": false + "additionalProperties": true } }, "asset_selectors": { @@ -1224,13 +1224,13 @@ } } }, - "additionalProperties": false + "additionalProperties": true } }, "required": [ "brand_manifest" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "brand_manifest": { @@ -1283,7 +1283,7 @@ "required": [ "url" ], - "additionalProperties": false + "additionalProperties": true } ] } @@ -1339,14 +1339,14 @@ "assigned_date", "status" ], - "additionalProperties": false + "additionalProperties": true } } }, "required": [ "assignment_count" ], - "additionalProperties": false + "additionalProperties": true }, "performance": { "type": "object", @@ -1389,7 +1389,7 @@ "required": [ "last_updated" ], - "additionalProperties": false + "additionalProperties": true }, "sub_assets": { "type": "array", @@ -1426,7 +1426,7 @@ "asset_id", "content_uri" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -1467,7 +1467,7 @@ "asset_id", "content" ], - "additionalProperties": false + "additionalProperties": true } ] } @@ -1481,7 +1481,7 @@ "created_date", "updated_date" ], - "additionalProperties": false + "additionalProperties": true } }, "format_summary": { @@ -1494,7 +1494,7 @@ "minimum": 0 } }, - "additionalProperties": false + "additionalProperties": true }, "status_summary": { "type": "object", @@ -1521,7 +1521,7 @@ "minimum": 0 } }, - "additionalProperties": false + "additionalProperties": true }, "context": { "title": "Context Object", @@ -1541,7 +1541,7 @@ "pagination", "creatives" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Successful library query with results", @@ -1648,7 +1648,7 @@ } ], "_bundled": { - "generatedAt": "2026-01-05T18:19:47.429Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.835Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/package-request.json b/dist/schemas/2.6.0/bundled/media-buy/package-request.json index 5b67b17850..7bc4ce1b75 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/package-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/package-request.json @@ -51,7 +51,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -148,10 +148,10 @@ "required": [ "suppress_minutes" ], - "additionalProperties": false + "additionalProperties": true } }, - "additionalProperties": false + "additionalProperties": true }, "creative_ids": { "type": "array", @@ -211,7 +211,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -261,7 +261,7 @@ "width", "height" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Video Asset", @@ -303,7 +303,7 @@ "width", "height" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Audio Asset", @@ -333,7 +333,7 @@ "required": [ "url" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Text Asset", @@ -352,7 +352,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "HTML Asset", @@ -371,7 +371,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "CSS Asset", @@ -390,7 +390,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "JavaScript Asset", @@ -415,7 +415,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "VAST Asset", @@ -487,7 +487,7 @@ "delivery_type", "url" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -554,7 +554,7 @@ "delivery_type", "content" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -620,7 +620,7 @@ "delivery_type", "url" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -679,7 +679,7 @@ "delivery_type", "content" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -880,7 +880,7 @@ "asset_type", "url" ], - "additionalProperties": false + "additionalProperties": true } }, "product_catalog": { @@ -928,7 +928,7 @@ "required": [ "feed_url" ], - "additionalProperties": false + "additionalProperties": true }, "disclaimers": { "type": "array", @@ -1002,7 +1002,7 @@ "required": [ "name" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Example with both URL and name", @@ -1166,7 +1166,7 @@ "description": "Natural language query to select products from the brand manifest (e.g., 'all Kraft Heinz pasta sauces', 'organic products under $20')" } }, - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Direct SKU selection for specific products from brand manifest", @@ -1230,7 +1230,7 @@ "required": [ "name" ], - "additionalProperties": false + "additionalProperties": true } }, "asset_selectors": { @@ -1272,13 +1272,13 @@ } } }, - "additionalProperties": false + "additionalProperties": true } }, "required": [ "brand_manifest" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "brand_manifest": { @@ -1331,12 +1331,12 @@ "required": [ "url" ], - "additionalProperties": false + "additionalProperties": true } ] } }, - "additionalProperties": false + "additionalProperties": true }, "inputs": { "type": "array", @@ -1363,7 +1363,7 @@ "required": [ "name" ], - "additionalProperties": false + "additionalProperties": true } }, "tags": { @@ -1398,7 +1398,7 @@ "format_id", "assets" ], - "additionalProperties": false + "additionalProperties": true }, "maxItems": 100 }, @@ -1415,9 +1415,9 @@ "budget", "pricing_option_id" ], - "additionalProperties": false, + "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-05T18:19:47.434Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.836Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/provide-performance-feedback-request.json b/dist/schemas/2.6.0/bundled/media-buy/provide-performance-feedback-request.json index f69755a28f..6a8d687c7f 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/provide-performance-feedback-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/provide-performance-feedback-request.json @@ -34,7 +34,7 @@ "start", "end" ], - "additionalProperties": false + "additionalProperties": true }, "performance_index": { "type": "number", @@ -108,9 +108,9 @@ ] } ], - "additionalProperties": false, + "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-05T18:19:47.435Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.837Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/provide-performance-feedback-response.json b/dist/schemas/2.6.0/bundled/media-buy/provide-performance-feedback-response.json index 638ca9efd4..6b4878652f 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/provide-performance-feedback-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/provide-performance-feedback-response.json @@ -31,7 +31,7 @@ "required": [ "success" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "required": [ "errors" @@ -73,14 +73,16 @@ "minimum": 0 }, "details": { - "description": "Additional task-specific error details" + "type": "object", + "description": "Additional task-specific error details", + "additionalProperties": true } }, "required": [ "code", "message" ], - "additionalProperties": false + "additionalProperties": true }, "minItems": 1 }, @@ -100,7 +102,7 @@ "required": [ "errors" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "required": [ "success" @@ -109,7 +111,7 @@ } ], "_bundled": { - "generatedAt": "2026-01-05T18:19:47.435Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.837Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/sync-creatives-request.json b/dist/schemas/2.6.0/bundled/media-buy/sync-creatives-request.json index f636e64908..0b56121358 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/sync-creatives-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/sync-creatives-request.json @@ -56,7 +56,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -106,7 +106,7 @@ "width", "height" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Video Asset", @@ -148,7 +148,7 @@ "width", "height" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Audio Asset", @@ -178,7 +178,7 @@ "required": [ "url" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Text Asset", @@ -197,7 +197,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "HTML Asset", @@ -216,7 +216,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "CSS Asset", @@ -235,7 +235,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "JavaScript Asset", @@ -260,7 +260,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "VAST Asset", @@ -332,7 +332,7 @@ "delivery_type", "url" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -399,7 +399,7 @@ "delivery_type", "content" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -465,7 +465,7 @@ "delivery_type", "url" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -524,7 +524,7 @@ "delivery_type", "content" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -725,7 +725,7 @@ "asset_type", "url" ], - "additionalProperties": false + "additionalProperties": true } }, "product_catalog": { @@ -773,7 +773,7 @@ "required": [ "feed_url" ], - "additionalProperties": false + "additionalProperties": true }, "disclaimers": { "type": "array", @@ -847,7 +847,7 @@ "required": [ "name" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Example with both URL and name", @@ -1011,7 +1011,7 @@ "description": "Natural language query to select products from the brand manifest (e.g., 'all Kraft Heinz pasta sauces', 'organic products under $20')" } }, - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Direct SKU selection for specific products from brand manifest", @@ -1075,7 +1075,7 @@ "required": [ "name" ], - "additionalProperties": false + "additionalProperties": true } }, "asset_selectors": { @@ -1117,13 +1117,13 @@ } } }, - "additionalProperties": false + "additionalProperties": true } }, "required": [ "brand_manifest" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "brand_manifest": { @@ -1176,12 +1176,12 @@ "required": [ "url" ], - "additionalProperties": false + "additionalProperties": true } ] } }, - "additionalProperties": false + "additionalProperties": true }, "inputs": { "type": "array", @@ -1208,7 +1208,7 @@ "required": [ "name" ], - "additionalProperties": false + "additionalProperties": true } }, "tags": { @@ -1243,7 +1243,7 @@ "format_id", "assets" ], - "additionalProperties": false + "additionalProperties": true }, "maxItems": 100 }, @@ -1267,7 +1267,7 @@ } } }, - "additionalProperties": false + "additionalProperties": true }, "delete_missing": { "type": "boolean", @@ -1357,7 +1357,7 @@ "required": [ "creatives" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Full sync with hosted video creative", @@ -1469,7 +1469,7 @@ } ], "_bundled": { - "generatedAt": "2026-01-05T18:19:47.436Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.839Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/sync-creatives-response.json b/dist/schemas/2.6.0/bundled/media-buy/sync-creatives-response.json index f519c1ac77..cab162ded4 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/sync-creatives-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/sync-creatives-response.json @@ -87,14 +87,14 @@ "description": "Error message for this package assignment" } }, - "additionalProperties": false + "additionalProperties": true } }, "required": [ "creative_id", "action" ], - "additionalProperties": false + "additionalProperties": true } }, "context": { @@ -113,7 +113,7 @@ "required": [ "creatives" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "required": [ "errors" @@ -155,14 +155,16 @@ "minimum": 0 }, "details": { - "description": "Additional task-specific error details" + "type": "object", + "description": "Additional task-specific error details", + "additionalProperties": true } }, "required": [ "code", "message" ], - "additionalProperties": false + "additionalProperties": true }, "minItems": 1 }, @@ -182,7 +184,7 @@ "required": [ "errors" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "anyOf": [ { @@ -200,7 +202,7 @@ } ], "_bundled": { - "generatedAt": "2026-01-05T18:19:47.437Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.839Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/update-media-buy-request.json b/dist/schemas/2.6.0/bundled/media-buy/update-media-buy-request.json index a0d3994d9d..c8d8fa805e 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/update-media-buy-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/update-media-buy-request.json @@ -137,10 +137,10 @@ "required": [ "suppress_minutes" ], - "additionalProperties": false + "additionalProperties": true } }, - "additionalProperties": false + "additionalProperties": true }, "creative_ids": { "type": "array", @@ -200,7 +200,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -250,7 +250,7 @@ "width", "height" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Video Asset", @@ -292,7 +292,7 @@ "width", "height" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Audio Asset", @@ -322,7 +322,7 @@ "required": [ "url" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "Text Asset", @@ -341,7 +341,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "HTML Asset", @@ -360,7 +360,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "CSS Asset", @@ -379,7 +379,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "JavaScript Asset", @@ -404,7 +404,7 @@ "required": [ "content" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "VAST Asset", @@ -476,7 +476,7 @@ "delivery_type", "url" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -543,7 +543,7 @@ "delivery_type", "content" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -609,7 +609,7 @@ "delivery_type", "url" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -668,7 +668,7 @@ "delivery_type", "content" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -869,7 +869,7 @@ "asset_type", "url" ], - "additionalProperties": false + "additionalProperties": true } }, "product_catalog": { @@ -917,7 +917,7 @@ "required": [ "feed_url" ], - "additionalProperties": false + "additionalProperties": true }, "disclaimers": { "type": "array", @@ -991,7 +991,7 @@ "required": [ "name" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Example with both URL and name", @@ -1155,7 +1155,7 @@ "description": "Natural language query to select products from the brand manifest (e.g., 'all Kraft Heinz pasta sauces', 'organic products under $20')" } }, - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Direct SKU selection for specific products from brand manifest", @@ -1219,7 +1219,7 @@ "required": [ "name" ], - "additionalProperties": false + "additionalProperties": true } }, "asset_selectors": { @@ -1261,13 +1261,13 @@ } } }, - "additionalProperties": false + "additionalProperties": true } }, "required": [ "brand_manifest" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "brand_manifest": { @@ -1320,12 +1320,12 @@ "required": [ "url" ], - "additionalProperties": false + "additionalProperties": true } ] } }, - "additionalProperties": false + "additionalProperties": true }, "inputs": { "type": "array", @@ -1352,7 +1352,7 @@ "required": [ "name" ], - "additionalProperties": false + "additionalProperties": true } }, "tags": { @@ -1387,7 +1387,7 @@ "format_id", "assets" ], - "additionalProperties": false + "additionalProperties": true }, "maxItems": 100 }, @@ -1421,7 +1421,7 @@ "required": [ "creative_id" ], - "additionalProperties": false + "additionalProperties": true } } }, @@ -1437,7 +1437,7 @@ ] } ], - "additionalProperties": false + "additionalProperties": true } }, "push_notification_config": { @@ -1517,9 +1517,9 @@ ] } ], - "additionalProperties": false, + "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-05T18:19:47.446Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.840Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/update-media-buy-response.json b/dist/schemas/2.6.0/bundled/media-buy/update-media-buy-response.json index 8579e96e80..23ebd57c19 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/update-media-buy-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/update-media-buy-response.json @@ -136,10 +136,10 @@ "required": [ "suppress_minutes" ], - "additionalProperties": false + "additionalProperties": true } }, - "additionalProperties": false + "additionalProperties": true }, "creative_assignments": { "type": "array", @@ -171,7 +171,7 @@ "required": [ "creative_id" ], - "additionalProperties": false + "additionalProperties": true } }, "format_ids_to_provide": { @@ -212,7 +212,7 @@ "agent_url", "id" ], - "additionalProperties": false, + "additionalProperties": true, "dependencies": { "width": [ "height" @@ -238,7 +238,7 @@ "required": [ "package_id" ], - "additionalProperties": false + "additionalProperties": true } }, "context": { @@ -258,7 +258,7 @@ "media_buy_id", "buyer_ref" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "required": [ "errors" @@ -300,14 +300,16 @@ "minimum": 0 }, "details": { - "description": "Additional task-specific error details" + "type": "object", + "description": "Additional task-specific error details", + "additionalProperties": true } }, "required": [ "code", "message" ], - "additionalProperties": false + "additionalProperties": true }, "minItems": 1 }, @@ -327,7 +329,7 @@ "required": [ "errors" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "anyOf": [ { @@ -350,7 +352,7 @@ } ], "_bundled": { - "generatedAt": "2026-01-05T18:19:47.447Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.841Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/signals/activate-signal-request.json b/dist/schemas/2.6.0/bundled/signals/activate-signal-request.json index 4b091ebe4d..24de122d6e 100644 --- a/dist/schemas/2.6.0/bundled/signals/activate-signal-request.json +++ b/dist/schemas/2.6.0/bundled/signals/activate-signal-request.json @@ -36,7 +36,7 @@ "type", "platform" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -59,7 +59,7 @@ "type", "agent_url" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -82,9 +82,9 @@ "signal_agent_segment_id", "deployments" ], - "additionalProperties": false, + "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-05T18:19:47.447Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.841Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/signals/activate-signal-response.json b/dist/schemas/2.6.0/bundled/signals/activate-signal-response.json index 63018d77ec..6e532f84b2 100644 --- a/dist/schemas/2.6.0/bundled/signals/activate-signal-response.json +++ b/dist/schemas/2.6.0/bundled/signals/activate-signal-response.json @@ -58,7 +58,7 @@ "type", "segment_id" ], - "additionalProperties": false + "additionalProperties": true }, { "properties": { @@ -81,7 +81,7 @@ "key", "value" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -101,7 +101,7 @@ "platform", "is_live" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -145,7 +145,7 @@ "type", "segment_id" ], - "additionalProperties": false + "additionalProperties": true }, { "properties": { @@ -168,7 +168,7 @@ "key", "value" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -188,7 +188,7 @@ "agent_url", "is_live" ], - "additionalProperties": false + "additionalProperties": true } ] } @@ -209,7 +209,7 @@ "required": [ "deployments" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "required": [ "errors" @@ -251,14 +251,16 @@ "minimum": 0 }, "details": { - "description": "Additional task-specific error details" + "type": "object", + "description": "Additional task-specific error details", + "additionalProperties": true } }, "required": [ "code", "message" ], - "additionalProperties": false + "additionalProperties": true }, "minItems": 1 }, @@ -278,7 +280,7 @@ "required": [ "errors" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "required": [ "deployments" @@ -287,7 +289,7 @@ } ], "_bundled": { - "generatedAt": "2026-01-05T18:19:47.448Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.842Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/signals/get-signals-request.json b/dist/schemas/2.6.0/bundled/signals/get-signals-request.json index 9b49e8f9b1..85d7c3eb36 100644 --- a/dist/schemas/2.6.0/bundled/signals/get-signals-request.json +++ b/dist/schemas/2.6.0/bundled/signals/get-signals-request.json @@ -40,7 +40,7 @@ "type", "platform" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -63,7 +63,7 @@ "type", "agent_url" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -82,7 +82,7 @@ "deployments", "countries" ], - "additionalProperties": false + "additionalProperties": true }, "filters": { "title": "Signal Filters", @@ -122,7 +122,7 @@ "maximum": 100 } }, - "additionalProperties": false + "additionalProperties": true }, "max_results": { "type": "integer", @@ -146,9 +146,9 @@ "signal_spec", "deliver_to" ], - "additionalProperties": false, + "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-05T18:19:47.451Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.842Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/signals/get-signals-response.json b/dist/schemas/2.6.0/bundled/signals/get-signals-response.json index b382f9cfff..d7acb6f13f 100644 --- a/dist/schemas/2.6.0/bundled/signals/get-signals-response.json +++ b/dist/schemas/2.6.0/bundled/signals/get-signals-response.json @@ -91,7 +91,7 @@ "type", "segment_id" ], - "additionalProperties": false + "additionalProperties": true }, { "properties": { @@ -114,7 +114,7 @@ "key", "value" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -134,7 +134,7 @@ "platform", "is_live" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -178,7 +178,7 @@ "type", "segment_id" ], - "additionalProperties": false + "additionalProperties": true }, { "properties": { @@ -201,7 +201,7 @@ "key", "value" ], - "additionalProperties": false + "additionalProperties": true } ] }, @@ -221,7 +221,7 @@ "agent_url", "is_live" ], - "additionalProperties": false + "additionalProperties": true } ] } @@ -245,7 +245,7 @@ "cpm", "currency" ], - "additionalProperties": false + "additionalProperties": true } }, "required": [ @@ -258,7 +258,7 @@ "deployments", "pricing" ], - "additionalProperties": false + "additionalProperties": true } }, "errors": { @@ -291,14 +291,16 @@ "minimum": 0 }, "details": { - "description": "Additional task-specific error details" + "type": "object", + "description": "Additional task-specific error details", + "additionalProperties": true } }, "required": [ "code", "message" ], - "additionalProperties": false + "additionalProperties": true } }, "context": { @@ -317,9 +319,9 @@ "required": [ "signals" ], - "additionalProperties": false, + "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-05T18:19:47.452Z", - "note": "This is a bundled schema with all $ref resolved inline." + "generatedAt": "2026-01-07T19:56:54.842Z", + "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/core/activation-key.json b/dist/schemas/2.6.0/core/activation-key.json index 6d0d6d941d..67b3e2817c 100644 --- a/dist/schemas/2.6.0/core/activation-key.json +++ b/dist/schemas/2.6.0/core/activation-key.json @@ -17,8 +17,11 @@ "description": "The platform-specific segment identifier to use in campaign targeting" } }, - "required": ["type", "segment_id"], - "additionalProperties": false + "required": [ + "type", + "segment_id" + ], + "additionalProperties": true }, { "properties": { @@ -36,8 +39,12 @@ "description": "The targeting parameter value" } }, - "required": ["type", "key", "value"], - "additionalProperties": false + "required": [ + "type", + "key", + "value" + ], + "additionalProperties": true } ] } diff --git a/dist/schemas/2.6.0/core/assets/audio-asset.json b/dist/schemas/2.6.0/core/assets/audio-asset.json index d895834c41..8c48b4b4a4 100644 --- a/dist/schemas/2.6.0/core/assets/audio-asset.json +++ b/dist/schemas/2.6.0/core/assets/audio-asset.json @@ -25,6 +25,8 @@ "minimum": 1 } }, - "required": ["url"], - "additionalProperties": false + "required": [ + "url" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/core/assets/css-asset.json b/dist/schemas/2.6.0/core/assets/css-asset.json index bb69a9ba28..0d36107978 100644 --- a/dist/schemas/2.6.0/core/assets/css-asset.json +++ b/dist/schemas/2.6.0/core/assets/css-asset.json @@ -14,6 +14,8 @@ "description": "CSS media query context (e.g., 'screen', 'print')" } }, - "required": ["content"], - "additionalProperties": false + "required": [ + "content" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/core/assets/daast-asset.json b/dist/schemas/2.6.0/core/assets/daast-asset.json index 1039e4b43d..dfbbad197f 100644 --- a/dist/schemas/2.6.0/core/assets/daast-asset.json +++ b/dist/schemas/2.6.0/core/assets/daast-asset.json @@ -38,8 +38,11 @@ "description": "Whether companion display ads are included" } }, - "required": ["delivery_type", "url"], - "additionalProperties": false + "required": [ + "delivery_type", + "url" + ], + "additionalProperties": true }, { "type": "object", @@ -74,8 +77,11 @@ "description": "Whether companion display ads are included" } }, - "required": ["delivery_type", "content"], - "additionalProperties": false + "required": [ + "delivery_type", + "content" + ], + "additionalProperties": true } ] } diff --git a/dist/schemas/2.6.0/core/assets/html-asset.json b/dist/schemas/2.6.0/core/assets/html-asset.json index 7849f43d77..df1ba91c58 100644 --- a/dist/schemas/2.6.0/core/assets/html-asset.json +++ b/dist/schemas/2.6.0/core/assets/html-asset.json @@ -14,6 +14,8 @@ "description": "HTML version (e.g., 'HTML5')" } }, - "required": ["content"], - "additionalProperties": false + "required": [ + "content" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/core/assets/image-asset.json b/dist/schemas/2.6.0/core/assets/image-asset.json index 245ebbca3a..953598f30a 100644 --- a/dist/schemas/2.6.0/core/assets/image-asset.json +++ b/dist/schemas/2.6.0/core/assets/image-asset.json @@ -29,6 +29,10 @@ "description": "Alternative text for accessibility" } }, - "required": ["url", "width", "height"], - "additionalProperties": false + "required": [ + "url", + "width", + "height" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/core/assets/javascript-asset.json b/dist/schemas/2.6.0/core/assets/javascript-asset.json index 09eb42614b..7c7cb94ed8 100644 --- a/dist/schemas/2.6.0/core/assets/javascript-asset.json +++ b/dist/schemas/2.6.0/core/assets/javascript-asset.json @@ -14,6 +14,8 @@ "description": "JavaScript module type" } }, - "required": ["content"], - "additionalProperties": false + "required": [ + "content" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/core/assets/markdown-asset.json b/dist/schemas/2.6.0/core/assets/markdown-asset.json index efcf4ff69b..de825fd86f 100644 --- a/dist/schemas/2.6.0/core/assets/markdown-asset.json +++ b/dist/schemas/2.6.0/core/assets/markdown-asset.json @@ -24,6 +24,8 @@ "description": "Whether raw HTML blocks are allowed in the markdown. False recommended for security." } }, - "required": ["content"], - "additionalProperties": false + "required": [ + "content" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/core/assets/text-asset.json b/dist/schemas/2.6.0/core/assets/text-asset.json index 0a459194e1..0fdd358ddd 100644 --- a/dist/schemas/2.6.0/core/assets/text-asset.json +++ b/dist/schemas/2.6.0/core/assets/text-asset.json @@ -14,6 +14,8 @@ "description": "Language code (e.g., 'en', 'es', 'fr')" } }, - "required": ["content"], - "additionalProperties": false + "required": [ + "content" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/core/assets/url-asset.json b/dist/schemas/2.6.0/core/assets/url-asset.json index 4276e8d2d8..2a2d44da44 100644 --- a/dist/schemas/2.6.0/core/assets/url-asset.json +++ b/dist/schemas/2.6.0/core/assets/url-asset.json @@ -19,6 +19,8 @@ "description": "Description of what this URL points to" } }, - "required": ["url"], - "additionalProperties": false + "required": [ + "url" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/core/assets/vast-asset.json b/dist/schemas/2.6.0/core/assets/vast-asset.json index 4bfef29e0d..a4c3d5d4b3 100644 --- a/dist/schemas/2.6.0/core/assets/vast-asset.json +++ b/dist/schemas/2.6.0/core/assets/vast-asset.json @@ -38,8 +38,11 @@ "description": "Tracking events supported by this VAST tag" } }, - "required": ["delivery_type", "url"], - "additionalProperties": false + "required": [ + "delivery_type", + "url" + ], + "additionalProperties": true }, { "type": "object", @@ -74,8 +77,11 @@ "description": "Tracking events supported by this VAST tag" } }, - "required": ["delivery_type", "content"], - "additionalProperties": false + "required": [ + "delivery_type", + "content" + ], + "additionalProperties": true } ] } diff --git a/dist/schemas/2.6.0/core/assets/video-asset.json b/dist/schemas/2.6.0/core/assets/video-asset.json index a47ebd686a..f25f7a34f6 100644 --- a/dist/schemas/2.6.0/core/assets/video-asset.json +++ b/dist/schemas/2.6.0/core/assets/video-asset.json @@ -35,6 +35,10 @@ "minimum": 1 } }, - "required": ["url", "width", "height"], - "additionalProperties": false + "required": [ + "url", + "width", + "height" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/core/assets/webhook-asset.json b/dist/schemas/2.6.0/core/assets/webhook-asset.json index 235d12283c..e19570f383 100644 --- a/dist/schemas/2.6.0/core/assets/webhook-asset.json +++ b/dist/schemas/2.6.0/core/assets/webhook-asset.json @@ -57,9 +57,15 @@ "description": "Header name for API key (e.g., 'X-API-Key')" } }, - "required": ["method"] + "required": [ + "method" + ] } }, - "required": ["url", "response_type", "security"], - "additionalProperties": false + "required": [ + "url", + "response_type", + "security" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/core/async-response-data.json b/dist/schemas/2.6.0/core/async-response-data.json index f7f2334272..5ce383d420 100644 --- a/dist/schemas/2.6.0/core/async-response-data.json +++ b/dist/schemas/2.6.0/core/async-response-data.json @@ -86,4 +86,3 @@ } ] } - diff --git a/dist/schemas/2.6.0/core/brand-manifest.json b/dist/schemas/2.6.0/core/brand-manifest.json index 8192be555d..92a3e69f75 100644 --- a/dist/schemas/2.6.0/core/brand-manifest.json +++ b/dist/schemas/2.6.0/core/brand-manifest.json @@ -41,7 +41,9 @@ "description": "Logo height in pixels" } }, - "required": ["url"] + "required": [ + "url" + ] } }, "colors": { @@ -165,8 +167,12 @@ "additionalProperties": true } }, - "required": ["asset_id", "asset_type", "url"], - "additionalProperties": false + "required": [ + "asset_id", + "asset_type", + "url" + ], + "additionalProperties": true } }, "product_catalog": { @@ -180,7 +186,11 @@ }, "feed_format": { "type": "string", - "enum": ["google_merchant_center", "facebook_catalog", "custom"], + "enum": [ + "google_merchant_center", + "facebook_catalog", + "custom" + ], "default": "google_merchant_center", "description": "Format of the product feed" }, @@ -198,12 +208,19 @@ }, "update_frequency": { "type": "string", - "enum": ["realtime", "hourly", "daily", "weekly"], + "enum": [ + "realtime", + "hourly", + "daily", + "weekly" + ], "description": "How frequently the product catalog is updated" } }, - "required": ["feed_url"], - "additionalProperties": false + "required": [ + "feed_url" + ], + "additionalProperties": true }, "disclaimers": { "type": "array", @@ -225,7 +242,9 @@ "default": true } }, - "required": ["text"] + "required": [ + "text" + ] } }, "industry": { @@ -272,8 +291,10 @@ } } }, - "required": ["name"], - "additionalProperties": false, + "required": [ + "name" + ], + "additionalProperties": true, "examples": [ { "description": "Example with both URL and name", @@ -299,71 +320,89 @@ "url": "https://acmecorp.com", "name": "ACME Corporation", "logos": [ - { - "url": "https://cdn.acmecorp.com/logo-square-dark.png", - "tags": ["dark", "square"], - "width": 512, - "height": 512 + { + "url": "https://cdn.acmecorp.com/logo-square-dark.png", + "tags": [ + "dark", + "square" + ], + "width": 512, + "height": 512 + }, + { + "url": "https://cdn.acmecorp.com/logo-horizontal-light.png", + "tags": [ + "light", + "horizontal" + ], + "width": 1200, + "height": 400 + } + ], + "colors": { + "primary": "#FF6B35", + "secondary": "#004E89", + "accent": "#F7931E", + "background": "#FFFFFF", + "text": "#1A1A1A" }, - { - "url": "https://cdn.acmecorp.com/logo-horizontal-light.png", - "tags": ["light", "horizontal"], - "width": 1200, - "height": 400 - } - ], - "colors": { - "primary": "#FF6B35", - "secondary": "#004E89", - "accent": "#F7931E", - "background": "#FFFFFF", - "text": "#1A1A1A" - }, - "fonts": { - "primary": "Helvetica Neue", - "secondary": "Georgia" - }, - "tone": "professional and trustworthy", - "tagline": "Innovation You Can Trust", - "assets": [ - { - "asset_id": "hero_winter_2024", - "asset_type": "image", - "url": "https://cdn.acmecorp.com/hero-winter-2024.jpg", - "tags": ["hero", "winter", "holiday", "lifestyle"], - "name": "Winter Campaign Hero", - "width": 1920, - "height": 1080, - "format": "jpg" + "fonts": { + "primary": "Helvetica Neue", + "secondary": "Georgia" }, - { - "asset_id": "product_video_30s", - "asset_type": "video", - "url": "https://cdn.acmecorp.com/product-demo-30s.mp4", - "tags": ["product", "demo", "30s"], - "name": "Product Demo 30 Second", - "width": 1920, - "height": 1080, - "duration_seconds": 30, - "format": "mp4" - } - ], - "product_catalog": { - "feed_url": "https://acmecorp.com/products.xml", - "feed_format": "google_merchant_center", - "categories": ["electronics/computers", "electronics/accessories"], - "last_updated": "2024-03-15T10:00:00Z", - "update_frequency": "hourly" - }, - "disclaimers": [ - { - "text": "Results may vary. Consult a professional before use.", - "context": "health_claims", - "required": true - } - ], - "industry": "technology", - "target_audience": "business decision-makers aged 35-55" + "tone": "professional and trustworthy", + "tagline": "Innovation You Can Trust", + "assets": [ + { + "asset_id": "hero_winter_2024", + "asset_type": "image", + "url": "https://cdn.acmecorp.com/hero-winter-2024.jpg", + "tags": [ + "hero", + "winter", + "holiday", + "lifestyle" + ], + "name": "Winter Campaign Hero", + "width": 1920, + "height": 1080, + "format": "jpg" + }, + { + "asset_id": "product_video_30s", + "asset_type": "video", + "url": "https://cdn.acmecorp.com/product-demo-30s.mp4", + "tags": [ + "product", + "demo", + "30s" + ], + "name": "Product Demo 30 Second", + "width": 1920, + "height": 1080, + "duration_seconds": 30, + "format": "mp4" + } + ], + "product_catalog": { + "feed_url": "https://acmecorp.com/products.xml", + "feed_format": "google_merchant_center", + "categories": [ + "electronics/computers", + "electronics/accessories" + ], + "last_updated": "2024-03-15T10:00:00Z", + "update_frequency": "hourly" + }, + "disclaimers": [ + { + "text": "Results may vary. Consult a professional before use.", + "context": "health_claims", + "required": true + } + ], + "industry": "technology", + "target_audience": "business decision-makers aged 35-55" } } ] diff --git a/dist/schemas/2.6.0/core/creative-asset.json b/dist/schemas/2.6.0/core/creative-asset.json index d3ca9a1615..37a5d24868 100644 --- a/dist/schemas/2.6.0/core/creative-asset.json +++ b/dist/schemas/2.6.0/core/creative-asset.json @@ -23,21 +23,43 @@ "patternProperties": { "^[a-zA-Z0-9_-]+$": { "oneOf": [ - {"$ref": "/schemas/2.6.0/core/assets/image-asset.json"}, - {"$ref": "/schemas/2.6.0/core/assets/video-asset.json"}, - {"$ref": "/schemas/2.6.0/core/assets/audio-asset.json"}, - {"$ref": "/schemas/2.6.0/core/assets/text-asset.json"}, - {"$ref": "/schemas/2.6.0/core/assets/html-asset.json"}, - {"$ref": "/schemas/2.6.0/core/assets/css-asset.json"}, - {"$ref": "/schemas/2.6.0/core/assets/javascript-asset.json"}, - {"$ref": "/schemas/2.6.0/core/assets/vast-asset.json"}, - {"$ref": "/schemas/2.6.0/core/assets/daast-asset.json"}, - {"$ref": "/schemas/2.6.0/core/promoted-offerings.json"}, - {"$ref": "/schemas/2.6.0/core/assets/url-asset.json"} + { + "$ref": "/schemas/2.6.0/core/assets/image-asset.json" + }, + { + "$ref": "/schemas/2.6.0/core/assets/video-asset.json" + }, + { + "$ref": "/schemas/2.6.0/core/assets/audio-asset.json" + }, + { + "$ref": "/schemas/2.6.0/core/assets/text-asset.json" + }, + { + "$ref": "/schemas/2.6.0/core/assets/html-asset.json" + }, + { + "$ref": "/schemas/2.6.0/core/assets/css-asset.json" + }, + { + "$ref": "/schemas/2.6.0/core/assets/javascript-asset.json" + }, + { + "$ref": "/schemas/2.6.0/core/assets/vast-asset.json" + }, + { + "$ref": "/schemas/2.6.0/core/assets/daast-asset.json" + }, + { + "$ref": "/schemas/2.6.0/core/promoted-offerings.json" + }, + { + "$ref": "/schemas/2.6.0/core/assets/url-asset.json" + } ] } }, - "additionalProperties": false + "additionalProperties": true }, "inputs": { "type": "array", @@ -52,15 +74,19 @@ "macros": { "type": "object", "description": "Macro values to apply for this preview", - "additionalProperties": {"type": "string"} + "additionalProperties": { + "type": "string" + } }, "context_description": { "type": "string", "description": "Natural language description of the context for AI-generated content" } }, - "required": ["name"], - "additionalProperties": false + "required": [ + "name" + ], + "additionalProperties": true } }, "tags": { @@ -89,6 +115,11 @@ "minItems": 1 } }, - "required": ["creative_id", "name", "format_id", "assets"], - "additionalProperties": false -} \ No newline at end of file + "required": [ + "creative_id", + "name", + "format_id", + "assets" + ], + "additionalProperties": true +} diff --git a/dist/schemas/2.6.0/core/creative-assignment.json b/dist/schemas/2.6.0/core/creative-assignment.json index 5c39fb1d59..e21ba19216 100644 --- a/dist/schemas/2.6.0/core/creative-assignment.json +++ b/dist/schemas/2.6.0/core/creative-assignment.json @@ -24,6 +24,8 @@ "minItems": 1 } }, - "required": ["creative_id"], - "additionalProperties": false -} \ No newline at end of file + "required": [ + "creative_id" + ], + "additionalProperties": true +} diff --git a/dist/schemas/2.6.0/core/creative-filters.json b/dist/schemas/2.6.0/core/creative-filters.json index 2c167a8b90..9d27db988d 100644 --- a/dist/schemas/2.6.0/core/creative-filters.json +++ b/dist/schemas/2.6.0/core/creative-filters.json @@ -107,5 +107,5 @@ "description": "Filter creatives that have performance data when true" } }, - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/core/creative-manifest.json b/dist/schemas/2.6.0/core/creative-manifest.json index acd9cfea1e..91b76b9521 100644 --- a/dist/schemas/2.6.0/core/creative-manifest.json +++ b/dist/schemas/2.6.0/core/creative-manifest.json @@ -58,7 +58,7 @@ ] } }, - "additionalProperties": false + "additionalProperties": true }, "ext": { "$ref": "/schemas/2.6.0/core/ext.json" @@ -68,5 +68,5 @@ "format_id", "assets" ], - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/core/creative-policy.json b/dist/schemas/2.6.0/core/creative-policy.json index cbf64b262e..663add85a0 100644 --- a/dist/schemas/2.6.0/core/creative-policy.json +++ b/dist/schemas/2.6.0/core/creative-policy.json @@ -18,6 +18,10 @@ "description": "Whether creative templates are provided" } }, - "required": ["co_branding", "landing_page", "templates_available"], - "additionalProperties": false -} \ No newline at end of file + "required": [ + "co_branding", + "landing_page", + "templates_available" + ], + "additionalProperties": true +} diff --git a/dist/schemas/2.6.0/core/delivery-metrics.json b/dist/schemas/2.6.0/core/delivery-metrics.json index d3d4bb9922..660f3c12b8 100644 --- a/dist/schemas/2.6.0/core/delivery-metrics.json +++ b/dist/schemas/2.6.0/core/delivery-metrics.json @@ -156,12 +156,15 @@ "minimum": 0 } }, - "required": ["venue_id", "impressions"], - "additionalProperties": false + "required": [ + "venue_id", + "impressions" + ], + "additionalProperties": true } } }, - "additionalProperties": false + "additionalProperties": true } }, "additionalProperties": true diff --git a/dist/schemas/2.6.0/core/deployment.json b/dist/schemas/2.6.0/core/deployment.json index 58fa211e54..bab2e716e9 100644 --- a/dist/schemas/2.6.0/core/deployment.json +++ b/dist/schemas/2.6.0/core/deployment.json @@ -39,8 +39,12 @@ "description": "Timestamp when activation completed (if is_live=true)" } }, - "required": ["type", "platform", "is_live"], - "additionalProperties": false + "required": [ + "type", + "platform", + "is_live" + ], + "additionalProperties": true }, { "type": "object", @@ -78,8 +82,12 @@ "description": "Timestamp when activation completed (if is_live=true)" } }, - "required": ["type", "agent_url", "is_live"], - "additionalProperties": false + "required": [ + "type", + "agent_url", + "is_live" + ], + "additionalProperties": true } ] } diff --git a/dist/schemas/2.6.0/core/destination.json b/dist/schemas/2.6.0/core/destination.json index 1cc303a471..a3ddae6bb9 100644 --- a/dist/schemas/2.6.0/core/destination.json +++ b/dist/schemas/2.6.0/core/destination.json @@ -20,8 +20,11 @@ "description": "Optional account identifier on the platform" } }, - "required": ["type", "platform"], - "additionalProperties": false + "required": [ + "type", + "platform" + ], + "additionalProperties": true }, { "type": "object", @@ -40,8 +43,11 @@ "description": "Optional account identifier on the agent" } }, - "required": ["type", "agent_url"], - "additionalProperties": false + "required": [ + "type", + "agent_url" + ], + "additionalProperties": true } ] } diff --git a/dist/schemas/2.6.0/core/error.json b/dist/schemas/2.6.0/core/error.json index cd6f83dbc2..feef52fdf7 100644 --- a/dist/schemas/2.6.0/core/error.json +++ b/dist/schemas/2.6.0/core/error.json @@ -27,9 +27,14 @@ "minimum": 0 }, "details": { - "description": "Additional task-specific error details" + "type": "object", + "description": "Additional task-specific error details", + "additionalProperties": true } }, - "required": ["code", "message"], - "additionalProperties": false -} \ No newline at end of file + "required": [ + "code", + "message" + ], + "additionalProperties": true +} diff --git a/dist/schemas/2.6.0/core/format-id.json b/dist/schemas/2.6.0/core/format-id.json index a47080b219..235caae4fd 100644 --- a/dist/schemas/2.6.0/core/format-id.json +++ b/dist/schemas/2.6.0/core/format-id.json @@ -31,10 +31,17 @@ "description": "Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters." } }, - "required": ["agent_url", "id"], - "additionalProperties": false, + "required": [ + "agent_url", + "id" + ], + "additionalProperties": true, "dependencies": { - "width": ["height"], - "height": ["width"] + "width": [ + "height" + ], + "height": [ + "width" + ] } } diff --git a/dist/schemas/2.6.0/core/format.json b/dist/schemas/2.6.0/core/format.json index ab8999a800..e824efe5dc 100644 --- a/dist/schemas/2.6.0/core/format.json +++ b/dist/schemas/2.6.0/core/format.json @@ -91,10 +91,17 @@ "type": "object", "description": "Indicates which dimensions are responsive/fluid", "properties": { - "width": {"type": "boolean"}, - "height": {"type": "boolean"} + "width": { + "type": "boolean" + }, + "height": { + "type": "boolean" + } }, - "required": ["width", "height"] + "required": [ + "width", + "height" + ] }, "aspect_ratio": { "type": "string", @@ -104,16 +111,34 @@ } } }, - "required": ["role"], + "required": [ + "role" + ], "oneOf": [ { - "required": ["dimensions"], - "not": {"required": ["parameters_from_format_id"]} + "required": [ + "dimensions" + ], + "not": { + "required": [ + "parameters_from_format_id" + ] + } }, { - "required": ["parameters_from_format_id"], - "properties": {"parameters_from_format_id": {"const": true}}, - "not": {"required": ["dimensions"]} + "required": [ + "parameters_from_format_id" + ], + "properties": { + "parameters_from_format_id": { + "const": true + } + }, + "not": { + "required": [ + "dimensions" + ] + } } ] }, @@ -144,7 +169,7 @@ }, "asset_role": { "type": "string", - "description": "Optional descriptive label for this asset's purpose (e.g., 'hero_image', 'logo'). Not used for referencing assets in manifests—use asset_id instead. This field is for human-readable documentation and UI display only." + "description": "Optional descriptive label for this asset's purpose (e.g., 'hero_image', 'logo'). Not used for referencing assets in manifests\u2014use asset_id instead. This field is for human-readable documentation and UI display only." }, "required": { "type": "boolean", @@ -156,7 +181,11 @@ "additionalProperties": true } }, - "required": ["item_type", "asset_id", "asset_type"] + "required": [ + "item_type", + "asset_id", + "asset_type" + ] }, { "description": "Repeatable asset group (for carousels, slideshows, playlists, etc.)", @@ -197,7 +226,7 @@ }, "asset_role": { "type": "string", - "description": "Optional descriptive label for this asset's purpose (e.g., 'hero_image', 'logo'). Not used for referencing assets in manifests—use asset_id instead. This field is for human-readable documentation and UI display only." + "description": "Optional descriptive label for this asset's purpose (e.g., 'hero_image', 'logo'). Not used for referencing assets in manifests\u2014use asset_id instead. This field is for human-readable documentation and UI display only." }, "required": { "type": "boolean", @@ -209,11 +238,20 @@ "additionalProperties": true } }, - "required": ["asset_id", "asset_type"] + "required": [ + "asset_id", + "asset_type" + ] } } }, - "required": ["item_type", "asset_group_id", "min_count", "max_count", "assets"] + "required": [ + "item_type", + "asset_group_id", + "min_count", + "max_count", + "assets" + ] } ] } @@ -354,8 +392,11 @@ "additionalProperties": true } }, - "required": ["format_id", "manifest"], - "additionalProperties": false + "required": [ + "format_id", + "manifest" + ], + "additionalProperties": true }, "format_card_detailed": { "type": "object", @@ -371,10 +412,17 @@ "additionalProperties": true } }, - "required": ["format_id", "manifest"], - "additionalProperties": false + "required": [ + "format_id", + "manifest" + ], + "additionalProperties": true } }, - "required": ["format_id", "name", "type"], - "additionalProperties": false -} \ No newline at end of file + "required": [ + "format_id", + "name", + "type" + ], + "additionalProperties": true +} diff --git a/dist/schemas/2.6.0/core/frequency-cap.json b/dist/schemas/2.6.0/core/frequency-cap.json index 5aeb769f62..682b4dcbd5 100644 --- a/dist/schemas/2.6.0/core/frequency-cap.json +++ b/dist/schemas/2.6.0/core/frequency-cap.json @@ -11,6 +11,8 @@ "minimum": 0 } }, - "required": ["suppress_minutes"], - "additionalProperties": false -} \ No newline at end of file + "required": [ + "suppress_minutes" + ], + "additionalProperties": true +} diff --git a/dist/schemas/2.6.0/core/mcp-webhook-payload.json b/dist/schemas/2.6.0/core/mcp-webhook-payload.json index 076fe3cbf2..8ec58d76a2 100644 --- a/dist/schemas/2.6.0/core/mcp-webhook-payload.json +++ b/dist/schemas/2.6.0/core/mcp-webhook-payload.json @@ -43,7 +43,12 @@ "description": "Task-specific payload matching the status. For completed/failed, contains the full task response. For working/input-required/submitted, contains status-specific data. This is the data layer that AdCP specs - same structure used in A2A status.message.parts[].data." } }, - "required": ["task_id", "task_type", "status", "timestamp"], + "required": [ + "task_id", + "task_type", + "status", + "timestamp" + ], "additionalProperties": true, "examples": [ { diff --git a/dist/schemas/2.6.0/core/measurement.json b/dist/schemas/2.6.0/core/measurement.json index c2c9bdcb8c..2d8d405900 100644 --- a/dist/schemas/2.6.0/core/measurement.json +++ b/dist/schemas/2.6.0/core/measurement.json @@ -8,24 +8,41 @@ "type": { "type": "string", "description": "Type of measurement", - "examples": ["incremental_sales_lift", "brand_lift", "foot_traffic"] + "examples": [ + "incremental_sales_lift", + "brand_lift", + "foot_traffic" + ] }, "attribution": { "type": "string", "description": "Attribution methodology", - "examples": ["deterministic_purchase", "probabilistic"] + "examples": [ + "deterministic_purchase", + "probabilistic" + ] }, "window": { "type": "string", "description": "Attribution window", - "examples": ["30_days", "7_days"] + "examples": [ + "30_days", + "7_days" + ] }, "reporting": { "type": "string", "description": "Reporting frequency and format", - "examples": ["weekly_dashboard", "real_time_api"] + "examples": [ + "weekly_dashboard", + "real_time_api" + ] } }, - "required": ["type", "attribution", "reporting"], - "additionalProperties": false -} \ No newline at end of file + "required": [ + "type", + "attribution", + "reporting" + ], + "additionalProperties": true +} diff --git a/dist/schemas/2.6.0/core/media-buy.json b/dist/schemas/2.6.0/core/media-buy.json index 233411205f..dc5e1a5f59 100644 --- a/dist/schemas/2.6.0/core/media-buy.json +++ b/dist/schemas/2.6.0/core/media-buy.json @@ -58,5 +58,5 @@ "total_budget", "packages" ], - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/core/package.json b/dist/schemas/2.6.0/core/package.json index 24fc457003..9c736e9388 100644 --- a/dist/schemas/2.6.0/core/package.json +++ b/dist/schemas/2.6.0/core/package.json @@ -68,5 +68,5 @@ "required": [ "package_id" ], - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/core/performance-feedback.json b/dist/schemas/2.6.0/core/performance-feedback.json index 2df893bda1..c561f7e307 100644 --- a/dist/schemas/2.6.0/core/performance-feedback.json +++ b/dist/schemas/2.6.0/core/performance-feedback.json @@ -36,8 +36,11 @@ "description": "ISO 8601 end timestamp for measurement period" } }, - "required": ["start", "end"], - "additionalProperties": false + "required": [ + "start", + "end" + ], + "additionalProperties": true }, "performance_index": { "type": "number", @@ -55,7 +58,12 @@ "status": { "type": "string", "description": "Processing status of the performance feedback", - "enum": ["accepted", "queued", "applied", "rejected"] + "enum": [ + "accepted", + "queued", + "applied", + "rejected" + ] }, "submitted_at": { "type": "string", @@ -78,5 +86,5 @@ "status", "submitted_at" ], - "additionalProperties": false -} \ No newline at end of file + "additionalProperties": true +} diff --git a/dist/schemas/2.6.0/core/placement.json b/dist/schemas/2.6.0/core/placement.json index 200f525dd2..812e2ab610 100644 --- a/dist/schemas/2.6.0/core/placement.json +++ b/dist/schemas/2.6.0/core/placement.json @@ -26,6 +26,9 @@ "minItems": 1 } }, - "required": ["placement_id", "name"], - "additionalProperties": false + "required": [ + "placement_id", + "name" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/core/product-filters.json b/dist/schemas/2.6.0/core/product-filters.json index 7019a3333e..5f10c19eee 100644 --- a/dist/schemas/2.6.0/core/product-filters.json +++ b/dist/schemas/2.6.0/core/product-filters.json @@ -65,12 +65,22 @@ "pattern": "^[A-Z]{3}$" } }, - "required": ["currency"], + "required": [ + "currency" + ], "anyOf": [ - {"required": ["min"]}, - {"required": ["max"]} + { + "required": [ + "min" + ] + }, + { + "required": [ + "max" + ] + } ], - "additionalProperties": false + "additionalProperties": true }, "countries": { "type": "array", @@ -88,5 +98,5 @@ } } }, - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/core/product.json b/dist/schemas/2.6.0/core/product.json index f8a5c8f598..04b13a22a2 100644 --- a/dist/schemas/2.6.0/core/product.json +++ b/dist/schemas/2.6.0/core/product.json @@ -113,7 +113,7 @@ "format_id", "manifest" ], - "additionalProperties": false + "additionalProperties": true }, "product_card_detailed": { "type": "object", @@ -133,7 +133,7 @@ "format_id", "manifest" ], - "additionalProperties": false + "additionalProperties": true }, "ext": { "$ref": "/schemas/2.6.0/core/ext.json" @@ -149,5 +149,5 @@ "delivery_measurement", "pricing_options" ], - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/core/promoted-offerings.json b/dist/schemas/2.6.0/core/promoted-offerings.json index 2e312029c4..d465e6d1e2 100644 --- a/dist/schemas/2.6.0/core/promoted-offerings.json +++ b/dist/schemas/2.6.0/core/promoted-offerings.json @@ -37,8 +37,10 @@ } } }, - "required": ["name"], - "additionalProperties": false + "required": [ + "name" + ], + "additionalProperties": true } }, "asset_selectors": { @@ -57,7 +59,19 @@ "description": "Filter by asset type (e.g., ['image', 'video'])", "items": { "type": "string", - "enum": ["image", "video", "audio", "vast", "daast", "text", "url", "html", "css", "javascript", "webhook"] + "enum": [ + "image", + "video", + "audio", + "vast", + "daast", + "text", + "url", + "html", + "css", + "javascript", + "webhook" + ] } }, "exclude_tags": { @@ -68,11 +82,13 @@ } } }, - "additionalProperties": false + "additionalProperties": true } }, - "required": ["brand_manifest"], - "additionalProperties": false, + "required": [ + "brand_manifest" + ], + "additionalProperties": true, "examples": [ { "brand_manifest": { @@ -80,11 +96,19 @@ "url": "https://brand.com" }, "product_selectors": { - "manifest_skus": ["SKU-123", "SKU-456"] + "manifest_skus": [ + "SKU-123", + "SKU-456" + ] }, "asset_selectors": { - "tags": ["holiday"], - "asset_types": ["image", "video"] + "tags": [ + "holiday" + ], + "asset_types": [ + "image", + "video" + ] } } ] diff --git a/dist/schemas/2.6.0/core/promoted-products.json b/dist/schemas/2.6.0/core/promoted-products.json index 4a1dceafb6..451197fbb4 100644 --- a/dist/schemas/2.6.0/core/promoted-products.json +++ b/dist/schemas/2.6.0/core/promoted-products.json @@ -28,18 +28,24 @@ "description": "Natural language query to select products from the brand manifest (e.g., 'all Kraft Heinz pasta sauces', 'organic products under $20')" } }, - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Direct SKU selection for specific products from brand manifest", "data": { - "manifest_skus": ["SKU-12345", "SKU-67890"] + "manifest_skus": [ + "SKU-12345", + "SKU-67890" + ] } }, { "description": "UNION selection: products tagged 'organic' OR 'sauces' OR in 'food/condiments' category from brand manifest", "data": { - "manifest_tags": ["organic", "sauces"], + "manifest_tags": [ + "organic", + "sauces" + ], "manifest_category": "food/condiments" } }, @@ -52,7 +58,9 @@ { "description": "Select products by tags", "data": { - "manifest_tags": ["holiday"] + "manifest_tags": [ + "holiday" + ] } } ] diff --git a/dist/schemas/2.6.0/core/property-id.json b/dist/schemas/2.6.0/core/property-id.json index c06fd5a395..35df52aa47 100644 --- a/dist/schemas/2.6.0/core/property-id.json +++ b/dist/schemas/2.6.0/core/property-id.json @@ -5,5 +5,10 @@ "description": "Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.", "type": "string", "pattern": "^[a-z0-9_]+$", - "examples": ["cnn_ctv_app", "homepage", "mobile_ios", "instagram"] + "examples": [ + "cnn_ctv_app", + "homepage", + "mobile_ios", + "instagram" + ] } diff --git a/dist/schemas/2.6.0/core/property-tag.json b/dist/schemas/2.6.0/core/property-tag.json index c7907f6d2c..7acf065931 100644 --- a/dist/schemas/2.6.0/core/property-tag.json +++ b/dist/schemas/2.6.0/core/property-tag.json @@ -5,5 +5,12 @@ "description": "Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.", "type": "string", "pattern": "^[a-z0-9_]+$", - "examples": ["ctv", "premium", "news", "sports", "meta_network", "social_media"] + "examples": [ + "ctv", + "premium", + "news", + "sports", + "meta_network", + "social_media" + ] } diff --git a/dist/schemas/2.6.0/core/property.json b/dist/schemas/2.6.0/core/property.json index e7333f5a21..9c617af973 100644 --- a/dist/schemas/2.6.0/core/property.json +++ b/dist/schemas/2.6.0/core/property.json @@ -32,8 +32,11 @@ "description": "The identifier value. For domain type: 'example.com' matches base domain plus www and m subdomains; 'edition.example.com' matches that specific subdomain; '*.example.com' matches ALL subdomains but NOT base domain" } }, - "required": ["type", "value"], - "additionalProperties": false + "required": [ + "type", + "value" + ], + "additionalProperties": true }, "minItems": 1 }, @@ -50,6 +53,10 @@ "description": "Domain where adagents.json should be checked for authorization validation. Required for list_authorized_properties response. Optional in adagents.json (file location implies domain)." } }, - "required": ["property_type", "name", "identifiers"], - "additionalProperties": false -} \ No newline at end of file + "required": [ + "property_type", + "name", + "identifiers" + ], + "additionalProperties": true +} diff --git a/dist/schemas/2.6.0/core/protocol-envelope.json b/dist/schemas/2.6.0/core/protocol-envelope.json index 32d1304ed7..a82988a643 100644 --- a/dist/schemas/2.6.0/core/protocol-envelope.json +++ b/dist/schemas/2.6.0/core/protocol-envelope.json @@ -40,7 +40,7 @@ "status", "payload" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Synchronous task response with immediate results", @@ -57,7 +57,7 @@ "description": "Premium connected TV inventory across California", "pricing": { "model": "cpm", - "amount": 45.00, + "amount": 45.0, "currency": "USD" } } @@ -76,7 +76,9 @@ "push_notification_config": { "url": "https://buyer.example.com/webhooks/adcp", "authentication": { - "schemes": ["HMAC-SHA256"], + "schemes": [ + "HMAC-SHA256" + ], "credentials": "shared_secret_exchanged_during_onboarding_min_32_chars" } }, diff --git a/dist/schemas/2.6.0/core/publisher-property-selector.json b/dist/schemas/2.6.0/core/publisher-property-selector.json index 8a9158fac3..6a7c1ea562 100644 --- a/dist/schemas/2.6.0/core/publisher-property-selector.json +++ b/dist/schemas/2.6.0/core/publisher-property-selector.json @@ -22,8 +22,11 @@ "description": "Discriminator indicating all properties from this publisher are included" } }, - "required": ["publisher_domain", "selection_type"], - "additionalProperties": false + "required": [ + "publisher_domain", + "selection_type" + ], + "additionalProperties": true }, { "type": "object", @@ -48,8 +51,12 @@ "minItems": 1 } }, - "required": ["publisher_domain", "selection_type", "property_ids"], - "additionalProperties": false + "required": [ + "publisher_domain", + "selection_type", + "property_ids" + ], + "additionalProperties": true }, { "type": "object", @@ -74,8 +81,12 @@ "minItems": 1 } }, - "required": ["publisher_domain", "selection_type", "property_tags"], - "additionalProperties": false + "required": [ + "publisher_domain", + "selection_type", + "property_tags" + ], + "additionalProperties": true } ] } diff --git a/dist/schemas/2.6.0/core/push-notification-config.json b/dist/schemas/2.6.0/core/push-notification-config.json index 4e3d29b53c..421bce6237 100644 --- a/dist/schemas/2.6.0/core/push-notification-config.json +++ b/dist/schemas/2.6.0/core/push-notification-config.json @@ -34,9 +34,15 @@ "minLength": 32 } }, - "required": ["schemes", "credentials"], + "required": [ + "schemes", + "credentials" + ], "additionalProperties": false } }, - "required": ["url", "authentication"] + "required": [ + "url", + "authentication" + ] } diff --git a/dist/schemas/2.6.0/core/reporting-capabilities.json b/dist/schemas/2.6.0/core/reporting-capabilities.json index 87f4afb7fb..09e7a3a3a5 100644 --- a/dist/schemas/2.6.0/core/reporting-capabilities.json +++ b/dist/schemas/2.6.0/core/reporting-capabilities.json @@ -18,12 +18,21 @@ "type": "integer", "description": "Expected delay in minutes before reporting data becomes available (e.g., 240 for 4-hour delay)", "minimum": 0, - "examples": [240, 300, 1440] + "examples": [ + 240, + 300, + 1440 + ] }, "timezone": { "type": "string", "description": "Timezone for reporting periods. Use 'UTC' or IANA timezone (e.g., 'America/New_York'). Critical for daily/monthly frequency alignment.", - "examples": ["UTC", "America/New_York", "Europe/London", "America/Los_Angeles"] + "examples": [ + "UTC", + "America/New_York", + "Europe/London", + "America/Los_Angeles" + ] }, "supports_webhooks": { "type": "boolean", @@ -37,11 +46,26 @@ }, "uniqueItems": true, "examples": [ - ["impressions", "spend", "clicks", "video_completions"], - ["impressions", "spend", "conversions"] + [ + "impressions", + "spend", + "clicks", + "video_completions" + ], + [ + "impressions", + "spend", + "conversions" + ] ] } }, - "required": ["available_reporting_frequencies", "expected_delay_minutes", "timezone", "supports_webhooks", "available_metrics"], - "additionalProperties": false -} \ No newline at end of file + "required": [ + "available_reporting_frequencies", + "expected_delay_minutes", + "timezone", + "supports_webhooks", + "available_metrics" + ], + "additionalProperties": true +} diff --git a/dist/schemas/2.6.0/core/response.json b/dist/schemas/2.6.0/core/response.json index 86658df008..793d99e789 100644 --- a/dist/schemas/2.6.0/core/response.json +++ b/dist/schemas/2.6.0/core/response.json @@ -17,6 +17,8 @@ "description": "AdCP task-specific response data (see individual task response schemas)" } }, - "required": ["message"], - "additionalProperties": false -} \ No newline at end of file + "required": [ + "message" + ], + "additionalProperties": true +} diff --git a/dist/schemas/2.6.0/core/signal-filters.json b/dist/schemas/2.6.0/core/signal-filters.json index 00afa4c4e3..928eff4007 100644 --- a/dist/schemas/2.6.0/core/signal-filters.json +++ b/dist/schemas/2.6.0/core/signal-filters.json @@ -31,5 +31,5 @@ "maximum": 100 } }, - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/core/sub-asset.json b/dist/schemas/2.6.0/core/sub-asset.json index 276062d9b6..3b15939420 100644 --- a/dist/schemas/2.6.0/core/sub-asset.json +++ b/dist/schemas/2.6.0/core/sub-asset.json @@ -26,8 +26,13 @@ "description": "URL for media assets (images, videos, etc.)" } }, - "required": ["asset_kind", "asset_type", "asset_id", "content_uri"], - "additionalProperties": false + "required": [ + "asset_kind", + "asset_type", + "asset_id", + "content_uri" + ], + "additionalProperties": true }, { "type": "object", @@ -62,8 +67,13 @@ "description": "Text content for text-based assets like headlines, body text, CTA text, etc." } }, - "required": ["asset_kind", "asset_type", "asset_id", "content"], - "additionalProperties": false + "required": [ + "asset_kind", + "asset_type", + "asset_id", + "content" + ], + "additionalProperties": true } ] } diff --git a/dist/schemas/2.6.0/core/targeting.json b/dist/schemas/2.6.0/core/targeting.json index de1bf199dd..38e029bbad 100644 --- a/dist/schemas/2.6.0/core/targeting.json +++ b/dist/schemas/2.6.0/core/targeting.json @@ -46,5 +46,5 @@ "$ref": "/schemas/2.6.0/core/frequency-cap.json" } }, - "additionalProperties": false -} \ No newline at end of file + "additionalProperties": true +} diff --git a/dist/schemas/2.6.0/core/tasks-get-request.json b/dist/schemas/2.6.0/core/tasks-get-request.json index 5a8e754ae8..63a3dd2293 100644 --- a/dist/schemas/2.6.0/core/tasks-get-request.json +++ b/dist/schemas/2.6.0/core/tasks-get-request.json @@ -24,7 +24,7 @@ "required": [ "task_id" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Check task status", diff --git a/dist/schemas/2.6.0/core/tasks-get-response.json b/dist/schemas/2.6.0/core/tasks-get-response.json index 012f831e68..589628a570 100644 --- a/dist/schemas/2.6.0/core/tasks-get-response.json +++ b/dist/schemas/2.6.0/core/tasks-get-response.json @@ -69,7 +69,7 @@ "description": "Current step number" } }, - "additionalProperties": false + "additionalProperties": true }, "error": { "type": "object", @@ -112,7 +112,7 @@ "code", "message" ], - "additionalProperties": false + "additionalProperties": true }, "history": { "type": "array", @@ -144,7 +144,7 @@ "type", "data" ], - "additionalProperties": false + "additionalProperties": true } }, "context": { @@ -162,5 +162,5 @@ "created_at", "updated_at" ], - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/core/tasks-list-request.json b/dist/schemas/2.6.0/core/tasks-list-request.json index e2d8c5e60b..d073fb7d67 100644 --- a/dist/schemas/2.6.0/core/tasks-list-request.json +++ b/dist/schemas/2.6.0/core/tasks-list-request.json @@ -79,7 +79,7 @@ "description": "Filter tasks that have webhook configuration when true" } }, - "additionalProperties": false + "additionalProperties": true }, "sort": { "type": "object", @@ -103,7 +103,7 @@ "description": "Sort direction" } }, - "additionalProperties": false + "additionalProperties": true }, "pagination": { "type": "object", @@ -123,7 +123,7 @@ "description": "Number of tasks to skip" } }, - "additionalProperties": false + "additionalProperties": true }, "include_history": { "type": "boolean", @@ -137,7 +137,7 @@ "$ref": "/schemas/2.6.0/core/ext.json" } }, - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "List all pending tasks across domains", diff --git a/dist/schemas/2.6.0/core/tasks-list-response.json b/dist/schemas/2.6.0/core/tasks-list-response.json index 8d6770433a..b739a0e8c2 100644 --- a/dist/schemas/2.6.0/core/tasks-list-response.json +++ b/dist/schemas/2.6.0/core/tasks-list-response.json @@ -34,7 +34,7 @@ "description": "Number of signals tasks in results" } }, - "additionalProperties": false + "additionalProperties": true }, "status_breakdown": { "type": "object", @@ -70,14 +70,14 @@ "field", "direction" ], - "additionalProperties": false + "additionalProperties": true } }, "required": [ "total_matching", "returned" ], - "additionalProperties": false + "additionalProperties": true }, "tasks": { "type": "array", @@ -133,7 +133,7 @@ "created_at", "updated_at" ], - "additionalProperties": false + "additionalProperties": true } }, "pagination": { @@ -165,7 +165,7 @@ "offset", "has_more" ], - "additionalProperties": false + "additionalProperties": true }, "context": { "$ref": "/schemas/2.6.0/core/context.json" @@ -179,5 +179,5 @@ "tasks", "pagination" ], - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/creative/asset-types/index.json b/dist/schemas/2.6.0/creative/asset-types/index.json index d92d15944e..14a3531866 100644 --- a/dist/schemas/2.6.0/creative/asset-types/index.json +++ b/dist/schemas/2.6.0/creative/asset-types/index.json @@ -4,7 +4,7 @@ "title": "AdCP Asset Type Registry", "description": "Registry of asset types used in AdCP creative manifests. Each asset type defines the structure of actual content payloads (what you send), not requirements or constraints (which belong in format specifications).", "version": "1.0.0", - "lastUpdated": "2026-01-05", + "lastUpdated": "2026-01-07", "asset_types": { "image": { "description": "Static image asset (JPG, PNG, GIF, WebP, SVG)", @@ -99,5 +99,8 @@ "example_flow": "Format says 'hero_image' must be type 'image' with width 1200, height 627. Manifest provides hero_image: {url: '...', width: 1200, height: 627}. The format spec tells us it's an image type." }, "adcp_version": "2.6.0", - "baseUrl": "/schemas/2.6.0" + "baseUrl": "/schemas/2.6.0", + "versioning": { + "note": "AdCP uses build-time versioning. This directory contains schemas for AdCP 2.6.0. Full semantic versions are available at /schemas/{version}/ (e.g., /schemas/2.5.0/). Major version aliases point to the latest release: /schemas/v2/ → /schemas/2.6.0/." + } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/creative/list-creative-formats-request.json b/dist/schemas/2.6.0/creative/list-creative-formats-request.json index b59e05cf96..f6f55c48d8 100644 --- a/dist/schemas/2.6.0/creative/list-creative-formats-request.json +++ b/dist/schemas/2.6.0/creative/list-creative-formats-request.json @@ -69,5 +69,5 @@ "$ref": "/schemas/2.6.0/core/ext.json" } }, - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/creative/list-creative-formats-response.json b/dist/schemas/2.6.0/creative/list-creative-formats-response.json index 9d87b39bd3..628944be54 100644 --- a/dist/schemas/2.6.0/creative/list-creative-formats-response.json +++ b/dist/schemas/2.6.0/creative/list-creative-formats-response.json @@ -57,5 +57,5 @@ "required": [ "formats" ], - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/creative/preview-creative-request.json b/dist/schemas/2.6.0/creative/preview-creative-request.json index de8fa1dccb..803cbf16b2 100644 --- a/dist/schemas/2.6.0/creative/preview-creative-request.json +++ b/dist/schemas/2.6.0/creative/preview-creative-request.json @@ -33,7 +33,7 @@ }, "macros": { "type": "object", - "description": "Macro values to use for this preview. Supports all universal macros from the format's supported_macros list. See docs/media-buy/creatives/universal-macros.md for available macros.", + "description": "Macro values to use for this preview. Supports all universal macros from the format's supported_macros list. See docs/creative/universal-macros.md for available macros.", "additionalProperties": { "type": "string" } @@ -46,7 +46,7 @@ "required": [ "name" ], - "additionalProperties": false + "additionalProperties": true } }, "template_id": { @@ -70,7 +70,7 @@ "format_id", "creative_manifest" ], - "additionalProperties": false + "additionalProperties": true }, { "type": "object", @@ -120,7 +120,7 @@ "required": [ "name" ], - "additionalProperties": false + "additionalProperties": true } }, "template_id": { @@ -137,7 +137,7 @@ "format_id", "creative_manifest" ], - "additionalProperties": false + "additionalProperties": true }, "minItems": 1, "maxItems": 50 @@ -158,7 +158,7 @@ "request_type", "requests" ], - "additionalProperties": false + "additionalProperties": true } ] } diff --git a/dist/schemas/2.6.0/creative/preview-creative-response.json b/dist/schemas/2.6.0/creative/preview-creative-response.json index 9a24e445fe..4591e3a3f9 100644 --- a/dist/schemas/2.6.0/creative/preview-creative-response.json +++ b/dist/schemas/2.6.0/creative/preview-creative-response.json @@ -87,7 +87,7 @@ "previews", "expires_at" ], - "additionalProperties": false + "additionalProperties": true }, { "title": "PreviewCreativeBatchResponse", @@ -239,7 +239,7 @@ "response_type", "results" ], - "additionalProperties": false + "additionalProperties": true } ] } diff --git a/dist/schemas/2.6.0/creative/preview-render.json b/dist/schemas/2.6.0/creative/preview-render.json index 0fc66d72eb..dae9e43edd 100644 --- a/dist/schemas/2.6.0/creative/preview-render.json +++ b/dist/schemas/2.6.0/creative/preview-render.json @@ -39,7 +39,10 @@ "minimum": 0 } }, - "required": ["width", "height"] + "required": [ + "width", + "height" + ] }, "embedding": { "type": "object", @@ -64,8 +67,13 @@ } } }, - "required": ["render_id", "output_format", "preview_url", "role"], - "additionalProperties": false + "required": [ + "render_id", + "output_format", + "preview_url", + "role" + ], + "additionalProperties": true }, { "type": "object", @@ -101,7 +109,10 @@ "minimum": 0 } }, - "required": ["width", "height"] + "required": [ + "width", + "height" + ] }, "embedding": { "type": "object", @@ -126,8 +137,13 @@ } } }, - "required": ["render_id", "output_format", "preview_html", "role"], - "additionalProperties": false + "required": [ + "render_id", + "output_format", + "preview_html", + "role" + ], + "additionalProperties": true }, { "type": "object", @@ -168,7 +184,10 @@ "minimum": 0 } }, - "required": ["width", "height"] + "required": [ + "width", + "height" + ] }, "embedding": { "type": "object", @@ -193,8 +212,14 @@ } } }, - "required": ["render_id", "output_format", "preview_url", "preview_html", "role"], - "additionalProperties": false + "required": [ + "render_id", + "output_format", + "preview_url", + "preview_html", + "role" + ], + "additionalProperties": true } ] } diff --git a/dist/schemas/2.6.0/enums/adcp-domain.json b/dist/schemas/2.6.0/enums/adcp-domain.json index 515fed77f1..2d48f76e84 100644 --- a/dist/schemas/2.6.0/enums/adcp-domain.json +++ b/dist/schemas/2.6.0/enums/adcp-domain.json @@ -4,5 +4,8 @@ "title": "AdCP Domain", "description": "AdCP protocol domains for task categorization", "type": "string", - "enum": ["media-buy", "signals"] + "enum": [ + "media-buy", + "signals" + ] } diff --git a/dist/schemas/2.6.0/enums/auth-scheme.json b/dist/schemas/2.6.0/enums/auth-scheme.json index 9e53c32112..71feb29afa 100644 --- a/dist/schemas/2.6.0/enums/auth-scheme.json +++ b/dist/schemas/2.6.0/enums/auth-scheme.json @@ -4,5 +4,8 @@ "title": "Authentication Scheme", "description": "Authentication schemes for push notification endpoints", "type": "string", - "enum": ["Bearer", "HMAC-SHA256"] + "enum": [ + "Bearer", + "HMAC-SHA256" + ] } diff --git a/dist/schemas/2.6.0/enums/co-branding-requirement.json b/dist/schemas/2.6.0/enums/co-branding-requirement.json index 5375e6093c..7d363a36fc 100644 --- a/dist/schemas/2.6.0/enums/co-branding-requirement.json +++ b/dist/schemas/2.6.0/enums/co-branding-requirement.json @@ -4,5 +4,9 @@ "title": "Co-Branding Requirement", "description": "Co-branding policy for creatives (retailer/publisher brand inclusion)", "type": "string", - "enum": ["required", "optional", "none"] + "enum": [ + "required", + "optional", + "none" + ] } diff --git a/dist/schemas/2.6.0/enums/creative-action.json b/dist/schemas/2.6.0/enums/creative-action.json index afa0dbb510..a1a74c0953 100644 --- a/dist/schemas/2.6.0/enums/creative-action.json +++ b/dist/schemas/2.6.0/enums/creative-action.json @@ -4,5 +4,11 @@ "title": "Creative Action", "description": "Action taken on a creative during sync operation", "type": "string", - "enum": ["created", "updated", "unchanged", "failed", "deleted"] + "enum": [ + "created", + "updated", + "unchanged", + "failed", + "deleted" + ] } diff --git a/dist/schemas/2.6.0/enums/creative-agent-capability.json b/dist/schemas/2.6.0/enums/creative-agent-capability.json index ad9c08555c..d7a47923e6 100644 --- a/dist/schemas/2.6.0/enums/creative-agent-capability.json +++ b/dist/schemas/2.6.0/enums/creative-agent-capability.json @@ -4,5 +4,10 @@ "title": "Creative Agent Capability", "description": "Capabilities supported by creative agents for format handling", "type": "string", - "enum": ["validation", "assembly", "generation", "preview"] + "enum": [ + "validation", + "assembly", + "generation", + "preview" + ] } diff --git a/dist/schemas/2.6.0/enums/creative-status.json b/dist/schemas/2.6.0/enums/creative-status.json index a2010e240e..acaead85ec 100644 --- a/dist/schemas/2.6.0/enums/creative-status.json +++ b/dist/schemas/2.6.0/enums/creative-status.json @@ -4,11 +4,16 @@ "title": "Creative Status", "description": "Status of a creative asset", "type": "string", - "enum": ["processing", "approved", "rejected", "pending_review"], + "enum": [ + "processing", + "approved", + "rejected", + "pending_review" + ], "enumDescriptions": { "processing": "Creative is being processed or transcoded", "approved": "Creative has been approved and is ready for delivery", "rejected": "Creative has been rejected due to policy or technical issues", "pending_review": "Creative is under review" } -} \ No newline at end of file +} diff --git a/dist/schemas/2.6.0/enums/daast-version.json b/dist/schemas/2.6.0/enums/daast-version.json index 4be2d3589a..c2229db626 100644 --- a/dist/schemas/2.6.0/enums/daast-version.json +++ b/dist/schemas/2.6.0/enums/daast-version.json @@ -4,5 +4,8 @@ "title": "DAAST Version", "description": "Supported DAAST (Digital Audio Ad Serving Template) specification versions", "type": "string", - "enum": ["1.0", "1.1"] + "enum": [ + "1.0", + "1.1" + ] } diff --git a/dist/schemas/2.6.0/enums/delivery-type.json b/dist/schemas/2.6.0/enums/delivery-type.json index 1ae5167f22..30c0d2d1f8 100644 --- a/dist/schemas/2.6.0/enums/delivery-type.json +++ b/dist/schemas/2.6.0/enums/delivery-type.json @@ -4,9 +4,12 @@ "title": "Delivery Type", "description": "Type of inventory delivery", "type": "string", - "enum": ["guaranteed", "non_guaranteed"], + "enum": [ + "guaranteed", + "non_guaranteed" + ], "enumDescriptions": { "guaranteed": "Reserved inventory with guaranteed delivery", "non_guaranteed": "Auction-based inventory without delivery guarantees" } -} \ No newline at end of file +} diff --git a/dist/schemas/2.6.0/enums/dimension-unit.json b/dist/schemas/2.6.0/enums/dimension-unit.json index ee64a64f0b..fb914e344a 100644 --- a/dist/schemas/2.6.0/enums/dimension-unit.json +++ b/dist/schemas/2.6.0/enums/dimension-unit.json @@ -4,5 +4,10 @@ "title": "Dimension Unit", "description": "Units of measurement for creative format dimensions", "type": "string", - "enum": ["px", "dp", "inches", "cm"] + "enum": [ + "px", + "dp", + "inches", + "cm" + ] } diff --git a/dist/schemas/2.6.0/enums/feed-format.json b/dist/schemas/2.6.0/enums/feed-format.json index 9b2af3263d..7e1d2f4551 100644 --- a/dist/schemas/2.6.0/enums/feed-format.json +++ b/dist/schemas/2.6.0/enums/feed-format.json @@ -4,5 +4,9 @@ "title": "Feed Format", "description": "Product catalog feed format types", "type": "string", - "enum": ["google_merchant_center", "facebook_catalog", "custom"] + "enum": [ + "google_merchant_center", + "facebook_catalog", + "custom" + ] } diff --git a/dist/schemas/2.6.0/enums/format-id-parameter.json b/dist/schemas/2.6.0/enums/format-id-parameter.json index 85652e0562..724eb1a3f3 100644 --- a/dist/schemas/2.6.0/enums/format-id-parameter.json +++ b/dist/schemas/2.6.0/enums/format-id-parameter.json @@ -4,5 +4,8 @@ "title": "Format ID Parameter", "description": "Types of parameters that template formats accept in format_id objects to create parameterized format identifiers", "type": "string", - "enum": ["dimensions", "duration"] + "enum": [ + "dimensions", + "duration" + ] } diff --git a/dist/schemas/2.6.0/enums/frequency-cap-scope.json b/dist/schemas/2.6.0/enums/frequency-cap-scope.json index 149d37f822..739461d547 100644 --- a/dist/schemas/2.6.0/enums/frequency-cap-scope.json +++ b/dist/schemas/2.6.0/enums/frequency-cap-scope.json @@ -4,8 +4,10 @@ "title": "Frequency Cap Scope", "description": "Scope for frequency cap application", "type": "string", - "enum": ["package"], + "enum": [ + "package" + ], "enumDescriptions": { "package": "Apply frequency cap at the package level" } -} \ No newline at end of file +} diff --git a/dist/schemas/2.6.0/enums/history-entry-type.json b/dist/schemas/2.6.0/enums/history-entry-type.json index bfb82dea88..e1fdc6f054 100644 --- a/dist/schemas/2.6.0/enums/history-entry-type.json +++ b/dist/schemas/2.6.0/enums/history-entry-type.json @@ -4,5 +4,8 @@ "title": "History Entry Type", "description": "Type of entry in task execution history", "type": "string", - "enum": ["request", "response"] + "enum": [ + "request", + "response" + ] } diff --git a/dist/schemas/2.6.0/enums/http-method.json b/dist/schemas/2.6.0/enums/http-method.json index 290117fbaf..caeccbae98 100644 --- a/dist/schemas/2.6.0/enums/http-method.json +++ b/dist/schemas/2.6.0/enums/http-method.json @@ -4,5 +4,8 @@ "title": "HTTP Method", "description": "HTTP methods supported for webhook requests", "type": "string", - "enum": ["GET", "POST"] + "enum": [ + "GET", + "POST" + ] } diff --git a/dist/schemas/2.6.0/enums/identifier-types.json b/dist/schemas/2.6.0/enums/identifier-types.json index e744a2534f..c7f65f3ecd 100644 --- a/dist/schemas/2.6.0/enums/identifier-types.json +++ b/dist/schemas/2.6.0/enums/identifier-types.json @@ -6,7 +6,7 @@ "type": "string", "enum": [ "domain", - "subdomain", + "subdomain", "network_id", "ios_bundle", "android_package", @@ -27,8 +27,8 @@ ], "examples": [ "domain", - "ios_bundle", + "ios_bundle", "venue_id", "apple_podcast_id" ] -} \ No newline at end of file +} diff --git a/dist/schemas/2.6.0/enums/javascript-module-type.json b/dist/schemas/2.6.0/enums/javascript-module-type.json index a0790e6856..d53b8cf59c 100644 --- a/dist/schemas/2.6.0/enums/javascript-module-type.json +++ b/dist/schemas/2.6.0/enums/javascript-module-type.json @@ -4,5 +4,9 @@ "title": "JavaScript Module Type", "description": "JavaScript module format types for creative assets", "type": "string", - "enum": ["esm", "commonjs", "script"] + "enum": [ + "esm", + "commonjs", + "script" + ] } diff --git a/dist/schemas/2.6.0/enums/landing-page-requirement.json b/dist/schemas/2.6.0/enums/landing-page-requirement.json index b80e591900..84bb74b9f4 100644 --- a/dist/schemas/2.6.0/enums/landing-page-requirement.json +++ b/dist/schemas/2.6.0/enums/landing-page-requirement.json @@ -4,5 +4,9 @@ "title": "Landing Page Requirement", "description": "Landing page policy for creative click-through destinations", "type": "string", - "enum": ["any", "retailer_site_only", "must_include_retailer"] + "enum": [ + "any", + "retailer_site_only", + "must_include_retailer" + ] } diff --git a/dist/schemas/2.6.0/enums/markdown-flavor.json b/dist/schemas/2.6.0/enums/markdown-flavor.json index 6195171dd6..1d9a1fa265 100644 --- a/dist/schemas/2.6.0/enums/markdown-flavor.json +++ b/dist/schemas/2.6.0/enums/markdown-flavor.json @@ -4,5 +4,8 @@ "title": "Markdown Flavor", "description": "Markdown specification flavors supported for text assets", "type": "string", - "enum": ["commonmark", "gfm"] + "enum": [ + "commonmark", + "gfm" + ] } diff --git a/dist/schemas/2.6.0/enums/media-buy-status.json b/dist/schemas/2.6.0/enums/media-buy-status.json index 0c8ff74923..3b09ad8a94 100644 --- a/dist/schemas/2.6.0/enums/media-buy-status.json +++ b/dist/schemas/2.6.0/enums/media-buy-status.json @@ -4,11 +4,16 @@ "title": "Media Buy Status", "description": "Status of a media buy", "type": "string", - "enum": ["pending_activation", "active", "paused", "completed"], + "enum": [ + "pending_activation", + "active", + "paused", + "completed" + ], "enumDescriptions": { "pending_activation": "Media buy created but not yet activated", "active": "Media buy is currently running", "paused": "Media buy is temporarily paused", "completed": "Media buy has finished running" } -} \ No newline at end of file +} diff --git a/dist/schemas/2.6.0/enums/notification-type.json b/dist/schemas/2.6.0/enums/notification-type.json index 3ebec716b8..466418fd35 100644 --- a/dist/schemas/2.6.0/enums/notification-type.json +++ b/dist/schemas/2.6.0/enums/notification-type.json @@ -4,5 +4,10 @@ "title": "Notification Type", "description": "Type of delivery notification for media buy reporting", "type": "string", - "enum": ["scheduled", "final", "delayed", "adjusted"] + "enum": [ + "scheduled", + "final", + "delayed", + "adjusted" + ] } diff --git a/dist/schemas/2.6.0/enums/pacing.json b/dist/schemas/2.6.0/enums/pacing.json index 86e8c6a0a5..7028b5a850 100644 --- a/dist/schemas/2.6.0/enums/pacing.json +++ b/dist/schemas/2.6.0/enums/pacing.json @@ -4,10 +4,14 @@ "title": "Pacing", "description": "Budget pacing strategy", "type": "string", - "enum": ["even", "asap", "front_loaded"], + "enum": [ + "even", + "asap", + "front_loaded" + ], "enumDescriptions": { "even": "Allocate remaining budget evenly over remaining campaign duration (default)", "asap": "Spend remaining budget as quickly as possible", "front_loaded": "Allocate more remaining budget earlier in the remaining campaign period" } -} \ No newline at end of file +} diff --git a/dist/schemas/2.6.0/enums/preview-output-format.json b/dist/schemas/2.6.0/enums/preview-output-format.json index 6d090e0fcf..896ef82afa 100644 --- a/dist/schemas/2.6.0/enums/preview-output-format.json +++ b/dist/schemas/2.6.0/enums/preview-output-format.json @@ -4,5 +4,8 @@ "title": "Preview Output Format", "description": "Output format for creative previews", "type": "string", - "enum": ["url", "html"] + "enum": [ + "url", + "html" + ] } diff --git a/dist/schemas/2.6.0/enums/reporting-frequency.json b/dist/schemas/2.6.0/enums/reporting-frequency.json index 6b1b5b80b4..d55f2eeb41 100644 --- a/dist/schemas/2.6.0/enums/reporting-frequency.json +++ b/dist/schemas/2.6.0/enums/reporting-frequency.json @@ -4,5 +4,9 @@ "title": "Reporting Frequency", "description": "Available frequencies for delivery reports and metrics updates", "type": "string", - "enum": ["hourly", "daily", "monthly"] + "enum": [ + "hourly", + "daily", + "monthly" + ] } diff --git a/dist/schemas/2.6.0/enums/signal-catalog-type.json b/dist/schemas/2.6.0/enums/signal-catalog-type.json index cb30a52d5b..bce70e89a9 100644 --- a/dist/schemas/2.6.0/enums/signal-catalog-type.json +++ b/dist/schemas/2.6.0/enums/signal-catalog-type.json @@ -4,5 +4,9 @@ "title": "Signal Catalog Type", "description": "Types of signal catalogs available for audience targeting", "type": "string", - "enum": ["marketplace", "custom", "owned"] + "enum": [ + "marketplace", + "custom", + "owned" + ] } diff --git a/dist/schemas/2.6.0/enums/sort-direction.json b/dist/schemas/2.6.0/enums/sort-direction.json index c88e53fe20..884b5c4f50 100644 --- a/dist/schemas/2.6.0/enums/sort-direction.json +++ b/dist/schemas/2.6.0/enums/sort-direction.json @@ -4,5 +4,8 @@ "title": "Sort Direction", "description": "Sort direction for list queries", "type": "string", - "enum": ["asc", "desc"] + "enum": [ + "asc", + "desc" + ] } diff --git a/dist/schemas/2.6.0/enums/standard-format-ids.json b/dist/schemas/2.6.0/enums/standard-format-ids.json index 8ca9047467..0b59c055d9 100644 --- a/dist/schemas/2.6.0/enums/standard-format-ids.json +++ b/dist/schemas/2.6.0/enums/standard-format-ids.json @@ -100,4 +100,4 @@ "dynamic_creative_formats": [ "display_dynamic_300x250" ] -} \ No newline at end of file +} diff --git a/dist/schemas/2.6.0/enums/task-status.json b/dist/schemas/2.6.0/enums/task-status.json index 7095a972c7..d2c5c138c3 100644 --- a/dist/schemas/2.6.0/enums/task-status.json +++ b/dist/schemas/2.6.0/enums/task-status.json @@ -6,12 +6,12 @@ "type": "string", "enum": [ "submitted", - "working", + "working", "input-required", "completed", "canceled", "failed", - "rejected", + "rejected", "auth-required", "unknown" ], @@ -26,4 +26,4 @@ "auth-required": "Task requires authentication to proceed", "unknown": "Task is in an unknown or indeterminate state" } -} \ No newline at end of file +} diff --git a/dist/schemas/2.6.0/enums/update-frequency.json b/dist/schemas/2.6.0/enums/update-frequency.json index 6a14b8124e..82bd2f967c 100644 --- a/dist/schemas/2.6.0/enums/update-frequency.json +++ b/dist/schemas/2.6.0/enums/update-frequency.json @@ -4,5 +4,10 @@ "title": "Update Frequency", "description": "Frequency of product catalog updates", "type": "string", - "enum": ["realtime", "hourly", "daily", "weekly"] + "enum": [ + "realtime", + "hourly", + "daily", + "weekly" + ] } diff --git a/dist/schemas/2.6.0/enums/url-asset-type.json b/dist/schemas/2.6.0/enums/url-asset-type.json index 5866c5a627..7169df42cd 100644 --- a/dist/schemas/2.6.0/enums/url-asset-type.json +++ b/dist/schemas/2.6.0/enums/url-asset-type.json @@ -4,5 +4,9 @@ "title": "URL Asset Type", "description": "Types of URL assets for tracking and click-through purposes", "type": "string", - "enum": ["clickthrough", "tracker_pixel", "tracker_script"] + "enum": [ + "clickthrough", + "tracker_pixel", + "tracker_script" + ] } diff --git a/dist/schemas/2.6.0/enums/validation-mode.json b/dist/schemas/2.6.0/enums/validation-mode.json index ad22bb60a4..c27c0cce57 100644 --- a/dist/schemas/2.6.0/enums/validation-mode.json +++ b/dist/schemas/2.6.0/enums/validation-mode.json @@ -4,5 +4,8 @@ "title": "Validation Mode", "description": "Creative validation strictness levels", "type": "string", - "enum": ["strict", "lenient"] + "enum": [ + "strict", + "lenient" + ] } diff --git a/dist/schemas/2.6.0/enums/vast-version.json b/dist/schemas/2.6.0/enums/vast-version.json index ecc5a669c1..fe3c48b8c7 100644 --- a/dist/schemas/2.6.0/enums/vast-version.json +++ b/dist/schemas/2.6.0/enums/vast-version.json @@ -4,5 +4,11 @@ "title": "VAST Version", "description": "Supported VAST (Video Ad Serving Template) specification versions", "type": "string", - "enum": ["2.0", "3.0", "4.0", "4.1", "4.2"] + "enum": [ + "2.0", + "3.0", + "4.0", + "4.1", + "4.2" + ] } diff --git a/dist/schemas/2.6.0/enums/webhook-response-type.json b/dist/schemas/2.6.0/enums/webhook-response-type.json index ae75b3696f..e0423fbb6b 100644 --- a/dist/schemas/2.6.0/enums/webhook-response-type.json +++ b/dist/schemas/2.6.0/enums/webhook-response-type.json @@ -4,5 +4,10 @@ "title": "Webhook Response Type", "description": "Expected response content type from webhook endpoints", "type": "string", - "enum": ["html", "json", "xml", "javascript"] + "enum": [ + "html", + "json", + "xml", + "javascript" + ] } diff --git a/dist/schemas/2.6.0/enums/webhook-security-method.json b/dist/schemas/2.6.0/enums/webhook-security-method.json index ea5df0e0d3..61f23eb2a4 100644 --- a/dist/schemas/2.6.0/enums/webhook-security-method.json +++ b/dist/schemas/2.6.0/enums/webhook-security-method.json @@ -4,5 +4,9 @@ "title": "Webhook Security Method", "description": "Security methods for authenticating webhook requests", "type": "string", - "enum": ["hmac_sha256", "api_key", "none"] + "enum": [ + "hmac_sha256", + "api_key", + "none" + ] } diff --git a/dist/schemas/2.6.0/index.json b/dist/schemas/2.6.0/index.json index 169089e4a9..ecb888befd 100644 --- a/dist/schemas/2.6.0/index.json +++ b/dist/schemas/2.6.0/index.json @@ -7,7 +7,7 @@ "adcp_version": "2.6.0", "standard_formats_version": "2.0.0", "versioning": { - "note": "AdCP uses path-based versioning. The schema URL path (/schemas/) indicates the version. Individual request/response schemas do NOT include adcp_version fields. Compatibility follows semantic versioning rules." + "note": "AdCP uses build-time versioning. This directory contains schemas for AdCP 2.6.0. Full semantic versions are available at /schemas/{version}/ (e.g., /schemas/2.5.0/). Major version aliases point to the latest release: /schemas/v2/ → /schemas/2.6.0/." }, "changelog": { "2.0.0": { @@ -31,7 +31,7 @@ ] } }, - "lastUpdated": "2026-01-05", + "lastUpdated": "2026-01-07", "baseUrl": "/schemas/2.6.0", "schemas": { "core": { diff --git a/dist/schemas/2.6.0/media-buy/build-creative-request.json b/dist/schemas/2.6.0/media-buy/build-creative-request.json index 27abfef9b0..a14b3c7a4d 100644 --- a/dist/schemas/2.6.0/media-buy/build-creative-request.json +++ b/dist/schemas/2.6.0/media-buy/build-creative-request.json @@ -27,5 +27,5 @@ "required": [ "target_format_id" ], - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/media-buy/build-creative-response.json b/dist/schemas/2.6.0/media-buy/build-creative-response.json index 8457ce4843..7cf9bdf53f 100644 --- a/dist/schemas/2.6.0/media-buy/build-creative-response.json +++ b/dist/schemas/2.6.0/media-buy/build-creative-response.json @@ -24,7 +24,7 @@ "required": [ "creative_manifest" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "required": [ "errors" @@ -54,7 +54,7 @@ "required": [ "errors" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "required": [ "creative_manifest" diff --git a/dist/schemas/2.6.0/media-buy/create-media-buy-async-response-input-required.json b/dist/schemas/2.6.0/media-buy/create-media-buy-async-response-input-required.json index 5b9127414a..da9db3b3f6 100644 --- a/dist/schemas/2.6.0/media-buy/create-media-buy-async-response-input-required.json +++ b/dist/schemas/2.6.0/media-buy/create-media-buy-async-response-input-required.json @@ -27,5 +27,5 @@ "$ref": "/schemas/2.6.0/core/ext.json" } }, - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/media-buy/create-media-buy-async-response-submitted.json b/dist/schemas/2.6.0/media-buy/create-media-buy-async-response-submitted.json index 92eed288e4..c69678834d 100644 --- a/dist/schemas/2.6.0/media-buy/create-media-buy-async-response-submitted.json +++ b/dist/schemas/2.6.0/media-buy/create-media-buy-async-response-submitted.json @@ -12,6 +12,5 @@ "$ref": "/schemas/2.6.0/core/ext.json" } }, - "additionalProperties": false + "additionalProperties": true } - diff --git a/dist/schemas/2.6.0/media-buy/create-media-buy-async-response-working.json b/dist/schemas/2.6.0/media-buy/create-media-buy-async-response-working.json index 2b332a7162..32c6ad1692 100644 --- a/dist/schemas/2.6.0/media-buy/create-media-buy-async-response-working.json +++ b/dist/schemas/2.6.0/media-buy/create-media-buy-async-response-working.json @@ -32,6 +32,5 @@ "$ref": "/schemas/2.6.0/core/ext.json" } }, - "additionalProperties": false + "additionalProperties": true } - diff --git a/dist/schemas/2.6.0/media-buy/create-media-buy-request.json b/dist/schemas/2.6.0/media-buy/create-media-buy-request.json index d7b023d5b3..38ea017ddd 100644 --- a/dist/schemas/2.6.0/media-buy/create-media-buy-request.json +++ b/dist/schemas/2.6.0/media-buy/create-media-buy-request.json @@ -66,7 +66,10 @@ "minLength": 32 } }, - "required": ["schemes", "credentials"], + "required": [ + "schemes", + "credentials" + ], "additionalProperties": false }, "reporting_frequency": { @@ -98,8 +101,12 @@ "uniqueItems": true } }, - "required": ["url", "authentication", "reporting_frequency"], - "additionalProperties": false + "required": [ + "url", + "authentication", + "reporting_frequency" + ], + "additionalProperties": true }, "context": { "$ref": "/schemas/2.6.0/core/context.json" @@ -115,5 +122,5 @@ "start_time", "end_time" ], - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/media-buy/create-media-buy-response.json b/dist/schemas/2.6.0/media-buy/create-media-buy-response.json index 3e799bf5c0..75bc8a63dc 100644 --- a/dist/schemas/2.6.0/media-buy/create-media-buy-response.json +++ b/dist/schemas/2.6.0/media-buy/create-media-buy-response.json @@ -42,7 +42,7 @@ "buyer_ref", "packages" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "required": [ "errors" @@ -72,7 +72,7 @@ "required": [ "errors" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "anyOf": [ { diff --git a/dist/schemas/2.6.0/media-buy/get-media-buy-delivery-request.json b/dist/schemas/2.6.0/media-buy/get-media-buy-delivery-request.json index d5d489ab15..74e834e676 100644 --- a/dist/schemas/2.6.0/media-buy/get-media-buy-delivery-request.json +++ b/dist/schemas/2.6.0/media-buy/get-media-buy-delivery-request.json @@ -50,5 +50,5 @@ "$ref": "/schemas/2.6.0/core/ext.json" } }, - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/media-buy/get-media-buy-delivery-response.json b/dist/schemas/2.6.0/media-buy/get-media-buy-delivery-response.json index 3c9f11e0ff..df29e40738 100644 --- a/dist/schemas/2.6.0/media-buy/get-media-buy-delivery-response.json +++ b/dist/schemas/2.6.0/media-buy/get-media-buy-delivery-response.json @@ -53,7 +53,7 @@ "start", "end" ], - "additionalProperties": false + "additionalProperties": true }, "currency": { "type": "string", @@ -95,7 +95,7 @@ "spend", "media_buy_count" ], - "additionalProperties": false + "additionalProperties": true }, "media_buy_deliveries": { "type": "array", @@ -249,7 +249,7 @@ "impressions", "spend" ], - "additionalProperties": false + "additionalProperties": true } } }, @@ -259,7 +259,7 @@ "totals", "by_package" ], - "additionalProperties": false + "additionalProperties": true } }, "errors": { @@ -281,5 +281,5 @@ "currency", "media_buy_deliveries" ], - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/media-buy/get-products-async-response-input-required.json b/dist/schemas/2.6.0/media-buy/get-products-async-response-input-required.json index 3b00b220ce..ff47e7cf88 100644 --- a/dist/schemas/2.6.0/media-buy/get-products-async-response-input-required.json +++ b/dist/schemas/2.6.0/media-buy/get-products-async-response-input-required.json @@ -34,6 +34,5 @@ "$ref": "/schemas/2.6.0/core/ext.json" } }, - "additionalProperties": false + "additionalProperties": true } - diff --git a/dist/schemas/2.6.0/media-buy/get-products-async-response-submitted.json b/dist/schemas/2.6.0/media-buy/get-products-async-response-submitted.json index ab5b818ab3..34d610a6c5 100644 --- a/dist/schemas/2.6.0/media-buy/get-products-async-response-submitted.json +++ b/dist/schemas/2.6.0/media-buy/get-products-async-response-submitted.json @@ -17,6 +17,5 @@ "$ref": "/schemas/2.6.0/core/ext.json" } }, - "additionalProperties": false + "additionalProperties": true } - diff --git a/dist/schemas/2.6.0/media-buy/get-products-async-response-working.json b/dist/schemas/2.6.0/media-buy/get-products-async-response-working.json index 8379996650..4adf970054 100644 --- a/dist/schemas/2.6.0/media-buy/get-products-async-response-working.json +++ b/dist/schemas/2.6.0/media-buy/get-products-async-response-working.json @@ -30,6 +30,5 @@ "$ref": "/schemas/2.6.0/core/ext.json" } }, - "additionalProperties": false + "additionalProperties": true } - diff --git a/dist/schemas/2.6.0/media-buy/get-products-request.json b/dist/schemas/2.6.0/media-buy/get-products-request.json index 05409ea608..b44dfabdbe 100644 --- a/dist/schemas/2.6.0/media-buy/get-products-request.json +++ b/dist/schemas/2.6.0/media-buy/get-products-request.json @@ -24,5 +24,5 @@ } }, "required": [], - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/media-buy/get-products-response.json b/dist/schemas/2.6.0/media-buy/get-products-response.json index ad86771aab..7212c9b4db 100644 --- a/dist/schemas/2.6.0/media-buy/get-products-response.json +++ b/dist/schemas/2.6.0/media-buy/get-products-response.json @@ -29,5 +29,5 @@ "required": [ "products" ], - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/media-buy/list-authorized-properties-request.json b/dist/schemas/2.6.0/media-buy/list-authorized-properties-request.json index 12527ac715..ced98a32bf 100644 --- a/dist/schemas/2.6.0/media-buy/list-authorized-properties-request.json +++ b/dist/schemas/2.6.0/media-buy/list-authorized-properties-request.json @@ -22,5 +22,5 @@ "$ref": "/schemas/2.6.0/core/ext.json" } }, - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/media-buy/list-authorized-properties-response.json b/dist/schemas/2.6.0/media-buy/list-authorized-properties-response.json index 755197622c..1c1101324a 100644 --- a/dist/schemas/2.6.0/media-buy/list-authorized-properties-response.json +++ b/dist/schemas/2.6.0/media-buy/list-authorized-properties-response.json @@ -66,5 +66,5 @@ "required": [ "publisher_domains" ], - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/media-buy/list-creative-formats-request.json b/dist/schemas/2.6.0/media-buy/list-creative-formats-request.json index 74a3e77143..c0a7c4b7b3 100644 --- a/dist/schemas/2.6.0/media-buy/list-creative-formats-request.json +++ b/dist/schemas/2.6.0/media-buy/list-creative-formats-request.json @@ -54,5 +54,5 @@ "$ref": "/schemas/2.6.0/core/ext.json" } }, - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/media-buy/list-creative-formats-response.json b/dist/schemas/2.6.0/media-buy/list-creative-formats-response.json index d80d679c19..30641c06b2 100644 --- a/dist/schemas/2.6.0/media-buy/list-creative-formats-response.json +++ b/dist/schemas/2.6.0/media-buy/list-creative-formats-response.json @@ -57,5 +57,5 @@ "required": [ "formats" ], - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/media-buy/list-creatives-request.json b/dist/schemas/2.6.0/media-buy/list-creatives-request.json index 17e0bd5674..b96c9fa9a1 100644 --- a/dist/schemas/2.6.0/media-buy/list-creatives-request.json +++ b/dist/schemas/2.6.0/media-buy/list-creatives-request.json @@ -23,7 +23,7 @@ "description": "Sort direction" } }, - "additionalProperties": false + "additionalProperties": true }, "pagination": { "type": "object", @@ -43,7 +43,7 @@ "description": "Number of creatives to skip" } }, - "additionalProperties": false + "additionalProperties": true }, "include_assignments": { "type": "boolean", @@ -86,7 +86,7 @@ "$ref": "/schemas/2.6.0/core/ext.json" } }, - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "List all approved video creatives", diff --git a/dist/schemas/2.6.0/media-buy/list-creatives-response.json b/dist/schemas/2.6.0/media-buy/list-creatives-response.json index 600796b6a9..99a9a73837 100644 --- a/dist/schemas/2.6.0/media-buy/list-creatives-response.json +++ b/dist/schemas/2.6.0/media-buy/list-creatives-response.json @@ -43,7 +43,7 @@ "total_matching", "returned" ], - "additionalProperties": false + "additionalProperties": true }, "pagination": { "type": "object", @@ -79,7 +79,7 @@ "offset", "has_more" ], - "additionalProperties": false + "additionalProperties": true }, "creatives": { "type": "array", @@ -206,14 +206,14 @@ "assigned_date", "status" ], - "additionalProperties": false + "additionalProperties": true } } }, "required": [ "assignment_count" ], - "additionalProperties": false + "additionalProperties": true }, "performance": { "type": "object", @@ -256,7 +256,7 @@ "required": [ "last_updated" ], - "additionalProperties": false + "additionalProperties": true }, "sub_assets": { "type": "array", @@ -274,7 +274,7 @@ "created_date", "updated_date" ], - "additionalProperties": false + "additionalProperties": true } }, "format_summary": { @@ -287,7 +287,7 @@ "minimum": 0 } }, - "additionalProperties": false + "additionalProperties": true }, "status_summary": { "type": "object", @@ -314,7 +314,7 @@ "minimum": 0 } }, - "additionalProperties": false + "additionalProperties": true }, "context": { "$ref": "/schemas/2.6.0/core/context.json" @@ -328,7 +328,7 @@ "pagination", "creatives" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Successful library query with results", diff --git a/dist/schemas/2.6.0/media-buy/package-request.json b/dist/schemas/2.6.0/media-buy/package-request.json index 2f424ddcf6..785ccc02f9 100644 --- a/dist/schemas/2.6.0/media-buy/package-request.json +++ b/dist/schemas/2.6.0/media-buy/package-request.json @@ -66,5 +66,5 @@ "budget", "pricing_option_id" ], - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/media-buy/provide-performance-feedback-request.json b/dist/schemas/2.6.0/media-buy/provide-performance-feedback-request.json index 93410c3068..a40f25c9f7 100644 --- a/dist/schemas/2.6.0/media-buy/provide-performance-feedback-request.json +++ b/dist/schemas/2.6.0/media-buy/provide-performance-feedback-request.json @@ -34,7 +34,7 @@ "start", "end" ], - "additionalProperties": false + "additionalProperties": true }, "performance_index": { "type": "number", @@ -84,5 +84,5 @@ ] } ], - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/media-buy/provide-performance-feedback-response.json b/dist/schemas/2.6.0/media-buy/provide-performance-feedback-response.json index fb1a1c3210..13db97f1bb 100644 --- a/dist/schemas/2.6.0/media-buy/provide-performance-feedback-response.json +++ b/dist/schemas/2.6.0/media-buy/provide-performance-feedback-response.json @@ -25,7 +25,7 @@ "required": [ "success" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "required": [ "errors" @@ -55,7 +55,7 @@ "required": [ "errors" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "required": [ "success" diff --git a/dist/schemas/2.6.0/media-buy/sync-creatives-async-response-input-required.json b/dist/schemas/2.6.0/media-buy/sync-creatives-async-response-input-required.json index 0e9078156d..27afb16e59 100644 --- a/dist/schemas/2.6.0/media-buy/sync-creatives-async-response-input-required.json +++ b/dist/schemas/2.6.0/media-buy/sync-creatives-async-response-input-required.json @@ -21,5 +21,5 @@ "$ref": "/schemas/2.6.0/core/ext.json" } }, - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/media-buy/sync-creatives-async-response-submitted.json b/dist/schemas/2.6.0/media-buy/sync-creatives-async-response-submitted.json index dd8d802b4b..c891b92780 100644 --- a/dist/schemas/2.6.0/media-buy/sync-creatives-async-response-submitted.json +++ b/dist/schemas/2.6.0/media-buy/sync-creatives-async-response-submitted.json @@ -12,6 +12,5 @@ "$ref": "/schemas/2.6.0/core/ext.json" } }, - "additionalProperties": false + "additionalProperties": true } - diff --git a/dist/schemas/2.6.0/media-buy/sync-creatives-async-response-working.json b/dist/schemas/2.6.0/media-buy/sync-creatives-async-response-working.json index 64cb94257f..0ff7c3cc2e 100644 --- a/dist/schemas/2.6.0/media-buy/sync-creatives-async-response-working.json +++ b/dist/schemas/2.6.0/media-buy/sync-creatives-async-response-working.json @@ -42,6 +42,5 @@ "$ref": "/schemas/2.6.0/core/ext.json" } }, - "additionalProperties": false + "additionalProperties": true } - diff --git a/dist/schemas/2.6.0/media-buy/sync-creatives-request.json b/dist/schemas/2.6.0/media-buy/sync-creatives-request.json index e9d95a40e8..94cbff9d03 100644 --- a/dist/schemas/2.6.0/media-buy/sync-creatives-request.json +++ b/dist/schemas/2.6.0/media-buy/sync-creatives-request.json @@ -33,7 +33,7 @@ } } }, - "additionalProperties": false + "additionalProperties": true }, "delete_missing": { "type": "boolean", @@ -64,7 +64,7 @@ "required": [ "creatives" ], - "additionalProperties": false, + "additionalProperties": true, "examples": [ { "description": "Full sync with hosted video creative", @@ -135,7 +135,10 @@ { "description": "Scoped update using creative_ids filter", "data": { - "creative_ids": ["hero_video_30s", "banner_300x250"], + "creative_ids": [ + "hero_video_30s", + "banner_300x250" + ], "creatives": [ { "creative_id": "hero_video_30s", diff --git a/dist/schemas/2.6.0/media-buy/sync-creatives-response.json b/dist/schemas/2.6.0/media-buy/sync-creatives-response.json index d9ba729f07..8e8c894bba 100644 --- a/dist/schemas/2.6.0/media-buy/sync-creatives-response.json +++ b/dist/schemas/2.6.0/media-buy/sync-creatives-response.json @@ -79,14 +79,14 @@ "description": "Error message for this package assignment" } }, - "additionalProperties": false + "additionalProperties": true } }, "required": [ "creative_id", "action" ], - "additionalProperties": false + "additionalProperties": true } }, "context": { @@ -99,7 +99,7 @@ "required": [ "creatives" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "required": [ "errors" @@ -129,7 +129,7 @@ "required": [ "errors" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "anyOf": [ { diff --git a/dist/schemas/2.6.0/media-buy/update-media-buy-async-response-input-required.json b/dist/schemas/2.6.0/media-buy/update-media-buy-async-response-input-required.json index f9dfa75e12..a46decd07b 100644 --- a/dist/schemas/2.6.0/media-buy/update-media-buy-async-response-input-required.json +++ b/dist/schemas/2.6.0/media-buy/update-media-buy-async-response-input-required.json @@ -20,6 +20,5 @@ "$ref": "/schemas/2.6.0/core/ext.json" } }, - "additionalProperties": false + "additionalProperties": true } - diff --git a/dist/schemas/2.6.0/media-buy/update-media-buy-async-response-submitted.json b/dist/schemas/2.6.0/media-buy/update-media-buy-async-response-submitted.json index 15903c4f05..d76d16ec57 100644 --- a/dist/schemas/2.6.0/media-buy/update-media-buy-async-response-submitted.json +++ b/dist/schemas/2.6.0/media-buy/update-media-buy-async-response-submitted.json @@ -12,6 +12,5 @@ "$ref": "/schemas/2.6.0/core/ext.json" } }, - "additionalProperties": false + "additionalProperties": true } - diff --git a/dist/schemas/2.6.0/media-buy/update-media-buy-async-response-working.json b/dist/schemas/2.6.0/media-buy/update-media-buy-async-response-working.json index e0d39646df..1779120a8b 100644 --- a/dist/schemas/2.6.0/media-buy/update-media-buy-async-response-working.json +++ b/dist/schemas/2.6.0/media-buy/update-media-buy-async-response-working.json @@ -32,6 +32,5 @@ "$ref": "/schemas/2.6.0/core/ext.json" } }, - "additionalProperties": false + "additionalProperties": true } - diff --git a/dist/schemas/2.6.0/media-buy/update-media-buy-request.json b/dist/schemas/2.6.0/media-buy/update-media-buy-request.json index 7bb4495a5d..40033c5589 100644 --- a/dist/schemas/2.6.0/media-buy/update-media-buy-request.json +++ b/dist/schemas/2.6.0/media-buy/update-media-buy-request.json @@ -94,7 +94,7 @@ ] } ], - "additionalProperties": false + "additionalProperties": true } }, "push_notification_config": { @@ -120,5 +120,5 @@ ] } ], - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/media-buy/update-media-buy-response.json b/dist/schemas/2.6.0/media-buy/update-media-buy-response.json index 595f958ecc..8f2338be07 100644 --- a/dist/schemas/2.6.0/media-buy/update-media-buy-response.json +++ b/dist/schemas/2.6.0/media-buy/update-media-buy-response.json @@ -44,7 +44,7 @@ "media_buy_id", "buyer_ref" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "required": [ "errors" @@ -74,7 +74,7 @@ "required": [ "errors" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "anyOf": [ { diff --git a/dist/schemas/2.6.0/pricing-options/cpc-option.json b/dist/schemas/2.6.0/pricing-options/cpc-option.json index 69f9d8c11e..109b04024b 100644 --- a/dist/schemas/2.6.0/pricing-options/cpc-option.json +++ b/dist/schemas/2.6.0/pricing-options/cpc-option.json @@ -23,7 +23,12 @@ "type": "string", "description": "ISO 4217 currency code", "pattern": "^[A-Z]{3}$", - "examples": ["USD", "EUR", "GBP", "JPY"] + "examples": [ + "USD", + "EUR", + "GBP", + "JPY" + ] }, "is_fixed": { "type": "boolean", @@ -36,6 +41,12 @@ "minimum": 0 } }, - "required": ["pricing_option_id", "pricing_model", "rate", "currency", "is_fixed"], - "additionalProperties": false + "required": [ + "pricing_option_id", + "pricing_model", + "rate", + "currency", + "is_fixed" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/pricing-options/cpcv-option.json b/dist/schemas/2.6.0/pricing-options/cpcv-option.json index 96d2dd1dcd..811866ec5c 100644 --- a/dist/schemas/2.6.0/pricing-options/cpcv-option.json +++ b/dist/schemas/2.6.0/pricing-options/cpcv-option.json @@ -23,7 +23,12 @@ "type": "string", "description": "ISO 4217 currency code", "pattern": "^[A-Z]{3}$", - "examples": ["USD", "EUR", "GBP", "JPY"] + "examples": [ + "USD", + "EUR", + "GBP", + "JPY" + ] }, "is_fixed": { "type": "boolean", @@ -36,6 +41,12 @@ "minimum": 0 } }, - "required": ["pricing_option_id", "pricing_model", "rate", "currency", "is_fixed"], - "additionalProperties": false + "required": [ + "pricing_option_id", + "pricing_model", + "rate", + "currency", + "is_fixed" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/pricing-options/cpm-auction-option.json b/dist/schemas/2.6.0/pricing-options/cpm-auction-option.json index f3c85b2cee..6ebefa0196 100644 --- a/dist/schemas/2.6.0/pricing-options/cpm-auction-option.json +++ b/dist/schemas/2.6.0/pricing-options/cpm-auction-option.json @@ -18,7 +18,12 @@ "type": "string", "description": "ISO 4217 currency code", "pattern": "^[A-Z]{3}$", - "examples": ["USD", "EUR", "GBP", "JPY"] + "examples": [ + "USD", + "EUR", + "GBP", + "JPY" + ] }, "is_fixed": { "type": "boolean", @@ -55,7 +60,9 @@ "minimum": 0 } }, - "required": ["floor"] + "required": [ + "floor" + ] }, "min_spend_per_package": { "type": "number", @@ -63,6 +70,12 @@ "minimum": 0 } }, - "required": ["pricing_option_id", "pricing_model", "price_guidance", "currency", "is_fixed"], - "additionalProperties": false + "required": [ + "pricing_option_id", + "pricing_model", + "price_guidance", + "currency", + "is_fixed" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/pricing-options/cpm-fixed-option.json b/dist/schemas/2.6.0/pricing-options/cpm-fixed-option.json index 161ccada20..f5b6befc2b 100644 --- a/dist/schemas/2.6.0/pricing-options/cpm-fixed-option.json +++ b/dist/schemas/2.6.0/pricing-options/cpm-fixed-option.json @@ -23,7 +23,12 @@ "type": "string", "description": "ISO 4217 currency code", "pattern": "^[A-Z]{3}$", - "examples": ["USD", "EUR", "GBP", "JPY"] + "examples": [ + "USD", + "EUR", + "GBP", + "JPY" + ] }, "is_fixed": { "type": "boolean", @@ -36,6 +41,12 @@ "minimum": 0 } }, - "required": ["pricing_option_id", "pricing_model", "rate", "currency", "is_fixed"], - "additionalProperties": false + "required": [ + "pricing_option_id", + "pricing_model", + "rate", + "currency", + "is_fixed" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/pricing-options/cpp-option.json b/dist/schemas/2.6.0/pricing-options/cpp-option.json index b6ddc5a516..223f37fb54 100644 --- a/dist/schemas/2.6.0/pricing-options/cpp-option.json +++ b/dist/schemas/2.6.0/pricing-options/cpp-option.json @@ -23,7 +23,12 @@ "type": "string", "description": "ISO 4217 currency code", "pattern": "^[A-Z]{3}$", - "examples": ["USD", "EUR", "GBP", "JPY"] + "examples": [ + "USD", + "EUR", + "GBP", + "JPY" + ] }, "is_fixed": { "type": "boolean", @@ -45,8 +50,10 @@ "minimum": 0 } }, - "required": ["demographic"], - "additionalProperties": false + "required": [ + "demographic" + ], + "additionalProperties": true }, "min_spend_per_package": { "type": "number", @@ -54,6 +61,13 @@ "minimum": 0 } }, - "required": ["pricing_option_id", "pricing_model", "rate", "currency", "is_fixed", "parameters"], - "additionalProperties": false + "required": [ + "pricing_option_id", + "pricing_model", + "rate", + "currency", + "is_fixed", + "parameters" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/pricing-options/cpv-option.json b/dist/schemas/2.6.0/pricing-options/cpv-option.json index a8d9c715bb..3d37c7e0f5 100644 --- a/dist/schemas/2.6.0/pricing-options/cpv-option.json +++ b/dist/schemas/2.6.0/pricing-options/cpv-option.json @@ -23,7 +23,12 @@ "type": "string", "description": "ISO 4217 currency code", "pattern": "^[A-Z]{3}$", - "examples": ["USD", "EUR", "GBP", "JPY"] + "examples": [ + "USD", + "EUR", + "GBP", + "JPY" + ] }, "is_fixed": { "type": "boolean", @@ -52,14 +57,18 @@ "minimum": 1 } }, - "required": ["duration_seconds"], - "additionalProperties": false + "required": [ + "duration_seconds" + ], + "additionalProperties": true } ] } }, - "required": ["view_threshold"], - "additionalProperties": false + "required": [ + "view_threshold" + ], + "additionalProperties": true }, "min_spend_per_package": { "type": "number", @@ -67,6 +76,13 @@ "minimum": 0 } }, - "required": ["pricing_option_id", "pricing_model", "rate", "currency", "is_fixed", "parameters"], - "additionalProperties": false + "required": [ + "pricing_option_id", + "pricing_model", + "rate", + "currency", + "is_fixed", + "parameters" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/pricing-options/flat-rate-option.json b/dist/schemas/2.6.0/pricing-options/flat-rate-option.json index 9cffaa843a..12d145d72f 100644 --- a/dist/schemas/2.6.0/pricing-options/flat-rate-option.json +++ b/dist/schemas/2.6.0/pricing-options/flat-rate-option.json @@ -23,7 +23,12 @@ "type": "string", "description": "ISO 4217 currency code", "pattern": "^[A-Z]{3}$", - "examples": ["USD", "EUR", "GBP", "JPY"] + "examples": [ + "USD", + "EUR", + "GBP", + "JPY" + ] }, "is_fixed": { "type": "boolean", @@ -69,7 +74,7 @@ "description": "Specific daypart for time-based pricing (e.g., 'morning_commute', 'evening_prime', 'overnight')" } }, - "additionalProperties": false + "additionalProperties": true }, "min_spend_per_package": { "type": "number", @@ -77,6 +82,12 @@ "minimum": 0 } }, - "required": ["pricing_option_id", "pricing_model", "currency", "is_fixed", "rate"], - "additionalProperties": false + "required": [ + "pricing_option_id", + "pricing_model", + "currency", + "is_fixed", + "rate" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/pricing-options/vcpm-auction-option.json b/dist/schemas/2.6.0/pricing-options/vcpm-auction-option.json index 859cf9e8e5..324d73652a 100644 --- a/dist/schemas/2.6.0/pricing-options/vcpm-auction-option.json +++ b/dist/schemas/2.6.0/pricing-options/vcpm-auction-option.json @@ -18,7 +18,12 @@ "type": "string", "description": "ISO 4217 currency code", "pattern": "^[A-Z]{3}$", - "examples": ["USD", "EUR", "GBP", "JPY"] + "examples": [ + "USD", + "EUR", + "GBP", + "JPY" + ] }, "is_fixed": { "type": "boolean", @@ -55,7 +60,9 @@ "minimum": 0 } }, - "required": ["floor"] + "required": [ + "floor" + ] }, "min_spend_per_package": { "type": "number", @@ -63,6 +70,12 @@ "minimum": 0 } }, - "required": ["pricing_option_id", "pricing_model", "currency", "price_guidance", "is_fixed"], - "additionalProperties": false + "required": [ + "pricing_option_id", + "pricing_model", + "currency", + "price_guidance", + "is_fixed" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/pricing-options/vcpm-fixed-option.json b/dist/schemas/2.6.0/pricing-options/vcpm-fixed-option.json index 5b554bd475..2905d94dd3 100644 --- a/dist/schemas/2.6.0/pricing-options/vcpm-fixed-option.json +++ b/dist/schemas/2.6.0/pricing-options/vcpm-fixed-option.json @@ -23,7 +23,12 @@ "type": "string", "description": "ISO 4217 currency code", "pattern": "^[A-Z]{3}$", - "examples": ["USD", "EUR", "GBP", "JPY"] + "examples": [ + "USD", + "EUR", + "GBP", + "JPY" + ] }, "is_fixed": { "type": "boolean", @@ -36,6 +41,12 @@ "minimum": 0 } }, - "required": ["pricing_option_id", "pricing_model", "rate", "currency", "is_fixed"], - "additionalProperties": false + "required": [ + "pricing_option_id", + "pricing_model", + "rate", + "currency", + "is_fixed" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/protocols/adcp-extension.json b/dist/schemas/2.6.0/protocols/adcp-extension.json index 6dc0abc5ea..3d31caf104 100644 --- a/dist/schemas/2.6.0/protocols/adcp-extension.json +++ b/dist/schemas/2.6.0/protocols/adcp-extension.json @@ -14,13 +14,20 @@ "type": "array", "items": { "type": "string", - "enum": ["media_buy", "creative", "signals"] + "enum": [ + "media_buy", + "creative", + "signals" + ] }, "minItems": 1, "uniqueItems": true, "description": "AdCP protocol domains supported by this agent. At least one must be specified." } }, - "required": ["adcp_version", "protocols_supported"], - "additionalProperties": false + "required": [ + "adcp_version", + "protocols_supported" + ], + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/signals/activate-signal-request.json b/dist/schemas/2.6.0/signals/activate-signal-request.json index 6f6684fdd8..b262f187a8 100644 --- a/dist/schemas/2.6.0/signals/activate-signal-request.json +++ b/dist/schemas/2.6.0/signals/activate-signal-request.json @@ -28,5 +28,5 @@ "signal_agent_segment_id", "deployments" ], - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/signals/activate-signal-response.json b/dist/schemas/2.6.0/signals/activate-signal-response.json index 8d09d78783..06b773eb9d 100644 --- a/dist/schemas/2.6.0/signals/activate-signal-response.json +++ b/dist/schemas/2.6.0/signals/activate-signal-response.json @@ -27,7 +27,7 @@ "required": [ "deployments" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "required": [ "errors" @@ -57,7 +57,7 @@ "required": [ "errors" ], - "additionalProperties": false, + "additionalProperties": true, "not": { "required": [ "deployments" diff --git a/dist/schemas/2.6.0/signals/get-signals-request.json b/dist/schemas/2.6.0/signals/get-signals-request.json index 638bfa8c5f..bcb4597f02 100644 --- a/dist/schemas/2.6.0/signals/get-signals-request.json +++ b/dist/schemas/2.6.0/signals/get-signals-request.json @@ -34,7 +34,7 @@ "deployments", "countries" ], - "additionalProperties": false + "additionalProperties": true }, "filters": { "$ref": "/schemas/2.6.0/core/signal-filters.json" @@ -55,5 +55,5 @@ "signal_spec", "deliver_to" ], - "additionalProperties": false + "additionalProperties": true } diff --git a/dist/schemas/2.6.0/signals/get-signals-response.json b/dist/schemas/2.6.0/signals/get-signals-response.json index 657ca19f3a..2751dd8455 100644 --- a/dist/schemas/2.6.0/signals/get-signals-response.json +++ b/dist/schemas/2.6.0/signals/get-signals-response.json @@ -63,7 +63,7 @@ "cpm", "currency" ], - "additionalProperties": false + "additionalProperties": true } }, "required": [ @@ -76,7 +76,7 @@ "deployments", "pricing" ], - "additionalProperties": false + "additionalProperties": true } }, "errors": { @@ -96,5 +96,5 @@ "required": [ "signals" ], - "additionalProperties": false + "additionalProperties": true } diff --git a/static/schemas/source/index.json b/static/schemas/source/index.json index 2e89bca9ce..6ea9e42a77 100644 --- a/static/schemas/source/index.json +++ b/static/schemas/source/index.json @@ -4,7 +4,7 @@ "title": "AdCP Schema Registry v1", "version": "1.0.0", "description": "Registry of all AdCP JSON schemas for validation and discovery", -"adcp_version": "2.6.0", + "adcp_version": "2.6.0", "standard_formats_version": "2.0.0", "versioning": { "note": "AdCP uses path-based versioning. The schema URL path (/schemas/) indicates the version. Individual request/response schemas do NOT include adcp_version fields. Compatibility follows semantic versioning rules." From c94fe34c03463370f01dcd61ade2713a8414cf07 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Thu, 8 Jan 2026 18:44:21 -0800 Subject: [PATCH 15/49] feat: Add typed extensions infrastructure with auto-discovery (#648) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Add typed extensions infrastructure with auto-discovery Typed extensions provide formal JSON schemas for vendor-agnostic extension data in `ext.{namespace}` fields. This enables SDK code generation, schema validation, and cross-vendor interoperability. **New features:** - Extension meta schema defining valid_from/valid_until version bounds - Auto-discovery of extensions during schema build - Version-filtered extension registries per schema version - New `extensions_supported` field in agent card adcp extension - Comprehensive test suite for extension infrastructure **How it works:** 1. Extensions are defined in `/schemas/source/extensions/{namespace}.json` 2. Build script auto-discovers extensions and filters by version compatibility 3. Each versioned build includes only compatible extensions in registry 4. Agents declare supported extensions via `extensions_supported` array This is a MINOR release adding new optional capabilities without breaking existing functionality. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * docs: Fix A2A extension format and add MCP server card support Updates documentation to be compliant with both protocol specs: **A2A Agent Cards:** - Changed from `extensions.adcp` object to proper A2A `extensions[]` array format - Each extension now has uri, description, required, and params fields - Extension URI: https://adcontextprotocol.org/extensions/adcp **MCP Server Cards:** - Added support for declaring AdCP via `_meta['adcontextprotocol.org']` - Supports both `/.well-known/mcp.json` and `/.well-known/server.json` - Uses reverse DNS namespacing per MCP server.json spec **Extension Params Schema:** - Updated description to clarify usage in both protocols - Same params structure works for both A2A and MCP 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix: Remove external $schema reference from MCP server card examples The JSON schema validation test was failing because it tried to validate against https://registry.modelcontextprotocol.io/server.schema.json which is an external schema we don't have locally. Removed the $schema field from MCP server card examples since they reference an external MCP registry schema, not an AdCP schema. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * feat: Add reserved namespaces and deprecation policy for extensions Addresses PR review feedback: **Reserved Namespaces (@BaiyuScope3)** - Added RESERVED_NAMESPACES list: adcp, core, protocol, schema, meta, ext, context - Build script now validates and rejects reserved namespaces - Documented in extensions reference **Deprecation Policy (@BaiyuScope3)** - Added "Extension Deprecation Policy" section with: - When extensions may be deprecated (superseded, promoted, low adoption, incompatible) - Standard deprecation process (valid_until + 1 minor, CHANGELOG, migration guide) - Emergency deprecation for security issues **Tests** - Added test verifying reserved namespaces are not used 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Claude Opus 4.5 --- docs/protocols/a2a-guide.mdx | 39 +- docs/protocols/mcp-guide.mdx | 52 +- docs/reference/extensions-and-context.mdx | 207 ++++++++ package.json | 3 +- scripts/build-schemas.cjs | 252 ++++++++++ .../source/extensions/extension-meta.json | 59 +++ static/schemas/source/extensions/index.json | 9 + static/schemas/source/index.json | 14 +- .../source/protocols/adcp-extension.json | 16 +- tests/extension-schemas.test.cjs | 445 ++++++++++++++++++ 10 files changed, 1074 insertions(+), 22 deletions(-) create mode 100644 static/schemas/source/extensions/extension-meta.json create mode 100644 static/schemas/source/extensions/index.json create mode 100644 tests/extension-schemas.test.cjs diff --git a/docs/protocols/a2a-guide.mdx b/docs/protocols/a2a-guide.mdx index 8bfe9b4f54..f66867dc49 100644 --- a/docs/protocols/a2a-guide.mdx +++ b/docs/protocols/a2a-guide.mdx @@ -711,32 +711,51 @@ console.log('Examples:', getProductsSkill.examples); ] } ], - "extensions": { - "adcp": { - "adcp_version": "2.4.0", - "protocols_supported": ["media_buy"] + "extensions": [ + { + "uri": "https://adcontextprotocol.org/extensions/adcp", + "description": "AdCP media buying protocol support", + "required": false, + "params": { + "adcp_version": "2.6.0", + "protocols_supported": ["media_buy"], + "extensions_supported": ["sustainability"] + } } - } + ] } ``` ### AdCP Extension -**Recommended**: Include the `extensions.adcp` field in your agent card to declare AdCP support programmatically. +**Recommended**: Include the AdCP extension in your agent card's `extensions` array to declare AdCP support programmatically. + +The A2A protocol uses an `extensions` array where each extension has: +- **`uri`**: Extension identifier (use `https://adcontextprotocol.org/extensions/adcp`) +- **`description`**: Human-readable description of how you use AdCP +- **`required`**: Whether clients must support this extension (typically `false` for AdCP) +- **`params`**: AdCP-specific configuration (see schema below) ```javascript // Check if agent supports AdCP const agentCard = await fetch('https://sales.example.com/.well-known/agent.json') .then(r => r.json()); -if (agentCard.extensions?.adcp) { - console.log('AdCP Version:', agentCard.extensions.adcp.adcp_version); - console.log('Supported domains:', agentCard.extensions.adcp.protocols_supported); +// Find the AdCP extension in the extensions array +const adcpExt = agentCard.extensions?.find( + ext => ext.uri === 'https://adcontextprotocol.org/extensions/adcp' +); + +if (adcpExt) { + console.log('AdCP Version:', adcpExt.params.adcp_version); + console.log('Supported domains:', adcpExt.params.protocols_supported); // ["media_buy", "creative", "signals"] + console.log('Typed extensions:', adcpExt.params.extensions_supported); + // ["sustainability"] } ``` -**Extension Structure**: See the [AdCP extension schema](https://adcontextprotocol.org/schemas/v2/protocols/adcp-extension.json) for complete specification. +**Extension Params Schema**: See the [AdCP extension schema](https://adcontextprotocol.org/schemas/v2/protocols/adcp-extension.json) for the complete `params` specification. **Benefits**: - Clients can discover AdCP capabilities without making test calls diff --git a/docs/protocols/mcp-guide.mdx b/docs/protocols/mcp-guide.mdx index c3303454dd..a8581b776e 100644 --- a/docs/protocols/mcp-guide.mdx +++ b/docs/protocols/mcp-guide.mdx @@ -632,22 +632,60 @@ const adcpTools = tools.filter(t => t.name.startsWith('adcp_') || ['get_products', 'create_media_buy'].includes(t.name)); ``` -### AdCP Extension (Future) +### AdCP Extension via MCP Server Card -**Status**: MCP server cards are expected in a future MCP release. When available, AdCP servers will include the AdCP extension. +MCP servers can declare AdCP support via a server card at `/.well-known/mcp.json` (or `/.well-known/server.json`). AdCP-specific metadata goes in the `_meta` field using the `adcontextprotocol.org` namespace. ```json { - "extensions": { - "adcp": { - "adcp_version": "2.4.0", - "protocols_supported": ["media_buy", "creative", "signals"] + "name": "io.adcontextprotocol/media-buy-agent", + "version": "1.0.0", + "title": "AdCP Media Buy Agent", + "description": "AI-powered media buying agent implementing AdCP", + "tools": [ + { "name": "get_products" }, + { "name": "create_media_buy" }, + { "name": "list_creative_formats" } + ], + "_meta": { + "adcontextprotocol.org": { + "adcp_version": "2.6.0", + "protocols_supported": ["media_buy"], + "extensions_supported": ["sustainability"] } } } ``` -This will allow clients to programmatically discover which AdCP version and protocol domains an MCP server implements. See the [AdCP extension schema](https://adcontextprotocol.org/schemas/v2/protocols/adcp-extension.json) for specification details. +**Discovering AdCP support:** + +```javascript +// Check both possible locations for MCP server card +const serverCard = await fetch('https://sales.example.com/.well-known/mcp.json') + .then(r => r.ok ? r.json() : null) + .catch(() => null) + || await fetch('https://sales.example.com/.well-known/server.json') + .then(r => r.json()); + +// Check for AdCP metadata +const adcpMeta = serverCard?._meta?.['adcontextprotocol.org']; + +if (adcpMeta) { + console.log('AdCP Version:', adcpMeta.adcp_version); + console.log('Supported domains:', adcpMeta.protocols_supported); + // ["media_buy", "creative", "signals"] + console.log('Typed extensions:', adcpMeta.extensions_supported); + // ["sustainability"] +} +``` + +**Benefits:** +- Clients can discover AdCP capabilities without making test calls +- Declare which protocol domains you implement (media_buy, creative, signals) +- Declare which [typed extensions](/docs/reference/extensions-and-context#typed-extensions) you support +- Enable compatibility checks based on version + +**Note:** The `_meta` field uses reverse DNS namespacing per the [MCP server.json spec](https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/generic-server-json.md). AdCP servers should support both `/.well-known/mcp.json` and `/.well-known/server.json` locations. ### Parameter Validation ```javascript diff --git a/docs/reference/extensions-and-context.mdx b/docs/reference/extensions-and-context.mdx index 48484c0201..e29ec563c3 100644 --- a/docs/reference/extensions-and-context.mdx +++ b/docs/reference/extensions-and-context.mdx @@ -181,6 +181,213 @@ Without namespacing: - Impossible to support multiple platforms simultaneously - Harder to deprecate platform-specific features +## Typed Extensions + +AdCP supports formal JSON schemas for extensions, enabling type safety and validation. Typed extensions are published in the `/schemas/extensions/` directory and can be declared in agent cards. + +### How Typed Extensions Work + +Typed extensions provide a middle ground between untyped vendor extensions and core AdCP fields: + +- **Untyped extensions** (`ext.vendor.*`) - Any JSON, no validation, vendor-specific +- **Typed extensions** (`ext.sustainability.*`) - Formal schema, validated, cross-vendor +- **Core fields** - Part of AdCP specification, required for interoperability + +Typed extensions enable SDK code generation, schema validation, and cross-vendor interoperability while remaining optional. + +### Extension Schema Registry + +The registry of available typed extensions is auto-generated from extension files: +- **Registry**: `/schemas/v1/extensions/index.json` +- **Extension schemas**: `/schemas/v1/extensions/{namespace}.json` +- **Meta schema**: `/schemas/v1/extensions/extension-meta.json` + +Each versioned schema path (`/schemas/v2.5/`, `/schemas/v2.6/`) includes only extensions valid for that version, based on the `valid_from` and `valid_until` fields in each extension. + +### Extension Versioning + +Extensions are versioned independently of the AdCP specification: + +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/extensions/sustainability.json", + "title": "Sustainability Extension", + "description": "Carbon footprint and green certification data", + "valid_from": "2.5", + "valid_until": "4.0", + "docs_url": "https://adcontextprotocol.org/docs/extensions/sustainability", + "type": "object", + "properties": { + "carbon_kg_per_impression": { "type": "number" }, + "certified_green": { "type": "boolean" } + } +} +``` + +- **`valid_from`** (required): Minimum AdCP version this extension works with (e.g., `"2.5"`) +- **`valid_until`** (optional): Last AdCP version this extension works with. Omit if still valid. + +This allows extensions to be backported to earlier AdCP versions and deprecated gracefully. + +### Declaring Extension Support + +Agents declare which typed extensions they support in their discovery metadata. The location depends on the protocol: + +#### A2A Agent Cards + +A2A agents declare AdCP support in the `extensions` array at `/.well-known/agent.json`: + +```json +{ + "name": "AdCP Media Buy Agent", + "skills": [{ "name": "get_products" }, { "name": "create_media_buy" }], + "extensions": [ + { + "uri": "https://adcontextprotocol.org/extensions/adcp", + "description": "AdCP media buying protocol support", + "required": false, + "params": { + "adcp_version": "2.6.0", + "protocols_supported": ["media_buy"], + "extensions_supported": ["sustainability"] + } + } + ] +} +``` + +See the [A2A Guide](/docs/protocols/a2a-guide#adcp-extension) for complete details. + +#### MCP Server Cards + +MCP servers declare AdCP support via the `_meta` field at `/.well-known/mcp.json` or `/.well-known/server.json`: + +```json +{ + "name": "io.adcontextprotocol/media-buy-agent", + "tools": [{ "name": "get_products" }, { "name": "create_media_buy" }], + "_meta": { + "adcontextprotocol.org": { + "adcp_version": "2.6.0", + "protocols_supported": ["media_buy"], + "extensions_supported": ["sustainability"] + } + } +} +``` + +See the [MCP Guide](/docs/protocols/mcp-guide#adcp-extension-via-mcp-server-card) for complete details. + +#### Extension Params Schema + +Both protocols use the same params structure, defined in the [AdCP extension schema](https://adcontextprotocol.org/schemas/v2/protocols/adcp-extension.json): +- **`adcp_version`** (required): Semantic version of AdCP implemented (e.g., "2.6.0") +- **`protocols_supported`** (required): Array of protocol domains (media_buy, creative, signals) +- **`extensions_supported`** (optional): Array of typed extension namespaces + +When an agent declares support for an extension, clients can expect: +- The agent will accept and populate `ext.{namespace}` fields conforming to that extension's schema +- Data in `ext.{namespace}` is strongly typed and can be validated +- SDK code generation can produce type-safe interfaces for the extension + +### Using Typed Extensions + +When an agent supports a typed extension, you can include and expect data in the corresponding namespace: + +```json +{ + "product_id": "premium_ctv", + "name": "Premium CTV Package", + "ext": { + "sustainability": { + "carbon_kg_per_impression": 0.0012, + "certified_green": true + } + } +} +``` + +### Creating a Typed Extension + +To create a typed extension: + +1. **Create the extension file** at `/schemas/extensions/{namespace}.json` +2. **Follow the meta schema** defined in `/schemas/extensions/extension-meta.json` +3. **Set `valid_from`** to the minimum AdCP version your extension requires +4. **Add documentation** via the `docs_url` field + +Example extension file: + +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/extensions/sustainability.json", + "title": "Sustainability Extension", + "description": "Carbon footprint and green certification data for ad products", + "valid_from": "2.5", + "docs_url": "https://adcontextprotocol.org/docs/extensions/sustainability", + "type": "object", + "properties": { + "carbon_kg_per_impression": { + "type": "number", + "description": "Estimated CO2 emissions per impression in kilograms" + }, + "certified_green": { + "type": "boolean", + "description": "Whether the inventory is certified by a green advertising program" + }, + "certification_provider": { + "type": "string", + "description": "Name of the green certification provider" + } + } +} +``` + +The build process auto-discovers extension files and includes them in the appropriate versioned schema builds. + +### Proposing Typed Extensions + +To propose a typed extension for inclusion in AdCP: + +1. **Validate in production first** - Use vendor-namespaced extensions (`ext.yourcompany.*`) to prove value +2. **Document the schema** - Define clear property names, types, and descriptions +3. **Gather adoption evidence** - Show multiple implementations or strong demand +4. **Submit an RFC** - Propose the extension with schema and use cases +5. **If accepted** - Extension is added to `/schemas/extensions/` with appropriate `valid_from` + +### Reserved Namespaces + +The following namespaces are reserved and cannot be used for typed extensions: +- `adcp`, `core`, `protocol`, `schema`, `meta`, `ext`, `context` + +These are reserved to prevent confusion with core AdCP concepts. + +### Extension Deprecation Policy + +#### When Extensions May Be Deprecated + +1. **Superseded** - A better extension exists with a documented migration path +2. **Promoted to core** - The extension becomes a core AdCP field +3. **Low adoption** - Insufficient usage across implementations +4. **Incompatible** - Breaking changes in AdCP require removal + +#### Deprecation Process + +1. Set `valid_until` to current version + 1 minor release (minimum grace period) +2. Announce deprecation in CHANGELOG +3. Publish migration guide if replacement exists +4. Add deprecation warning to extension docs +5. Extension removed from builds after `valid_until` version + +#### Emergency Deprecation + +Security issues may require immediate deprecation: +- `valid_until` set to current version +- Security advisory published +- Patch release issued + ## Proposing Spec Additions If your extension field represents **common ad tech functionality** that would benefit all AdCP implementations: diff --git a/package.json b/package.json index 5303772f60..3528a0e731 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "test:schemas": "node tests/schema-validation.test.cjs", "test:examples": "node tests/example-validation-simple.test.cjs", "test:extensions": "node tests/extension-fields.test.cjs", + "test:extension-schemas": "node tests/extension-schemas.test.cjs", "test:snippets": "node tests/snippet-validation.test.cjs", "test:json-schema": "node tests/json-schema-validation.test.cjs", "test:error-handling": "node tests/check-error-handling.cjs", @@ -27,7 +28,7 @@ "test:registry": "vitest run server/tests", "test:docker": "docker compose -f docker-compose.test.yml up --build --abort-on-container-exit", "generate-openapi": "node scripts/generate-openapi.cjs", - "test": "npm run test:schemas && npm run test:examples && npm run test:extensions && npm run test:error-handling && npm run test:json-schema && npm run test:composed && npm run test:migrations && npm run test:unit && npm run typecheck", + "test": "npm run test:schemas && npm run test:examples && npm run test:extensions && npm run test:extension-schemas && npm run test:error-handling && npm run test:json-schema && npm run test:composed && npm run test:migrations && npm run test:unit && npm run typecheck", "test:all": "npm run test:schemas && npm run test:examples && npm run test:extensions && npm run test:error-handling && npm run test:snippets && npm run typecheck", "precommit": "npm test", "prepare": "husky", diff --git a/scripts/build-schemas.cjs b/scripts/build-schemas.cjs index b915b37b32..80bad287c6 100644 --- a/scripts/build-schemas.cjs +++ b/scripts/build-schemas.cjs @@ -21,6 +21,11 @@ * - /schemas/v{major}/ - Points to latest release of that major version * - /schemas/v{major}.{minor}/ - Points to latest release of that minor version * - /schemas/v1/ - Backward compatibility (always points to latest/) + * + * Extension handling: + * - Extensions are auto-discovered from static/schemas/source/extensions/ + * - Each extension has valid_from/valid_until to specify compatible AdCP versions + * - The build generates extensions/index.json with extensions valid for the target version */ const fs = require('fs'); @@ -109,6 +114,228 @@ function ensureDir(dir) { } } +/** + * Compare two minor versions (e.g., "2.5" vs "2.6") + * Returns: negative if a < b, 0 if equal, positive if a > b + */ +function compareMinorVersions(a, b) { + const [aMajor, aMinor] = a.split('.').map(Number); + const [bMajor, bMinor] = b.split('.').map(Number); + if (aMajor !== bMajor) return aMajor - bMajor; + return aMinor - bMinor; +} + +/** + * Reserved namespaces that cannot be used for typed extensions + * These could cause confusion with core AdCP concepts + */ +const RESERVED_NAMESPACES = ['adcp', 'core', 'protocol', 'schema', 'meta', 'ext', 'context']; + +/** + * Validate that an extension namespace is not reserved + * @param {string} namespace - Extension namespace to validate + * @throws {Error} If namespace is reserved + */ +function validateExtensionNamespace(namespace) { + if (RESERVED_NAMESPACES.includes(namespace.toLowerCase())) { + throw new Error(`Namespace "${namespace}" is reserved and cannot be used for extensions`); + } +} + +/** + * Discover extension files from the extensions directory + * Returns array of { namespace, schema, path } objects + */ +function discoverExtensions(extensionsDir) { + if (!fs.existsSync(extensionsDir)) { + return []; + } + + const extensions = []; + const files = fs.readdirSync(extensionsDir); + + for (const file of files) { + // Skip non-JSON files and special files + if (!file.endsWith('.json')) continue; + if (file === 'index.json' || file === 'extension-meta.json') continue; + + const filePath = path.join(extensionsDir, file); + try { + const content = JSON.parse(fs.readFileSync(filePath, 'utf8')); + + // Extract namespace from $id (e.g., /schemas/extensions/sustainability.json -> sustainability) + const namespace = file.replace('.json', ''); + + // Validate namespace is not reserved + validateExtensionNamespace(namespace); + + extensions.push({ + namespace, + schema: content, + path: filePath + }); + } catch (error) { + console.warn(` ⚠️ Failed to parse extension ${file}: ${error.message}`); + } + } + + return extensions; +} + +/** + * Filter extensions to those valid for a given AdCP version + * @param {Array} extensions - Array of extension objects from discoverExtensions + * @param {string} targetVersion - Target AdCP version (e.g., "2.5.0" or "2.5") + * @returns {Array} Extensions valid for the target version + */ +function filterExtensionsForVersion(extensions, targetVersion) { + // Normalize to minor version for comparison + const targetMinor = getMinorVersion(targetVersion); + + return extensions.filter(ext => { + const { valid_from, valid_until } = ext.schema; + + // Must have valid_from + if (!valid_from) { + console.warn(` ⚠️ Extension ${ext.namespace} missing valid_from, skipping`); + return false; + } + + // Check valid_from <= targetVersion + if (compareMinorVersions(valid_from, targetMinor) > 0) { + return false; // Extension requires newer version + } + + // Check valid_until >= targetVersion (if specified) + if (valid_until && compareMinorVersions(valid_until, targetMinor) < 0) { + return false; // Extension no longer valid for this version + } + + return true; + }); +} + +/** + * Generate the extensions/index.json registry for a target version + * @param {Array} extensions - Array of valid extension objects + * @param {string} targetVersion - Target version string for $id paths + * @returns {Object} The generated registry object + */ +function generateExtensionRegistry(extensions, targetVersion) { + const registry = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: `/schemas/${targetVersion}/extensions/index.json`, + title: 'AdCP Extension Registry', + description: 'Auto-generated registry of formal AdCP extensions. Extensions provide typed schemas for vendor-specific or domain-specific data within the ext field. Agents declare which extensions they support in their agent card.', + _generated: true, + _generatedAt: new Date().toISOString(), + extensions: {} + }; + + for (const ext of extensions) { + registry.extensions[ext.namespace] = { + $ref: `/schemas/${targetVersion}/extensions/${ext.namespace}.json`, + title: ext.schema.title, + description: ext.schema.description, + valid_from: ext.schema.valid_from + }; + + // Include valid_until if specified + if (ext.schema.valid_until) { + registry.extensions[ext.namespace].valid_until = ext.schema.valid_until; + } + + // Include docs_url if specified + if (ext.schema.docs_url) { + registry.extensions[ext.namespace].docs_url = ext.schema.docs_url; + } + } + + return registry; +} + +/** + * Build extensions for a target directory + * - Discovers all extensions from source + * - Filters to those valid for target version + * - Copies valid extension schemas + * - Generates the index.json registry + */ +function buildExtensions(sourceDir, targetDir, version) { + const sourceExtensionsDir = path.join(sourceDir, 'extensions'); + const targetExtensionsDir = path.join(targetDir, 'extensions'); + + // Always ensure extensions directory exists + ensureDir(targetExtensionsDir); + + // Discover all extensions + const allExtensions = discoverExtensions(sourceExtensionsDir); + + if (allExtensions.length === 0) { + // No extensions yet - just copy the meta schema and generate empty registry + const metaSchemaPath = path.join(sourceExtensionsDir, 'extension-meta.json'); + if (fs.existsSync(metaSchemaPath)) { + let content = fs.readFileSync(metaSchemaPath, 'utf8'); + // Update $id to include version + content = content.replace( + /"\$id":\s*"\/schemas\//g, + `"$id": "/schemas/${version}/` + ); + fs.writeFileSync(path.join(targetExtensionsDir, 'extension-meta.json'), content); + } + + // Generate empty registry + const registry = generateExtensionRegistry([], version); + fs.writeFileSync( + path.join(targetExtensionsDir, 'index.json'), + JSON.stringify(registry, null, 2) + ); + + return { total: 0, included: 0 }; + } + + // Filter extensions valid for this version + const validExtensions = filterExtensionsForVersion(allExtensions, version); + + // Copy extension-meta.json (with version transform) + const metaSchemaPath = path.join(sourceExtensionsDir, 'extension-meta.json'); + if (fs.existsSync(metaSchemaPath)) { + let content = fs.readFileSync(metaSchemaPath, 'utf8'); + content = content.replace( + /"\$id":\s*"\/schemas\//g, + `"$id": "/schemas/${version}/` + ); + fs.writeFileSync(path.join(targetExtensionsDir, 'extension-meta.json'), content); + } + + // Copy each valid extension schema (with version transform) + for (const ext of validExtensions) { + let content = JSON.stringify(ext.schema, null, 2); + // Update $id to include version + content = content.replace( + /"\$id":\s*"\/schemas\//g, + `"$id": "/schemas/${version}/` + ); + fs.writeFileSync( + path.join(targetExtensionsDir, `${ext.namespace}.json`), + content + ); + } + + // Generate the registry index + const registry = generateExtensionRegistry(validExtensions, version); + fs.writeFileSync( + path.join(targetExtensionsDir, 'index.json'), + JSON.stringify(registry, null, 2) + ); + + return { + total: allExtensions.length, + included: validExtensions.length, + extensions: validExtensions.map(e => e.namespace) + }; +} + function copyAndTransformSchemas(sourceDir, targetDir, version) { const entries = fs.readdirSync(sourceDir, { withFileTypes: true }); @@ -117,6 +344,10 @@ function copyAndTransformSchemas(sourceDir, targetDir, version) { const targetPath = path.join(targetDir, entry.name); if (entry.isDirectory()) { + // Skip extensions directory - handled separately by buildExtensions() + if (entry.name === 'extensions') { + continue; + } ensureDir(targetPath); copyAndTransformSchemas(sourcePath, targetPath, version); } else if (entry.name.endsWith('.json')) { @@ -332,6 +563,15 @@ async function main() { ensureDir(versionDir); copyAndTransformSchemas(SOURCE_DIR, versionDir, version); + // Build extensions (auto-discovered, filtered by version) + console.log(`🔌 Building extensions for ${version}`); + const extResult = buildExtensions(SOURCE_DIR, versionDir, version); + if (extResult.total === 0) { + console.log(` ✓ No extensions defined yet (empty registry created)`); + } else { + console.log(` ✓ Included ${extResult.included}/${extResult.total} extensions: ${extResult.extensions.join(', ') || 'none'}`); + } + // Generate bundled schemas for release const bundledDir = path.join(versionDir, 'bundled'); console.log(`📦 Generating bundled schemas to dist/schemas/${version}/bundled/`); @@ -350,6 +590,9 @@ async function main() { ensureDir(latestDir); copyAndTransformSchemas(SOURCE_DIR, latestDir, 'latest'); + // Build extensions for latest (using full version for filtering) + buildExtensions(SOURCE_DIR, latestDir, version); + // Generate bundled schemas for latest const latestBundledDir = path.join(latestDir, 'bundled'); await generateBundledSchemas(SOURCE_DIR, latestBundledDir, 'latest'); @@ -395,6 +638,15 @@ async function main() { ensureDir(latestDir); copyAndTransformSchemas(SOURCE_DIR, latestDir, 'latest'); + // Build extensions (auto-discovered, filtered by current version) + console.log(`🔌 Building extensions for ${version}`); + const extResult = buildExtensions(SOURCE_DIR, latestDir, version); + if (extResult.total === 0) { + console.log(` ✓ No extensions defined yet (empty registry created)`); + } else { + console.log(` ✓ Included ${extResult.included}/${extResult.total} extensions: ${extResult.extensions.join(', ') || 'none'}`); + } + // Generate bundled schemas for latest const bundledDir = path.join(latestDir, 'bundled'); console.log(`📦 Generating bundled schemas to dist/schemas/latest/bundled/`); diff --git a/static/schemas/source/extensions/extension-meta.json b/static/schemas/source/extensions/extension-meta.json new file mode 100644 index 0000000000..75e199a579 --- /dev/null +++ b/static/schemas/source/extensions/extension-meta.json @@ -0,0 +1,59 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/extensions/extension-meta.json", + "title": "AdCP Extension File Schema", + "description": "Schema that all extension files must follow. Combines metadata (valid_from, docs_url) with the actual extension data schema. Extensions are auto-discovered from /schemas/extensions/*.json and included in versioned builds based on valid_from/valid_until.", + "type": "object", + "properties": { + "$schema": { + "type": "string", + "const": "http://json-schema.org/draft-07/schema#" + }, + "$id": { + "type": "string", + "pattern": "^/schemas/extensions/[a-z][a-z0-9_]*\\.json$", + "description": "Extension ID following pattern /schemas/extensions/{namespace}.json" + }, + "title": { + "type": "string", + "description": "Human-readable title for the extension" + }, + "description": { + "type": "string", + "description": "Description of what this extension provides" + }, + "valid_from": { + "type": "string", + "pattern": "^\\d+\\.\\d+$", + "description": "Minimum AdCP version this extension is compatible with (e.g., '2.5'). Extension will be included in all versioned schema builds >= this version." + }, + "valid_until": { + "type": "string", + "pattern": "^\\d+\\.\\d+$", + "description": "Last AdCP version this extension is compatible with (e.g., '3.0'). Omit if extension is still valid for current and future versions." + }, + "docs_url": { + "type": "string", + "format": "uri", + "description": "URL to documentation for implementors of this extension" + }, + "type": { + "const": "object", + "description": "Extensions must be objects (data within ext.{namespace})" + }, + "properties": { + "type": "object", + "description": "Schema properties defining the structure of ext.{namespace} data", + "additionalProperties": true + }, + "required": { + "type": "array", + "items": { "type": "string" }, + "description": "Required properties within the extension data" + }, + "additionalProperties": { + "description": "Whether additional properties are allowed in the extension data" + } + }, + "required": ["$schema", "$id", "title", "description", "valid_from", "type", "properties"] +} diff --git a/static/schemas/source/extensions/index.json b/static/schemas/source/extensions/index.json new file mode 100644 index 0000000000..9b393e9ad2 --- /dev/null +++ b/static/schemas/source/extensions/index.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/extensions/index.json", + "title": "AdCP Extension Registry", + "description": "Auto-generated registry of formal AdCP extensions. Extensions provide typed schemas for vendor-specific or domain-specific data within the ext field. Agents declare which extensions they support in their agent card.", + "_generated": true, + "_note": "This file is auto-generated by the build process. Do not edit manually. Add extensions by creating new .json files in this directory following extension-meta.json schema.", + "extensions": {} +} diff --git a/static/schemas/source/index.json b/static/schemas/source/index.json index 6ea9e42a77..7a0db1fbd7 100644 --- a/static/schemas/source/index.json +++ b/static/schemas/source/index.json @@ -573,9 +573,21 @@ "schemas": { "adcp-extension": { "$ref": "/schemas/protocols/adcp-extension.json", - "description": "AdCP extension for agent cards (MCP and A2A). Declares AdCP version and supported protocol domains." + "description": "AdCP extension for agent cards (MCP and A2A). Declares AdCP version, supported protocol domains, and typed extensions." } } + }, + "extensions": { + "description": "Typed extension schemas for vendor-specific or domain-specific data. Extensions define the structure of data within the ext.{namespace} field. Agents declare which extensions they support in their agent card.", + "registry": { + "$ref": "/schemas/extensions/index.json", + "description": "Auto-generated registry of all available extensions with metadata" + }, + "meta": { + "$ref": "/schemas/extensions/extension-meta.json", + "description": "Schema that all extension files must follow. Defines valid_from, valid_until, and extension data structure." + }, + "schemas": {} } }, "usage": { diff --git a/static/schemas/source/protocols/adcp-extension.json b/static/schemas/source/protocols/adcp-extension.json index 6e3c070374..6c27ac7491 100644 --- a/static/schemas/source/protocols/adcp-extension.json +++ b/static/schemas/source/protocols/adcp-extension.json @@ -1,14 +1,14 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/protocols/adcp-extension.json", - "title": "AdCP Agent Card Extension", - "description": "Extension metadata for agent cards (MCP and A2A) declaring AdCP version and supported protocol domains", + "title": "AdCP Agent Card Extension Params", + "description": "Parameters for declaring AdCP support in agent discovery. For A2A, use in extensions[].params. For MCP, use in _meta['adcontextprotocol.org'].", "type": "object", "properties": { "adcp_version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$", - "description": "Semantic version of the AdCP specification this agent implements (e.g., '2.4.0')" + "description": "Semantic version of the AdCP specification this agent implements (e.g., '2.5.0'). Extension schemas are versioned along with the AdCP spec." }, "protocols_supported": { "type": "array", @@ -23,6 +23,16 @@ "minItems": 1, "uniqueItems": true, "description": "AdCP protocol domains supported by this agent. At least one must be specified." + }, + "extensions_supported": { + "type": "array", + "description": "Typed extensions this agent supports. Each extension has a formal schema in /schemas/extensions/. Extension version is determined by adcp_version.", + "items": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]*$", + "description": "Extension namespace (e.g., 'sustainability'). Must be lowercase alphanumeric with underscores." + }, + "uniqueItems": true } }, "required": [ diff --git a/tests/extension-schemas.test.cjs b/tests/extension-schemas.test.cjs new file mode 100644 index 0000000000..cf27260202 --- /dev/null +++ b/tests/extension-schemas.test.cjs @@ -0,0 +1,445 @@ +#!/usr/bin/env node +/** + * Extension schema validation test suite + * Tests that the typed extension infrastructure is properly configured + */ + +const fs = require('fs'); +const path = require('path'); +const Ajv = require('ajv'); +const addFormats = require('ajv-formats'); + +const SCHEMA_BASE_DIR = path.join(__dirname, '../static/schemas/source'); +const EXTENSIONS_DIR = path.join(SCHEMA_BASE_DIR, 'extensions'); + +// Initialize AJV with formats and custom loader +const ajv = new Ajv({ + allErrors: true, + verbose: true, + strict: false, + loadSchema: loadExternalSchema +}); +addFormats(ajv); + +// Schema loader for resolving $ref +async function loadExternalSchema(uri) { + let relativePath; + if (uri.startsWith('/schemas/v1/')) { + relativePath = uri.replace('/schemas/v1/', ''); + } else if (uri.startsWith('/schemas/')) { + relativePath = uri.replace('/schemas/', ''); + } else { + throw new Error(`Cannot load external schema: ${uri}`); + } + + const schemaPath = path.join(SCHEMA_BASE_DIR, relativePath); + try { + const content = fs.readFileSync(schemaPath, 'utf8'); + return JSON.parse(content); + } catch (error) { + throw new Error(`Failed to load referenced schema ${uri}: ${error.message}`); + } +} + +let totalTests = 0; +let passedTests = 0; +let failedTests = 0; + +function log(message, type = 'info') { + const colors = { + info: '\x1b[0m', + success: '\x1b[32m', + error: '\x1b[31m', + warning: '\x1b[33m' + }; + console.log(`${colors[type]}${message}\x1b[0m`); +} + +async function test(description, testFn) { + totalTests++; + try { + const result = await testFn(); + if (result === true || result === undefined) { + log(`✅ ${description}`, 'success'); + passedTests++; + } else { + log(`❌ ${description}: ${result}`, 'error'); + failedTests++; + } + } catch (error) { + log(`❌ ${description}: ${error.message}`, 'error'); + if (error.errors) { + console.error('Validation errors:', JSON.stringify(error.errors, null, 2)); + } + failedTests++; + } +} + +// Cache for compiled schemas +const schemaCache = new Map(); + +async function loadAndCompileSchema(schemaPath) { + if (schemaCache.has(schemaPath)) { + return schemaCache.get(schemaPath); + } + + const schemaContent = fs.readFileSync(schemaPath, 'utf8'); + const schema = JSON.parse(schemaContent); + const validate = await ajv.compileAsync(schema); + + schemaCache.set(schemaPath, validate); + return validate; +} + +// Discover extension files (excluding index.json and extension-meta.json) +function discoverExtensionFiles() { + if (!fs.existsSync(EXTENSIONS_DIR)) { + return []; + } + + return fs.readdirSync(EXTENSIONS_DIR) + .filter(file => + file.endsWith('.json') && + file !== 'index.json' && + file !== 'extension-meta.json' + ) + .map(file => path.join(EXTENSIONS_DIR, file)); +} + +async function runTests() { + log('🧪 Starting Extension Schema Validation Tests'); + log('=============================================='); + + // Test 1: Extension directory exists + await test('Extension directory exists', async () => { + if (!fs.existsSync(EXTENSIONS_DIR)) { + throw new Error(`Extension directory not found: ${EXTENSIONS_DIR}`); + } + return true; + }); + + // Test 2: Extension index exists and is valid JSON + await test('Extension index exists and is valid JSON', async () => { + const indexPath = path.join(EXTENSIONS_DIR, 'index.json'); + if (!fs.existsSync(indexPath)) { + throw new Error('Extension index.json not found'); + } + const content = fs.readFileSync(indexPath, 'utf8'); + JSON.parse(content); // Will throw if invalid JSON + return true; + }); + + // Test 3: Extension index has proper structure + await test('Extension index has proper structure', async () => { + const indexPath = path.join(EXTENSIONS_DIR, 'index.json'); + const index = JSON.parse(fs.readFileSync(indexPath, 'utf8')); + + if (!index.$schema) { + throw new Error('Extension index missing $schema'); + } + if (!index.$id) { + throw new Error('Extension index missing $id'); + } + if (!index.title) { + throw new Error('Extension index missing title'); + } + if (typeof index.extensions !== 'object') { + throw new Error('Extension index missing extensions object'); + } + if (!index._generated) { + throw new Error('Extension index should have _generated: true marker'); + } + return true; + }); + + // Test 4: Extension meta schema exists and is valid + await test('Extension meta schema exists and is valid', async () => { + const metaPath = path.join(EXTENSIONS_DIR, 'extension-meta.json'); + if (!fs.existsSync(metaPath)) { + throw new Error('Extension extension-meta.json not found'); + } + const content = fs.readFileSync(metaPath, 'utf8'); + const meta = JSON.parse(content); + + // Verify it's a proper schema + if (!meta.$schema) { + throw new Error('Extension meta schema missing $schema'); + } + if (!meta.$id) { + throw new Error('Extension meta schema missing $id'); + } + if (!meta.properties.valid_from) { + throw new Error('Extension meta schema missing valid_from property'); + } + if (!meta.properties.valid_until) { + throw new Error('Extension meta schema missing valid_until property'); + } + return true; + }); + + // Test 5: Agent card extension schema supports extensions_supported field + await test('Agent card extension schema supports extensions_supported', async () => { + const validate = await loadAndCompileSchema( + path.join(SCHEMA_BASE_DIR, 'protocols/adcp-extension.json') + ); + + const validData = { + adcp_version: '2.5.0', + protocols_supported: ['media_buy'], + extensions_supported: ['sustainability', 'custom_vendor'] + }; + + const valid = validate(validData); + if (!valid) { + throw new Error(`Validation failed: ${JSON.stringify(validate.errors)}`); + } + return true; + }); + + // Test 6: Agent card validates without extensions_supported (optional field) + await test('Agent card validates without extensions_supported', async () => { + const validate = await loadAndCompileSchema( + path.join(SCHEMA_BASE_DIR, 'protocols/adcp-extension.json') + ); + + const validData = { + adcp_version: '2.5.0', + protocols_supported: ['media_buy', 'creative'] + }; + + const valid = validate(validData); + if (!valid) { + throw new Error(`Validation failed: ${JSON.stringify(validate.errors)}`); + } + return true; + }); + + // Test 7: Agent card rejects invalid extension namespace format + await test('Agent card rejects invalid extension namespace format', async () => { + const validate = await loadAndCompileSchema( + path.join(SCHEMA_BASE_DIR, 'protocols/adcp-extension.json') + ); + + const invalidData = { + adcp_version: '2.5.0', + protocols_supported: ['media_buy'], + extensions_supported: ['Invalid-Namespace'] // Must be lowercase + }; + + const valid = validate(invalidData); + if (valid) { + throw new Error('Should have rejected invalid namespace format'); + } + return true; + }); + + // Test 8: Product can include arbitrary extension data (untyped) + await test('Product validates with arbitrary extension data', async () => { + const validate = await loadAndCompileSchema( + path.join(SCHEMA_BASE_DIR, 'core/product.json') + ); + + const product = { + product_id: 'test_product', + name: 'Test Product', + description: 'Test description', + publisher_properties: [{ + publisher_domain: 'example.com', + selection_type: 'all' + }], + format_ids: [{ + agent_url: 'https://creative.adcontextprotocol.org', + id: 'display_300x250' + }], + delivery_type: 'guaranteed', + delivery_measurement: { + provider: 'Test Provider' + }, + pricing_options: [{ + is_fixed: true, + pricing_option_id: 'fixed_cpm', + pricing_model: 'cpm', + rate: 10.00, + currency: 'USD' + }], + ext: { + custom_vendor: { + some_field: 'some_value', + nested: { data: true } + }, + another_vendor: { + config: [1, 2, 3] + } + } + }; + + const valid = validate(product); + if (!valid) { + throw new Error(`Validation failed: ${JSON.stringify(validate.errors)}`); + } + return true; + }); + + // Test 9: Extension meta schema validates a sample extension + await test('Extension meta schema validates sample extension structure', async () => { + const validate = await loadAndCompileSchema( + path.join(EXTENSIONS_DIR, 'extension-meta.json') + ); + + const sampleExtension = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: '/schemas/extensions/sustainability.json', + title: 'Sustainability Extension', + description: 'Carbon footprint and green certification data', + valid_from: '2.5', + docs_url: 'https://adcontextprotocol.org/docs/extensions/sustainability', + type: 'object', + properties: { + carbon_kg_per_impression: { type: 'number' }, + certified_green: { type: 'boolean' } + } + }; + + const valid = validate(sampleExtension); + if (!valid) { + throw new Error(`Validation failed: ${JSON.stringify(validate.errors)}`); + } + return true; + }); + + // Test 10: Extension meta schema validates extension with valid_until + await test('Extension meta schema validates extension with valid_until', async () => { + const validate = await loadAndCompileSchema( + path.join(EXTENSIONS_DIR, 'extension-meta.json') + ); + + const sampleExtension = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: '/schemas/extensions/deprecated_feature.json', + title: 'Deprecated Feature Extension', + description: 'A feature that was deprecated in version 3.0', + valid_from: '2.0', + valid_until: '3.0', + type: 'object', + properties: { + legacy_field: { type: 'string' } + } + }; + + const valid = validate(sampleExtension); + if (!valid) { + throw new Error(`Validation failed: ${JSON.stringify(validate.errors)}`); + } + return true; + }); + + // Test 11: Extension meta schema rejects invalid valid_from format + await test('Extension meta schema rejects invalid valid_from format', async () => { + const validate = await loadAndCompileSchema( + path.join(EXTENSIONS_DIR, 'extension-meta.json') + ); + + const invalidExtension = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: '/schemas/extensions/bad_version.json', + title: 'Bad Version Extension', + description: 'Has invalid version format', + valid_from: '2.5.0', // Should be "2.5" not "2.5.0" + type: 'object', + properties: {} + }; + + const valid = validate(invalidExtension); + if (valid) { + throw new Error('Should have rejected invalid valid_from format (semver patch level not allowed)'); + } + return true; + }); + + // Test 12: Extension meta schema rejects invalid $id pattern + await test('Extension meta schema rejects invalid $id pattern', async () => { + const validate = await loadAndCompileSchema( + path.join(EXTENSIONS_DIR, 'extension-meta.json') + ); + + const invalidExtension = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: '/schemas/core/not_an_extension.json', // Wrong path + title: 'Wrong Path Extension', + description: 'Has invalid $id path', + valid_from: '2.5', + type: 'object', + properties: {} + }; + + const valid = validate(invalidExtension); + if (valid) { + throw new Error('Should have rejected invalid $id pattern'); + } + return true; + }); + + // Test 13: Reserved namespaces should be rejected by build script validation + await test('Reserved namespaces are documented', async () => { + // These namespaces are reserved in scripts/build-schemas.cjs + const RESERVED_NAMESPACES = ['adcp', 'core', 'protocol', 'schema', 'meta', 'ext', 'context']; + + // Verify none of the reserved namespaces exist as extension files + for (const reserved of RESERVED_NAMESPACES) { + const reservedPath = path.join(EXTENSIONS_DIR, `${reserved}.json`); + if (fs.existsSync(reservedPath)) { + throw new Error(`Reserved namespace "${reserved}" should not exist as an extension file`); + } + } + return true; + }); + + // Test 14: Discovered extensions validate against meta schema + const extensionFiles = discoverExtensionFiles(); + if (extensionFiles.length > 0) { + for (const extensionPath of extensionFiles) { + const filename = path.basename(extensionPath); + await test(`Extension file ${filename} validates against meta schema`, async () => { + const validate = await loadAndCompileSchema( + path.join(EXTENSIONS_DIR, 'extension-meta.json') + ); + + const content = fs.readFileSync(extensionPath, 'utf8'); + const extension = JSON.parse(content); + + const valid = validate(extension); + if (!valid) { + throw new Error(`Validation failed: ${JSON.stringify(validate.errors)}`); + } + return true; + }); + } + } else { + await test('No extension files to validate (registry is empty)', async () => { + log(' ℹ️ No extension files found - this is expected for initial setup', 'warning'); + return true; + }); + } + + // Summary + log(''); + log('=============================================='); + log(`Tests completed: ${totalTests}`); + log(`✅ Passed: ${passedTests}`, 'success'); + if (failedTests > 0) { + log(`❌ Failed: ${failedTests}`, 'error'); + log(''); + process.exit(1); + } else { + log(''); + log('🎉 All extension schema tests passed!', 'success'); + process.exit(0); + } +} + +// Run tests +runTests().catch(error => { + log(`Fatal error: ${error.message}`, 'error'); + console.error(error); + process.exit(1); +}); From 5cd83b819f6bbac049c3429c5ec1ac8a3b65d431 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Mon, 12 Jan 2026 15:54:09 -0500 Subject: [PATCH 16/49] Add Property Governance Protocol for AdCP 3.0 (#588) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Property Governance Protocol for AdCP 3.0 This introduces a stateful property list management model for governance agents: ## Key Concepts - **Property Lists**: Managed resources combining base property sets with dynamic filters - **Feature Discovery**: Agents advertise scoring capabilities via `list_property_features` - **Brand Manifests**: Buyers describe their brand identity; agents apply appropriate rules - **Discriminated Unions**: `base_properties` uses `selection_type` discriminator for type safety ## Design Principles - **Scores are internal**: Governance agents filter properties; raw scores never leave the agent - **Setup-time, not bid-time**: Lists resolved during campaign planning, cached for real-time - **Identifiers-only responses**: `get_property_list` returns identifiers, not full property objects - **Notification webhooks**: Webhooks provide summary; recipients call `get_property_list` to refresh ## Schemas Added - Property list CRUD operations (create, update, get, list, delete) - Property list filters with explicit logic (`countries_all`, `channels_any`, `feature_requirements`) - Feature requirements for quantitative (`min_value`/`max_value`) and categorical (`allowed_values`) - Base property source with discriminated union (publisher_tags, publisher_ids, identifiers) - Webhook notification schema - Core identifier schema for reuse ## Documentation - Governance Protocol overview - Property Governance specification - Task reference for all CRUD operations - Brand Standards placeholder 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Add changeset for Property Governance Protocol 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Use empty changeset for draft 3.0 proposal No version bump needed for draft/proposal specs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Address review feedback on Property Governance Protocol Schema fixes: - Add methodology_url (required) and methodology_version to property-feature-definition - Add signature field (required) to webhook schema for security - Make countries_all and channels_any required in property-list-filters - Move auth_token from get_property_list to create_property_list response Documentation fixes: - Add terminology note: Orchestrator = Buyer Agent - Update examples to show auth_token in create response only - Add note that auth_token is only returned at creation time 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Add validate_property_delivery task for post-campaign compliance validation Introduces the "what I wanted vs what I got" validation concept: - validate_property_delivery task validates delivery records against property lists - Three statuses: compliant, non_compliant, unknown - Unknown records are valid (detection gaps shouldn't penalize compliance) - Compliance rate excludes unknown impressions from calculation - Batch support up to 10,000 records per request New schemas: - delivery-record.json: Input record with identifier + impressions - validation-result.json: Per-record validation result with status and violations - validate-property-delivery-request.json: Request payload - validate-property-delivery-response.json: Response with summary and per-record results 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Add supply path authorization to validate_property_delivery Extends validate_property_delivery to validate sales agent authorization: - Add optional sales_agent_url to delivery records - Check if sales agent is listed in publisher's adagents.json - Return authorization status (authorized/unauthorized/unknown) per record - Add authorization_summary with aggregate stats and authorization_rate - Two independent checks: property compliance + supply path authorization New schema: - authorization-result.json: Per-record authorization validation result Authorization uses same "unknown excludes from rate" pattern as property compliance - you cannot penalize for detection gaps or unreachable files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Simplify validation response: raw counts + optional feature details Changes: - Remove compliance_rate and authorization_rate from response schemas - Return raw counts only (1.2M verified, 0.8M unknown, 0.3M invalid) - Consumers calculate rates as needed, excluding unknowns from denominator - Add optional feature_id and requirement to violation objects - For feature_failed violations, shows which feature and what threshold failed - Helps debugging why a property was marked non-compliant 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Split unknown into unmodeled/unidentified, add optional aggregate metrics - Split "unknown" status into two distinct statuses: - unmodeled: identifier recognized but agent has no data (e.g., property too new) - unidentified: identifier type not resolvable (e.g., detection failed) - Add optional aggregate field for computed metrics: - score, grade, label fields for agent-specific measurements - methodology_url for transparency - Update summary to track both unmodeled and unidentified separately - Update documentation with new statuses and aggregate concept Inspired by Scope3's inventory_coverage (modeled/unmodeled) pattern. The distinction helps identify whether gaps are in data coverage vs identifier resolution. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Fix include_compliant description to reference new statuses Updated the description to list non_compliant, unmodeled, and unidentified (instead of the old "unknown" status). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Move authorized-properties doc to Governance Protocol section - Moved authorized-properties.mdx from media-buy/capability-discovery to governance/property (renamed to "Understanding Authorization") - Added validate_property_delivery task to navigation - Navigation now shows conceptual explainer before tech spec 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Fix broken links to authorized-properties after move Updated links in media-buy docs to point to new location in governance/property section. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Add property_list filtering to get_products for 2.6.x - Add optional property_list parameter to get_products request schema - Add property_list_applied response field to indicate filtering status - Update get_products docs with new parameter, response field, and usage example - Mark changeset as minor for 2.6.x release 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Add governance overview enhancements for working group alignment - Change banner from Info to Warning for draft status - Add mission statement for governance protocols - Add Working Group Areas table mapping to protocol domains - Add Prompt-Based Policies section for natural language policy support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Fix broken links in v2.6-rc docs after authorized-properties move Update links from /docs/media-buy/capability-discovery/adagents and /docs/media-buy/capability-discovery/authorized-properties to their new locations in /docs/governance/property/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Add get_property_features task and property_features discovery - Add get_property_features task schemas for governance agents to return property certification/measurement data (carbon scores, TAG certified, etc.) - Support two request modes: explicit property list or publisher discovery - Add publisher_domain, property_types, property_tags filtering for discovery - Add property_features field to adagents.json for feature provider discovery - Rename from governance_agents to property_features for flexibility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Add filter semantics for geographic and channel compliance Clarifies what "compliant in a country" means for property list filters: - Refers to user location (where impressions are delivered) - NOT publisher headquarters, server hosting, or incorporation country - Requires regulatory compliance, ad delivery capability, and valid consent Also clarifies channel filter semantics (technical capability, availability, quality standards) and adds cross-references between docs and schema. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Revert filter semantics changes The Filter Semantics section overstated the meaning of countries_all - it's about whether the agent has feature data for properties in that country, not about legal compliance. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Add if_not_covered handling and standardize on 'covered' terminology - Add if_not_covered field to feature-requirement.json (exclude|include) - Add coverage_gaps to get-property-list-response.json for transparency - Change 'unmodeled' to 'not_covered' throughout for consistency: - validation-result.json status enum - validate-property-delivery-response.json summary fields - specification.mdx and validate_property_delivery.mdx documentation - Document coverage handling in property_lists.mdx This allows callers to specify how missing feature data should be handled (strict vs lenient enforcement) and provides visibility into which properties were included despite coverage gaps. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Clarify countries_all semantics: feature data, not compliance Remove "compliant" language from countries_all descriptions to avoid confusion with legal/regulatory compliance. The field means "property must have feature data for these countries" - same semantics as content standards PR 621 ("standards apply in these countries"). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Claude Opus 4.5 --- .changeset/property-governance-protocol.md | 9 + docs.json | 37 +- docs/governance/brand-standards/index.mdx | 90 +++ docs/governance/index.mdx | 187 +++++ .../property}/adagents.mdx | 4 +- .../property}/authorized-properties.mdx | 7 +- docs/governance/property/index.mdx | 227 ++++++ docs/governance/property/specification.mdx | 611 +++++++++++++++ docs/governance/property/tasks/index.mdx | 153 ++++ .../property/tasks/list_property_features.mdx | 235 ++++++ .../property/tasks/property_lists.mdx | 701 ++++++++++++++++++ .../tasks/validate_property_delivery.mdx | 402 ++++++++++ docs/media-buy/capability-discovery/index.mdx | 6 +- docs/media-buy/index.mdx | 2 +- .../media-buy/task-reference/get_products.mdx | 81 ++ .../list_authorized_properties.mdx | 2 +- docs/reference/implementor-faq.mdx | 2 +- docs/reference/roadmap.mdx | 4 +- package-lock.json | 4 +- static/schemas/source/adagents.json | 92 +++ static/schemas/source/core/identifier.json | 19 + .../source/core/property-list-ref.json | 25 + static/schemas/source/enums/adcp-domain.json | 11 +- static/schemas/source/enums/task-type.json | 18 +- static/schemas/source/index.json | 111 ++- .../media-buy/get-products-request.json | 4 + .../media-buy/get-products-response.json | 4 + .../source/property/authorization-result.json | 41 + .../source/property/base-property-source.json | 87 +++ .../create-property-list-request.json | 40 + .../create-property-list-response.json | 22 + .../delete-property-list-request.json | 21 + .../delete-property-list-response.json | 22 + .../source/property/delivery-record.json | 32 + .../source/property/feature-requirement.json | 34 + .../get-property-features-request.json | 68 ++ .../get-property-features-response.json | 28 + .../property/get-property-list-request.json | 35 + .../property/get-property-list-response.json | 68 ++ .../list-property-features-request.json | 27 + .../list-property-features-response.json | 21 + .../property/list-property-lists-request.json | 34 + .../list-property-lists-response.json | 44 ++ .../source/property/property-error.json | 31 + .../property/property-feature-definition.json | 84 +++ .../property/property-feature-result.json | 43 ++ .../property/property-feature-value.json | 51 ++ .../source/property/property-feature.json | 23 + .../property-list-changed-webhook.json | 60 ++ .../property/property-list-filters.json | 47 ++ .../source/property/property-list.json | 67 ++ .../update-property-list-request.json | 49 ++ .../update-property-list-response.json | 18 + .../validate-property-delivery-request.json | 32 + .../validate-property-delivery-response.json | 167 +++++ .../source/property/validation-result.json | 79 ++ .../authorized-properties.mdx | 4 +- .../media-buy/capability-discovery/index.mdx | 6 +- v2.6-rc/docs/media-buy/index.mdx | 2 +- .../list_authorized_properties.mdx | 2 +- v2.6-rc/docs/reference/implementor-faq.mdx | 2 +- 61 files changed, 4400 insertions(+), 39 deletions(-) create mode 100644 .changeset/property-governance-protocol.md create mode 100644 docs/governance/brand-standards/index.mdx create mode 100644 docs/governance/index.mdx rename docs/{media-buy/capability-discovery => governance/property}/adagents.mdx (98%) rename docs/{media-buy/capability-discovery => governance/property}/authorized-properties.mdx (97%) create mode 100644 docs/governance/property/index.mdx create mode 100644 docs/governance/property/specification.mdx create mode 100644 docs/governance/property/tasks/index.mdx create mode 100644 docs/governance/property/tasks/list_property_features.mdx create mode 100644 docs/governance/property/tasks/property_lists.mdx create mode 100644 docs/governance/property/tasks/validate_property_delivery.mdx create mode 100644 static/schemas/source/core/identifier.json create mode 100644 static/schemas/source/core/property-list-ref.json create mode 100644 static/schemas/source/property/authorization-result.json create mode 100644 static/schemas/source/property/base-property-source.json create mode 100644 static/schemas/source/property/create-property-list-request.json create mode 100644 static/schemas/source/property/create-property-list-response.json create mode 100644 static/schemas/source/property/delete-property-list-request.json create mode 100644 static/schemas/source/property/delete-property-list-response.json create mode 100644 static/schemas/source/property/delivery-record.json create mode 100644 static/schemas/source/property/feature-requirement.json create mode 100644 static/schemas/source/property/get-property-features-request.json create mode 100644 static/schemas/source/property/get-property-features-response.json create mode 100644 static/schemas/source/property/get-property-list-request.json create mode 100644 static/schemas/source/property/get-property-list-response.json create mode 100644 static/schemas/source/property/list-property-features-request.json create mode 100644 static/schemas/source/property/list-property-features-response.json create mode 100644 static/schemas/source/property/list-property-lists-request.json create mode 100644 static/schemas/source/property/list-property-lists-response.json create mode 100644 static/schemas/source/property/property-error.json create mode 100644 static/schemas/source/property/property-feature-definition.json create mode 100644 static/schemas/source/property/property-feature-result.json create mode 100644 static/schemas/source/property/property-feature-value.json create mode 100644 static/schemas/source/property/property-feature.json create mode 100644 static/schemas/source/property/property-list-changed-webhook.json create mode 100644 static/schemas/source/property/property-list-filters.json create mode 100644 static/schemas/source/property/property-list.json create mode 100644 static/schemas/source/property/update-property-list-request.json create mode 100644 static/schemas/source/property/update-property-list-response.json create mode 100644 static/schemas/source/property/validate-property-delivery-request.json create mode 100644 static/schemas/source/property/validate-property-delivery-response.json create mode 100644 static/schemas/source/property/validation-result.json diff --git a/.changeset/property-governance-protocol.md b/.changeset/property-governance-protocol.md new file mode 100644 index 0000000000..882945edce --- /dev/null +++ b/.changeset/property-governance-protocol.md @@ -0,0 +1,9 @@ +--- +"adcontextprotocol": minor +--- + +Add Property Governance Protocol support to get_products + +- Add optional `property_list` parameter to get_products request for filtering products by property list +- Add `property_list_applied` response field to indicate whether filtering was applied +- Enables buyers to pass property lists from governance agents to sales agents for compliant inventory discovery diff --git a/docs.json b/docs.json index cf2262d30a..ca5b1d84a3 100644 --- a/docs.json +++ b/docs.json @@ -80,13 +80,6 @@ "docs/protocols/envelope-examples" ] }, - { - "group": "adagents.json", - "pages": [ - "docs/media-buy/capability-discovery/adagents", - "docs/media-buy/capability-discovery/authorized-properties" - ] - }, { "group": "Signals Protocol", "pages": [ @@ -101,6 +94,36 @@ } ] }, + { + "group": "Governance Protocol [3.0]", + "pages": [ + "docs/governance/index", + { + "group": "Property Governance", + "pages": [ + "docs/governance/property/index", + "docs/governance/property/specification", + "docs/governance/property/authorized-properties", + "docs/governance/property/adagents", + { + "group": "Tasks", + "pages": [ + "docs/governance/property/tasks/index", + "docs/governance/property/tasks/list_property_features", + "docs/governance/property/tasks/property_lists", + "docs/governance/property/tasks/validate_property_delivery" + ] + } + ] + }, + { + "group": "Brand Standards", + "pages": [ + "docs/governance/brand-standards/index" + ] + } + ] + }, { "group": "Curation Protocol", "pages": [ diff --git a/docs/governance/brand-standards/index.mdx b/docs/governance/brand-standards/index.mdx new file mode 100644 index 0000000000..15a738adc0 --- /dev/null +++ b/docs/governance/brand-standards/index.mdx @@ -0,0 +1,90 @@ +--- +sidebar_position: 1 +title: Brand Standards +--- + + +**AdCP 3.0 Proposal** - This protocol is under development for AdCP 3.0. Feedback welcome via [GitHub Discussions](https://github.com/adcontextprotocol/adcp/discussions). + + +Brand Standards governance enables buyers to declare brand requirements that governance agents use to automatically apply appropriate rules across advertising workflows. + +## Overview + +Brand Standards addresses three concerns: + +| Concern | Description | Mechanism | +|---------|-------------|-----------| +| **Brand Identity** | Who is the advertiser? | Brand manifest declarations | +| **Creative Guidelines** | What can the brand show? | Creative constraints and policies | +| **Audience Constraints** | Who should see the ads? | Industry-specific targeting rules | + +## Brand Manifest + +The brand manifest is the core declaration that buyers provide to governance agents: + +```json +{ + "brand_manifest": { + "brand_name": "ToyBrand", + "industry": "toys", + "target_audience": "children_under_13", + "prohibited_categories": ["gambling", "alcohol", "tobacco"], + "creative_guidelines": { + "max_animation_duration": 15, + "require_accessibility": true + } + } +} +``` + +When governance agents receive a brand manifest, they automatically apply: + +- **Regulatory compliance**: COPPA for children's brands, GDPR consent requirements +- **Content filtering**: Block categories inappropriate for the brand's audience +- **Creative validation**: Enforce brand-specific creative guidelines + +## Integration with Property Governance + +Brand manifests work alongside property lists. When creating a property list, include the brand manifest to get automatic rule application: + +```json +{ + "tool": "create_property_list", + "arguments": { + "name": "ToyBrand Q1 Properties", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "kidsmedia.com", + "tags": ["kids_family", "educational"] + } + ], + "filters": { + "countries_all": ["US"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "brand_safety", "min_value": 90, "max_value": 100 } + ] + }, + "brand_manifest": { + "brand_name": "ToyBrand", + "industry": "toys", + "target_audience": "children_under_13" + } + } +} +``` + +The governance agent uses the brand manifest to apply COPPA requirements, filter out non-child-safe properties, and enforce relevant industry rules. + +## Coming Soon + +Detailed specification and task definitions for Brand Standards governance are under development. Key planned features: + +- **Brand manifest validation**: Verify manifest completeness and consistency +- **Industry rule sets**: Pre-defined rules for common industries (toys, alcohol, pharmaceuticals, finance) +- **Creative compliance**: Validate creatives against brand guidelines +- **Audience verification**: Ensure targeting complies with brand audience constraints + +See the [Governance Protocol overview](/docs/governance/index) for the broader context of how Brand Standards fits into AdCP governance. diff --git a/docs/governance/index.mdx b/docs/governance/index.mdx new file mode 100644 index 0000000000..f08e368f2d --- /dev/null +++ b/docs/governance/index.mdx @@ -0,0 +1,187 @@ +--- +sidebar_position: 1 +title: Governance Protocol +--- + + +**Draft for AdCP 3.0** - These governance protocols are under active development by the AAO Governance Working Group. Feedback welcome via [GitHub Discussions](https://github.com/adcontextprotocol/adcp/discussions). + + +The Governance Protocol provides standardized mechanisms for compliance, brand safety, and quality control across advertising workflows. It enables specialized agents to evaluate, filter, and score advertising entities against configurable criteria. + +## Mission + +> Define and operate transparent, privacy-safe, brand-suitable campaign governance that encodes human heuristics in open protocol objects, making campaigns easy to launch, safe to scale, and measurable against verifiable outcomes for buyers, sellers, and auditors. + +## Protocol Domains + +The Governance Protocol covers four distinct governance domains: + +| Domain | What It Governs | Key Mechanisms | +|--------|-----------------|----------------| +| **[Property Governance](/docs/governance/property/index)** | Where ads can run | Property lists, compliance filtering, adagents.json authorization | +| **[Brand Standards](/docs/governance/brand-standards/index)** | Brand requirements | Brand manifests, creative guidelines, audience constraints | +| **Creative Governance** | What creatives are compliant | Format validation, content moderation, accessibility | +| **Campaign Governance** | What can be bought | Budget controls, approval workflows, policy compliance | + +Each domain has specialized governance agents that provide data, filtering, and scoring capabilities. + +## Working Group Areas + +The AAO Governance Working Group is developing standards across six areas that inform these protocols: + +| Area | Focus | Protocol Impact | +|------|-------|-----------------| +| Brand Safety & Suitability | Safe, appropriate ad contexts | Property Governance, Content filtering | +| Brand Manifesto & Standards Library | Machine-readable brand policies | Brand Standards | +| Creative Standards & Validation | Technical and ethical creative criteria | Creative Governance | +| Process & Human Oversight | Human-in-the-loop checkpoints | All protocols | +| Regulatory & Compliance | Legal and regional frameworks | All protocols | +| Measurement & Verification | Proving compliance | All protocols | + +## Architecture + +Governance agents operate across the **full campaign lifecycle**: + +- **Setup time**: Configure property lists, brand requirements, and compliance rules +- **Real-time**: Influence bid-time decisions through cached rules and scoring +- **Post-bid**: Measurement, verification, and reporting on compliance + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ BUYER AGENT │ +│ - Aggregates governance from specialized agents │ +│ - Issues auth_tokens for sellers to access governance data │ +│ - Source of truth for compliant lists and brand requirements │ +└───────────────────────────┬─────────────────────────────────────────────┘ + │ create_property_list, webhooks + ┌───────────────────┼───────────────────┐ + ▼ ▼ ▼ +┌───────────────┐ ┌───────────────┐ ┌───────────────┐ +│ Consent Agent │ │ Scope3 Agent │ │ Brand Safety │ +│ (Compliance) │ │ │ │ Agent │ +├───────────────┤ ├───────────────┤ ├───────────────┤ +│ consent_qual │ │ carbon_score │ │ content_cat │ +│ tcf_version │ │ climate_risk │ │ brand_risk │ +│ coppa_cert │ │ green_host │ │ sentiment │ +└───────────────┘ └───────────────┘ └───────────────┘ + │ + │ get_property_list (with auth_token) + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ SELLER AGENT (DSP/SSP) │ +│ - Caches resolved property lists for bid-time decisions │ +│ - Receives updates via webhooks when lists change │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +## Common Patterns + +### Feature Discovery + +All governance agents advertise their capabilities via discovery tasks: + +```json +{ + "tool": "list_property_features", + "arguments": {} +} +``` + +Response: +```json +{ + "features": [ + { "feature_id": "consent_quality", "type": "quantitative", "range": { "min": 0, "max": 100 } }, + { "feature_id": "coppa_certified", "type": "binary" }, + { "feature_id": "carbon_score", "type": "quantitative", "range": { "min": 0, "max": 100 } } + ] +} +``` + +### Brand Manifest + +Buyers declare their brand identity, and governance agents automatically apply appropriate rules: + +```json +{ + "brand_manifest": { + "brand_name": "ToyBrand", + "industry": "toys", + "target_audience": "children_under_13" + } +} +``` + +When a governance agent sees this manifest, it automatically applies COPPA requirements, content filtering for children's brands, and other industry-specific rules. + +### Prompt-Based Policies + +Governance protocols support **natural language prompts** for policy definitions rather than rigid keyword lists: + +```json +{ + "policy": "Avoid content about violence, controversial politics, or adult themes. Sports news is excellent. Entertainment is generally acceptable.", + "exclusions_prompt": "Block content containing hate speech, illegal activities, or ongoing litigation against our company." +} +``` + +This enables AI-powered verification agents to understand context and nuance. + +### Webhook Notifications + +Governance agents notify subscribers when evaluations change: + +```json +{ + "webhook_url": "https://buyer.example.com/webhooks/list-changed", + "events": ["list_updated", "property_removed", "score_changed"] +} +``` + +## Integration with Media Buy + +The Media Buy Protocol consumes governance data at multiple stages: + +- **Product discovery**: Pass `property_list_ref` to `get_products` to filter inventory +- **Media buy creation**: Reference property lists to constrain where ads can run +- **Authorization**: adagents.json validates agent authority to sell properties + +```json +{ + "tool": "get_products", + "arguments": { + "brief": "UK video inventory for Q1", + "property_list_ref": { + "agent_url": "https://buyer-agent.example.com", + "list_id": "pl_q1_uk_premium", + "auth_token": "..." + } + } +} +``` + +## Getting Started + +**Buyers:** +1. Subscribe to governance agents for property data and brand safety +2. Create property lists with filters and brand manifests +3. Aggregate results into final compliant lists +4. Share references with sellers (with auth tokens) + +**Governance Agent Implementers:** +1. Implement feature discovery tasks (`list_property_features`, etc.) +2. Implement stateful list management (CRUD operations) +3. Support webhooks to notify when evaluations change +4. See the [Property Protocol Specification](/docs/governance/property/specification) for detailed implementation guidance + +## Protocol Sections + + + + Control where ads can run with property lists, compliance filtering, and publisher authorization via adagents.json. + + + Define brand requirements including creative guidelines, audience constraints, and industry-specific rules. + + diff --git a/docs/media-buy/capability-discovery/adagents.mdx b/docs/governance/property/adagents.mdx similarity index 98% rename from docs/media-buy/capability-discovery/adagents.mdx rename to docs/governance/property/adagents.mdx index 442dee1805..2732d1d8f1 100644 --- a/docs/media-buy/capability-discovery/adagents.mdx +++ b/docs/governance/property/adagents.mdx @@ -1,9 +1,9 @@ --- -sidebar_position: 8 +sidebar_position: 3 title: adagents.json Tech Spec --- -The `adagents.json` file provides a standardized way for publishers to declare which sales agents are authorized to sell their advertising inventory. This specification addresses authorization transparency and helps prevent unauthorized reselling of publisher inventory. +The `adagents.json` file provides a standardized way for publishers to declare their properties and authorize sales agents. This is the foundation of Property Governance - it defines what properties exist and who can sell them. **[AdAgents.json Builder](https://adcontextprotocol.org/adagents)** - Validate existing files or create new ones with guided validation diff --git a/docs/media-buy/capability-discovery/authorized-properties.mdx b/docs/governance/property/authorized-properties.mdx similarity index 97% rename from docs/media-buy/capability-discovery/authorized-properties.mdx rename to docs/governance/property/authorized-properties.mdx index 11d72b671c..37981ec9e5 100644 --- a/docs/media-buy/capability-discovery/authorized-properties.mdx +++ b/docs/governance/property/authorized-properties.mdx @@ -1,5 +1,6 @@ --- -title: Authorized Properties +sidebar_position: 2.5 +title: Understanding Authorization description: Understanding property authorization in AdCP - preventing unauthorized resale and validating sales agent relationships keywords: [authorized properties, adagents.json, unauthorized resale, ads.txt, sales agent authorization, property validation] --- @@ -268,11 +269,11 @@ For complete technical details on implementing the `adagents.json` file format, - Validation code examples and error handling - Security considerations and best practices -See the **[adagents.json Tech Spec](/docs/media-buy/capability-discovery/adagents)** for complete implementation guidance. +See the **[adagents.json Tech Spec](/docs/governance/property/adagents)** for complete implementation guidance. ## Related Documentation - **[`list_authorized_properties`](/docs/media-buy/task-reference/list_authorized_properties)** - API reference for property discovery - **[Product Discovery](/docs/media-buy/product-discovery/)** - How authorization integrates with product discovery - **[Properties Schema](https://adcontextprotocol.org/schemas/v2/core/property.json)** - Technical property data model -- **[adagents.json Tech Spec](/docs/media-buy/capability-discovery/adagents)** - Complete `adagents.json` implementation guide \ No newline at end of file +- **[adagents.json Tech Spec](/docs/governance/property/adagents)** - Complete `adagents.json` implementation guide \ No newline at end of file diff --git a/docs/governance/property/index.mdx b/docs/governance/property/index.mdx new file mode 100644 index 0000000000..d4f8468777 --- /dev/null +++ b/docs/governance/property/index.mdx @@ -0,0 +1,227 @@ +--- +sidebar_position: 1 +title: Overview +--- + + +**AdCP 3.0 Proposal** - This protocol is under development for AdCP 3.0. Feedback welcome via [GitHub Discussions](https://github.com/adcontextprotocol/adcp/discussions). + + +Property Governance standardizes how advertising properties (websites, apps, CTV, podcasts, billboards) are identified, authorized, enriched with data, and selected for campaigns. + +## Overview + +Property Governance addresses four distinct concerns: + +| Concern | Question | Owner | Mechanism | +|---------|----------|-------|-----------| +| **Property Identity** | What properties exist? | Publishers | `adagents.json` properties array | +| **Sales Authorization** | Who can sell this property? | Publishers | `adagents.json` authorized_agents | +| **Property Data** | What do we know about this property? | Data providers | Governance agents via `list_property_features` | +| **Property Selection** | Which properties meet my requirements? | Buyers | Property lists with filters | + +The first two are **publisher-side declarations** via adagents.json. The last two are **buyer-side operations** that consume property data from governance agents. + +## Publisher Side: adagents.json + +Publishers declare their properties and authorize sales agents via `/.well-known/adagents.json`: + +```json +{ + "$schema": "https://adcontextprotocol.org/schemas/v2/adagents.json", + "properties": [ + { + "property_id": "example_site", + "property_type": "website", + "name": "Example Site", + "identifiers": [{"type": "domain", "value": "example.com"}] + } + ], + "authorized_agents": [ + { + "url": "https://agent.example.com", + "authorized_for": "Official sales agent", + "authorization_type": "property_ids", + "property_ids": ["example_site"] + } + ] +} +``` + +See the [adagents.json Tech Spec](/docs/governance/property/adagents) for complete documentation. + +## Buyer Side: Property Data and Selection + +### Property Data Providers + +Governance agents provide data about properties - compliance scores, brand safety ratings, sustainability metrics, etc. They advertise their capabilities via `list_property_features`: + +```json +{ + "features": [ + { "feature_id": "consent_quality", "type": "quantitative", "range": { "min": 0, "max": 100 } }, + { "feature_id": "coppa_certified", "type": "binary" }, + { "feature_id": "carbon_score", "type": "quantitative", "range": { "min": 0, "max": 100 } } + ] +} +``` + +Buyers send property lists to these agents, and the agents filter and score the properties based on their specialized data. Different agents specialize in different data: + +- **Compliance vendors** (data integrity, consent quality) +- **Brand safety providers** (content classification, risk scoring) +- **Quality measurement** (viewability, fraud detection) +- **Sustainability providers** (carbon scoring, green hosting) + +### Property Selection via Governance Agents + +Buyers create **property lists on governance agents** - the agents manage these lists and apply their filtering logic: + +```json +{ + "tool": "create_property_list", + "arguments": { + "name": "Q1 Campaign - UK Premium", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news"] + } + ], + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 85, "max_value": 100 } + ] + }, + "brand_manifest": { + "brand_name": "ToyBrand", + "industry": "toys", + "target_audience": "children_under_13" + } + } +} +``` + +When you provide a brand manifest, governance agents automatically apply appropriate rules (COPPA for children's brands, content filtering based on industry, etc.). + +A buyer agent typically works with **multiple governance agents** (consent, brand safety, sustainability) and aggregates/intersects their results into a final compliant list. + +## How It Fits Together + +```mermaid +flowchart TB + subgraph Buyer["BUYER AGENT"] + B1[Source of truth for compliant list] + B2[Aggregates results from specialized agents] + B3[Issues auth_tokens for sellers] + end + + subgraph Governance["GOVERNANCE AGENTS"] + CA["Consent Agent
consent_quality
tcf_version
coppa_certified"] + S3["Scope3 Agent
carbon_score
climate_risk
green_hosting"] + BS["Brand Safety Agent
content_category
brand_risk
sentiment"] + end + + subgraph Seller["SELLER AGENT (DSP/SSP)"] + SE1[Caches resolved property lists] + SE2[Uses cached lists for bid-time decisions] + end + + Buyer -->|create_property_list + webhooks| CA + Buyer -->|create_property_list + webhooks| S3 + Buyer -->|create_property_list + webhooks| BS + + Buyer -->|get_property_list with auth_token| Seller +``` + +## Sharing Property Lists with Sellers + +Once a buyer has a compliant property list, they share it with sellers: + +1. **Get a list reference**: The buyer agent exposes the list via `get_property_list` +2. **Issue an auth token**: The buyer generates a token that authorizes access to the list +3. **Pass to seller**: Include `property_list_ref` with `auth_token` in product discovery or media buy requests +4. **Seller caches locally**: Sellers fetch and cache the resolved list for bid-time decisions +5. **Webhooks for updates**: When the list changes, sellers are notified to refresh their cache + +```json +{ + "property_list_ref": { + "agent_url": "https://buyer-agent.example.com", + "list_id": "pl_q1_uk_premium", + "auth_token": "eyJhbGciOiJIUzI1NiIs..." + } +} +``` + +Sellers use this reference in `get_products` to filter available inventory: + +```json +{ + "tool": "get_products", + "arguments": { + "brief": "UK video inventory for Q1", + "property_list_ref": { + "agent_url": "https://buyer-agent.example.com", + "list_id": "pl_q1_uk_premium", + "auth_token": "..." + } + } +} +``` + +## Relationship to Other Protocols + +### Property Governance + Media Buy + +The Media Buy Protocol consumes property lists at multiple stages: + +- **Product discovery**: Pass `property_list_ref` to `get_products` to filter inventory to compliant properties +- **Media buy creation**: Reference property lists to constrain where ads can run +- **Authorization**: adagents.json validates agent authority to sell + +### Property Governance + Signals + +Both protocols operate on properties but serve different purposes: + +| Signals Protocol | Property Governance | +|------------------|---------------------| +| Audience/contextual data | Property metadata and compliance | +| "Who should see this ad?" | "Where can this ad run?" | +| Signal activation | Property filtering | + +## Tasks + +### Discovery + +- **[list_property_features](/docs/governance/property/tasks/list_property_features)**: Discover what data a governance agent can provide about properties + +### Property List Management + +- **[create_property_list](/docs/governance/property/tasks/property_lists#create_property_list)**: Create a new property list on a governance agent +- **[get_property_list](/docs/governance/property/tasks/property_lists#get_property_list)**: Retrieve resolved properties (with caching guidance) +- **[update_property_list](/docs/governance/property/tasks/property_lists#update_property_list)**: Modify filters or base properties +- **[delete_property_list](/docs/governance/property/tasks/property_lists#delete_property_list)**: Remove a property list + +## Getting Started + +**Publishers:** +1. Create `/.well-known/adagents.json` with property definitions +2. Authorize sales agents for your properties + +**Buyers:** +1. Subscribe to governance agents for property data +2. Create property lists on each governance agent with filters and brand manifests +3. Aggregate results into a final compliant list +4. Share property list references with sellers (with auth tokens) + +**Governance Agent Implementers:** +1. Implement `list_property_features` to advertise your capabilities +2. Implement property list CRUD operations +3. Support webhooks to notify buyers when evaluations change +4. See the [Protocol Specification](/docs/governance/property/specification) for implementation details + +See the [Protocol Specification](/docs/governance/property/specification) for detailed implementation guidance. diff --git a/docs/governance/property/specification.mdx b/docs/governance/property/specification.mdx new file mode 100644 index 0000000000..343d1c7e32 --- /dev/null +++ b/docs/governance/property/specification.mdx @@ -0,0 +1,611 @@ +--- +sidebar_position: 2 +title: Protocol Specification +--- + + +**AdCP 3.0 Proposal** - This specification is under development for AdCP 3.0. Feedback welcome via [GitHub Discussions](https://github.com/adcontextprotocol/adcp/discussions). + + +**Status**: Request for Comments +**Last Updated**: January 2026 + +## Abstract + +The Property Protocol defines a standard Model Context Protocol (MCP) and Agent-to-Agent (A2A) interface for property identity, authorization, data provision, and selection. This protocol enables publishers to declare properties and authorized agents, data providers to offer property intelligence, and buyers to select compliant property sets. + +## Overview + +The Property Protocol addresses four distinct concerns: + +| Concern | Question | Owner | Mechanism | +|---------|----------|-------|-----------| +| **Property Identity** | What properties exist? | Publishers | `adagents.json` properties array | +| **Sales Authorization** | Who can sell this property? | Publishers | `adagents.json` authorized_agents | +| **Property Data** | What do we know about this property? | Data providers | Governance agents via `list_property_features` | +| **Property Selection** | Which properties meet my requirements? | Buyers | Property lists with filters | + +The first two are **publisher-side declarations** via adagents.json. The last two are **buyer-side operations** that consume property data from governance agents. + +### Property Data and Selection + +Property data and selection use a **stateful** model: + +- **Feature discovery**: Agents advertise what they can evaluate via `list_property_features` +- **Property list management**: CRUD operations for managed property lists with filters +- **Brand manifests**: Let agents automatically apply rules based on brand characteristics +- **Webhook notifications**: Real-time updates when resolved lists change +- **Marketplace architecture**: Multiple specialized agents as subscription services + +All evaluation (scoring, filtering, discovery) happens implicitly when property lists are resolved via `get_property_list`. + +## Core Concepts + +### Request Roles and Relationships + +Every governance request involves two key roles: + +#### Orchestrator (Buyer Agent) +The platform or system making the API request to the governance agent. In AdCP documentation, this role is often called a "buyer agent" when operating in the media buying context. +- **Examples**: DSP, trading desk platform, campaign management tool +- **Responsibilities**: Makes API calls, handles authentication, manages the technical interaction +- **Account**: Has technical credentials and API access to the governance agent + +#### Principal +The entity on whose behalf the request is being made: +- **Examples**: Advertiser (Nike), agency (Omnicom), brand team +- **Responsibilities**: Owns the campaign objectives and policy requirements +- **Policies**: May have custom thresholds, blocklists, or compliance requirements + +### Property Identification + +Properties are identified using the standard AdCP property model: + +```json +{ + "property_type": "website", + "name": "Example News", + "identifiers": [ + { "type": "domain", "value": "example.com" } + ] +} +``` + +Property types include: `website`, `mobile_app`, `ctv_app`, `dooh`, `podcast`, `radio`, `streaming_audio`. + +### Property List References + +For large property sets, use property list references instead of embedding properties: + +```json +{ + "property_list_ref": { + "agent_url": "https://lists.example.com", + "list_id": "premium_news_sites", + "auth_token": "eyJhbGciOiJIUzI1NiIs..." + } +} +``` + +The receiving agent fetches and caches the list independently, enabling: +- **Scale**: Pass 50,000+ properties without payload bloat +- **Updates**: Lists evolve without changing requests +- **Authorization**: Token controls access to the list + +### Governance Agent Types + +#### Compliance Agents +Specialized vendors providing property compliance intelligence: +- **Examples**: Data integrity scoring, consent quality measurement +- **Business Model**: Subscription or per-query pricing +- **Methodology**: Published rubrics for transparency + +#### Brand Safety Agents +Content classification and risk assessment: +- **Examples**: Content categorization, brand safety scoring +- **Coverage**: May specialize by channel or geography + +#### Quality Agents +Performance and fraud measurement: +- **Examples**: Viewability prediction, IVT detection +- **Integration**: May correlate with campaign outcomes + +### Scoring and Data Privacy + +#### Scores Are Internal + +**Critical design principle**: Raw scores are NOT shared with buyers or downstream clients. This prevents data leakage. + +Governance agents maintain internal scoring models, but the protocol is designed around **list management**, not score exposure: + +- Buyers specify **thresholds** via `feature_requirements` (e.g., `"min_value": 85`) +- Agents return **pass/fail lists** of properties that meet the thresholds +- Raw scores never leave the governance agent + +This design prevents: +- Score enumeration attacks (running lists with different thresholds to reverse-engineer scores) +- Competitive intelligence leakage +- Data arbitrage where buyers resell scoring data + +#### What Buyers Receive + +When calling `get_property_list`, buyers receive a compact list of identifiers (not full property objects) for efficiency: + +```json +{ + "list_id": "pl_abc123", + "identifiers": [ + { "type": "domain", "value": "bbc.co.uk" }, + { "type": "domain", "value": "theguardian.com" }, + { "type": "domain", "value": "ft.com" } + ], + "total_count": 847 +} +``` + +Properties that pass the threshold are included. Properties that fail are excluded. No scores or property metadata are returned - just the identifiers needed for bid-time lookups. + +#### Methodology Discovery + +The `list_property_features` task returns information about what features an agent evaluates and their methodology, but NOT the underlying scores: + +```json +{ + "features": [ + { + "feature_id": "consent_quality", + "name": "Consent Quality Score", + "type": "quantitative", + "range": { "min": 0, "max": 100 }, + "methodology": "data_integrity_index", + "methodology_version": "v2.1", + "methodology_url": "https://compliance.example.com/methodology" + } + ] +} +``` + +This allows buyers to: +- Understand what an agent measures +- Compare methodologies across agents +- Set appropriate thresholds + +But they cannot retrieve the actual scores for individual properties. + +## Tasks + +### Discovery + +#### list_property_features + +Discover what features a governance agent can evaluate. + +**Use Cases**: +- Capability discovery: Understand what an agent can evaluate +- Marketplace browsing: Compare features across agents +- Integration planning: Know what filters are available before creating lists + +### Property List Management + +#### create_property_list + +Create a new property list with filters and optional brand manifest. + +**Required Parameters**: +- At least one country in `countries_all` (ISO country code) +- At least one channel in `channels_any` (display, video, audio, etc.) + +**Base Properties**: An array of property sources to evaluate. Each entry is a discriminated union with `selection_type` as the discriminator: +- **`publisher_tags`**: `{ "selection_type": "publisher_tags", "publisher_domain": "...", "tags": [...] }` - tags scoped to publisher +- **`publisher_ids`**: `{ "selection_type": "publisher_ids", "publisher_domain": "...", "property_ids": [...] }` - property IDs scoped to publisher +- **`identifiers`**: `{ "selection_type": "identifiers", "identifiers": [...] }` - no publisher context needed +- **Omitted**: Query the agent's entire property database + +See the [base-property-source schema](https://adcontextprotocol.org/schemas/v2/property/base-property-source.json) for the full specification. + +**Filter Logic** (explicit in field names): +- `countries_all`: Property must have feature data for **ALL** listed countries +- `channels_any`: Property must support **ANY** of the listed channels +- `feature_requirements`: Property must pass **ALL** requirements (AND) + +**Use Cases**: +- Define compliant property sets with filters (country, channel, feature thresholds) +- Provide brand manifest for automatic rule application +- Register webhook URL for change notifications + +#### update_property_list + +Modify an existing property list. + +**Use Cases**: +- Add or remove properties from base list +- Adjust filters based on campaign needs +- Update webhook URL + +#### get_property_list + +Retrieve a property list with resolved properties. + +**Use Cases**: +- Get the current list of compliant properties after filters applied +- Cache resolved list for bid-time use +- Retrieve updated list after webhook notification + +#### list_property_lists + +List all property lists accessible to the authenticated principal. + +#### delete_property_list + +Remove a property list. + +### Validation + +#### validate_property_delivery + +Validates delivery records against a property list to determine compliance. Closes the loop between "what I wanted" and "what I got." + +Performs two independent validations: +1. **Property compliance**: Is the identifier in the resolved property list? +2. **Supply path authorization**: Was the sales agent authorized to sell that property? (optional, requires `sales_agent_url`) + +**Use Cases**: +- Post-campaign validation: Verify impressions landed on compliant properties +- Supply path verification: Confirm sales agents were authorized by publishers +- Real-time monitoring: Check compliance rate during campaign execution +- Audit trails: Generate compliance reports for regulatory or brand safety reviews + +**Property Validation Statuses**: +- `compliant`: Identifier is in the resolved property list +- `non_compliant`: Identifier is NOT in the resolved property list +- `not_covered`: Identifier recognized but governance agent has no data for it (e.g., property too new) +- `unidentified`: Identifier type not resolvable by this agent (e.g., detection failed, unsupported type) + +**Authorization Validation Statuses** (when `sales_agent_url` provided): +- `authorized`: Sales agent is listed in publisher's adagents.json +- `unauthorized`: Sales agent is NOT in publisher's authorized_agents list +- `unknown`: Could not fetch or parse adagents.json + +**Unverifiable Records**: Both `not_covered` and `unidentified` records should be excluded when calculating compliance rates - you cannot penalize for detection gaps or coverage limitations. The distinction helps identify whether the gap is in the agent's data coverage vs the identifier resolution. + +**Response Format**: The response returns raw counts (compliant, non_compliant, not_covered, unidentified impressions). Consumers calculate rates as needed. Governance agents may optionally include an `aggregate` field with computed metrics (score, grade, label) - the format and meaning are agent-specific. + +## Typical Flows + +### Property List Flow + +Property lists enable buyers to define and manage compliant property sets: + +1. **Create property list**: Buyer defines list on governance agent with filters +2. **Resolve and iterate**: Buyer calls `get_property_list` to see resolved properties +3. **Share list reference**: Buyer provides `list_id` to orchestrator/seller +4. **Cache locally**: Orchestrator/seller fetches and caches resolved properties +5. **Use at bid time**: Orchestrator/seller uses local cache (no governance agent calls) +6. **Refresh periodically**: Re-fetch based on `cache_valid_until` (typically 1-24 hours) + +**Important**: Governance agents are NOT in the real-time bid path. All bid-time decisions use locally cached property sets. + +### Webhook and Caching Pattern + +Webhooks provide **notification** that a property list has changed. The webhook payload contains a summary of changes, but you must call `get_property_list` to retrieve the actual updated properties. + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Webhook Flow │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. Governance agent re-evaluates properties (background) │ +│ 2. Webhook fires with change summary (added/removed counts) │ +│ 3. Recipient calls get_property_list to fetch updated list │ +│ 4. Recipient updates local cache │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +**Best Practices for Downstream Consumers**: + +Consumers of property lists (orchestrators, sellers, buyer agents) should implement **at least one** of these patterns: + +1. **Webhook-driven updates** (recommended): Register a webhook URL when creating the property list. Re-fetch via `get_property_list` when notified of changes. + +2. **Polling with cache hints**: Use `cache_valid_until` from `get_property_list` responses to schedule periodic re-fetches. Typical validity periods are 1-24 hours. + +3. **Hybrid approach**: Use webhooks for immediate updates, with polling as a fallback safety net. + +**Cache Expiry Guidance**: + +Every `get_property_list` response includes: +- `resolved_at`: When the list was evaluated +- `cache_valid_until`: When consumers should consider the cache stale + +```json +{ + "resolved_at": "2026-01-04T10:00:00Z", + "cache_valid_until": "2026-01-04T22:00:00Z" +} +``` + +Consumers MUST NOT use cached data beyond `cache_valid_until` without re-fetching. + +### Property Discovery Flow + +1. **Define filters**: Specify country, channel, quality thresholds when creating property list +2. **Resolve list**: Call `get_property_list` with `resolve=true` to get matching properties +3. **Review candidates**: Evaluate returned properties for fit +4. **Add to campaign**: Include property list reference in media buy + +## Response Structure + +All AdCP Governance responses follow a consistent structure: + +### Core Response Fields +- **message**: Human-readable summary of the operation result +- **context_id**: Session continuity identifier for follow-up requests +- **data**: Task-specific payload (varies by task) + +### Protocol Transport +- **MCP**: Returns complete response as flat JSON object +- **A2A**: Returns as structured artifacts with message in text part, data in data part +- **Data Consistency**: Both protocols contain identical AdCP data structures + +## Error Handling + +### Error Codes + +- `PROPERTY_NOT_FOUND`: Property identifier not recognized +- `PROPERTY_NOT_MONITORED`: Governance agent doesn't cover this property +- `POLICY_NOT_FOUND`: Referenced policy doesn't exist +- `LIST_ACCESS_DENIED`: Cannot access property list (auth failed) +- `LIST_NOT_FOUND`: Property list reference invalid +- `METHODOLOGY_NOT_SUPPORTED`: Requested methodology version unavailable +- `PARTIAL_RESULTS`: Some properties couldn't be evaluated + +### Partial Success + +For bulk operations, the response may include partial results: + +```json +{ + "message": "Evaluated 847 of 850 properties. 3 properties not in coverage.", + "context_id": "ctx-gov-123", + "scores": [...], + "errors": [ + { + "code": "PROPERTY_NOT_MONITORED", + "property": { "identifiers": [{ "type": "domain", "value": "unknown.com" }] }, + "message": "Property not in monitoring coverage" + } + ] +} +``` + +## Implementation Notes + +### Caching Architecture + +Governance decisions are highly cacheable: + +#### Orchestrator-Side Caching +- **Score cache**: Store scores with TTL from `valid_until` field +- **Decision cache**: Pre-compute pass/fail for campaigns +- **List cache**: Cache property lists from `property_list_ref` + +#### Agent-Side Caching +- **Profile cache**: Maintain pre-computed property profiles +- **Methodology cache**: Cache scoring algorithm results + +### Performance Requirements + +| Operation | Target Latency | +|-----------|----------------| +| Single property score | < 100ms | +| Bulk scoring (100 properties) | < 2s | +| Filter decision (cached) | < 5ms | +| Property discovery | < 5s | + +### Multi-Agent Strategies + +Orchestrators may consult multiple governance agents: + +1. **Primary + Validation**: Use primary agent, validate with secondary +2. **Specialization**: Route by property type to specialist agents +3. **Consensus**: Require multiple agents to agree +4. **Competitive**: Track agent accuracy, weight by performance + +## Agent Discovery + +Governance agents expose capabilities via `.well-known/agent-card.json`: + +```json +{ + "name": "Example Compliance Provider", + "url": "https://compliance.example.com", + "capabilities": { + "tasks": [ + "list_property_features", + "create_property_list", + "get_property_list", + "update_property_list", + "delete_property_list", + "list_property_lists", + "validate_property_delivery" + ], + "protocols": ["MCP", "A2A"], + "schema_version": "v1" + }, + "methodology": { + "documentation_url": "https://compliance.example.com/methodology", + "scoring_frameworks": ["data_integrity_index", "brand_safety_score"], + "coverage": { + "property_types": ["website", "mobile_app", "ctv_app"], + "jurisdictions": ["GDPR", "CCPA", "COPPA"] + } + } +} +``` + +Use `list_property_features` for detailed capability discovery: + +```json +{ + "tool": "list_property_features", + "arguments": {} +} +``` + +Returns the specific features the agent can evaluate (consent_quality, carbon_score, brand_risk, etc.). + +## Marketplace Architecture + +The Property Protocol enables a marketplace of specialized data agents: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ SELLER AGENT (DSP/SSP) │ +│ "Give me the compliant property list for this campaign" │ +└───────────────────────────────┬─────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ BUYER AGENT (implements Property Protocol) │ +│ - Exposes: list_property_features, get_property_list, webhooks │ +│ - Source of truth for final compliant list │ +│ - Intersects results from specialized agents │ +└───────────────────────────┬─────────────────────────────────────────────┘ + │ + ┌───────────────────┼───────────────────┐ + ▼ ▼ ▼ +┌───────────────┐ ┌───────────────┐ ┌───────────────┐ +│ Consent Agent │ │ Scope3 Agent │ │ Brand Safety │ +│ (Compliant) │ │ │ │ Agent │ +├───────────────┤ ├───────────────┤ ├───────────────┤ +│ Features: │ │ Features: │ │ Features: │ +│ consent_qual │ │ carbon_score │ │ content_cat │ +│ tcf_version │ │ climate_risk │ │ brand_risk │ +│ coppa_cert │ │ green_host │ │ sentiment │ +├───────────────┤ ├───────────────┤ ├───────────────┤ +│ Subscription │ │ Subscription │ │ Subscription │ +└───────────────┘ └───────────────┘ └───────────────┘ +``` + +### Key Principles + +1. **Buyer agent is source of truth**: The buyer agent aggregates data from multiple specialized governance agents +2. **Seller sees one interface**: Sellers interact only with the buyer agent using standard Property Protocol +3. **Subscription model**: Each specialized agent is a paid service with its own features and coverage +4. **Webhook-driven updates**: Specialized agents notify the buyer agent when property evaluations change + +### Multi-Agent Orchestration + +A buyer agent can distribute a master property list to multiple specialized agents: + +```python +# Buyer agent creates variants on each specialized agent +consent_list = consent_agent.create_property_list( + name="Q1 Campaign - Consent", + base_properties=master_list, + brand_manifest=brand_manifest +) +# Configure webhook for updates +consent_agent.update_property_list( + list_id=consent_list.list_id, + webhook_url="https://buyer.example.com/webhooks/consent" +) + +scope3_list = scope3_agent.create_property_list( + name="Q1 Campaign - Sustainability", + base_properties=master_list, + brand_manifest=brand_manifest +) +scope3_agent.update_property_list( + list_id=scope3_list.list_id, + webhook_url="https://buyer.example.com/webhooks/scope3" +) + +# Buyer agent intersects filtered results +def on_list_changed(event): + consent_props = consent_agent.get_property_list(consent_list.list_id, resolve=True) + scope3_props = scope3_agent.get_property_list(scope3_list.list_id, resolve=True) + + # Intersection = properties that pass ALL governance agents + compliant_props = intersect(consent_props, scope3_props) + + # Update buyer agent's exposed list + update_compliant_list(compliant_props) +``` + +### Brand Manifest + +Instead of specifying complex filters, buyers provide a brand manifest: + +```json +{ + "brand_manifest": { + "brand_name": "ToyBrand", + "industry": "toys", + "target_audience": "children_under_13", + "content_adjacency": ["kids_family", "education"], + "excluded_content": ["violence", "adult"] + } +} +``` + +Each governance agent interprets the manifest according to their domain expertise: +- **Consent agent**: Applies COPPA requirements for children_under_13 +- **Brand safety agent**: Filters to kids_family content, excludes violence/adult +- **Sustainability agent**: Applies any green media requirements + +The buyer doesn't need to know the specific rules - they declare who they are, and agents figure out what applies. + +## Integration with Media Buy Protocol + +### Property Lists in Media Buys + +The Media Buy Protocol accepts property list references: + +```json +{ + "task": "create_media_buy", + "arguments": { + "packages": [{ + "property_list_ref": { + "agent_url": "https://governance.example.com", + "list_id": "approved_q1_campaign", + "auth_token": "..." + } + }] + } +} +``` + +### Policy Compliance + +Media buys can reference governance policies via property list references: + +```json +{ + "compliance_requirements": { + "property_list_ref": { + "agent_url": "https://compliance.example.com", + "list_id": "pl_q1_compliant", + "auth_token": "eyJhbGciOiJIUzI1NiIs..." + } + } +} +``` + +## Best Practices + +1. **Cache aggressively**: Property scores change slowly; cache for hours/days +2. **Bulk where possible**: Use batch operations for planning, not per-property calls +3. **Pre-compute decisions**: Build pass/fail lookups before bid-time +4. **Monitor coverage**: Track which properties agents don't cover +5. **Log methodology versions**: For audit trails, record which scoring version was used +6. **Handle partial results**: Not all properties will be scorable; plan for gaps + +## Next Steps + +- See the [adagents.json Tech Spec](/docs/governance/property/adagents) for property declaration and authorization +- See the [list_property_features task reference](/docs/governance/property/tasks/list_property_features) for capability discovery +- See the [Property List Management](/docs/governance/property/tasks/property_lists) for CRUD operations and webhooks +- See the [validate_property_delivery task reference](/docs/governance/property/tasks/validate_property_delivery) for post-campaign compliance validation diff --git a/docs/governance/property/tasks/index.mdx b/docs/governance/property/tasks/index.mdx new file mode 100644 index 0000000000..4679e3704b --- /dev/null +++ b/docs/governance/property/tasks/index.mdx @@ -0,0 +1,153 @@ +--- +sidebar_position: 1 +title: Task Reference +--- + +# Property Governance Tasks + + +**AdCP 3.0 Proposal** - These tasks are under development for AdCP 3.0. + + +Property governance uses a **stateful** model where all evaluation happens through property list management. Create lists with filters and brand manifests, then resolve them to get compliant properties. + +## Discovery + +| Task | Purpose | Response Time | +|------|---------|---------------| +| [list_property_features](/docs/governance/property/tasks/list_property_features) | Discover agent capabilities | ~200ms | + +## Property List Management + +| Task | Purpose | Response Time | +|------|---------|---------------| +| [create_property_list](/docs/governance/property/tasks/property_lists#create_property_list) | Create a new property list | ~500ms | +| [update_property_list](/docs/governance/property/tasks/property_lists#update_property_list) | Modify an existing list | ~500ms | +| [get_property_list](/docs/governance/property/tasks/property_lists#get_property_list) | Retrieve list with resolved properties | ~2-5s | +| [list_property_lists](/docs/governance/property/tasks/property_lists#list_property_lists) | List all property lists | ~500ms | +| [delete_property_list](/docs/governance/property/tasks/property_lists#delete_property_list) | Delete a property list | ~200ms | + +See [Property List Management](/docs/governance/property/tasks/property_lists) for complete CRUD documentation. + +## Validation + +| Task | Purpose | Response Time | +|------|---------|---------------| +| [validate_property_delivery](/docs/governance/property/tasks/validate_property_delivery) | Validate delivery records against a list | ~1-5s | + +See [validate_property_delivery](/docs/governance/property/tasks/validate_property_delivery) for post-campaign compliance validation. + +## Task Selection Guide + +### Creating a Property List + +Use `create_property_list` with filters and brand manifest: + +```json +{ + "tool": "create_property_list", + "arguments": { + "name": "Q1 Campaign - UK Premium", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news"] + } + ], + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { + "feature_id": "consent_quality", + "min_value": 85, + "max_value": 100 + }, + { + "feature_id": "coppa_certified", + "allowed_values": [true] + } + ] + }, + "brand_manifest": { + "name": "ToyBrand", + "industry": "toys", + "target_audience": "children_under_13" + } + } +} +``` + +**Required filters**: At least one country in `countries_all` and one channel in `channels_any` must be provided. + +**Base properties**: An array of property sources to evaluate. Each entry is a discriminated union with `selection_type`: +- **`publisher_tags`**: `{ "selection_type": "publisher_tags", "publisher_domain": "...", "tags": [...] }` +- **`publisher_ids`**: `{ "selection_type": "publisher_ids", "publisher_domain": "...", "property_ids": [...] }` +- **`identifiers`**: `{ "selection_type": "identifiers", "identifiers": [...] }` +- **Omitted**: Query the agent's entire property database + +**Filter logic** (explicit in field names): +- `countries_all`: Property must have feature data for ALL listed countries +- `channels_any`: Property must support ANY of the listed channels +- `feature_requirements`: Property must pass ALL requirements (AND) + +Filters have two built-in fields (`countries_all`, `channels_any`) plus `feature_requirements` which reference features the agent provides (discovered via `list_property_features`). For quantitative features, use `min_value`/`max_value`. For binary or categorical features, use `allowed_values`. + +### Getting Resolved Properties + +Use `get_property_list` to retrieve the list with resolved identifiers: + +```json +{ + "tool": "get_property_list", + "arguments": { + "list_id": "pl_abc123", + "resolve": true + } +} +``` + +Response includes resolved identifiers. Note that **raw scores are not returned** - only identifiers that pass the filter thresholds are included: + +```json +{ + "list_id": "pl_abc123", + "identifiers": [ + { "type": "domain", "value": "bbc.co.uk" }, + { "type": "domain", "value": "news.sky.com" } + ], + "cache_valid_until": "2026-01-04T17:15:00Z" +} +``` + +The `auth_token` for sharing with sellers is returned at creation time (from `create_property_list`). Store it securely - it's only returned once. + +### Multi-Agent Integration + +Create the same property list on multiple governance agents, then configure webhooks to aggregate results: + +```python +# Create lists on specialized agents +consent_list = consent_agent.create_property_list( + name="Q1 - Consent", + base_properties=master_list, + brand_manifest=brand_manifest +) +consent_agent.update_property_list( + list_id=consent_list.list_id, + webhook_url="https://buyer.example.com/webhooks/consent" +) + +scope3_list = scope3_agent.create_property_list( + name="Q1 - Sustainability", + base_properties=master_list, + brand_manifest=brand_manifest +) +scope3_agent.update_property_list( + list_id=scope3_list.list_id, + webhook_url="https://buyer.example.com/webhooks/scope3" +) + +# Buyer agent intersects results when webhooks fire +``` diff --git a/docs/governance/property/tasks/list_property_features.mdx b/docs/governance/property/tasks/list_property_features.mdx new file mode 100644 index 0000000000..6c7dcd16a7 --- /dev/null +++ b/docs/governance/property/tasks/list_property_features.mdx @@ -0,0 +1,235 @@ +--- +sidebar_position: 1 +title: list_property_features +--- + + +**AdCP 3.0 Proposal** - This task is under development for AdCP 3.0. + + +**Task**: Discover what features a property governance agent can evaluate. + +**Response Time**: ~200ms + +**Request Schema**: [`https://adcontextprotocol.org/schemas/v1/governance/list-property-features-request.json`](https://adcontextprotocol.org/schemas/v1/governance/list-property-features-request.json) +**Response Schema**: [`https://adcontextprotocol.org/schemas/v1/governance/list-property-features-response.json`](https://adcontextprotocol.org/schemas/v1/governance/list-property-features-response.json) + +The `list_property_features` task returns the features a governance agent can evaluate. This is the discovery mechanism for understanding an agent's capabilities before using other governance tasks. + +## Request Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `property_types` | string[] | No | Filter to features available for these property types | +| `countries` | string[] | No | Filter to features available in these countries | + +## Response Structure + +All AdCP responses include: +- **message**: Human-readable summary of the operation result +- **context_id**: Session continuity identifier for follow-up requests +- **data**: Task-specific payload (see Response Data below) + +## Response Data + +```json +{ + "features": [ + { + "feature_id": "consent_quality", + "name": "Consent Quality Score", + "description": "Measures the quality of consent implementation including UX, granularity, and compliance", + "type": "quantitative", + "range": { "min": 0, "max": 100 }, + "coverage": { + "property_types": ["website", "mobile_app"], + "countries": [] + } + }, + { + "feature_id": "coppa_certified", + "name": "COPPA Certified", + "description": "Whether the property has COPPA certification for child-directed content", + "type": "binary", + "coverage": { + "property_types": ["website", "mobile_app", "ctv_app"], + "countries": ["US"] + } + }, + { + "feature_id": "content_category", + "name": "Content Category", + "description": "IAB content category classification", + "type": "categorical", + "allowed_values": ["news", "sports", "entertainment", "kids_family", "technology", "business", "adult", "violence"] + } + ] +} +``` + +### Feature Types + +| Type | Description | Schema Fields | +|------|-------------|---------------| +| `binary` | True/false values | None additional | +| `quantitative` | Numeric values within a range | `range: { min, max }` | +| `categorical` | Enumerated string values | `allowed_values: string[]` | + +### Coverage + +The `coverage` object indicates where a feature applies: +- **property_types**: Empty array means all property types +- **countries**: Empty array means all countries + +## Protocol-Specific Examples + +### MCP Request + +```json +{ + "tool": "list_property_features", + "arguments": {} +} +``` + +### MCP Response + +```json +{ + "message": "This agent evaluates 12 features across consent, brand safety, and sustainability.", + "context_id": "ctx-gov-features-123", + "features": [ + { + "feature_id": "consent_quality", + "name": "Consent Quality Score", + "description": "Measures the quality of consent implementation", + "type": "quantitative", + "range": { "min": 0, "max": 100 } + }, + { + "feature_id": "tcf_version", + "name": "TCF Version", + "description": "IAB Transparency & Consent Framework version supported", + "type": "categorical", + "allowed_values": ["none", "tcf_1.1", "tcf_2.0", "tcf_2.2"] + }, + { + "feature_id": "coppa_certified", + "name": "COPPA Certified", + "description": "Whether property has COPPA certification", + "type": "binary", + "coverage": { + "property_types": ["website", "mobile_app", "ctv_app"], + "countries": ["US"] + } + } + ] +} +``` + +### MCP Request - Filtered by Property Type + +```json +{ + "tool": "list_property_features", + "arguments": { + "property_types": ["mobile_app"] + } +} +``` + +### A2A Request + +```javascript +await a2a.send({ + message: { + parts: [{ + kind: "data", + data: { + skill: "list_property_features", + parameters: {} + } + }] + } +}); +``` + +## Example Feature Sets by Agent Type + +### Consent/Compliance Agent + +```json +{ + "features": [ + { "feature_id": "consent_quality", "type": "quantitative", "range": { "min": 0, "max": 100 } }, + { "feature_id": "tcf_version", "type": "categorical", "allowed_values": ["none", "tcf_2.0", "tcf_2.2"] }, + { "feature_id": "coppa_certified", "type": "binary" }, + { "feature_id": "gpp_supported", "type": "binary" }, + { "feature_id": "vendor_count", "type": "quantitative", "range": { "min": 0, "max": 1000 } } + ] +} +``` + +### Sustainability Agent (e.g., Scope3) + +```json +{ + "features": [ + { "feature_id": "carbon_score", "type": "quantitative", "range": { "min": 0, "max": 100 } }, + { "feature_id": "green_certified", "type": "binary" }, + { "feature_id": "renewable_hosting", "type": "binary" }, + { "feature_id": "emissions_per_impression", "type": "quantitative", "range": { "min": 0, "max": 100 } } + ] +} +``` + +### Brand Safety Agent + +```json +{ + "features": [ + { "feature_id": "content_category", "type": "categorical", "allowed_values": ["news", "sports", ...] }, + { "feature_id": "brand_risk_score", "type": "quantitative", "range": { "min": 0, "max": 100 } }, + { "feature_id": "sentiment", "type": "categorical", "allowed_values": ["positive", "neutral", "negative"] }, + { "feature_id": "made_for_advertising", "type": "binary" } + ] +} +``` + +## Integration Pattern + +Use `list_property_features` to understand agent capabilities before creating property lists: + +```python +# Step 1: Discover agent capabilities +features_response = governance_agent.list_property_features() + +# Step 2: Build feature_requirements using available features +feature_requirements = [] +for feature in features_response.features: + if feature.feature_id == "consent_quality" and feature.type == "quantitative": + feature_requirements.append({ + "feature_id": "consent_quality", + "min_value": 80, + "max_value": 100 + }) + if feature.feature_id == "coppa_certified" and feature.type == "binary": + feature_requirements.append({ + "feature_id": "coppa_certified", + "allowed_values": [True] + }) + +# Step 3: Create property list with feature requirements +governance_agent.create_property_list( + name="Q1 Campaign", + filters={"feature_requirements": feature_requirements}, + brand_manifest=my_brand_manifest +) +``` + +## Usage Notes + +1. **Call first**: Use this task to discover capabilities before other governance tasks +2. **Cache results**: Feature definitions change infrequently; cache for hours/days +3. **Check coverage**: Not all features apply to all property types or countries +4. **Marketplace discovery**: Use this to understand what different governance agents offer diff --git a/docs/governance/property/tasks/property_lists.mdx b/docs/governance/property/tasks/property_lists.mdx new file mode 100644 index 0000000000..a46e54545f --- /dev/null +++ b/docs/governance/property/tasks/property_lists.mdx @@ -0,0 +1,701 @@ +--- +sidebar_position: 5 +title: Property List Management +--- + + +**AdCP 3.0 Proposal** - These tasks are under development for AdCP 3.0. + + +**Tasks**: Create, update, get, list, and delete property lists. + +Property lists are managed resources that combine static property sets with dynamic filters. When resolved, filters are applied to produce the final property set. + +## Architecture: Setup Time, Not Real-Time + +Property lists are designed for **setup-time** operations, not real-time bid decisions: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ SETUP TIME (Campaign Planning) │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. Buyer creates/updates property list on governance agent │ +│ 2. Buyer resolves list to get current properties │ +│ 3. Buyer provides list_id to orchestrator/seller │ +│ 4. Orchestrator/seller fetches and caches resolved list │ +│ 5. Campaign targets only cached compliant properties │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ BID TIME (Milliseconds) │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ • Orchestrator/seller uses LOCAL cache only │ +│ • NO calls to governance agent │ +│ • Pass/fail from cached property set │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ REFRESH (Periodic) │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ • Orchestrator/seller re-fetches list on schedule │ +│ • Frequency based on cache_valid_until │ +│ • Typically every 1-24 hours │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +This enables: + +- **Static lists**: Curated sets of approved properties +- **Dynamic lists**: Properties matching criteria (country, channel, score thresholds) +- **Hybrid lists**: Base set modified by filters + +## Tasks Overview + +| Task | Purpose | Response Time | +|------|---------|---------------| +| `create_property_list` | Create a new property list | ~500ms | +| `update_property_list` | Modify an existing list | ~500ms | +| `get_property_list` | Retrieve list with resolved properties | ~2-5s (depending on size) | +| `list_property_lists` | List all property lists | ~500ms | +| `delete_property_list` | Delete a property list | ~200ms | + +## Property List Structure + +A property list contains: + +```json +{ + "list_id": "uk_premium_news_q1", + "name": "UK Premium News Sites Q1 2026", + "description": "High-quality UK news sites for Q1 campaign", + "principal": "did:principal:brand-x", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news", "uk_tier1"] + } + ], + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 90, "max_value": 100 } + ] + }, + "brand_manifest": { + "industry": "retail", + "target_audience": "general" + }, + "created_at": "2026-01-03T10:00:00Z", + "updated_at": "2026-01-03T10:00:00Z", + "property_count": 847 +} +``` + +## Brand Manifest + +Instead of manually specifying all filters, provide a brand manifest and let the governance agent apply appropriate rules based on who you are: + +```json +{ + "brand_manifest": { + "name": "ToyBrand", + "industry": "toys", + "target_audience": "children under 13", + "tone": "playful and safe" + } +} +``` + +The agent interprets the manifest based on its domain expertise: +- A consent agent applies COPPA requirements based on `target_audience` +- A brand safety agent infers content categories from `industry` +- A sustainability agent applies requirements from the brand's profile + +The brand manifest uses the standard [core/brand-manifest](https://adcontextprotocol.org/schemas/v2/core/brand-manifest.json) schema which includes fields for `industry`, `target_audience`, `tone`, and other brand identity information. + +## Webhooks + +Configure webhooks via `update_property_list` to receive notifications when the resolved list changes. + +**Important**: Webhooks provide **notification only**. They tell you that the list has changed, but do not stream the updated properties. After receiving a webhook, you must call `get_property_list` to retrieve the updated property set. + +### Webhook Flow + +``` +1. Governance agent re-evaluates properties (periodically or on trigger) +2. Agent detects changes to the resolved property list +3. Webhook fires with change summary (counts, not full list) +4. Recipient calls get_property_list(list_id, resolve=true) +5. Recipient updates local cache with new properties +``` + +### Webhook Payload + +```json +{ + "event": "property_list_changed", + "list_id": "uk_premium_news_q1", + "list_name": "UK Premium News Sites Q1 2026", + "change_summary": { + "properties_added": 12, + "properties_removed": 3, + "total_properties": 856 + }, + "resolved_at": "2026-01-03T18:00:00Z", + "cache_valid_until": "2026-01-04T18:00:00Z" +} +``` + +The webhook payload includes counts but NOT the actual properties. This keeps payloads small and avoids redundant data transfer when recipients may not need the full list immediately. + +### Webhook Use Cases + +1. **Buyer agent aggregation**: Receive updates from multiple specialized agents, intersect results +2. **Seller cache invalidation**: Know when to re-fetch the compliant property list +3. **Alerting**: Notify when significant changes occur to compliance status + +## Filters + +Filters are applied when the list is resolved (via `get_property_list`): + +| Filter | Type | Description | +|--------|------|-------------| +| `countries_all` | string[] | ISO country codes - property must have feature data for ALL (required) | +| `channels_any` | string[] | Advertising channels - property must support ANY (required) | +| `property_types` | string[] | Property types (website, mobile_app, ctv_app, etc.) | +| `feature_requirements` | FeatureRequirement[] | Requirements based on agent-provided features | +| `exclude_identifiers` | Identifier[] | Identifiers to always exclude | + +### Feature Requirements + +Feature requirements reference features discovered via `list_property_features`. Each agent exposes different features (consent_quality, carbon_score, coppa_certified, etc.). + +For **quantitative** features (scores, ranges): +```json +{ "feature_id": "consent_quality", "min_value": 85, "max_value": 100 } +``` + +For **binary** features (true/false): +```json +{ "feature_id": "coppa_certified", "allowed_values": [true] } +``` + +For **categorical** features (enum values): +```json +{ "feature_id": "content_category", "allowed_values": ["news", "sports", "technology"] } +``` + +### Handling Missing Coverage + +When a property doesn't have data for a required feature, you can control the behavior with `if_not_covered`: + +```json +{ + "feature_id": "viewability_score", + "min_value": 70, + "if_not_covered": "include" +} +``` + +| Value | Behavior | Use Case | +|-------|----------|----------| +| `exclude` (default) | Property is removed from the list | Strict enforcement - only include properties with verified data | +| `include` | Property passes this requirement | Lenient enforcement - don't penalize for coverage gaps | + +When `if_not_covered: "include"` is used, the response includes a `coverage_gaps` field showing which properties were included despite missing data: + +```json +{ + "identifiers": [...], + "coverage_gaps": { + "viewability_score": [ + { "type": "domain", "value": "app.example.com" }, + { "type": "domain", "value": "ctv.example.com" } + ] + } +} +``` + +This transparency helps agencies distinguish between properties that passed a requirement vs. those that couldn't be evaluated. + +### Required Filters + +Every property list must include at least: +- One country in `countries_all` (ISO country code) +- One channel in `channels_any` (display, video, audio, etc.) + +These are required because governance agents need to know which jurisdiction and context to evaluate properties against. + +### Filter Logic + +The filter field names make the logic explicit: + +- **`countries_all`**: Property must have feature data for **ALL** listed countries. +- **`channels_any`**: Property must support **ANY** of the listed channels. +- **`feature_requirements`**: Property must pass **ALL** requirements (AND). + +### Base Properties + +`base_properties` is an array of property sources to evaluate. Each entry is a **discriminated union** with `selection_type` as the discriminator: + +```json +{ + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news", "tier1"] + }, + { + "selection_type": "publisher_tags", + "publisher_domain": "mediavine.com", + "tags": ["lifestyle"] + }, + { + "selection_type": "identifiers", + "identifiers": [ + { "type": "domain", "value": "bbc.co.uk" }, + { "type": "domain", "value": "ft.com" } + ] + } + ] +} +``` + +Each entry must include `selection_type`: + +| selection_type | Required Fields | Description | +|-------------|-----------------|-------------| +| `publisher_tags` | `publisher_domain`, `tags` | All properties matching these tags within the publisher | +| `publisher_ids` | `publisher_domain`, `property_ids` | Specific property IDs within the publisher | +| `identifiers` | `identifiers` | Direct domain/app identifiers (no publisher context) | + +If `base_properties` is omitted, the agent queries its entire property database for properties matching the filters. + +See the [base-property-source schema](https://adcontextprotocol.org/schemas/v2/property/base-property-source.json) for the full specification. + +--- + +## create_property_list + +Create a new property list. + +### Request + +```json +{ + "tool": "create_property_list", + "arguments": { + "name": "UK Premium News Q1", + "description": "High-quality UK news sites for Q1 campaign", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news"] + } + ], + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 85, "max_value": 100 } + ] + } + } +} +``` + +### Response + +```json +{ + "message": "Created property list 'UK Premium News Q1'.", + "context_id": "ctx-gov-list-123", + "list": { + "list_id": "pl_abc123", + "name": "UK Premium News Q1", + "description": "High-quality UK news sites for Q1 campaign", + "principal": "did:principal:brand-x", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news"] + } + ], + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 85, "max_value": 100 } + ] + }, + "created_at": "2026-01-03T16:30:00Z", + "updated_at": "2026-01-03T16:30:00Z", + "property_count": 847 + }, + "auth_token": "eyJhbGciOiJIUzI1NiIs..." +} +``` + +### Dynamic List (Filters Only) + +Create a list that dynamically queries the governance agent's database (no base_properties - uses agent's full coverage): + +```json +{ + "tool": "create_property_list", + "arguments": { + "name": "GDPR-Compliant DE Video", + "description": "All DE properties supporting video with strong consent", + "filters": { + "countries_all": ["DE"], + "channels_any": ["video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 90, "max_value": 100 } + ] + } + } +} +``` + +--- + +## update_property_list + +Modify an existing property list. + +### Request - Update Filters + +```json +{ + "tool": "update_property_list", + "arguments": { + "list_id": "pl_abc123", + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 80, "max_value": 100 } + ] + } + } +} +``` + +### Request - Replace Base Properties + +```json +{ + "tool": "update_property_list", + "arguments": { + "list_id": "pl_abc123", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news", "uk_tier1"] + } + ] + } +} +``` + +### Request - Add Exclusions + +```json +{ + "tool": "update_property_list", + "arguments": { + "list_id": "pl_abc123", + "filters": { + "exclude_identifiers": [ + { "type": "domain", "value": "excluded-site.com" } + ] + } + } +} +``` + +### Response + +```json +{ + "message": "Updated property list 'UK Premium News Q1'.", + "context_id": "ctx-gov-list-456", + "list": { + "list_id": "pl_abc123", + "name": "UK Premium News Q1", + "updated_at": "2026-01-03T17:00:00Z", + "property_count": 845 + } +} +``` + +--- + +## get_property_list + +Retrieve a property list with optional resolution of filters. + +### Request - Get Resolved Properties + +```json +{ + "tool": "get_property_list", + "arguments": { + "list_id": "pl_abc123", + "resolve": true, + "max_results": 100 + } +} +``` + +### Response + +The response returns a compact list of **identifiers only** (not full property objects) for efficiency. Only identifiers that pass the feature requirements are included - no scores or metadata. + +```json +{ + "message": "Retrieved property list 'UK Premium News Q1' with 847 resolved identifiers.", + "context_id": "ctx-gov-list-789", + "list_id": "pl_abc123", + "identifiers": [ + { "type": "domain", "value": "bbc.co.uk" }, + { "type": "domain", "value": "news.sky.com" }, + { "type": "domain", "value": "ft.com" }, + { "type": "domain", "value": "theguardian.com" } + ], + "total_count": 847, + "returned_count": 100, + "pagination": { + "has_more": true, + "cursor": "eyJvZmZzZXQiOjEwMH0=" + }, + "resolved_at": "2026-01-03T17:15:00Z", + "cache_valid_until": "2026-01-04T17:15:00Z" +} +``` + + +The `auth_token` is only returned when the list is created via `create_property_list`. Store it securely - you'll need it to share access with sellers. + + +### Request - Get Metadata Only + +```json +{ + "tool": "get_property_list", + "arguments": { + "list_id": "pl_abc123", + "resolve": false + } +} +``` + +### Response (Metadata Only) + +```json +{ + "message": "Retrieved property list 'UK Premium News Q1' metadata.", + "context_id": "ctx-gov-list-790", + "list": { + "list_id": "pl_abc123", + "name": "UK Premium News Q1", + "description": "High-quality UK news sites for Q1 campaign", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news"] + } + ], + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 85, "max_value": 100 } + ] + }, + "created_at": "2026-01-03T16:30:00Z", + "updated_at": "2026-01-03T17:00:00Z", + "property_count": 847 + } +} +``` + +--- + +## list_property_lists + +List all property lists accessible to the authenticated principal. + +### Request + +```json +{ + "tool": "list_property_lists", + "arguments": { + "name_contains": "UK", + "max_results": 50 + } +} +``` + +### Response + +```json +{ + "message": "Found 3 property lists matching 'UK'.", + "context_id": "ctx-gov-list-list-123", + "lists": [ + { + "list_id": "pl_abc123", + "name": "UK Premium News Q1", + "description": "High-quality UK news sites for Q1 campaign", + "created_at": "2026-01-03T16:30:00Z", + "updated_at": "2026-01-03T17:00:00Z", + "property_count": 847 + }, + { + "list_id": "pl_def456", + "name": "UK Sports Sites", + "description": "UK sports content for sponsorship", + "created_at": "2026-01-02T10:00:00Z", + "updated_at": "2026-01-02T10:00:00Z", + "property_count": 156 + } + ], + "total_count": 3, + "returned_count": 3, + "pagination": { + "has_more": false + } +} +``` + +--- + +## delete_property_list + +Delete a property list. + +### Request + +```json +{ + "tool": "delete_property_list", + "arguments": { + "list_id": "pl_abc123" + } +} +``` + +### Response + +```json +{ + "message": "Deleted property list 'UK Premium News Q1'.", + "context_id": "ctx-gov-list-del-123", + "deleted": true, + "list_id": "pl_abc123" +} +``` + +--- + +## Integration with Other Tasks + +### Using Lists in score_properties + +Reference a property list instead of passing properties inline: + +```json +{ + "tool": "score_properties", + "arguments": { + "property_list_ref": { + "agent_url": "https://governance.example.com", + "list_id": "pl_abc123" + }, + "scoring_context": { + "jurisdiction": "GDPR" + } + } +} +``` + +### Using Lists in Media Buys + +Pass property lists to media buy creation: + +```json +{ + "tool": "create_media_buy", + "arguments": { + "packages": [{ + "property_list_ref": { + "agent_url": "https://governance.example.com", + "list_id": "pl_abc123" + } + }] + } +} +``` + +--- + +## Error Codes + +| Code | Description | +|------|-------------| +| `LIST_NOT_FOUND` | Property list ID doesn't exist | +| `LIST_ACCESS_DENIED` | Principal doesn't have access to this list | +| `INVALID_FILTER` | Filter configuration is invalid | +| `LIST_NAME_EXISTS` | A list with this name already exists | + +## Caching and Refresh + +The `get_property_list` response includes caching guidance: + +| Field | Description | +|-------|-------------| +| `resolved_at` | When filters were applied and properties resolved | +| `cache_valid_until` | When consumers should re-fetch the list | + +**Typical flow for orchestrators/sellers:** + +```python +# Initial setup +response = governance_agent.get_property_list(list_id, resolve=True) +local_cache = build_property_lookup(response.properties) +cache_expiry = response.cache_valid_until + +# Periodic refresh (background job) +if now() >= cache_expiry: + response = governance_agent.get_property_list(list_id, resolve=True) + local_cache = build_property_lookup(response.properties) + cache_expiry = response.cache_valid_until + +# Bid time (no external calls) +def should_bid(property_domain): + return property_domain in local_cache +``` + +## Usage Notes + +1. **Setup time only**: Governance agents are not in the real-time bid path; resolve lists during campaign setup +2. **Local caching**: Orchestrators/sellers must cache resolved properties locally for bid-time decisions +3. **Refresh on schedule**: Re-fetch lists based on `cache_valid_until` (typically every 1-24 hours) +4. **Dynamic vs Static**: Use filters-only lists when you want the agent to maintain the property set; use base_properties when you need explicit control +5. **Pagination**: Large lists may require multiple requests with cursor-based pagination +6. **No score leakage**: Raw scores are kept internal to governance agents; responses contain pass/fail lists, not scores diff --git a/docs/governance/property/tasks/validate_property_delivery.mdx b/docs/governance/property/tasks/validate_property_delivery.mdx new file mode 100644 index 0000000000..7f9acc8c42 --- /dev/null +++ b/docs/governance/property/tasks/validate_property_delivery.mdx @@ -0,0 +1,402 @@ +--- +sidebar_position: 4 +title: validate_property_delivery +--- + +# validate_property_delivery + + +**AdCP 3.0 Proposal** - This task is under development for AdCP 3.0. + + +Validates delivery records against a property list to determine compliance. Answers two questions: +1. **Property compliance**: Did my impressions land on properties in my list? +2. **Supply path authorization**: Was the sales agent authorized to sell that inventory? + +## Use Cases + +- **Post-campaign validation**: Verify that impressions were delivered to compliant properties +- **Supply path verification**: Confirm sales agents were authorized by publishers +- **Real-time monitoring**: Check compliance rate during campaign execution +- **Audit trails**: Generate compliance reports for regulatory or brand safety reviews + +## Request + +```json +{ + "$schema": "/schemas/property/validate-property-delivery-request.json", + "list_id": "pl_abc123", + "records": [ + { + "identifier": { "type": "domain", "value": "www.nytimes.com" }, + "impressions": 103 + }, + { + "identifier": { "type": "domain", "value": "sketchy-site.example" }, + "impressions": 47 + }, + { + "identifier": { "type": "android_package", "value": "com.unknown.app" }, + "impressions": 25 + } + ], + "include_compliant": false +} +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `list_id` | string | Yes | ID of the property list to validate against | +| `records` | array | Yes | Delivery records to validate (1-10,000 records) | +| `records[].identifier` | object | Yes | Property identifier (`type` and `value`) | +| `records[].impressions` | integer | Yes | Number of impressions delivered | +| `records[].record_id` | string | No | Client-provided ID for correlation | +| `records[].sales_agent_url` | string | No | Sales agent URL to validate authorization against adagents.json | +| `include_compliant` | boolean | No | Include compliant records in results (default: false) | + +## Response + +```json +{ + "$schema": "/schemas/property/validate-property-delivery-response.json", + "list_id": "pl_abc123", + "summary": { + "total_records": 4, + "total_impressions": 200, + "compliant_records": 1, + "compliant_impressions": 103, + "non_compliant_records": 1, + "non_compliant_impressions": 47, + "not_covered_records": 1, + "not_covered_impressions": 25, + "unidentified_records": 1, + "unidentified_impressions": 25 + }, + "aggregate": { + "score": 68.7, + "grade": "C+", + "label": "68.7% compliant", + "methodology_url": "https://governance.example.com/methodology/compliance-scoring" + }, + "results": [ + { + "identifier": { "type": "domain", "value": "sketchy-site.example" }, + "status": "non_compliant", + "impressions": 47, + "violations": [ + { + "code": "not_in_list", + "message": "Identifier not found in resolved property list" + } + ] + }, + { + "identifier": { "type": "domain", "value": "new-site.example" }, + "status": "not_covered", + "impressions": 25 + }, + { + "identifier": { "type": "android_package", "value": "com.unknown.app" }, + "status": "unidentified", + "impressions": 25 + } + ], + "validated_at": "2026-01-04T19:00:00Z", + "list_resolved_at": "2026-01-04T12:00:00Z" +} +``` + +### Response Fields + +| Field | Type | Description | +|-------|------|-------------| +| `list_id` | string | ID of the property list validated against | +| `summary` | object | Raw counts for property compliance validation | +| `aggregate` | object | Optional computed metrics from the governance agent | +| `results` | array | Per-record validation results | +| `validated_at` | datetime | When validation was performed | +| `list_resolved_at` | datetime | Resolution timestamp of the property list used | + +### Summary Fields + +The summary provides raw counts - consumers calculate rates as needed: + +| Field | Description | +|-------|-------------| +| `total_records` | Total records validated | +| `total_impressions` | Total impressions across all records | +| `compliant_records` / `compliant_impressions` | Records/impressions in the property list | +| `non_compliant_records` / `non_compliant_impressions` | Records/impressions NOT in the property list | +| `not_covered_records` / `not_covered_impressions` | Identifier recognized but no data available | +| `unidentified_records` / `unidentified_impressions` | Identifier type not resolvable | + +### Validation Statuses + +| Status | Meaning | +|--------|---------| +| `compliant` | Identifier is in the resolved property list | +| `non_compliant` | Identifier is NOT in the resolved property list | +| `not_covered` | Identifier recognized but governance agent has no data for it | +| `unidentified` | Identifier type not resolvable by this agent | + +## Understanding not_covered vs unidentified + +These two statuses distinguish different types of "unknown" scenarios: + +**`not_covered`** - The governance agent recognized the identifier (e.g., it's a valid domain) but doesn't have data for that specific property. This happens when: +- A property is too new to be in the agent's database +- The property exists but hasn't been evaluated yet +- The agent's coverage doesn't include that property category + +**`unidentified`** - The governance agent couldn't recognize the identifier at all. This happens when: +- Client-side detection failed to capture the property +- The identifier type isn't supported (e.g., agent handles domains but received an app ID) +- The identifier value is malformed or invalid + +Both statuses should be excluded from compliance rate calculations - you cannot penalize for detection or coverage gaps. + +## Optional Aggregate Metrics + +Governance agents can optionally return computed metrics in the `aggregate` field: + +```json +"aggregate": { + "score": 68.7, + "grade": "C+", + "label": "68.7% compliant", + "methodology_url": "https://governance.example.com/methodology" +} +``` + +| Field | Description | +|-------|-------------| +| `score` | Numeric score (scale is agent-defined, typically 0-100) | +| `grade` | Letter grade or category (e.g., "A+", "B-", "Gold") | +| `label` | Human-readable summary (e.g., "85% compliant") | +| `methodology_url` | URL explaining how the aggregate was calculated | + +The `aggregate` field is optional and agent-specific. Consumers should not assume a particular format - always check `methodology_url` for interpretation. + +## Calculating Your Own Rates + +The response always includes raw counts. Calculate rates as needed: + +```python +# Compliance rate (exclude unverifiable from denominator) +unverifiable = summary.not_covered_impressions + summary.unidentified_impressions +verifiable = summary.total_impressions - unverifiable +compliance_rate = summary.compliant_impressions / verifiable if verifiable > 0 else None +``` + +In the example above: +- Compliant impressions: 103 +- Non-compliant impressions: 47 +- not_covered + unidentified impressions: 50 (excluded) +- Compliance rate: 103 / (200 - 50) = 103 / 150 = 68.7% + +## Violation Codes + +| Code | Description | +|------|-------------| +| `not_in_list` | Identifier not found in the resolved property list | +| `excluded` | Identifier explicitly excluded via `exclude_identifiers` filter | +| `country_mismatch` | Property lacks feature data for required countries | +| `channel_mismatch` | Property doesn't support required channels | +| `feature_failed` | Property failed a `feature_requirements` filter | + +### Feature Violation Details + +When a property fails a feature requirement, the violation includes the feature details: + +```json +{ + "identifier": { "type": "domain", "value": "low-quality-site.example" }, + "status": "non_compliant", + "impressions": 150, + "violations": [ + { + "code": "feature_failed", + "message": "Property failed consent_quality requirement (min: 85)", + "feature_id": "consent_quality", + "requirement": { + "min_value": 85 + } + } + ] +} +``` + +The `feature_id` and `requirement` fields are optional - they're included when the violation is due to a feature requirement filter. + +## Supply Path Authorization + +When `sales_agent_url` is provided in delivery records, the governance agent validates that the sales agent is authorized to sell the property by checking the publisher's `adagents.json`. + +### Request with Authorization + +```json +{ + "list_id": "pl_abc123", + "records": [ + { + "identifier": { "type": "domain", "value": "www.nytimes.com" }, + "impressions": 103, + "sales_agent_url": "https://legitimate-ssp.example.com" + }, + { + "identifier": { "type": "domain", "value": "www.nytimes.com" }, + "impressions": 50, + "sales_agent_url": "https://unauthorized-reseller.example.com" + } + ] +} +``` + +### Response with Authorization + +When authorization is validated, each result includes an `authorization` field: + +```json +{ + "list_id": "pl_abc123", + "summary": { + "total_records": 2, + "total_impressions": 153, + "compliant_records": 2, + "compliant_impressions": 153, + "non_compliant_records": 0, + "non_compliant_impressions": 0, + "unknown_records": 0, + "unknown_impressions": 0 + }, + "authorization_summary": { + "records_checked": 2, + "impressions_checked": 153, + "authorized_records": 1, + "authorized_impressions": 103, + "unauthorized_records": 1, + "unauthorized_impressions": 50, + "unknown_records": 0, + "unknown_impressions": 0 + }, + "results": [ + { + "identifier": { "type": "domain", "value": "www.nytimes.com" }, + "status": "compliant", + "impressions": 50, + "authorization": { + "status": "unauthorized", + "publisher_domain": "nytimes.com", + "sales_agent_url": "https://unauthorized-reseller.example.com", + "violation": { + "code": "agent_not_authorized", + "message": "Sales agent not listed in nytimes.com/.well-known/adagents.json" + } + } + } + ], + "validated_at": "2026-01-04T19:00:00Z" +} +``` + +### Authorization Statuses + +| Status | Meaning | Included in authorization_rate? | +|--------|---------|--------------------------------| +| `authorized` | Sales agent is listed in publisher's adagents.json | Yes (numerator) | +| `unauthorized` | Sales agent is NOT listed in publisher's adagents.json | Yes (denominator only) | +| `unknown` | Could not fetch or parse adagents.json | No (excluded) | + +### Authorization Violation Codes + +| Code | Description | +|------|-------------| +| `agent_not_authorized` | Sales agent URL not found in publisher's authorized_agents list | +| `adagents_not_found` | Publisher's adagents.json could not be fetched (404, timeout, etc.) | +| `adagents_invalid` | Publisher's adagents.json exists but is malformed | +| `property_not_declared` | Property identifier not declared in publisher's adagents.json | + +### Two Independent Checks + +Property compliance and authorization are **independent checks**. A record can be: + +| Property Status | Authorization Status | Meaning | +|-----------------|---------------------|---------| +| compliant | authorized | Fully valid - property in list, sold by authorized agent | +| compliant | unauthorized | Property is approved but sold by unauthorized reseller | +| non_compliant | authorized | Authorized agent sold property outside your list | +| non_compliant | unauthorized | Neither property nor agent validated | + +Both checks use the same "unknown excludes from rate" pattern - you cannot penalize for detection gaps. + +## Best Practices + +### Batch Validation + +For large-scale validation, batch records up to the 10,000 limit: + +```python +def validate_delivery_batch(records, list_id, governance_agent): + """Validate delivery records in batches.""" + batch_size = 10000 + all_results = [] + + for i in range(0, len(records), batch_size): + batch = records[i:i + batch_size] + response = governance_agent.validate_property_delivery( + list_id=list_id, + records=batch + ) + all_results.extend(response.results) + + return all_results +``` + +### Sampling Strategy + +For real-time monitoring during campaign execution, validate a statistical sample rather than all records: + +```python +import random + +def sample_and_validate(records, sample_size=1000): + """Validate a random sample for real-time monitoring.""" + sample = random.sample(records, min(sample_size, len(records))) + return governance_agent.validate_property_delivery( + list_id=list_id, + records=sample + ) +``` + +### Handling Unknown Records + +Track unknown rates separately to identify detection gaps: + +```python +def analyze_validation(response): + """Analyze validation results with unknown handling.""" + summary = response.summary + + # Core compliance metric + compliance_rate = summary.compliance_rate + + # Detection quality metric + unknown_rate = summary.unknown_impressions / summary.total_impressions + + if unknown_rate > 0.1: + print(f"Warning: {unknown_rate:.1%} of impressions unresolvable") + + return { + "compliance_rate": compliance_rate, + "unknown_rate": unknown_rate, + "non_compliant_impressions": summary.non_compliant_impressions + } +``` + +## Related Tasks + +- [create_property_list](/docs/governance/property/tasks/property_lists#create_property_list) - Create the list to validate against +- [get_property_list](/docs/governance/property/tasks/property_lists#get_property_list) - Retrieve current list membership +- [list_property_features](/docs/governance/property/tasks/list_property_features) - Discover available filter features diff --git a/docs/media-buy/capability-discovery/index.mdx b/docs/media-buy/capability-discovery/index.mdx index 885a0e16c9..297984b4c1 100644 --- a/docs/media-buy/capability-discovery/index.mdx +++ b/docs/media-buy/capability-discovery/index.mdx @@ -21,7 +21,7 @@ Learn how sales agents can support standard creative formats through the referen - Leverage the Standard Creative Agent for standard formats - Work with publisher-specific creative agents for custom formats -### [Authorized Properties](/docs/media-buy/capability-discovery/authorized-properties) 🔐 +### [Understanding Authorization](/docs/governance/property/authorized-properties) 🔐 Learn how AdCP prevents unauthorized resale and ensures sales agents are legitimate. Understand: - The problem of unauthorized resale in digital advertising @@ -30,7 +30,7 @@ Learn how AdCP prevents unauthorized resale and ensures sales agents are legitim - Property tags and large-scale authorization management -**Cross-Protocol Foundation**: The `adagents.json` authorization system applies across ALL AdCP protocols (Media Buy, Signals, and future Curation). See the [adagents.json specification](/docs/media-buy/capability-discovery/adagents) for complete implementation details. +**Cross-Protocol Foundation**: The `adagents.json` authorization system applies across ALL AdCP protocols (Media Buy, Signals, and future Curation). See the [adagents.json specification](/docs/governance/property/adagents) for complete implementation details. @@ -80,4 +80,4 @@ Together, these capabilities provide the foundation for safe, efficient, and eff - **[Creative Protocol](/docs/creative/)** - Creative agents and manifests - **[Creative Channel Guides](/docs/creative/channels/video)** - Format examples and patterns - **[Creative Manifests](/docs/creative/creative-manifests)** - Understanding creative specifications -- **[adagents.json Specification](/docs/media-buy/capability-discovery/adagents)** - Publisher authorization system (applies across all AdCP protocols) \ No newline at end of file +- **[adagents.json Specification](/docs/governance/property/adagents)** - Publisher authorization system (applies across all AdCP protocols) \ No newline at end of file diff --git a/docs/media-buy/index.mdx b/docs/media-buy/index.mdx index 0ead41042a..ab06863c28 100644 --- a/docs/media-buy/index.mdx +++ b/docs/media-buy/index.mdx @@ -232,7 +232,7 @@ Choose your path based on your role and needs: 4. **Explore [Optimization & Reporting](/docs/media-buy/media-buys/optimization-reporting)** - Learn performance management ### **For Publishers/Sales Agents** -1. **Learn [Authorized Properties](/docs/media-buy/capability-discovery/authorized-properties)** - Understand authorization requirements +1. **Learn [Understanding Authorization](/docs/governance/property/authorized-properties)** - Understand authorization requirements 2. **Review [Creative Formats](/docs/creative/formats)** - See supported creative specifications 3. **Study [Advanced Topics](/docs/media-buy/advanced-topics/)** - Deep dive into technical implementation diff --git a/docs/media-buy/task-reference/get_products.mdx b/docs/media-buy/task-reference/get_products.mdx index 9a877d146e..fa00a940b7 100644 --- a/docs/media-buy/task-reference/get_products.mdx +++ b/docs/media-buy/task-reference/get_products.mdx @@ -129,6 +129,7 @@ asyncio.run(discover_with_filters()) | `brief` | string | No | Natural language description of campaign requirements | | `brand_manifest` | BrandManifest \| string | No | Brand information (inline object or URL). See [Brand Manifest](/docs/creative/brand-manifest) | | `filters` | Filters | No | Structured filters (see below) | +| `property_list` | PropertyListRef | No | [AdCP 3.0] Reference to a property list for filtering. See [Property Lists](/docs/governance/property/tasks/property_lists) | ### Filters Object @@ -172,6 +173,12 @@ Returns an array of `products`, each containing: | `pricing_options` | PricingOption[] | Available pricing models (CPM, CPCV, etc.) | | `brief_relevance` | string | Why this product matches the brief (when brief provided) | +### Response Metadata + +| Field | Type | Description | +|-------|------|-------------| +| `property_list_applied` | boolean | [AdCP 3.0] `true` if the agent filtered products based on the provided `property_list`. Absent or `false` if not provided or not supported. | + **See schema for complete field list**: [`get-products-response.json`](https://adcontextprotocol.org/schemas/v2/media-buy/get-products-response.json) ## Common Scenarios @@ -476,6 +483,80 @@ asyncio.run(discover_standard_formats()) +### Property List Filtering + + +**AdCP 3.0** - Property list filtering requires governance agent support. + + +Filter products to only those available on properties in your approved list: + + + +```javascript JavaScript +import { testAgent } from '@adcp/client/testing'; + +// Filter products by property list from governance agent +const result = await testAgent.getProducts({ + brief: 'Brand-safe inventory for family brand', + brand_manifest: { + name: 'Nike', + url: 'https://nike.com' + }, + property_list: { + agent_url: 'https://governance.example.com', + list_id: 'pl_brand_safe_2024' + } +}); + +if (result.success && result.data) { + // Check if filtering was actually applied + if (result.data.property_list_applied) { + console.log(`Found ${result.data.products.length} products on approved properties`); + } else { + console.log('Agent does not support property list filtering'); + console.log(`Found ${result.data.products.length} products (unfiltered)`); + } +} +``` + +```python Python +import asyncio +from adcp import test_agent + +async def discover_with_property_list(): + # Filter products by property list from governance agent + result = await test_agent.simple.get_products( + brief='Brand-safe inventory for family brand', + brand_manifest={ + 'name': 'Nike', + 'url': 'https://nike.com' + }, + property_list={ + 'agent_url': 'https://governance.example.com', + 'list_id': 'pl_brand_safe_2024' + } + ) + + # Check if filtering was actually applied + if result.get('property_list_applied'): + print(f"Found {len(result['products'])} products on approved properties") + else: + print("Agent does not support property list filtering") + print(f"Found {len(result['products'])} products (unfiltered)") + +asyncio.run(discover_with_property_list()) +``` + + + +**Note**: If `property_list_applied` is absent or `false`, the sales agent did not filter products. This can happen if: +- The agent doesn't support property governance features +- The agent couldn't access the property list +- The property list had no effect on the available inventory + +See [Property Governance](/docs/governance/property/specification) for more on property lists. + ## Error Handling | Error Code | Description | Resolution | diff --git a/docs/media-buy/task-reference/list_authorized_properties.mdx b/docs/media-buy/task-reference/list_authorized_properties.mdx index 5e7cb8ea9d..55bd3fcf87 100644 --- a/docs/media-buy/task-reference/list_authorized_properties.mdx +++ b/docs/media-buy/task-reference/list_authorized_properties.mdx @@ -498,6 +498,6 @@ After discovering authorized properties: ## Learn More -- [adagents.json Specification](/docs/media-buy/capability-discovery/adagents) - Publisher authorization file format +- [adagents.json Specification](/docs/governance/property/adagents) - Publisher authorization file format - [Property Schema](https://adcontextprotocol.org/schemas/v2/core/property.json) - Property definition structure - [Authorization Guide](/docs/reference/authentication) - How authorization works in AdCP diff --git a/docs/reference/implementor-faq.mdx b/docs/reference/implementor-faq.mdx index ce93b224df..747ada9c0c 100644 --- a/docs/reference/implementor-faq.mdx +++ b/docs/reference/implementor-faq.mdx @@ -289,7 +289,7 @@ assert(response.message.includes("policy")); 3. Verify the sales agent URL appears in `authorized_agents` 4. Reject products from unauthorized agents -See [Authorization Validation](/docs/media-buy/capability-discovery/adagents#buyer-agent-validation) for complete requirements. +See [Authorization Validation](/docs/governance/property/adagents#buyer-agent-validation) for complete requirements. ## Terminology diff --git a/docs/reference/roadmap.mdx b/docs/reference/roadmap.mdx index 35b1ec2d61..137ddeb29e 100644 --- a/docs/reference/roadmap.mdx +++ b/docs/reference/roadmap.mdx @@ -59,9 +59,9 @@ These features are in active development but may not make the 3.0 release: The following features are in the working group planning stages: -### Governance Protocol +### Governance Protocol [3.0] -A framework for multi-party approval workflows and delegated authority management. See the [Governance Protocol Working Document](https://docs.google.com/document/d/1osokTr5Xk2PyLBUHbx2jpPad0TLrQIDh2I6ZWzpHMmY/edit?tab=t.0) for details. +Property governance (property lists, filters, adagents.json authorization), brand standards (brand manifests, creative guidelines), and compliance controls. See the [Governance Protocol](/docs/governance/index) for current specification. ### Private Marketplace (PMP) Support - Deal ID integration diff --git a/package-lock.json b/package-lock.json index e3bc17f296..6cb4764f14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "adcontextprotocol", - "version": "2.5.2", + "version": "2.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "adcontextprotocol", - "version": "2.5.2", + "version": "2.6.0", "dependencies": { "@adcp/client": "^3.5.2", "@anthropic-ai/sdk": "^0.71.2", diff --git a/static/schemas/source/adagents.json b/static/schemas/source/adagents.json index 286aee996f..bc48225ca6 100644 --- a/static/schemas/source/adagents.json +++ b/static/schemas/source/adagents.json @@ -264,6 +264,38 @@ "type": "string", "format": "date-time", "description": "ISO 8601 timestamp indicating when this file was last updated" + }, + "property_features": { + "type": "array", + "description": "[AdCP 3.0] Optional list of agents that provide property feature data (certifications, scores, compliance status). Used for discovery - actual data comes from querying the agent's get_property_features task.", + "items": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "The agent's API endpoint URL (must implement get_property_features)" + }, + "name": { + "type": "string", + "description": "Human-readable name of the vendor/agent (e.g., 'Scope3', 'TAG', 'OneTrust')" + }, + "features": { + "type": "array", + "description": "Feature IDs this agent provides (e.g., 'carbon_score', 'tag_certified_against_fraud'). Use list_property_features on the agent for full definitions.", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "publisher_id": { + "type": "string", + "description": "Optional publisher identifier at this agent (for lookup)" + } + }, + "required": ["url", "name", "features"], + "additionalProperties": true + } } }, "required": [ @@ -489,6 +521,66 @@ } ], "last_updated": "2025-01-10T17:00:00Z" + }, + { + "$schema": "/schemas/adagents.json", + "contact": { + "name": "Premium News Publisher", + "email": "adops@news.example.com", + "domain": "news.example.com" + }, + "properties": [ + { + "property_type": "website", + "name": "News Example", + "identifiers": [ + { + "type": "domain", + "value": "news.example.com" + } + ], + "tags": ["premium", "news"], + "publisher_domain": "news.example.com" + } + ], + "tags": { + "premium": { + "name": "Premium Properties", + "description": "High-quality, brand-safe properties" + }, + "news": { + "name": "News Properties", + "description": "News and journalism content" + } + }, + "authorized_agents": [ + { + "url": "https://sales.news.example.com", + "authorized_for": "All news properties", + "authorization_type": "property_tags", + "property_tags": ["news"] + } + ], + "property_features": [ + { + "url": "https://api.scope3.com", + "name": "Scope3", + "features": ["carbon_score", "sustainability_grade"], + "publisher_id": "pub_news_12345" + }, + { + "url": "https://api.tagtoday.net", + "name": "TAG", + "features": ["tag_certified_against_fraud", "tag_brand_safety_certified"] + }, + { + "url": "https://api.onetrust.com", + "name": "OneTrust", + "features": ["gdpr_compliant", "tcf_registered", "ccpa_compliant"], + "publisher_id": "ot_news_67890" + } + ], + "last_updated": "2025-01-10T18:00:00Z" } ] } diff --git a/static/schemas/source/core/identifier.json b/static/schemas/source/core/identifier.json new file mode 100644 index 0000000000..f7e68aabe2 --- /dev/null +++ b/static/schemas/source/core/identifier.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/core/identifier.json", + "title": "Identifier", + "description": "A property identifier with type and value. Used to identify properties across platforms (domains, app store IDs, etc.).", + "type": "object", + "properties": { + "type": { + "$ref": "/schemas/enums/identifier-types.json", + "description": "Type of identifier" + }, + "value": { + "type": "string", + "description": "The identifier value. For domain type: 'example.com' matches base domain plus www and m subdomains; 'edition.example.com' matches that specific subdomain; '*.example.com' matches ALL subdomains but NOT base domain" + } + }, + "required": ["type", "value"], + "additionalProperties": false +} diff --git a/static/schemas/source/core/property-list-ref.json b/static/schemas/source/core/property-list-ref.json new file mode 100644 index 0000000000..06e5ced9ba --- /dev/null +++ b/static/schemas/source/core/property-list-ref.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/core/property-list-ref.json", + "title": "Property List Reference", + "description": "Reference to an externally managed property list. Enables passing large property sets (50,000+) without embedding them in requests. The receiving agent fetches and caches the list independently.", + "type": "object", + "properties": { + "agent_url": { + "type": "string", + "format": "uri", + "description": "URL of the agent managing the property list" + }, + "list_id": { + "type": "string", + "description": "Identifier for the property list within the agent", + "minLength": 1 + }, + "auth_token": { + "type": "string", + "description": "JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access." + } + }, + "required": ["agent_url", "list_id"], + "additionalProperties": false +} diff --git a/static/schemas/source/enums/adcp-domain.json b/static/schemas/source/enums/adcp-domain.json index 68db230066..ee392aeff0 100644 --- a/static/schemas/source/enums/adcp-domain.json +++ b/static/schemas/source/enums/adcp-domain.json @@ -4,8 +4,11 @@ "title": "AdCP Domain", "description": "AdCP protocol domains for task categorization", "type": "string", - "enum": [ - "media-buy", - "signals" - ] + "enum": ["media-buy", "signals", "governance", "creative"], + "enumDescriptions": { + "media-buy": "Campaign creation, package management, and delivery optimization", + "signals": "Audience signal discovery and activation", + "governance": "Property governance (identity, authorization, data, selection), brand standards, and compliance", + "creative": "Creative asset management, format discovery, and rendering" + } } diff --git a/static/schemas/source/enums/task-type.json b/static/schemas/source/enums/task-type.json index 063291bb42..7af80e9b9d 100644 --- a/static/schemas/source/enums/task-type.json +++ b/static/schemas/source/enums/task-type.json @@ -9,18 +9,30 @@ "update_media_buy", "sync_creatives", "activate_signal", - "get_signals" + "get_signals", + "list_property_features", + "create_property_list", + "update_property_list", + "get_property_list", + "list_property_lists", + "delete_property_list" ], "enumDescriptions": { "create_media_buy": "Media-buy domain: Create a new advertising campaign with one or more packages", "update_media_buy": "Media-buy domain: Update campaign settings, package configuration, or delivery parameters", "sync_creatives": "Media-buy domain: Sync creative assets to publisher's library with upsert semantics", "activate_signal": "Signals domain: Activate an audience signal on a specific platform or account", - "get_signals": "Signals domain: Discover available audience signals based on natural language description" + "get_signals": "Signals domain: Discover available audience signals based on natural language description", + "list_property_features": "Property domain: Discover what features a governance agent can evaluate", + "create_property_list": "Property domain: Create a new property list with filters and brand manifest", + "update_property_list": "Property domain: Update an existing property list", + "get_property_list": "Property domain: Retrieve a property list with resolved properties", + "list_property_lists": "Property domain: List all accessible property lists", + "delete_property_list": "Property domain: Delete a property list" }, "notes": [ "Task types map to specific AdCP task operations", - "Each task type belongs to either the 'media-buy' or 'signals' domain", + "Each task type belongs to the 'media-buy', 'signals', 'property', or 'creative' domain", "This enum is used in task management APIs (tasks/list, tasks/get) and webhook payloads", "New task types require a minor version bump per semantic versioning" ] diff --git a/static/schemas/source/index.json b/static/schemas/source/index.json index 7a0db1fbd7..635683aced 100644 --- a/static/schemas/source/index.json +++ b/static/schemas/source/index.json @@ -168,6 +168,14 @@ "property-tag": { "$ref": "/schemas/core/property-tag.json", "description": "Tag for categorizing publisher properties - lowercase alphanumeric with underscores only" + }, + "property-list-ref": { + "$ref": "/schemas/core/property-list-ref.json", + "description": "Reference to an externally managed property list for passing large property sets" + }, + "identifier": { + "$ref": "/schemas/core/identifier.json", + "description": "A property identifier with type and value" } } }, @@ -220,7 +228,7 @@ }, "task-type": { "$ref": "/schemas/enums/task-type.json", - "description": "Valid AdCP task types across all domains (create_media_buy, update_media_buy, sync_creatives, activate_signal, get_signals)" + "description": "Valid AdCP task types across all domains (create_media_buy, update_media_buy, sync_creatives, activate_signal, get_signals, list_property_features, create_property_list, get_property_list, update_property_list, delete_property_list)" }, "asset-content-type": { "$ref": "/schemas/enums/asset-content-type.json", @@ -280,7 +288,7 @@ }, "adcp-domain": { "$ref": "/schemas/enums/adcp-domain.json", - "description": "AdCP protocol domains (media-buy, signals)" + "description": "AdCP protocol domains (media-buy, signals, governance)" }, "http-method": { "$ref": "/schemas/enums/http-method.json", @@ -562,6 +570,105 @@ } } }, + "governance": { + "description": "Governance protocol for property governance, brand standards, and compliance", + "supporting-schemas": { + "property-feature-definition": { + "$ref": "/schemas/property/property-feature-definition.json", + "description": "Definition of a feature that a governance agent can evaluate" + }, + "property-feature": { + "$ref": "/schemas/property/property-feature.json", + "description": "A discrete feature assessment for a property" + }, + "feature-requirement": { + "$ref": "/schemas/property/feature-requirement.json", + "description": "A feature-based requirement for property filtering" + }, + "property-error": { + "$ref": "/schemas/property/property-error.json", + "description": "Error information for a property that could not be evaluated" + }, + "property-list": { + "$ref": "/schemas/property/property-list.json", + "description": "A managed property list with optional filters for dynamic evaluation" + }, + "property-list-filters": { + "$ref": "/schemas/property/property-list-filters.json", + "description": "Filters that dynamically modify a property list when resolved" + }, + "property-list-changed-webhook": { + "$ref": "/schemas/property/property-list-changed-webhook.json", + "description": "Webhook payload when a property list changes" + }, + "base-property-source": { + "$ref": "/schemas/property/base-property-source.json", + "description": "A source of properties for a property list - supports publisher+tags, publisher+property_ids, or direct identifiers" + } + }, + "tasks": { + "list-property-features": { + "request": { + "$ref": "/schemas/property/list-property-features-request.json", + "description": "Request parameters for discovering governance agent capabilities" + }, + "response": { + "$ref": "/schemas/property/list-property-features-response.json", + "description": "Response payload for list_property_features task" + } + }, + "create-property-list": { + "request": { + "$ref": "/schemas/property/create-property-list-request.json", + "description": "Request parameters for creating a new property list" + }, + "response": { + "$ref": "/schemas/property/create-property-list-response.json", + "description": "Response payload for create_property_list task" + } + }, + "update-property-list": { + "request": { + "$ref": "/schemas/property/update-property-list-request.json", + "description": "Request parameters for updating an existing property list" + }, + "response": { + "$ref": "/schemas/property/update-property-list-response.json", + "description": "Response payload for update_property_list task" + } + }, + "get-property-list": { + "request": { + "$ref": "/schemas/property/get-property-list-request.json", + "description": "Request parameters for retrieving a property list with resolved properties" + }, + "response": { + "$ref": "/schemas/property/get-property-list-response.json", + "description": "Response payload for get_property_list task" + } + }, + "list-property-lists": { + "request": { + "$ref": "/schemas/property/list-property-lists-request.json", + "description": "Request parameters for listing property lists" + }, + "response": { + "$ref": "/schemas/property/list-property-lists-response.json", + "description": "Response payload for list_property_lists task" + } + }, + "delete-property-list": { + "request": { + "$ref": "/schemas/property/delete-property-list-request.json", + "description": "Request parameters for deleting a property list" + }, + "response": { + "$ref": "/schemas/property/delete-property-list-response.json", + "description": "Response payload for delete_property_list task" + } + } + } + }, "adagents": { "description": "Authorized sales agents file format specification", "$ref": "/schemas/adagents.json", diff --git a/static/schemas/source/media-buy/get-products-request.json b/static/schemas/source/media-buy/get-products-request.json index 1c7483fd84..02817c0f38 100644 --- a/static/schemas/source/media-buy/get-products-request.json +++ b/static/schemas/source/media-buy/get-products-request.json @@ -16,6 +16,10 @@ "filters": { "$ref": "/schemas/core/product-filters.json" }, + "property_list": { + "$ref": "/schemas/core/property-list-ref.json", + "description": "[AdCP 3.0] Reference to an externally managed property list. When provided, the sales agent should filter products to only those available on properties in the list." + }, "context": { "$ref": "/schemas/core/context.json" }, diff --git a/static/schemas/source/media-buy/get-products-response.json b/static/schemas/source/media-buy/get-products-response.json index 3a54bd3c8e..692787b91c 100644 --- a/static/schemas/source/media-buy/get-products-response.json +++ b/static/schemas/source/media-buy/get-products-response.json @@ -19,6 +19,10 @@ "$ref": "/schemas/core/error.json" } }, + "property_list_applied": { + "type": "boolean", + "description": "[AdCP 3.0] Indicates whether property_list filtering was applied. True if the agent filtered products based on the provided property_list. Absent or false if property_list was not provided or not supported by this agent." + }, "context": { "$ref": "/schemas/core/context.json" }, diff --git a/static/schemas/source/property/authorization-result.json b/static/schemas/source/property/authorization-result.json new file mode 100644 index 0000000000..48187649fc --- /dev/null +++ b/static/schemas/source/property/authorization-result.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/authorization-result.json", + "title": "Authorization Result", + "description": "Result of validating sales agent authorization for a property. Checks if the sales agent is listed in the property's adagents.json.", + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": ["authorized", "unauthorized", "unknown"], + "description": "Authorization status: authorized (agent in adagents.json), unauthorized (agent not in adagents.json), unknown (could not fetch or parse adagents.json)" + }, + "publisher_domain": { + "type": "string", + "description": "The publisher domain where adagents.json was checked" + }, + "sales_agent_url": { + "type": "string", + "format": "uri", + "description": "The sales agent URL that was validated" + }, + "violation": { + "type": "object", + "description": "Details about the authorization failure (only present for unauthorized status)", + "properties": { + "code": { + "type": "string", + "description": "Machine-readable violation code" + }, + "message": { + "type": "string", + "description": "Human-readable violation description" + } + }, + "required": ["code", "message"], + "additionalProperties": false + } + }, + "required": ["status"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/base-property-source.json b/static/schemas/source/property/base-property-source.json new file mode 100644 index 0000000000..d23b0c2bc3 --- /dev/null +++ b/static/schemas/source/property/base-property-source.json @@ -0,0 +1,87 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/base-property-source.json", + "title": "Base Property Source", + "description": "A source of properties for a property list. Supports three selection patterns: publisher with tags, publisher with property IDs, or direct identifiers.", + "discriminator": { + "propertyName": "selection_type" + }, + "oneOf": [ + { + "type": "object", + "title": "Publisher Tags Source", + "description": "Select properties from a publisher by tag membership", + "properties": { + "selection_type": { + "type": "string", + "const": "publisher_tags", + "description": "Discriminator indicating selection by property tags within a publisher" + }, + "publisher_domain": { + "type": "string", + "description": "Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$" + }, + "tags": { + "type": "array", + "description": "Property tags from the publisher's adagents.json. Selects all properties with these tags.", + "items": { + "$ref": "/schemas/core/property-tag.json" + }, + "minItems": 1 + } + }, + "required": ["selection_type", "publisher_domain", "tags"], + "additionalProperties": false + }, + { + "type": "object", + "title": "Publisher Property IDs Source", + "description": "Select specific properties from a publisher by ID", + "properties": { + "selection_type": { + "type": "string", + "const": "publisher_ids", + "description": "Discriminator indicating selection by specific property IDs within a publisher" + }, + "publisher_domain": { + "type": "string", + "description": "Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$" + }, + "property_ids": { + "type": "array", + "description": "Specific property IDs from the publisher's adagents.json", + "items": { + "$ref": "/schemas/core/property-id.json" + }, + "minItems": 1 + } + }, + "required": ["selection_type", "publisher_domain", "property_ids"], + "additionalProperties": false + }, + { + "type": "object", + "title": "Direct Identifiers Source", + "description": "Select properties by direct identifiers (domains, app IDs, etc.) without publisher context", + "properties": { + "selection_type": { + "type": "string", + "const": "identifiers", + "description": "Discriminator indicating selection by direct identifiers" + }, + "identifiers": { + "type": "array", + "description": "Direct property identifiers (domains, app IDs, etc.)", + "items": { + "$ref": "/schemas/core/identifier.json" + }, + "minItems": 1 + } + }, + "required": ["selection_type", "identifiers"], + "additionalProperties": false + } + ] +} diff --git a/static/schemas/source/property/create-property-list-request.json b/static/schemas/source/property/create-property-list-request.json new file mode 100644 index 0000000000..b1c3185f11 --- /dev/null +++ b/static/schemas/source/property/create-property-list-request.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/create-property-list-request.json", + "title": "Create Property List Request", + "description": "Request parameters for creating a new property list", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Human-readable name for the list" + }, + "description": { + "type": "string", + "description": "Description of the list's purpose" + }, + "base_properties": { + "type": "array", + "description": "Array of property sources to evaluate. Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers). If omitted, queries the agent's entire property database.", + "items": { + "$ref": "/schemas/property/base-property-source.json" + } + }, + "filters": { + "$ref": "/schemas/property/property-list-filters.json", + "description": "Dynamic filters to apply when resolving the list" + }, + "brand_manifest": { + "$ref": "/schemas/core/brand-manifest.json", + "description": "Brand identity and requirements. When provided, the agent automatically applies appropriate rules based on brand characteristics (industry, target_audience, etc.)." + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["name"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/create-property-list-response.json b/static/schemas/source/property/create-property-list-response.json new file mode 100644 index 0000000000..2b608088db --- /dev/null +++ b/static/schemas/source/property/create-property-list-response.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/create-property-list-response.json", + "title": "Create Property List Response", + "description": "Response payload for create_property_list task", + "type": "object", + "properties": { + "list": { + "$ref": "/schemas/property/property-list.json", + "description": "The created property list" + }, + "auth_token": { + "type": "string", + "description": "Token that can be shared with sellers to authorize fetching this list. Store this - it is only returned at creation time." + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["list", "auth_token"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/delete-property-list-request.json b/static/schemas/source/property/delete-property-list-request.json new file mode 100644 index 0000000000..0e5217d260 --- /dev/null +++ b/static/schemas/source/property/delete-property-list-request.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/delete-property-list-request.json", + "title": "Delete Property List Request", + "description": "Request parameters for deleting a property list", + "type": "object", + "properties": { + "list_id": { + "type": "string", + "description": "ID of the property list to delete" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["list_id"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/delete-property-list-response.json b/static/schemas/source/property/delete-property-list-response.json new file mode 100644 index 0000000000..655bbed524 --- /dev/null +++ b/static/schemas/source/property/delete-property-list-response.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/delete-property-list-response.json", + "title": "Delete Property List Response", + "description": "Response payload for delete_property_list task", + "type": "object", + "properties": { + "deleted": { + "type": "boolean", + "description": "Whether the list was successfully deleted" + }, + "list_id": { + "type": "string", + "description": "ID of the deleted list" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["deleted", "list_id"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/delivery-record.json b/static/schemas/source/property/delivery-record.json new file mode 100644 index 0000000000..29e6bc17a7 --- /dev/null +++ b/static/schemas/source/property/delivery-record.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/delivery-record.json", + "title": "Delivery Record", + "description": "A single delivery record representing impressions served to a property identifier. Used as input to validate_property_delivery.", + "type": "object", + "properties": { + "identifier": { + "$ref": "/schemas/core/identifier.json", + "description": "The property identifier where impressions were delivered" + }, + "impressions": { + "type": "integer", + "minimum": 0, + "description": "Number of impressions delivered to this identifier" + }, + "record_id": { + "type": "string", + "description": "Optional client-provided ID for correlating results back to source data" + }, + "sales_agent_url": { + "type": "string", + "format": "uri", + "description": "URL of the sales agent that sold this inventory. If provided, authorization is validated against the property's adagents.json." + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["identifier", "impressions"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/feature-requirement.json b/static/schemas/source/property/feature-requirement.json new file mode 100644 index 0000000000..0a75a1a21f --- /dev/null +++ b/static/schemas/source/property/feature-requirement.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/feature-requirement.json", + "title": "Feature Requirement", + "description": "A feature-based requirement for property filtering. Use min_value/max_value for quantitative features, allowed_values for binary/categorical features.", + "type": "object", + "properties": { + "feature_id": { + "type": "string", + "description": "Feature to evaluate (discovered via list_property_features)" + }, + "min_value": { + "type": "number", + "description": "Minimum numeric value required (for quantitative features)" + }, + "max_value": { + "type": "number", + "description": "Maximum numeric value allowed (for quantitative features)" + }, + "allowed_values": { + "type": "array", + "description": "Values that pass the requirement (for binary/categorical features)", + "items": {} + }, + "if_not_covered": { + "type": "string", + "enum": ["exclude", "include"], + "default": "exclude", + "description": "How to handle properties where this feature is not covered. 'exclude' (default): property is removed from the list. 'include': property passes this requirement (fail-open)." + } + }, + "required": ["feature_id"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/get-property-features-request.json b/static/schemas/source/property/get-property-features-request.json new file mode 100644 index 0000000000..a664eb65a9 --- /dev/null +++ b/static/schemas/source/property/get-property-features-request.json @@ -0,0 +1,68 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/get-property-features-request.json", + "title": "Get Property Features Request", + "description": "Request payload for get_property_features task. Retrieves feature values for properties from a governance agent. Supports two modes: explicit property list OR publisher-based discovery.", + "type": "object", + "properties": { + "properties": { + "type": "array", + "description": "Explicit list of properties to get features for. Can be domain strings or structured property references. Use this when you know exactly which properties you want.", + "items": { + "oneOf": [ + { + "type": "string", + "description": "Domain string (e.g., 'example.com')" + }, + { + "$ref": "/schemas/core/property-id.json" + } + ] + }, + "minItems": 1, + "maxItems": 100 + }, + "publisher_domain": { + "type": "string", + "description": "Publisher domain to discover properties for. Use this to get features for all properties belonging to a publisher. Can be combined with property_types and property_tags for filtering." + }, + "property_types": { + "type": "array", + "description": "Filter to specific property types (e.g., 'website', 'mobile_app', 'ctv_app'). Only applies when using publisher_domain.", + "items": { + "$ref": "/schemas/enums/property-type.json" + } + }, + "property_tags": { + "type": "array", + "description": "Filter to properties with these tags. Only applies when using publisher_domain.", + "items": { + "$ref": "/schemas/core/property-tag.json" + } + }, + "feature_ids": { + "type": "array", + "description": "Optional filter to specific features. If omitted, returns all available features.", + "items": { + "type": "string" + } + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "oneOf": [ + { + "required": ["properties"], + "description": "Explicit property list mode" + }, + { + "required": ["publisher_domain"], + "description": "Publisher discovery mode" + } + ], + "additionalProperties": false +} diff --git a/static/schemas/source/property/get-property-features-response.json b/static/schemas/source/property/get-property-features-response.json new file mode 100644 index 0000000000..d07a0d480d --- /dev/null +++ b/static/schemas/source/property/get-property-features-response.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/get-property-features-response.json", + "title": "Get Property Features Response", + "description": "Response payload for get_property_features task. Returns feature values for requested properties.", + "type": "object", + "properties": { + "results": { + "type": "array", + "description": "Feature values for each requested property", + "items": { + "$ref": "/schemas/property/property-feature-result.json" + } + }, + "errors": { + "type": "array", + "description": "Errors for properties that could not be processed", + "items": { + "$ref": "/schemas/core/error.json" + } + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["results"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/get-property-list-request.json b/static/schemas/source/property/get-property-list-request.json new file mode 100644 index 0000000000..1fd852a761 --- /dev/null +++ b/static/schemas/source/property/get-property-list-request.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/get-property-list-request.json", + "title": "Get Property List Request", + "description": "Request parameters for retrieving a property list with resolved identifiers", + "type": "object", + "properties": { + "list_id": { + "type": "string", + "description": "ID of the property list to retrieve" + }, + "resolve": { + "type": "boolean", + "description": "Whether to apply filters and return resolved identifiers (default: true)", + "default": true + }, + "max_results": { + "type": "integer", + "description": "Maximum identifiers to return (for large lists)", + "minimum": 1 + }, + "cursor": { + "type": "string", + "description": "Pagination cursor for large result sets" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["list_id"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/get-property-list-response.json b/static/schemas/source/property/get-property-list-response.json new file mode 100644 index 0000000000..89ba3bfc5c --- /dev/null +++ b/static/schemas/source/property/get-property-list-response.json @@ -0,0 +1,68 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/get-property-list-response.json", + "title": "Get Property List Response", + "description": "Response payload for get_property_list task. Returns identifiers only (not full property objects or scores). Consumers should cache the resolved identifiers and refresh based on cache_valid_until.", + "type": "object", + "properties": { + "list": { + "$ref": "/schemas/property/property-list.json", + "description": "The property list metadata (always returned)" + }, + "identifiers": { + "type": "array", + "description": "Resolved identifiers that passed filters (if resolve=true). Cache these locally for real-time use.", + "items": { + "$ref": "/schemas/core/identifier.json" + } + }, + "total_count": { + "type": "integer", + "description": "Total number of identifiers in resolved list" + }, + "returned_count": { + "type": "integer", + "description": "Number of identifiers returned in this response" + }, + "pagination": { + "type": "object", + "description": "Pagination information", + "properties": { + "has_more": { + "type": "boolean", + "description": "Whether more results are available" + }, + "cursor": { + "type": "string", + "description": "Cursor for next page" + } + }, + "additionalProperties": false + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "description": "When the list was resolved" + }, + "cache_valid_until": { + "type": "string", + "format": "date-time", + "description": "Cache expiration timestamp. Re-fetch the list after this time to get updated identifiers." + }, + "coverage_gaps": { + "type": "object", + "description": "Properties included in the list despite missing feature data. Only present when a feature_requirement has if_not_covered='include'. Maps feature_id to list of identifiers not covered for that feature.", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "/schemas/core/identifier.json" + } + } + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["list"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/list-property-features-request.json b/static/schemas/source/property/list-property-features-request.json new file mode 100644 index 0000000000..3c16d096c1 --- /dev/null +++ b/static/schemas/source/property/list-property-features-request.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/list-property-features-request.json", + "title": "List Property Features Request", + "description": "Request payload for list_property_features task. Discovers what features a governance agent can evaluate.", + "type": "object", + "properties": { + "property_types": { + "type": "array", + "description": "Filter to features available for these property types", + "items": { + "type": "string" + } + }, + "countries": { + "type": "array", + "description": "Filter to features available in these countries", + "items": { + "type": "string" + } + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "additionalProperties": false +} diff --git a/static/schemas/source/property/list-property-features-response.json b/static/schemas/source/property/list-property-features-response.json new file mode 100644 index 0000000000..efbf849fd4 --- /dev/null +++ b/static/schemas/source/property/list-property-features-response.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/list-property-features-response.json", + "title": "List Property Features Response", + "description": "Response payload for list_property_features task. Returns the features this governance agent can evaluate.", + "type": "object", + "properties": { + "features": { + "type": "array", + "description": "Features this agent can evaluate", + "items": { + "$ref": "/schemas/property/property-feature-definition.json" + } + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["features"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/list-property-lists-request.json b/static/schemas/source/property/list-property-lists-request.json new file mode 100644 index 0000000000..034c38e475 --- /dev/null +++ b/static/schemas/source/property/list-property-lists-request.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/list-property-lists-request.json", + "title": "List Property Lists Request", + "description": "Request parameters for listing property lists", + "type": "object", + "properties": { + "principal": { + "type": "string", + "description": "Filter to lists owned by this principal" + }, + "name_contains": { + "type": "string", + "description": "Filter to lists whose name contains this string" + }, + "max_results": { + "type": "integer", + "description": "Maximum lists to return", + "minimum": 1, + "default": 100 + }, + "cursor": { + "type": "string", + "description": "Pagination cursor" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "additionalProperties": false +} diff --git a/static/schemas/source/property/list-property-lists-response.json b/static/schemas/source/property/list-property-lists-response.json new file mode 100644 index 0000000000..f80b67ed17 --- /dev/null +++ b/static/schemas/source/property/list-property-lists-response.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/list-property-lists-response.json", + "title": "List Property Lists Response", + "description": "Response payload for list_property_lists task", + "type": "object", + "properties": { + "lists": { + "type": "array", + "description": "Array of property lists (metadata only, not resolved properties)", + "items": { + "$ref": "/schemas/property/property-list.json" + } + }, + "total_count": { + "type": "integer", + "description": "Total number of lists matching criteria" + }, + "returned_count": { + "type": "integer", + "description": "Number of lists returned in this response" + }, + "pagination": { + "type": "object", + "description": "Pagination information", + "properties": { + "has_more": { + "type": "boolean", + "description": "Whether more results are available" + }, + "cursor": { + "type": "string", + "description": "Cursor for next page" + } + }, + "additionalProperties": false + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["lists"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/property-error.json b/static/schemas/source/property/property-error.json new file mode 100644 index 0000000000..7b46b096ef --- /dev/null +++ b/static/schemas/source/property/property-error.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/property-error.json", + "title": "Property Error", + "description": "Error information for a property that could not be evaluated", + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Error code", + "enum": [ + "PROPERTY_NOT_FOUND", + "PROPERTY_NOT_MONITORED", + "LIST_NOT_FOUND", + "LIST_ACCESS_DENIED", + "METHODOLOGY_NOT_SUPPORTED", + "JURISDICTION_NOT_SUPPORTED" + ] + }, + "property": { + "$ref": "/schemas/core/property.json", + "description": "The property that caused the error" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": ["code", "message"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/property-feature-definition.json b/static/schemas/source/property/property-feature-definition.json new file mode 100644 index 0000000000..baaf017bb7 --- /dev/null +++ b/static/schemas/source/property/property-feature-definition.json @@ -0,0 +1,84 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/property-feature-definition.json", + "title": "Property Feature Definition", + "description": "Defines a feature that a governance agent can evaluate for properties. Used in list_property_features to advertise agent capabilities.", + "type": "object", + "properties": { + "feature_id": { + "type": "string", + "description": "Unique identifier for this feature (e.g., 'consent_quality', 'carbon_score', 'coppa_certified')" + }, + "name": { + "type": "string", + "description": "Human-readable name for the feature" + }, + "description": { + "type": "string", + "description": "Description of what this feature measures or represents" + }, + "type": { + "type": "string", + "enum": ["binary", "quantitative", "categorical"], + "description": "The type of values this feature produces: binary (true/false), quantitative (numeric range), categorical (enumerated values)" + }, + "range": { + "type": "object", + "description": "For quantitative features, the valid range of values", + "properties": { + "min": { + "type": "number", + "description": "Minimum value" + }, + "max": { + "type": "number", + "description": "Maximum value" + } + }, + "required": ["min", "max"], + "additionalProperties": false + }, + "allowed_values": { + "type": "array", + "description": "For categorical features, the set of valid values", + "items": { + "type": "string" + } + }, + "coverage": { + "type": "object", + "description": "What this feature covers (empty arrays = all)", + "properties": { + "property_types": { + "type": "array", + "description": "Property types this feature applies to", + "items": { + "type": "string" + } + }, + "countries": { + "type": "array", + "description": "Countries where this feature is available", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "methodology_url": { + "type": "string", + "format": "uri", + "description": "URL to documentation explaining how this feature is calculated/measured" + }, + "methodology_version": { + "type": "string", + "description": "Version identifier for the methodology (for audit trails)" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["feature_id", "name", "type", "methodology_url"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/property-feature-result.json b/static/schemas/source/property/property-feature-result.json new file mode 100644 index 0000000000..25a43a6439 --- /dev/null +++ b/static/schemas/source/property/property-feature-result.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/property-feature-result.json", + "title": "Property Feature Result", + "description": "Feature values for a single property from a governance agent.", + "type": "object", + "properties": { + "property": { + "oneOf": [ + { + "type": "string", + "description": "Domain string (e.g., 'example.com')" + }, + { + "$ref": "/schemas/core/property-id.json" + } + ], + "description": "The property these features apply to" + }, + "features": { + "type": "object", + "description": "Map of feature_id to feature value", + "additionalProperties": { + "$ref": "/schemas/property/property-feature-value.json" + } + }, + "coverage_status": { + "type": "string", + "enum": ["covered", "not_covered", "pending"], + "description": "Whether this property is covered by this governance agent: covered (has data), not_covered (not measured), pending (measurement in progress)" + }, + "last_evaluated": { + "type": "string", + "format": "date-time", + "description": "When features were last evaluated for this property" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["property", "coverage_status"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/property-feature-value.json b/static/schemas/source/property/property-feature-value.json new file mode 100644 index 0000000000..c2b70ab712 --- /dev/null +++ b/static/schemas/source/property/property-feature-value.json @@ -0,0 +1,51 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/property-feature-value.json", + "title": "Property Feature Value", + "description": "A single feature value for a property. Structure varies by feature type (binary, quantitative, categorical).", + "type": "object", + "properties": { + "value": { + "description": "The feature value. Type depends on feature definition: boolean for binary, number for quantitative, string for categorical.", + "oneOf": [ + { "type": "boolean" }, + { "type": "number" }, + { "type": "string" } + ] + }, + "unit": { + "type": "string", + "description": "Unit of measurement for quantitative values (e.g., 'gCO2e/1000_impressions', 'percentage')" + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Confidence score for this value (0-1)" + }, + "measured_at": { + "type": "string", + "format": "date-time", + "description": "When this specific value was measured" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When this certification/value expires (for time-limited certifications)" + }, + "methodology_version": { + "type": "string", + "description": "Version of the methodology used to calculate this value" + }, + "details": { + "type": "object", + "description": "Additional vendor-specific details about this measurement", + "additionalProperties": true + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["value"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/property-feature.json b/static/schemas/source/property/property-feature.json new file mode 100644 index 0000000000..6c9939b595 --- /dev/null +++ b/static/schemas/source/property/property-feature.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/property-feature.json", + "title": "Property Feature", + "description": "A discrete feature assessment for a property (e.g., from app store privacy labels)", + "type": "object", + "properties": { + "feature_id": { + "type": "string", + "description": "Identifier for the feature being assessed" + }, + "value": { + "type": "string", + "description": "The feature value" + }, + "source": { + "type": "string", + "description": "Source of the feature data (e.g., app_store_privacy_label, tcf_string)" + } + }, + "required": ["feature_id", "value"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/property-list-changed-webhook.json b/static/schemas/source/property/property-list-changed-webhook.json new file mode 100644 index 0000000000..69b6c8f17f --- /dev/null +++ b/static/schemas/source/property/property-list-changed-webhook.json @@ -0,0 +1,60 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/property-list-changed-webhook.json", + "title": "Property List Changed Webhook", + "description": "Webhook notification sent when a property list's resolved properties change. Contains a summary only - recipients must call get_property_list to retrieve the updated properties. This keeps payloads small and avoids redundant data transfer.", + "type": "object", + "properties": { + "event": { + "type": "string", + "const": "property_list_changed", + "description": "The event type" + }, + "list_id": { + "type": "string", + "description": "ID of the property list that changed" + }, + "list_name": { + "type": "string", + "description": "Name of the property list" + }, + "change_summary": { + "type": "object", + "description": "Summary of changes to the resolved list", + "properties": { + "properties_added": { + "type": "integer", + "description": "Number of properties added since last resolution" + }, + "properties_removed": { + "type": "integer", + "description": "Number of properties removed since last resolution" + }, + "total_properties": { + "type": "integer", + "description": "Total properties in the resolved list" + } + }, + "additionalProperties": false + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "description": "When the list was re-resolved" + }, + "cache_valid_until": { + "type": "string", + "format": "date-time", + "description": "When the consumer should refresh from the governance agent" + }, + "signature": { + "type": "string", + "description": "Cryptographic signature of the webhook payload, signed with the agent's private key. Recipients MUST verify this signature." + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["event", "list_id", "resolved_at", "signature"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/property-list-filters.json b/static/schemas/source/property/property-list-filters.json new file mode 100644 index 0000000000..ec132f2bf3 --- /dev/null +++ b/static/schemas/source/property/property-list-filters.json @@ -0,0 +1,47 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/property-list-filters.json", + "title": "Property List Filters", + "description": "Filters that dynamically modify a property list when resolved", + "type": "object", + "properties": { + "countries_all": { + "type": "array", + "description": "Property must have feature data for ALL listed countries (ISO codes). Required.", + "items": { + "type": "string", + "pattern": "^[A-Z]{2}$" + } + }, + "channels_any": { + "type": "array", + "description": "Property must support ANY of the listed channels. Required.", + "items": { + "$ref": "/schemas/enums/channels.json" + } + }, + "property_types": { + "type": "array", + "description": "Filter to these property types", + "items": { + "$ref": "/schemas/enums/property-type.json" + } + }, + "feature_requirements": { + "type": "array", + "description": "Feature-based requirements. Property must pass ALL requirements (AND logic).", + "items": { + "$ref": "/schemas/property/feature-requirement.json" + } + }, + "exclude_identifiers": { + "type": "array", + "description": "Identifiers to always exclude from results", + "items": { + "$ref": "/schemas/core/identifier.json" + } + } + }, + "required": ["countries_all", "channels_any"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/property-list.json b/static/schemas/source/property/property-list.json new file mode 100644 index 0000000000..53a89a5cf3 --- /dev/null +++ b/static/schemas/source/property/property-list.json @@ -0,0 +1,67 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/property-list.json", + "title": "Property List", + "description": "A managed property list with optional filters for dynamic evaluation. Lists are resolved at setup time and cached by orchestrators/sellers for real-time use.", + "type": "object", + "properties": { + "list_id": { + "type": "string", + "description": "Unique identifier for this property list" + }, + "name": { + "type": "string", + "description": "Human-readable name for the list" + }, + "description": { + "type": "string", + "description": "Description of the list's purpose" + }, + "principal": { + "type": "string", + "description": "Principal identity that owns this list" + }, + "base_properties": { + "type": "array", + "description": "Array of property sources to evaluate. Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers). If omitted, queries the agent's entire property database.", + "items": { + "$ref": "/schemas/property/base-property-source.json" + } + }, + "filters": { + "$ref": "/schemas/property/property-list-filters.json", + "description": "Dynamic filters applied when resolving the list" + }, + "brand_manifest": { + "$ref": "/schemas/core/brand-manifest.json", + "description": "Brand identity used to automatically apply appropriate rules" + }, + "webhook_url": { + "type": "string", + "format": "uri", + "description": "URL to receive notifications when the resolved list changes" + }, + "cache_duration_hours": { + "type": "integer", + "description": "Recommended cache duration for resolved list. Consumers should re-fetch after this period.", + "minimum": 1, + "default": 24 + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the list was created" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When the list was last modified" + }, + "property_count": { + "type": "integer", + "description": "Number of properties in the resolved list (at time of last resolution)" + } + }, + "required": ["list_id", "name"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/update-property-list-request.json b/static/schemas/source/property/update-property-list-request.json new file mode 100644 index 0000000000..d83cdb49b0 --- /dev/null +++ b/static/schemas/source/property/update-property-list-request.json @@ -0,0 +1,49 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/update-property-list-request.json", + "title": "Update Property List Request", + "description": "Request parameters for updating an existing property list", + "type": "object", + "properties": { + "list_id": { + "type": "string", + "description": "ID of the property list to update" + }, + "name": { + "type": "string", + "description": "New name for the list" + }, + "description": { + "type": "string", + "description": "New description" + }, + "base_properties": { + "type": "array", + "description": "Complete replacement for the base properties list (not a patch). Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers).", + "items": { + "$ref": "/schemas/property/base-property-source.json" + } + }, + "filters": { + "$ref": "/schemas/property/property-list-filters.json", + "description": "Complete replacement for the filters (not a patch)" + }, + "brand_manifest": { + "$ref": "/schemas/core/brand-manifest.json", + "description": "Update brand identity and requirements" + }, + "webhook_url": { + "type": "string", + "format": "uri", + "description": "Update the webhook URL for list change notifications (set to empty string to remove)" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["list_id"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/update-property-list-response.json b/static/schemas/source/property/update-property-list-response.json new file mode 100644 index 0000000000..8258df259c --- /dev/null +++ b/static/schemas/source/property/update-property-list-response.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/update-property-list-response.json", + "title": "Update Property List Response", + "description": "Response payload for update_property_list task", + "type": "object", + "properties": { + "list": { + "$ref": "/schemas/property/property-list.json", + "description": "The updated property list" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["list"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/validate-property-delivery-request.json b/static/schemas/source/property/validate-property-delivery-request.json new file mode 100644 index 0000000000..16597cf433 --- /dev/null +++ b/static/schemas/source/property/validate-property-delivery-request.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/validate-property-delivery-request.json", + "title": "Validate Property Delivery Request", + "description": "Request payload for validate_property_delivery task. Validates delivery records against a property list to determine compliance.", + "type": "object", + "properties": { + "list_id": { + "type": "string", + "description": "ID of the property list to validate against" + }, + "records": { + "type": "array", + "description": "Delivery records to validate. Each record represents impressions delivered to a property identifier.", + "items": { + "$ref": "/schemas/property/delivery-record.json" + }, + "minItems": 1, + "maxItems": 10000 + }, + "include_compliant": { + "type": "boolean", + "default": false, + "description": "Include compliant records in results (default: only return non_compliant, unmodeled, and unidentified)" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["list_id", "records"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/validate-property-delivery-response.json b/static/schemas/source/property/validate-property-delivery-response.json new file mode 100644 index 0000000000..be229f4067 --- /dev/null +++ b/static/schemas/source/property/validate-property-delivery-response.json @@ -0,0 +1,167 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/validate-property-delivery-response.json", + "title": "Validate Property Delivery Response", + "description": "Response payload for validate_property_delivery task. Returns aggregate compliance statistics and per-record validation results.", + "type": "object", + "properties": { + "list_id": { + "type": "string", + "description": "ID of the property list validated against" + }, + "summary": { + "type": "object", + "description": "Aggregate validation statistics", + "properties": { + "total_records": { + "type": "integer", + "description": "Total number of records validated" + }, + "total_impressions": { + "type": "integer", + "description": "Total impressions across all records" + }, + "compliant_records": { + "type": "integer", + "description": "Number of records with compliant status" + }, + "compliant_impressions": { + "type": "integer", + "description": "Impressions from compliant records" + }, + "non_compliant_records": { + "type": "integer", + "description": "Number of records with non_compliant status" + }, + "non_compliant_impressions": { + "type": "integer", + "description": "Impressions from non_compliant records" + }, + "not_covered_records": { + "type": "integer", + "description": "Number of records where identifier was recognized but no data available" + }, + "not_covered_impressions": { + "type": "integer", + "description": "Impressions from not_covered records" + }, + "unidentified_records": { + "type": "integer", + "description": "Number of records where identifier type was not resolvable" + }, + "unidentified_impressions": { + "type": "integer", + "description": "Impressions from unidentified records" + } + }, + "required": [ + "total_records", + "total_impressions", + "compliant_records", + "compliant_impressions", + "non_compliant_records", + "non_compliant_impressions", + "not_covered_records", + "not_covered_impressions", + "unidentified_records", + "unidentified_impressions" + ], + "additionalProperties": false + }, + "aggregate": { + "type": "object", + "description": "Optional aggregate measurements computed by the governance agent. Format and meaning are agent-specific.", + "properties": { + "score": { + "type": "number", + "description": "Numeric score (0-100 scale typical, but agent-defined)" + }, + "grade": { + "type": "string", + "description": "Letter grade or category (e.g., 'A+', 'B-', 'Gold', 'Compliant')" + }, + "label": { + "type": "string", + "description": "Human-readable summary (e.g., '85% compliant', 'High quality')" + }, + "methodology_url": { + "type": "string", + "format": "uri", + "description": "URL explaining how this aggregate was calculated" + } + }, + "additionalProperties": true + }, + "authorization_summary": { + "type": "object", + "description": "Aggregate authorization statistics. Only present if any records included sales_agent_url.", + "properties": { + "records_checked": { + "type": "integer", + "description": "Number of records with sales_agent_url provided" + }, + "impressions_checked": { + "type": "integer", + "description": "Total impressions from records with sales_agent_url" + }, + "authorized_records": { + "type": "integer", + "description": "Number of records where sales agent was authorized" + }, + "authorized_impressions": { + "type": "integer", + "description": "Impressions from authorized records" + }, + "unauthorized_records": { + "type": "integer", + "description": "Number of records where sales agent was NOT authorized" + }, + "unauthorized_impressions": { + "type": "integer", + "description": "Impressions from unauthorized records" + }, + "unknown_records": { + "type": "integer", + "description": "Number of records where authorization could not be determined (adagents.json unavailable)" + }, + "unknown_impressions": { + "type": "integer", + "description": "Impressions from records where authorization could not be determined" + } + }, + "required": [ + "records_checked", + "impressions_checked", + "authorized_records", + "authorized_impressions", + "unauthorized_records", + "unauthorized_impressions", + "unknown_records", + "unknown_impressions" + ], + "additionalProperties": false + }, + "results": { + "type": "array", + "description": "Per-record validation results. By default only includes non_compliant and unknown records. Set include_compliant=true to include all records.", + "items": { + "$ref": "/schemas/property/validation-result.json" + } + }, + "validated_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp when validation was performed" + }, + "list_resolved_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of the property list resolution used for validation" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["list_id", "summary", "results", "validated_at"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/validation-result.json b/static/schemas/source/property/validation-result.json new file mode 100644 index 0000000000..b11db55aef --- /dev/null +++ b/static/schemas/source/property/validation-result.json @@ -0,0 +1,79 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/validation-result.json", + "title": "Validation Result", + "description": "Result of validating a single delivery record against a property list.", + "type": "object", + "properties": { + "identifier": { + "$ref": "/schemas/core/identifier.json", + "description": "The identifier that was validated" + }, + "record_id": { + "type": "string", + "description": "Client-provided ID from the delivery record (if provided)" + }, + "status": { + "type": "string", + "enum": ["compliant", "non_compliant", "not_covered", "unidentified"], + "description": "Validation status: compliant (in list), non_compliant (not in list), not_covered (identifier recognized but no data available), unidentified (identifier type not resolvable by this governance agent)" + }, + "impressions": { + "type": "integer", + "minimum": 0, + "description": "Number of impressions from this record" + }, + "violations": { + "type": "array", + "description": "Specific violations found (only present for non_compliant records)", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Machine-readable violation code" + }, + "message": { + "type": "string", + "description": "Human-readable violation description" + }, + "feature_id": { + "type": "string", + "description": "ID of the feature that caused the violation (only present for feature_failed violations)" + }, + "requirement": { + "type": "object", + "description": "The feature requirement that was not met (only present for feature_failed violations)", + "properties": { + "min_value": { + "type": "number", + "description": "Minimum value that was required" + }, + "max_value": { + "type": "number", + "description": "Maximum value that was allowed" + }, + "allowed_values": { + "type": "array", + "description": "Values that would have been acceptable", + "items": {} + } + }, + "additionalProperties": false + } + }, + "required": ["code", "message"], + "additionalProperties": false + } + }, + "authorization": { + "$ref": "/schemas/property/authorization-result.json", + "description": "Authorization validation result (only present if sales_agent_url was provided in the delivery record)" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["identifier", "status", "impressions"], + "additionalProperties": false +} diff --git a/v2.6-rc/docs/media-buy/capability-discovery/authorized-properties.mdx b/v2.6-rc/docs/media-buy/capability-discovery/authorized-properties.mdx index 11d72b671c..13264accab 100644 --- a/v2.6-rc/docs/media-buy/capability-discovery/authorized-properties.mdx +++ b/v2.6-rc/docs/media-buy/capability-discovery/authorized-properties.mdx @@ -268,11 +268,11 @@ For complete technical details on implementing the `adagents.json` file format, - Validation code examples and error handling - Security considerations and best practices -See the **[adagents.json Tech Spec](/docs/media-buy/capability-discovery/adagents)** for complete implementation guidance. +See the **[adagents.json Tech Spec](/docs/governance/property/adagents)** for complete implementation guidance. ## Related Documentation - **[`list_authorized_properties`](/docs/media-buy/task-reference/list_authorized_properties)** - API reference for property discovery - **[Product Discovery](/docs/media-buy/product-discovery/)** - How authorization integrates with product discovery - **[Properties Schema](https://adcontextprotocol.org/schemas/v2/core/property.json)** - Technical property data model -- **[adagents.json Tech Spec](/docs/media-buy/capability-discovery/adagents)** - Complete `adagents.json` implementation guide \ No newline at end of file +- **[adagents.json Tech Spec](/docs/governance/property/adagents)** - Complete `adagents.json` implementation guide \ No newline at end of file diff --git a/v2.6-rc/docs/media-buy/capability-discovery/index.mdx b/v2.6-rc/docs/media-buy/capability-discovery/index.mdx index 885a0e16c9..214806a992 100644 --- a/v2.6-rc/docs/media-buy/capability-discovery/index.mdx +++ b/v2.6-rc/docs/media-buy/capability-discovery/index.mdx @@ -21,7 +21,7 @@ Learn how sales agents can support standard creative formats through the referen - Leverage the Standard Creative Agent for standard formats - Work with publisher-specific creative agents for custom formats -### [Authorized Properties](/docs/media-buy/capability-discovery/authorized-properties) 🔐 +### [Authorized Properties](/docs/governance/property/authorized-properties) 🔐 Learn how AdCP prevents unauthorized resale and ensures sales agents are legitimate. Understand: - The problem of unauthorized resale in digital advertising @@ -30,7 +30,7 @@ Learn how AdCP prevents unauthorized resale and ensures sales agents are legitim - Property tags and large-scale authorization management -**Cross-Protocol Foundation**: The `adagents.json` authorization system applies across ALL AdCP protocols (Media Buy, Signals, and future Curation). See the [adagents.json specification](/docs/media-buy/capability-discovery/adagents) for complete implementation details. +**Cross-Protocol Foundation**: The `adagents.json` authorization system applies across ALL AdCP protocols (Media Buy, Signals, and future Curation). See the [adagents.json specification](/docs/governance/property/adagents) for complete implementation details. @@ -80,4 +80,4 @@ Together, these capabilities provide the foundation for safe, efficient, and eff - **[Creative Protocol](/docs/creative/)** - Creative agents and manifests - **[Creative Channel Guides](/docs/creative/channels/video)** - Format examples and patterns - **[Creative Manifests](/docs/creative/creative-manifests)** - Understanding creative specifications -- **[adagents.json Specification](/docs/media-buy/capability-discovery/adagents)** - Publisher authorization system (applies across all AdCP protocols) \ No newline at end of file +- **[adagents.json Specification](/docs/governance/property/adagents)** - Publisher authorization system (applies across all AdCP protocols) \ No newline at end of file diff --git a/v2.6-rc/docs/media-buy/index.mdx b/v2.6-rc/docs/media-buy/index.mdx index 0ead41042a..fcaed419b0 100644 --- a/v2.6-rc/docs/media-buy/index.mdx +++ b/v2.6-rc/docs/media-buy/index.mdx @@ -232,7 +232,7 @@ Choose your path based on your role and needs: 4. **Explore [Optimization & Reporting](/docs/media-buy/media-buys/optimization-reporting)** - Learn performance management ### **For Publishers/Sales Agents** -1. **Learn [Authorized Properties](/docs/media-buy/capability-discovery/authorized-properties)** - Understand authorization requirements +1. **Learn [Authorized Properties](/docs/governance/property/authorized-properties)** - Understand authorization requirements 2. **Review [Creative Formats](/docs/creative/formats)** - See supported creative specifications 3. **Study [Advanced Topics](/docs/media-buy/advanced-topics/)** - Deep dive into technical implementation diff --git a/v2.6-rc/docs/media-buy/task-reference/list_authorized_properties.mdx b/v2.6-rc/docs/media-buy/task-reference/list_authorized_properties.mdx index 5e7cb8ea9d..55bd3fcf87 100644 --- a/v2.6-rc/docs/media-buy/task-reference/list_authorized_properties.mdx +++ b/v2.6-rc/docs/media-buy/task-reference/list_authorized_properties.mdx @@ -498,6 +498,6 @@ After discovering authorized properties: ## Learn More -- [adagents.json Specification](/docs/media-buy/capability-discovery/adagents) - Publisher authorization file format +- [adagents.json Specification](/docs/governance/property/adagents) - Publisher authorization file format - [Property Schema](https://adcontextprotocol.org/schemas/v2/core/property.json) - Property definition structure - [Authorization Guide](/docs/reference/authentication) - How authorization works in AdCP diff --git a/v2.6-rc/docs/reference/implementor-faq.mdx b/v2.6-rc/docs/reference/implementor-faq.mdx index ce93b224df..747ada9c0c 100644 --- a/v2.6-rc/docs/reference/implementor-faq.mdx +++ b/v2.6-rc/docs/reference/implementor-faq.mdx @@ -289,7 +289,7 @@ assert(response.message.includes("policy")); 3. Verify the sales agent URL appears in `authorized_agents` 4. Reject products from unauthorized agents -See [Authorization Validation](/docs/media-buy/capability-discovery/adagents#buyer-agent-validation) for complete requirements. +See [Authorization Validation](/docs/governance/property/adagents#buyer-agent-validation) for complete requirements. ## Terminology From c453bc5f3e9e1cb96dada4c72a873b0a1319d329 Mon Sep 17 00:00:00 2001 From: BaiyuScope3 Date: Tue, 13 Jan 2026 18:32:58 -0500 Subject: [PATCH 17/49] build: rebuild dist/schemas/2.6.0 with impressions and paused fields Includes: - impressions field in package-request.json and update-media-buy-request.json - paused field in package-request.json - Other pending schema updates from 2.6.x source --- dist/schemas/2.6.0/adagents.json | 92 ++++++++++ .../media-buy/build-creative-request.json | 2 +- .../media-buy/build-creative-response.json | 2 +- .../media-buy/create-media-buy-request.json | 12 +- .../media-buy/create-media-buy-response.json | 2 +- .../get-media-buy-delivery-request.json | 2 +- .../get-media-buy-delivery-response.json | 2 +- .../media-buy/get-products-request.json | 28 ++- .../media-buy/get-products-response.json | 6 +- .../list-authorized-properties-request.json | 2 +- .../list-authorized-properties-response.json | 2 +- .../list-creative-formats-request.json | 2 +- .../list-creative-formats-response.json | 2 +- .../media-buy/list-creatives-request.json | 2 +- .../media-buy/list-creatives-response.json | 2 +- .../bundled/media-buy/package-request.json | 12 +- .../provide-performance-feedback-request.json | 2 +- ...provide-performance-feedback-response.json | 2 +- .../media-buy/sync-creatives-request.json | 2 +- .../media-buy/sync-creatives-response.json | 2 +- .../media-buy/update-media-buy-request.json | 7 +- .../media-buy/update-media-buy-response.json | 2 +- .../signals/activate-signal-request.json | 2 +- .../signals/activate-signal-response.json | 2 +- .../bundled/signals/get-signals-request.json | 2 +- .../bundled/signals/get-signals-response.json | 2 +- dist/schemas/2.6.0/core/identifier.json | 19 ++ .../schemas/2.6.0/core/property-list-ref.json | 25 +++ .../2.6.0/creative/asset-types/index.json | 2 +- dist/schemas/2.6.0/enums/adcp-domain.json | 11 +- dist/schemas/2.6.0/enums/task-type.json | 18 +- .../2.6.0/extensions/extension-meta.json | 59 +++++++ dist/schemas/2.6.0/extensions/index.json | 9 + dist/schemas/2.6.0/index.json | 127 ++++++++++++- .../2.6.0/media-buy/get-products-request.json | 4 + .../media-buy/get-products-response.json | 4 + .../2.6.0/media-buy/package-request.json | 10 ++ .../media-buy/update-media-buy-request.json | 5 + .../2.6.0/property/authorization-result.json | 41 +++++ .../2.6.0/property/base-property-source.json | 87 +++++++++ .../create-property-list-request.json | 40 +++++ .../create-property-list-response.json | 22 +++ .../delete-property-list-request.json | 21 +++ .../delete-property-list-response.json | 22 +++ .../2.6.0/property/delivery-record.json | 32 ++++ .../2.6.0/property/feature-requirement.json | 34 ++++ .../get-property-features-request.json | 68 +++++++ .../get-property-features-response.json | 28 +++ .../property/get-property-list-request.json | 35 ++++ .../property/get-property-list-response.json | 68 +++++++ .../list-property-features-request.json | 27 +++ .../list-property-features-response.json | 21 +++ .../property/list-property-lists-request.json | 34 ++++ .../list-property-lists-response.json | 44 +++++ .../2.6.0/property/property-error.json | 31 ++++ .../property/property-feature-definition.json | 84 +++++++++ .../property/property-feature-result.json | 43 +++++ .../property/property-feature-value.json | 51 ++++++ .../2.6.0/property/property-feature.json | 23 +++ .../property-list-changed-webhook.json | 60 +++++++ .../2.6.0/property/property-list-filters.json | 47 +++++ .../schemas/2.6.0/property/property-list.json | 67 +++++++ .../update-property-list-request.json | 49 +++++ .../update-property-list-response.json | 18 ++ .../validate-property-delivery-request.json | 32 ++++ .../validate-property-delivery-response.json | 167 ++++++++++++++++++ .../2.6.0/property/validation-result.json | 79 +++++++++ .../2.6.0/protocols/adcp-extension.json | 16 +- 68 files changed, 1841 insertions(+), 40 deletions(-) create mode 100644 dist/schemas/2.6.0/core/identifier.json create mode 100644 dist/schemas/2.6.0/core/property-list-ref.json create mode 100644 dist/schemas/2.6.0/extensions/extension-meta.json create mode 100644 dist/schemas/2.6.0/extensions/index.json create mode 100644 dist/schemas/2.6.0/property/authorization-result.json create mode 100644 dist/schemas/2.6.0/property/base-property-source.json create mode 100644 dist/schemas/2.6.0/property/create-property-list-request.json create mode 100644 dist/schemas/2.6.0/property/create-property-list-response.json create mode 100644 dist/schemas/2.6.0/property/delete-property-list-request.json create mode 100644 dist/schemas/2.6.0/property/delete-property-list-response.json create mode 100644 dist/schemas/2.6.0/property/delivery-record.json create mode 100644 dist/schemas/2.6.0/property/feature-requirement.json create mode 100644 dist/schemas/2.6.0/property/get-property-features-request.json create mode 100644 dist/schemas/2.6.0/property/get-property-features-response.json create mode 100644 dist/schemas/2.6.0/property/get-property-list-request.json create mode 100644 dist/schemas/2.6.0/property/get-property-list-response.json create mode 100644 dist/schemas/2.6.0/property/list-property-features-request.json create mode 100644 dist/schemas/2.6.0/property/list-property-features-response.json create mode 100644 dist/schemas/2.6.0/property/list-property-lists-request.json create mode 100644 dist/schemas/2.6.0/property/list-property-lists-response.json create mode 100644 dist/schemas/2.6.0/property/property-error.json create mode 100644 dist/schemas/2.6.0/property/property-feature-definition.json create mode 100644 dist/schemas/2.6.0/property/property-feature-result.json create mode 100644 dist/schemas/2.6.0/property/property-feature-value.json create mode 100644 dist/schemas/2.6.0/property/property-feature.json create mode 100644 dist/schemas/2.6.0/property/property-list-changed-webhook.json create mode 100644 dist/schemas/2.6.0/property/property-list-filters.json create mode 100644 dist/schemas/2.6.0/property/property-list.json create mode 100644 dist/schemas/2.6.0/property/update-property-list-request.json create mode 100644 dist/schemas/2.6.0/property/update-property-list-response.json create mode 100644 dist/schemas/2.6.0/property/validate-property-delivery-request.json create mode 100644 dist/schemas/2.6.0/property/validate-property-delivery-response.json create mode 100644 dist/schemas/2.6.0/property/validation-result.json diff --git a/dist/schemas/2.6.0/adagents.json b/dist/schemas/2.6.0/adagents.json index 4fafdaa844..f8f88a7be8 100644 --- a/dist/schemas/2.6.0/adagents.json +++ b/dist/schemas/2.6.0/adagents.json @@ -264,6 +264,38 @@ "type": "string", "format": "date-time", "description": "ISO 8601 timestamp indicating when this file was last updated" + }, + "property_features": { + "type": "array", + "description": "[AdCP 3.0] Optional list of agents that provide property feature data (certifications, scores, compliance status). Used for discovery - actual data comes from querying the agent's get_property_features task.", + "items": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "The agent's API endpoint URL (must implement get_property_features)" + }, + "name": { + "type": "string", + "description": "Human-readable name of the vendor/agent (e.g., 'Scope3', 'TAG', 'OneTrust')" + }, + "features": { + "type": "array", + "description": "Feature IDs this agent provides (e.g., 'carbon_score', 'tag_certified_against_fraud'). Use list_property_features on the agent for full definitions.", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "publisher_id": { + "type": "string", + "description": "Optional publisher identifier at this agent (for lookup)" + } + }, + "required": ["url", "name", "features"], + "additionalProperties": true + } } }, "required": [ @@ -489,6 +521,66 @@ } ], "last_updated": "2025-01-10T17:00:00Z" + }, + { + "$schema": "/schemas/2.6.0/adagents.json", + "contact": { + "name": "Premium News Publisher", + "email": "adops@news.example.com", + "domain": "news.example.com" + }, + "properties": [ + { + "property_type": "website", + "name": "News Example", + "identifiers": [ + { + "type": "domain", + "value": "news.example.com" + } + ], + "tags": ["premium", "news"], + "publisher_domain": "news.example.com" + } + ], + "tags": { + "premium": { + "name": "Premium Properties", + "description": "High-quality, brand-safe properties" + }, + "news": { + "name": "News Properties", + "description": "News and journalism content" + } + }, + "authorized_agents": [ + { + "url": "https://sales.news.example.com", + "authorized_for": "All news properties", + "authorization_type": "property_tags", + "property_tags": ["news"] + } + ], + "property_features": [ + { + "url": "https://api.scope3.com", + "name": "Scope3", + "features": ["carbon_score", "sustainability_grade"], + "publisher_id": "pub_news_12345" + }, + { + "url": "https://api.tagtoday.net", + "name": "TAG", + "features": ["tag_certified_against_fraud", "tag_brand_safety_certified"] + }, + { + "url": "https://api.onetrust.com", + "name": "OneTrust", + "features": ["gdpr_compliant", "tcf_registered", "ccpa_compliant"], + "publisher_id": "ot_news_67890" + } + ], + "last_updated": "2025-01-10T18:00:00Z" } ] } diff --git a/dist/schemas/2.6.0/bundled/media-buy/build-creative-request.json b/dist/schemas/2.6.0/bundled/media-buy/build-creative-request.json index add3c1ba85..47860690c1 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/build-creative-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/build-creative-request.json @@ -1343,7 +1343,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-07T19:56:54.823Z", + "generatedAt": "2026-01-13T23:32:36.212Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/build-creative-response.json b/dist/schemas/2.6.0/bundled/media-buy/build-creative-response.json index d3b8342b62..ea6e61cedb 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/build-creative-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/build-creative-response.json @@ -1377,7 +1377,7 @@ } ], "_bundled": { - "generatedAt": "2026-01-07T19:56:54.825Z", + "generatedAt": "2026-01-13T23:32:36.213Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/create-media-buy-request.json b/dist/schemas/2.6.0/bundled/media-buy/create-media-buy-request.json index 9b5f26e79c..4b8eeb36e8 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/create-media-buy-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/create-media-buy-request.json @@ -104,6 +104,16 @@ "description": "Bid price for auction-based CPM pricing (required if using cpm-auction-option)", "minimum": 0 }, + "impressions": { + "type": "number", + "description": "Impression goal for this package", + "minimum": 0 + }, + "paused": { + "type": "boolean", + "description": "Whether this package should be created in a paused state. Paused packages do not deliver impressions. Defaults to false.", + "default": false + }, "targeting_overlay": { "title": "Targeting Overlay", "description": "Optional geographic refinements for media buys. Most targeting should be expressed in the brief and handled by the publisher. These fields are primarily for geographic restrictions (RCT testing, regulatory compliance).", @@ -2009,7 +2019,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-07T19:56:54.826Z", + "generatedAt": "2026-01-13T23:32:36.215Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/create-media-buy-response.json b/dist/schemas/2.6.0/bundled/media-buy/create-media-buy-response.json index 8d99b96e93..e88aeb9d3b 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/create-media-buy-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/create-media-buy-response.json @@ -350,7 +350,7 @@ } ], "_bundled": { - "generatedAt": "2026-01-07T19:56:54.827Z", + "generatedAt": "2026-01-13T23:32:36.216Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/get-media-buy-delivery-request.json b/dist/schemas/2.6.0/bundled/media-buy/get-media-buy-delivery-request.json index 26de332bc0..d06ada53f5 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/get-media-buy-delivery-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/get-media-buy-delivery-request.json @@ -86,7 +86,7 @@ }, "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-07T19:56:54.827Z", + "generatedAt": "2026-01-13T23:32:36.216Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/get-media-buy-delivery-response.json b/dist/schemas/2.6.0/bundled/media-buy/get-media-buy-delivery-response.json index e0053adb76..a5d1a44f9e 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/get-media-buy-delivery-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/get-media-buy-delivery-response.json @@ -694,7 +694,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-07T19:56:54.828Z", + "generatedAt": "2026-01-13T23:32:36.216Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/get-products-request.json b/dist/schemas/2.6.0/bundled/media-buy/get-products-request.json index 63d7d0ab1f..1d9a99867c 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/get-products-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/get-products-request.json @@ -635,6 +635,32 @@ }, "additionalProperties": true }, + "property_list": { + "title": "Property List Reference", + "description": "[AdCP 3.0] Reference to an externally managed property list. When provided, the sales agent should filter products to only those available on properties in the list.", + "type": "object", + "properties": { + "agent_url": { + "type": "string", + "format": "uri", + "description": "URL of the agent managing the property list" + }, + "list_id": { + "type": "string", + "description": "Identifier for the property list within the agent", + "minLength": 1 + }, + "auth_token": { + "type": "string", + "description": "JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access." + } + }, + "required": [ + "agent_url", + "list_id" + ], + "additionalProperties": false + }, "context": { "title": "Context Object", "description": "Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", @@ -651,7 +677,7 @@ "required": [], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-07T19:56:54.829Z", + "generatedAt": "2026-01-13T23:32:36.217Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/get-products-response.json b/dist/schemas/2.6.0/bundled/media-buy/get-products-response.json index b0599f53d8..9b5ca4db25 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/get-products-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/get-products-response.json @@ -1289,6 +1289,10 @@ "additionalProperties": true } }, + "property_list_applied": { + "type": "boolean", + "description": "[AdCP 3.0] Indicates whether property_list filtering was applied. True if the agent filtered products based on the provided property_list. Absent or false if property_list was not provided or not supported by this agent." + }, "context": { "title": "Context Object", "description": "Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", @@ -1307,7 +1311,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-07T19:56:54.830Z", + "generatedAt": "2026-01-13T23:32:36.218Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/list-authorized-properties-request.json b/dist/schemas/2.6.0/bundled/media-buy/list-authorized-properties-request.json index 7e4e17f7cf..d73f077c31 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/list-authorized-properties-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/list-authorized-properties-request.json @@ -30,7 +30,7 @@ }, "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-07T19:56:54.830Z", + "generatedAt": "2026-01-13T23:32:36.219Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/list-authorized-properties-response.json b/dist/schemas/2.6.0/bundled/media-buy/list-authorized-properties-response.json index 6ae624ec4f..58f94bfd58 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/list-authorized-properties-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/list-authorized-properties-response.json @@ -122,7 +122,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-07T19:56:54.831Z", + "generatedAt": "2026-01-13T23:32:36.219Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/list-creative-formats-request.json b/dist/schemas/2.6.0/bundled/media-buy/list-creative-formats-request.json index d02fc863f7..6ab40d589b 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/list-creative-formats-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/list-creative-formats-request.json @@ -131,7 +131,7 @@ }, "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-07T19:56:54.831Z", + "generatedAt": "2026-01-13T23:32:36.219Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/list-creative-formats-response.json b/dist/schemas/2.6.0/bundled/media-buy/list-creative-formats-response.json index ed691f6fa6..536e1ab3b5 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/list-creative-formats-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/list-creative-formats-response.json @@ -792,7 +792,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-07T19:56:54.832Z", + "generatedAt": "2026-01-13T23:32:36.219Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/list-creatives-request.json b/dist/schemas/2.6.0/bundled/media-buy/list-creatives-request.json index 319341183d..b0b34dbd74 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/list-creatives-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/list-creatives-request.json @@ -288,7 +288,7 @@ } ], "_bundled": { - "generatedAt": "2026-01-07T19:56:54.834Z", + "generatedAt": "2026-01-13T23:32:36.220Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/list-creatives-response.json b/dist/schemas/2.6.0/bundled/media-buy/list-creatives-response.json index ab7c867de7..8adabb227a 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/list-creatives-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/list-creatives-response.json @@ -1648,7 +1648,7 @@ } ], "_bundled": { - "generatedAt": "2026-01-07T19:56:54.835Z", + "generatedAt": "2026-01-13T23:32:36.221Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/package-request.json b/dist/schemas/2.6.0/bundled/media-buy/package-request.json index 7bc4ce1b75..a6cd805bcf 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/package-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/package-request.json @@ -92,6 +92,16 @@ "description": "Bid price for auction-based CPM pricing (required if using cpm-auction-option)", "minimum": 0 }, + "impressions": { + "type": "number", + "description": "Impression goal for this package", + "minimum": 0 + }, + "paused": { + "type": "boolean", + "description": "Whether this package should be created in a paused state. Paused packages do not deliver impressions. Defaults to false.", + "default": false + }, "targeting_overlay": { "title": "Targeting Overlay", "description": "Optional geographic refinements for media buys. Most targeting should be expressed in the brief and handled by the publisher. These fields are primarily for geographic restrictions (RCT testing, regulatory compliance).", @@ -1417,7 +1427,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-07T19:56:54.836Z", + "generatedAt": "2026-01-13T23:32:36.222Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/provide-performance-feedback-request.json b/dist/schemas/2.6.0/bundled/media-buy/provide-performance-feedback-request.json index 6a8d687c7f..1f528b1e8a 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/provide-performance-feedback-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/provide-performance-feedback-request.json @@ -110,7 +110,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-07T19:56:54.837Z", + "generatedAt": "2026-01-13T23:32:36.222Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/provide-performance-feedback-response.json b/dist/schemas/2.6.0/bundled/media-buy/provide-performance-feedback-response.json index 6b4878652f..a9865aef05 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/provide-performance-feedback-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/provide-performance-feedback-response.json @@ -111,7 +111,7 @@ } ], "_bundled": { - "generatedAt": "2026-01-07T19:56:54.837Z", + "generatedAt": "2026-01-13T23:32:36.223Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/sync-creatives-request.json b/dist/schemas/2.6.0/bundled/media-buy/sync-creatives-request.json index 0b56121358..da02739601 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/sync-creatives-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/sync-creatives-request.json @@ -1469,7 +1469,7 @@ } ], "_bundled": { - "generatedAt": "2026-01-07T19:56:54.839Z", + "generatedAt": "2026-01-13T23:32:36.224Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/sync-creatives-response.json b/dist/schemas/2.6.0/bundled/media-buy/sync-creatives-response.json index cab162ded4..3bb14a7129 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/sync-creatives-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/sync-creatives-response.json @@ -202,7 +202,7 @@ } ], "_bundled": { - "generatedAt": "2026-01-07T19:56:54.839Z", + "generatedAt": "2026-01-13T23:32:36.224Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/update-media-buy-request.json b/dist/schemas/2.6.0/bundled/media-buy/update-media-buy-request.json index c8d8fa805e..ce16186f4d 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/update-media-buy-request.json +++ b/dist/schemas/2.6.0/bundled/media-buy/update-media-buy-request.json @@ -77,6 +77,11 @@ "description": "Updated bid price for auction-based pricing options (only applies when pricing_option is auction-based)", "minimum": 0 }, + "impressions": { + "type": "number", + "description": "Updated impression goal for this package", + "minimum": 0 + }, "paused": { "type": "boolean", "description": "Pause/resume specific package (true = paused, false = active)" @@ -1519,7 +1524,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-07T19:56:54.840Z", + "generatedAt": "2026-01-13T23:32:36.225Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/media-buy/update-media-buy-response.json b/dist/schemas/2.6.0/bundled/media-buy/update-media-buy-response.json index 23ebd57c19..7828c98760 100644 --- a/dist/schemas/2.6.0/bundled/media-buy/update-media-buy-response.json +++ b/dist/schemas/2.6.0/bundled/media-buy/update-media-buy-response.json @@ -352,7 +352,7 @@ } ], "_bundled": { - "generatedAt": "2026-01-07T19:56:54.841Z", + "generatedAt": "2026-01-13T23:32:36.226Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/signals/activate-signal-request.json b/dist/schemas/2.6.0/bundled/signals/activate-signal-request.json index 24de122d6e..a2c0e35d27 100644 --- a/dist/schemas/2.6.0/bundled/signals/activate-signal-request.json +++ b/dist/schemas/2.6.0/bundled/signals/activate-signal-request.json @@ -84,7 +84,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-07T19:56:54.841Z", + "generatedAt": "2026-01-13T23:32:36.226Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/signals/activate-signal-response.json b/dist/schemas/2.6.0/bundled/signals/activate-signal-response.json index 6e532f84b2..15b22110fa 100644 --- a/dist/schemas/2.6.0/bundled/signals/activate-signal-response.json +++ b/dist/schemas/2.6.0/bundled/signals/activate-signal-response.json @@ -289,7 +289,7 @@ } ], "_bundled": { - "generatedAt": "2026-01-07T19:56:54.842Z", + "generatedAt": "2026-01-13T23:32:36.226Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/signals/get-signals-request.json b/dist/schemas/2.6.0/bundled/signals/get-signals-request.json index 85d7c3eb36..91146046e7 100644 --- a/dist/schemas/2.6.0/bundled/signals/get-signals-request.json +++ b/dist/schemas/2.6.0/bundled/signals/get-signals-request.json @@ -148,7 +148,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-07T19:56:54.842Z", + "generatedAt": "2026-01-13T23:32:36.227Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/bundled/signals/get-signals-response.json b/dist/schemas/2.6.0/bundled/signals/get-signals-response.json index d7acb6f13f..cbb4e048fe 100644 --- a/dist/schemas/2.6.0/bundled/signals/get-signals-response.json +++ b/dist/schemas/2.6.0/bundled/signals/get-signals-response.json @@ -321,7 +321,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-01-07T19:56:54.842Z", + "generatedAt": "2026-01-13T23:32:36.227Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/dist/schemas/2.6.0/core/identifier.json b/dist/schemas/2.6.0/core/identifier.json new file mode 100644 index 0000000000..46ef4de592 --- /dev/null +++ b/dist/schemas/2.6.0/core/identifier.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/core/identifier.json", + "title": "Identifier", + "description": "A property identifier with type and value. Used to identify properties across platforms (domains, app store IDs, etc.).", + "type": "object", + "properties": { + "type": { + "$ref": "/schemas/2.6.0/enums/identifier-types.json", + "description": "Type of identifier" + }, + "value": { + "type": "string", + "description": "The identifier value. For domain type: 'example.com' matches base domain plus www and m subdomains; 'edition.example.com' matches that specific subdomain; '*.example.com' matches ALL subdomains but NOT base domain" + } + }, + "required": ["type", "value"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/core/property-list-ref.json b/dist/schemas/2.6.0/core/property-list-ref.json new file mode 100644 index 0000000000..0d2ea81e99 --- /dev/null +++ b/dist/schemas/2.6.0/core/property-list-ref.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/core/property-list-ref.json", + "title": "Property List Reference", + "description": "Reference to an externally managed property list. Enables passing large property sets (50,000+) without embedding them in requests. The receiving agent fetches and caches the list independently.", + "type": "object", + "properties": { + "agent_url": { + "type": "string", + "format": "uri", + "description": "URL of the agent managing the property list" + }, + "list_id": { + "type": "string", + "description": "Identifier for the property list within the agent", + "minLength": 1 + }, + "auth_token": { + "type": "string", + "description": "JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access." + } + }, + "required": ["agent_url", "list_id"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/creative/asset-types/index.json b/dist/schemas/2.6.0/creative/asset-types/index.json index 14a3531866..188def9e1c 100644 --- a/dist/schemas/2.6.0/creative/asset-types/index.json +++ b/dist/schemas/2.6.0/creative/asset-types/index.json @@ -4,7 +4,7 @@ "title": "AdCP Asset Type Registry", "description": "Registry of asset types used in AdCP creative manifests. Each asset type defines the structure of actual content payloads (what you send), not requirements or constraints (which belong in format specifications).", "version": "1.0.0", - "lastUpdated": "2026-01-07", + "lastUpdated": "2026-01-13", "asset_types": { "image": { "description": "Static image asset (JPG, PNG, GIF, WebP, SVG)", diff --git a/dist/schemas/2.6.0/enums/adcp-domain.json b/dist/schemas/2.6.0/enums/adcp-domain.json index 2d48f76e84..375666ca42 100644 --- a/dist/schemas/2.6.0/enums/adcp-domain.json +++ b/dist/schemas/2.6.0/enums/adcp-domain.json @@ -4,8 +4,11 @@ "title": "AdCP Domain", "description": "AdCP protocol domains for task categorization", "type": "string", - "enum": [ - "media-buy", - "signals" - ] + "enum": ["media-buy", "signals", "governance", "creative"], + "enumDescriptions": { + "media-buy": "Campaign creation, package management, and delivery optimization", + "signals": "Audience signal discovery and activation", + "governance": "Property governance (identity, authorization, data, selection), brand standards, and compliance", + "creative": "Creative asset management, format discovery, and rendering" + } } diff --git a/dist/schemas/2.6.0/enums/task-type.json b/dist/schemas/2.6.0/enums/task-type.json index 9dcf5a33b0..2690a3400a 100644 --- a/dist/schemas/2.6.0/enums/task-type.json +++ b/dist/schemas/2.6.0/enums/task-type.json @@ -9,18 +9,30 @@ "update_media_buy", "sync_creatives", "activate_signal", - "get_signals" + "get_signals", + "list_property_features", + "create_property_list", + "update_property_list", + "get_property_list", + "list_property_lists", + "delete_property_list" ], "enumDescriptions": { "create_media_buy": "Media-buy domain: Create a new advertising campaign with one or more packages", "update_media_buy": "Media-buy domain: Update campaign settings, package configuration, or delivery parameters", "sync_creatives": "Media-buy domain: Sync creative assets to publisher's library with upsert semantics", "activate_signal": "Signals domain: Activate an audience signal on a specific platform or account", - "get_signals": "Signals domain: Discover available audience signals based on natural language description" + "get_signals": "Signals domain: Discover available audience signals based on natural language description", + "list_property_features": "Property domain: Discover what features a governance agent can evaluate", + "create_property_list": "Property domain: Create a new property list with filters and brand manifest", + "update_property_list": "Property domain: Update an existing property list", + "get_property_list": "Property domain: Retrieve a property list with resolved properties", + "list_property_lists": "Property domain: List all accessible property lists", + "delete_property_list": "Property domain: Delete a property list" }, "notes": [ "Task types map to specific AdCP task operations", - "Each task type belongs to either the 'media-buy' or 'signals' domain", + "Each task type belongs to the 'media-buy', 'signals', 'property', or 'creative' domain", "This enum is used in task management APIs (tasks/list, tasks/get) and webhook payloads", "New task types require a minor version bump per semantic versioning" ] diff --git a/dist/schemas/2.6.0/extensions/extension-meta.json b/dist/schemas/2.6.0/extensions/extension-meta.json new file mode 100644 index 0000000000..3d86f6b51d --- /dev/null +++ b/dist/schemas/2.6.0/extensions/extension-meta.json @@ -0,0 +1,59 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/extensions/extension-meta.json", + "title": "AdCP Extension File Schema", + "description": "Schema that all extension files must follow. Combines metadata (valid_from, docs_url) with the actual extension data schema. Extensions are auto-discovered from /schemas/extensions/*.json and included in versioned builds based on valid_from/valid_until.", + "type": "object", + "properties": { + "$schema": { + "type": "string", + "const": "http://json-schema.org/draft-07/schema#" + }, + "$id": { + "type": "string", + "pattern": "^/schemas/extensions/[a-z][a-z0-9_]*\\.json$", + "description": "Extension ID following pattern /schemas/extensions/{namespace}.json" + }, + "title": { + "type": "string", + "description": "Human-readable title for the extension" + }, + "description": { + "type": "string", + "description": "Description of what this extension provides" + }, + "valid_from": { + "type": "string", + "pattern": "^\\d+\\.\\d+$", + "description": "Minimum AdCP version this extension is compatible with (e.g., '2.5'). Extension will be included in all versioned schema builds >= this version." + }, + "valid_until": { + "type": "string", + "pattern": "^\\d+\\.\\d+$", + "description": "Last AdCP version this extension is compatible with (e.g., '3.0'). Omit if extension is still valid for current and future versions." + }, + "docs_url": { + "type": "string", + "format": "uri", + "description": "URL to documentation for implementors of this extension" + }, + "type": { + "const": "object", + "description": "Extensions must be objects (data within ext.{namespace})" + }, + "properties": { + "type": "object", + "description": "Schema properties defining the structure of ext.{namespace} data", + "additionalProperties": true + }, + "required": { + "type": "array", + "items": { "type": "string" }, + "description": "Required properties within the extension data" + }, + "additionalProperties": { + "description": "Whether additional properties are allowed in the extension data" + } + }, + "required": ["$schema", "$id", "title", "description", "valid_from", "type", "properties"] +} diff --git a/dist/schemas/2.6.0/extensions/index.json b/dist/schemas/2.6.0/extensions/index.json new file mode 100644 index 0000000000..eae3746a98 --- /dev/null +++ b/dist/schemas/2.6.0/extensions/index.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/extensions/index.json", + "title": "AdCP Extension Registry", + "description": "Auto-generated registry of formal AdCP extensions. Extensions provide typed schemas for vendor-specific or domain-specific data within the ext field. Agents declare which extensions they support in their agent card.", + "_generated": true, + "_generatedAt": "2026-01-13T23:32:36.210Z", + "extensions": {} +} \ No newline at end of file diff --git a/dist/schemas/2.6.0/index.json b/dist/schemas/2.6.0/index.json index ecb888befd..ac15127520 100644 --- a/dist/schemas/2.6.0/index.json +++ b/dist/schemas/2.6.0/index.json @@ -31,7 +31,7 @@ ] } }, - "lastUpdated": "2026-01-07", + "lastUpdated": "2026-01-13", "baseUrl": "/schemas/2.6.0", "schemas": { "core": { @@ -168,6 +168,14 @@ "property-tag": { "$ref": "/schemas/2.6.0/core/property-tag.json", "description": "Tag for categorizing publisher properties - lowercase alphanumeric with underscores only" + }, + "property-list-ref": { + "$ref": "/schemas/2.6.0/core/property-list-ref.json", + "description": "Reference to an externally managed property list for passing large property sets" + }, + "identifier": { + "$ref": "/schemas/2.6.0/core/identifier.json", + "description": "A property identifier with type and value" } } }, @@ -220,7 +228,7 @@ }, "task-type": { "$ref": "/schemas/2.6.0/enums/task-type.json", - "description": "Valid AdCP task types across all domains (create_media_buy, update_media_buy, sync_creatives, activate_signal, get_signals)" + "description": "Valid AdCP task types across all domains (create_media_buy, update_media_buy, sync_creatives, activate_signal, get_signals, list_property_features, create_property_list, get_property_list, update_property_list, delete_property_list)" }, "asset-content-type": { "$ref": "/schemas/2.6.0/enums/asset-content-type.json", @@ -280,7 +288,7 @@ }, "adcp-domain": { "$ref": "/schemas/2.6.0/enums/adcp-domain.json", - "description": "AdCP protocol domains (media-buy, signals)" + "description": "AdCP protocol domains (media-buy, signals, governance)" }, "http-method": { "$ref": "/schemas/2.6.0/enums/http-method.json", @@ -562,6 +570,105 @@ } } }, + "governance": { + "description": "Governance protocol for property governance, brand standards, and compliance", + "supporting-schemas": { + "property-feature-definition": { + "$ref": "/schemas/2.6.0/property/property-feature-definition.json", + "description": "Definition of a feature that a governance agent can evaluate" + }, + "property-feature": { + "$ref": "/schemas/2.6.0/property/property-feature.json", + "description": "A discrete feature assessment for a property" + }, + "feature-requirement": { + "$ref": "/schemas/2.6.0/property/feature-requirement.json", + "description": "A feature-based requirement for property filtering" + }, + "property-error": { + "$ref": "/schemas/2.6.0/property/property-error.json", + "description": "Error information for a property that could not be evaluated" + }, + "property-list": { + "$ref": "/schemas/2.6.0/property/property-list.json", + "description": "A managed property list with optional filters for dynamic evaluation" + }, + "property-list-filters": { + "$ref": "/schemas/2.6.0/property/property-list-filters.json", + "description": "Filters that dynamically modify a property list when resolved" + }, + "property-list-changed-webhook": { + "$ref": "/schemas/2.6.0/property/property-list-changed-webhook.json", + "description": "Webhook payload when a property list changes" + }, + "base-property-source": { + "$ref": "/schemas/2.6.0/property/base-property-source.json", + "description": "A source of properties for a property list - supports publisher+tags, publisher+property_ids, or direct identifiers" + } + }, + "tasks": { + "list-property-features": { + "request": { + "$ref": "/schemas/2.6.0/property/list-property-features-request.json", + "description": "Request parameters for discovering governance agent capabilities" + }, + "response": { + "$ref": "/schemas/2.6.0/property/list-property-features-response.json", + "description": "Response payload for list_property_features task" + } + }, + "create-property-list": { + "request": { + "$ref": "/schemas/2.6.0/property/create-property-list-request.json", + "description": "Request parameters for creating a new property list" + }, + "response": { + "$ref": "/schemas/2.6.0/property/create-property-list-response.json", + "description": "Response payload for create_property_list task" + } + }, + "update-property-list": { + "request": { + "$ref": "/schemas/2.6.0/property/update-property-list-request.json", + "description": "Request parameters for updating an existing property list" + }, + "response": { + "$ref": "/schemas/2.6.0/property/update-property-list-response.json", + "description": "Response payload for update_property_list task" + } + }, + "get-property-list": { + "request": { + "$ref": "/schemas/2.6.0/property/get-property-list-request.json", + "description": "Request parameters for retrieving a property list with resolved properties" + }, + "response": { + "$ref": "/schemas/2.6.0/property/get-property-list-response.json", + "description": "Response payload for get_property_list task" + } + }, + "list-property-lists": { + "request": { + "$ref": "/schemas/2.6.0/property/list-property-lists-request.json", + "description": "Request parameters for listing property lists" + }, + "response": { + "$ref": "/schemas/2.6.0/property/list-property-lists-response.json", + "description": "Response payload for list_property_lists task" + } + }, + "delete-property-list": { + "request": { + "$ref": "/schemas/2.6.0/property/delete-property-list-request.json", + "description": "Request parameters for deleting a property list" + }, + "response": { + "$ref": "/schemas/2.6.0/property/delete-property-list-response.json", + "description": "Response payload for delete_property_list task" + } + } + } + }, "adagents": { "description": "Authorized sales agents file format specification", "$ref": "/schemas/2.6.0/adagents.json", @@ -573,9 +680,21 @@ "schemas": { "adcp-extension": { "$ref": "/schemas/2.6.0/protocols/adcp-extension.json", - "description": "AdCP extension for agent cards (MCP and A2A). Declares AdCP version and supported protocol domains." + "description": "AdCP extension for agent cards (MCP and A2A). Declares AdCP version, supported protocol domains, and typed extensions." } } + }, + "extensions": { + "description": "Typed extension schemas for vendor-specific or domain-specific data. Extensions define the structure of data within the ext.{namespace} field. Agents declare which extensions they support in their agent card.", + "registry": { + "$ref": "/schemas/2.6.0/extensions/index.json", + "description": "Auto-generated registry of all available extensions with metadata" + }, + "meta": { + "$ref": "/schemas/2.6.0/extensions/extension-meta.json", + "description": "Schema that all extension files must follow. Defines valid_from, valid_until, and extension data structure." + }, + "schemas": {} } }, "usage": { diff --git a/dist/schemas/2.6.0/media-buy/get-products-request.json b/dist/schemas/2.6.0/media-buy/get-products-request.json index b44dfabdbe..deab15d64c 100644 --- a/dist/schemas/2.6.0/media-buy/get-products-request.json +++ b/dist/schemas/2.6.0/media-buy/get-products-request.json @@ -16,6 +16,10 @@ "filters": { "$ref": "/schemas/2.6.0/core/product-filters.json" }, + "property_list": { + "$ref": "/schemas/2.6.0/core/property-list-ref.json", + "description": "[AdCP 3.0] Reference to an externally managed property list. When provided, the sales agent should filter products to only those available on properties in the list." + }, "context": { "$ref": "/schemas/2.6.0/core/context.json" }, diff --git a/dist/schemas/2.6.0/media-buy/get-products-response.json b/dist/schemas/2.6.0/media-buy/get-products-response.json index 7212c9b4db..ae3c07fcaf 100644 --- a/dist/schemas/2.6.0/media-buy/get-products-response.json +++ b/dist/schemas/2.6.0/media-buy/get-products-response.json @@ -19,6 +19,10 @@ "$ref": "/schemas/2.6.0/core/error.json" } }, + "property_list_applied": { + "type": "boolean", + "description": "[AdCP 3.0] Indicates whether property_list filtering was applied. True if the agent filtered products based on the provided property_list. Absent or false if property_list was not provided or not supported by this agent." + }, "context": { "$ref": "/schemas/2.6.0/core/context.json" }, diff --git a/dist/schemas/2.6.0/media-buy/package-request.json b/dist/schemas/2.6.0/media-buy/package-request.json index 785ccc02f9..fc2fb88841 100644 --- a/dist/schemas/2.6.0/media-buy/package-request.json +++ b/dist/schemas/2.6.0/media-buy/package-request.json @@ -38,6 +38,16 @@ "description": "Bid price for auction-based CPM pricing (required if using cpm-auction-option)", "minimum": 0 }, + "impressions": { + "type": "number", + "description": "Impression goal for this package", + "minimum": 0 + }, + "paused": { + "type": "boolean", + "description": "Whether this package should be created in a paused state. Paused packages do not deliver impressions. Defaults to false.", + "default": false + }, "targeting_overlay": { "$ref": "/schemas/2.6.0/core/targeting.json" }, diff --git a/dist/schemas/2.6.0/media-buy/update-media-buy-request.json b/dist/schemas/2.6.0/media-buy/update-media-buy-request.json index 40033c5589..f1d9d3e6db 100644 --- a/dist/schemas/2.6.0/media-buy/update-media-buy-request.json +++ b/dist/schemas/2.6.0/media-buy/update-media-buy-request.json @@ -52,6 +52,11 @@ "description": "Updated bid price for auction-based pricing options (only applies when pricing_option is auction-based)", "minimum": 0 }, + "impressions": { + "type": "number", + "description": "Updated impression goal for this package", + "minimum": 0 + }, "paused": { "type": "boolean", "description": "Pause/resume specific package (true = paused, false = active)" diff --git a/dist/schemas/2.6.0/property/authorization-result.json b/dist/schemas/2.6.0/property/authorization-result.json new file mode 100644 index 0000000000..e43b55cee8 --- /dev/null +++ b/dist/schemas/2.6.0/property/authorization-result.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/authorization-result.json", + "title": "Authorization Result", + "description": "Result of validating sales agent authorization for a property. Checks if the sales agent is listed in the property's adagents.json.", + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": ["authorized", "unauthorized", "unknown"], + "description": "Authorization status: authorized (agent in adagents.json), unauthorized (agent not in adagents.json), unknown (could not fetch or parse adagents.json)" + }, + "publisher_domain": { + "type": "string", + "description": "The publisher domain where adagents.json was checked" + }, + "sales_agent_url": { + "type": "string", + "format": "uri", + "description": "The sales agent URL that was validated" + }, + "violation": { + "type": "object", + "description": "Details about the authorization failure (only present for unauthorized status)", + "properties": { + "code": { + "type": "string", + "description": "Machine-readable violation code" + }, + "message": { + "type": "string", + "description": "Human-readable violation description" + } + }, + "required": ["code", "message"], + "additionalProperties": false + } + }, + "required": ["status"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/base-property-source.json b/dist/schemas/2.6.0/property/base-property-source.json new file mode 100644 index 0000000000..107ab2048e --- /dev/null +++ b/dist/schemas/2.6.0/property/base-property-source.json @@ -0,0 +1,87 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/base-property-source.json", + "title": "Base Property Source", + "description": "A source of properties for a property list. Supports three selection patterns: publisher with tags, publisher with property IDs, or direct identifiers.", + "discriminator": { + "propertyName": "selection_type" + }, + "oneOf": [ + { + "type": "object", + "title": "Publisher Tags Source", + "description": "Select properties from a publisher by tag membership", + "properties": { + "selection_type": { + "type": "string", + "const": "publisher_tags", + "description": "Discriminator indicating selection by property tags within a publisher" + }, + "publisher_domain": { + "type": "string", + "description": "Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$" + }, + "tags": { + "type": "array", + "description": "Property tags from the publisher's adagents.json. Selects all properties with these tags.", + "items": { + "$ref": "/schemas/2.6.0/core/property-tag.json" + }, + "minItems": 1 + } + }, + "required": ["selection_type", "publisher_domain", "tags"], + "additionalProperties": false + }, + { + "type": "object", + "title": "Publisher Property IDs Source", + "description": "Select specific properties from a publisher by ID", + "properties": { + "selection_type": { + "type": "string", + "const": "publisher_ids", + "description": "Discriminator indicating selection by specific property IDs within a publisher" + }, + "publisher_domain": { + "type": "string", + "description": "Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$" + }, + "property_ids": { + "type": "array", + "description": "Specific property IDs from the publisher's adagents.json", + "items": { + "$ref": "/schemas/2.6.0/core/property-id.json" + }, + "minItems": 1 + } + }, + "required": ["selection_type", "publisher_domain", "property_ids"], + "additionalProperties": false + }, + { + "type": "object", + "title": "Direct Identifiers Source", + "description": "Select properties by direct identifiers (domains, app IDs, etc.) without publisher context", + "properties": { + "selection_type": { + "type": "string", + "const": "identifiers", + "description": "Discriminator indicating selection by direct identifiers" + }, + "identifiers": { + "type": "array", + "description": "Direct property identifiers (domains, app IDs, etc.)", + "items": { + "$ref": "/schemas/2.6.0/core/identifier.json" + }, + "minItems": 1 + } + }, + "required": ["selection_type", "identifiers"], + "additionalProperties": false + } + ] +} diff --git a/dist/schemas/2.6.0/property/create-property-list-request.json b/dist/schemas/2.6.0/property/create-property-list-request.json new file mode 100644 index 0000000000..1391c41730 --- /dev/null +++ b/dist/schemas/2.6.0/property/create-property-list-request.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/create-property-list-request.json", + "title": "Create Property List Request", + "description": "Request parameters for creating a new property list", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Human-readable name for the list" + }, + "description": { + "type": "string", + "description": "Description of the list's purpose" + }, + "base_properties": { + "type": "array", + "description": "Array of property sources to evaluate. Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers). If omitted, queries the agent's entire property database.", + "items": { + "$ref": "/schemas/2.6.0/property/base-property-source.json" + } + }, + "filters": { + "$ref": "/schemas/2.6.0/property/property-list-filters.json", + "description": "Dynamic filters to apply when resolving the list" + }, + "brand_manifest": { + "$ref": "/schemas/2.6.0/core/brand-manifest.json", + "description": "Brand identity and requirements. When provided, the agent automatically applies appropriate rules based on brand characteristics (industry, target_audience, etc.)." + }, + "context": { + "$ref": "/schemas/2.6.0/core/context.json" + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "required": ["name"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/create-property-list-response.json b/dist/schemas/2.6.0/property/create-property-list-response.json new file mode 100644 index 0000000000..d745d41653 --- /dev/null +++ b/dist/schemas/2.6.0/property/create-property-list-response.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/create-property-list-response.json", + "title": "Create Property List Response", + "description": "Response payload for create_property_list task", + "type": "object", + "properties": { + "list": { + "$ref": "/schemas/2.6.0/property/property-list.json", + "description": "The created property list" + }, + "auth_token": { + "type": "string", + "description": "Token that can be shared with sellers to authorize fetching this list. Store this - it is only returned at creation time." + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "required": ["list", "auth_token"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/delete-property-list-request.json b/dist/schemas/2.6.0/property/delete-property-list-request.json new file mode 100644 index 0000000000..f0b9bf2b7c --- /dev/null +++ b/dist/schemas/2.6.0/property/delete-property-list-request.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/delete-property-list-request.json", + "title": "Delete Property List Request", + "description": "Request parameters for deleting a property list", + "type": "object", + "properties": { + "list_id": { + "type": "string", + "description": "ID of the property list to delete" + }, + "context": { + "$ref": "/schemas/2.6.0/core/context.json" + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "required": ["list_id"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/delete-property-list-response.json b/dist/schemas/2.6.0/property/delete-property-list-response.json new file mode 100644 index 0000000000..548b2383c1 --- /dev/null +++ b/dist/schemas/2.6.0/property/delete-property-list-response.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/delete-property-list-response.json", + "title": "Delete Property List Response", + "description": "Response payload for delete_property_list task", + "type": "object", + "properties": { + "deleted": { + "type": "boolean", + "description": "Whether the list was successfully deleted" + }, + "list_id": { + "type": "string", + "description": "ID of the deleted list" + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "required": ["deleted", "list_id"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/delivery-record.json b/dist/schemas/2.6.0/property/delivery-record.json new file mode 100644 index 0000000000..dcc200dcc2 --- /dev/null +++ b/dist/schemas/2.6.0/property/delivery-record.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/delivery-record.json", + "title": "Delivery Record", + "description": "A single delivery record representing impressions served to a property identifier. Used as input to validate_property_delivery.", + "type": "object", + "properties": { + "identifier": { + "$ref": "/schemas/2.6.0/core/identifier.json", + "description": "The property identifier where impressions were delivered" + }, + "impressions": { + "type": "integer", + "minimum": 0, + "description": "Number of impressions delivered to this identifier" + }, + "record_id": { + "type": "string", + "description": "Optional client-provided ID for correlating results back to source data" + }, + "sales_agent_url": { + "type": "string", + "format": "uri", + "description": "URL of the sales agent that sold this inventory. If provided, authorization is validated against the property's adagents.json." + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "required": ["identifier", "impressions"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/feature-requirement.json b/dist/schemas/2.6.0/property/feature-requirement.json new file mode 100644 index 0000000000..87ffb718ec --- /dev/null +++ b/dist/schemas/2.6.0/property/feature-requirement.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/feature-requirement.json", + "title": "Feature Requirement", + "description": "A feature-based requirement for property filtering. Use min_value/max_value for quantitative features, allowed_values for binary/categorical features.", + "type": "object", + "properties": { + "feature_id": { + "type": "string", + "description": "Feature to evaluate (discovered via list_property_features)" + }, + "min_value": { + "type": "number", + "description": "Minimum numeric value required (for quantitative features)" + }, + "max_value": { + "type": "number", + "description": "Maximum numeric value allowed (for quantitative features)" + }, + "allowed_values": { + "type": "array", + "description": "Values that pass the requirement (for binary/categorical features)", + "items": {} + }, + "if_not_covered": { + "type": "string", + "enum": ["exclude", "include"], + "default": "exclude", + "description": "How to handle properties where this feature is not covered. 'exclude' (default): property is removed from the list. 'include': property passes this requirement (fail-open)." + } + }, + "required": ["feature_id"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/get-property-features-request.json b/dist/schemas/2.6.0/property/get-property-features-request.json new file mode 100644 index 0000000000..b74b43887a --- /dev/null +++ b/dist/schemas/2.6.0/property/get-property-features-request.json @@ -0,0 +1,68 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/get-property-features-request.json", + "title": "Get Property Features Request", + "description": "Request payload for get_property_features task. Retrieves feature values for properties from a governance agent. Supports two modes: explicit property list OR publisher-based discovery.", + "type": "object", + "properties": { + "properties": { + "type": "array", + "description": "Explicit list of properties to get features for. Can be domain strings or structured property references. Use this when you know exactly which properties you want.", + "items": { + "oneOf": [ + { + "type": "string", + "description": "Domain string (e.g., 'example.com')" + }, + { + "$ref": "/schemas/2.6.0/core/property-id.json" + } + ] + }, + "minItems": 1, + "maxItems": 100 + }, + "publisher_domain": { + "type": "string", + "description": "Publisher domain to discover properties for. Use this to get features for all properties belonging to a publisher. Can be combined with property_types and property_tags for filtering." + }, + "property_types": { + "type": "array", + "description": "Filter to specific property types (e.g., 'website', 'mobile_app', 'ctv_app'). Only applies when using publisher_domain.", + "items": { + "$ref": "/schemas/2.6.0/enums/property-type.json" + } + }, + "property_tags": { + "type": "array", + "description": "Filter to properties with these tags. Only applies when using publisher_domain.", + "items": { + "$ref": "/schemas/2.6.0/core/property-tag.json" + } + }, + "feature_ids": { + "type": "array", + "description": "Optional filter to specific features. If omitted, returns all available features.", + "items": { + "type": "string" + } + }, + "context": { + "$ref": "/schemas/2.6.0/core/context.json" + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "oneOf": [ + { + "required": ["properties"], + "description": "Explicit property list mode" + }, + { + "required": ["publisher_domain"], + "description": "Publisher discovery mode" + } + ], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/get-property-features-response.json b/dist/schemas/2.6.0/property/get-property-features-response.json new file mode 100644 index 0000000000..56b2334b29 --- /dev/null +++ b/dist/schemas/2.6.0/property/get-property-features-response.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/get-property-features-response.json", + "title": "Get Property Features Response", + "description": "Response payload for get_property_features task. Returns feature values for requested properties.", + "type": "object", + "properties": { + "results": { + "type": "array", + "description": "Feature values for each requested property", + "items": { + "$ref": "/schemas/2.6.0/property/property-feature-result.json" + } + }, + "errors": { + "type": "array", + "description": "Errors for properties that could not be processed", + "items": { + "$ref": "/schemas/2.6.0/core/error.json" + } + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "required": ["results"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/get-property-list-request.json b/dist/schemas/2.6.0/property/get-property-list-request.json new file mode 100644 index 0000000000..eb7d75488e --- /dev/null +++ b/dist/schemas/2.6.0/property/get-property-list-request.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/get-property-list-request.json", + "title": "Get Property List Request", + "description": "Request parameters for retrieving a property list with resolved identifiers", + "type": "object", + "properties": { + "list_id": { + "type": "string", + "description": "ID of the property list to retrieve" + }, + "resolve": { + "type": "boolean", + "description": "Whether to apply filters and return resolved identifiers (default: true)", + "default": true + }, + "max_results": { + "type": "integer", + "description": "Maximum identifiers to return (for large lists)", + "minimum": 1 + }, + "cursor": { + "type": "string", + "description": "Pagination cursor for large result sets" + }, + "context": { + "$ref": "/schemas/2.6.0/core/context.json" + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "required": ["list_id"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/get-property-list-response.json b/dist/schemas/2.6.0/property/get-property-list-response.json new file mode 100644 index 0000000000..a4113455ac --- /dev/null +++ b/dist/schemas/2.6.0/property/get-property-list-response.json @@ -0,0 +1,68 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/get-property-list-response.json", + "title": "Get Property List Response", + "description": "Response payload for get_property_list task. Returns identifiers only (not full property objects or scores). Consumers should cache the resolved identifiers and refresh based on cache_valid_until.", + "type": "object", + "properties": { + "list": { + "$ref": "/schemas/2.6.0/property/property-list.json", + "description": "The property list metadata (always returned)" + }, + "identifiers": { + "type": "array", + "description": "Resolved identifiers that passed filters (if resolve=true). Cache these locally for real-time use.", + "items": { + "$ref": "/schemas/2.6.0/core/identifier.json" + } + }, + "total_count": { + "type": "integer", + "description": "Total number of identifiers in resolved list" + }, + "returned_count": { + "type": "integer", + "description": "Number of identifiers returned in this response" + }, + "pagination": { + "type": "object", + "description": "Pagination information", + "properties": { + "has_more": { + "type": "boolean", + "description": "Whether more results are available" + }, + "cursor": { + "type": "string", + "description": "Cursor for next page" + } + }, + "additionalProperties": false + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "description": "When the list was resolved" + }, + "cache_valid_until": { + "type": "string", + "format": "date-time", + "description": "Cache expiration timestamp. Re-fetch the list after this time to get updated identifiers." + }, + "coverage_gaps": { + "type": "object", + "description": "Properties included in the list despite missing feature data. Only present when a feature_requirement has if_not_covered='include'. Maps feature_id to list of identifiers not covered for that feature.", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "/schemas/2.6.0/core/identifier.json" + } + } + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "required": ["list"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/list-property-features-request.json b/dist/schemas/2.6.0/property/list-property-features-request.json new file mode 100644 index 0000000000..5beb1ded0d --- /dev/null +++ b/dist/schemas/2.6.0/property/list-property-features-request.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/list-property-features-request.json", + "title": "List Property Features Request", + "description": "Request payload for list_property_features task. Discovers what features a governance agent can evaluate.", + "type": "object", + "properties": { + "property_types": { + "type": "array", + "description": "Filter to features available for these property types", + "items": { + "type": "string" + } + }, + "countries": { + "type": "array", + "description": "Filter to features available in these countries", + "items": { + "type": "string" + } + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/list-property-features-response.json b/dist/schemas/2.6.0/property/list-property-features-response.json new file mode 100644 index 0000000000..9be8c83bbe --- /dev/null +++ b/dist/schemas/2.6.0/property/list-property-features-response.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/list-property-features-response.json", + "title": "List Property Features Response", + "description": "Response payload for list_property_features task. Returns the features this governance agent can evaluate.", + "type": "object", + "properties": { + "features": { + "type": "array", + "description": "Features this agent can evaluate", + "items": { + "$ref": "/schemas/2.6.0/property/property-feature-definition.json" + } + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "required": ["features"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/list-property-lists-request.json b/dist/schemas/2.6.0/property/list-property-lists-request.json new file mode 100644 index 0000000000..f7b860aa14 --- /dev/null +++ b/dist/schemas/2.6.0/property/list-property-lists-request.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/list-property-lists-request.json", + "title": "List Property Lists Request", + "description": "Request parameters for listing property lists", + "type": "object", + "properties": { + "principal": { + "type": "string", + "description": "Filter to lists owned by this principal" + }, + "name_contains": { + "type": "string", + "description": "Filter to lists whose name contains this string" + }, + "max_results": { + "type": "integer", + "description": "Maximum lists to return", + "minimum": 1, + "default": 100 + }, + "cursor": { + "type": "string", + "description": "Pagination cursor" + }, + "context": { + "$ref": "/schemas/2.6.0/core/context.json" + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/list-property-lists-response.json b/dist/schemas/2.6.0/property/list-property-lists-response.json new file mode 100644 index 0000000000..cf3b8e95c7 --- /dev/null +++ b/dist/schemas/2.6.0/property/list-property-lists-response.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/list-property-lists-response.json", + "title": "List Property Lists Response", + "description": "Response payload for list_property_lists task", + "type": "object", + "properties": { + "lists": { + "type": "array", + "description": "Array of property lists (metadata only, not resolved properties)", + "items": { + "$ref": "/schemas/2.6.0/property/property-list.json" + } + }, + "total_count": { + "type": "integer", + "description": "Total number of lists matching criteria" + }, + "returned_count": { + "type": "integer", + "description": "Number of lists returned in this response" + }, + "pagination": { + "type": "object", + "description": "Pagination information", + "properties": { + "has_more": { + "type": "boolean", + "description": "Whether more results are available" + }, + "cursor": { + "type": "string", + "description": "Cursor for next page" + } + }, + "additionalProperties": false + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "required": ["lists"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/property-error.json b/dist/schemas/2.6.0/property/property-error.json new file mode 100644 index 0000000000..e05248cf47 --- /dev/null +++ b/dist/schemas/2.6.0/property/property-error.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/property-error.json", + "title": "Property Error", + "description": "Error information for a property that could not be evaluated", + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Error code", + "enum": [ + "PROPERTY_NOT_FOUND", + "PROPERTY_NOT_MONITORED", + "LIST_NOT_FOUND", + "LIST_ACCESS_DENIED", + "METHODOLOGY_NOT_SUPPORTED", + "JURISDICTION_NOT_SUPPORTED" + ] + }, + "property": { + "$ref": "/schemas/2.6.0/core/property.json", + "description": "The property that caused the error" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": ["code", "message"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/property-feature-definition.json b/dist/schemas/2.6.0/property/property-feature-definition.json new file mode 100644 index 0000000000..5c9adcac77 --- /dev/null +++ b/dist/schemas/2.6.0/property/property-feature-definition.json @@ -0,0 +1,84 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/property-feature-definition.json", + "title": "Property Feature Definition", + "description": "Defines a feature that a governance agent can evaluate for properties. Used in list_property_features to advertise agent capabilities.", + "type": "object", + "properties": { + "feature_id": { + "type": "string", + "description": "Unique identifier for this feature (e.g., 'consent_quality', 'carbon_score', 'coppa_certified')" + }, + "name": { + "type": "string", + "description": "Human-readable name for the feature" + }, + "description": { + "type": "string", + "description": "Description of what this feature measures or represents" + }, + "type": { + "type": "string", + "enum": ["binary", "quantitative", "categorical"], + "description": "The type of values this feature produces: binary (true/false), quantitative (numeric range), categorical (enumerated values)" + }, + "range": { + "type": "object", + "description": "For quantitative features, the valid range of values", + "properties": { + "min": { + "type": "number", + "description": "Minimum value" + }, + "max": { + "type": "number", + "description": "Maximum value" + } + }, + "required": ["min", "max"], + "additionalProperties": false + }, + "allowed_values": { + "type": "array", + "description": "For categorical features, the set of valid values", + "items": { + "type": "string" + } + }, + "coverage": { + "type": "object", + "description": "What this feature covers (empty arrays = all)", + "properties": { + "property_types": { + "type": "array", + "description": "Property types this feature applies to", + "items": { + "type": "string" + } + }, + "countries": { + "type": "array", + "description": "Countries where this feature is available", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "methodology_url": { + "type": "string", + "format": "uri", + "description": "URL to documentation explaining how this feature is calculated/measured" + }, + "methodology_version": { + "type": "string", + "description": "Version identifier for the methodology (for audit trails)" + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "required": ["feature_id", "name", "type", "methodology_url"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/property-feature-result.json b/dist/schemas/2.6.0/property/property-feature-result.json new file mode 100644 index 0000000000..74fa6bfad5 --- /dev/null +++ b/dist/schemas/2.6.0/property/property-feature-result.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/property-feature-result.json", + "title": "Property Feature Result", + "description": "Feature values for a single property from a governance agent.", + "type": "object", + "properties": { + "property": { + "oneOf": [ + { + "type": "string", + "description": "Domain string (e.g., 'example.com')" + }, + { + "$ref": "/schemas/2.6.0/core/property-id.json" + } + ], + "description": "The property these features apply to" + }, + "features": { + "type": "object", + "description": "Map of feature_id to feature value", + "additionalProperties": { + "$ref": "/schemas/2.6.0/property/property-feature-value.json" + } + }, + "coverage_status": { + "type": "string", + "enum": ["covered", "not_covered", "pending"], + "description": "Whether this property is covered by this governance agent: covered (has data), not_covered (not measured), pending (measurement in progress)" + }, + "last_evaluated": { + "type": "string", + "format": "date-time", + "description": "When features were last evaluated for this property" + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "required": ["property", "coverage_status"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/property-feature-value.json b/dist/schemas/2.6.0/property/property-feature-value.json new file mode 100644 index 0000000000..aad2b0083f --- /dev/null +++ b/dist/schemas/2.6.0/property/property-feature-value.json @@ -0,0 +1,51 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/property-feature-value.json", + "title": "Property Feature Value", + "description": "A single feature value for a property. Structure varies by feature type (binary, quantitative, categorical).", + "type": "object", + "properties": { + "value": { + "description": "The feature value. Type depends on feature definition: boolean for binary, number for quantitative, string for categorical.", + "oneOf": [ + { "type": "boolean" }, + { "type": "number" }, + { "type": "string" } + ] + }, + "unit": { + "type": "string", + "description": "Unit of measurement for quantitative values (e.g., 'gCO2e/1000_impressions', 'percentage')" + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Confidence score for this value (0-1)" + }, + "measured_at": { + "type": "string", + "format": "date-time", + "description": "When this specific value was measured" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When this certification/value expires (for time-limited certifications)" + }, + "methodology_version": { + "type": "string", + "description": "Version of the methodology used to calculate this value" + }, + "details": { + "type": "object", + "description": "Additional vendor-specific details about this measurement", + "additionalProperties": true + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "required": ["value"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/property-feature.json b/dist/schemas/2.6.0/property/property-feature.json new file mode 100644 index 0000000000..81735ad433 --- /dev/null +++ b/dist/schemas/2.6.0/property/property-feature.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/property-feature.json", + "title": "Property Feature", + "description": "A discrete feature assessment for a property (e.g., from app store privacy labels)", + "type": "object", + "properties": { + "feature_id": { + "type": "string", + "description": "Identifier for the feature being assessed" + }, + "value": { + "type": "string", + "description": "The feature value" + }, + "source": { + "type": "string", + "description": "Source of the feature data (e.g., app_store_privacy_label, tcf_string)" + } + }, + "required": ["feature_id", "value"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/property-list-changed-webhook.json b/dist/schemas/2.6.0/property/property-list-changed-webhook.json new file mode 100644 index 0000000000..c1ce36f68a --- /dev/null +++ b/dist/schemas/2.6.0/property/property-list-changed-webhook.json @@ -0,0 +1,60 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/property-list-changed-webhook.json", + "title": "Property List Changed Webhook", + "description": "Webhook notification sent when a property list's resolved properties change. Contains a summary only - recipients must call get_property_list to retrieve the updated properties. This keeps payloads small and avoids redundant data transfer.", + "type": "object", + "properties": { + "event": { + "type": "string", + "const": "property_list_changed", + "description": "The event type" + }, + "list_id": { + "type": "string", + "description": "ID of the property list that changed" + }, + "list_name": { + "type": "string", + "description": "Name of the property list" + }, + "change_summary": { + "type": "object", + "description": "Summary of changes to the resolved list", + "properties": { + "properties_added": { + "type": "integer", + "description": "Number of properties added since last resolution" + }, + "properties_removed": { + "type": "integer", + "description": "Number of properties removed since last resolution" + }, + "total_properties": { + "type": "integer", + "description": "Total properties in the resolved list" + } + }, + "additionalProperties": false + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "description": "When the list was re-resolved" + }, + "cache_valid_until": { + "type": "string", + "format": "date-time", + "description": "When the consumer should refresh from the governance agent" + }, + "signature": { + "type": "string", + "description": "Cryptographic signature of the webhook payload, signed with the agent's private key. Recipients MUST verify this signature." + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "required": ["event", "list_id", "resolved_at", "signature"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/property-list-filters.json b/dist/schemas/2.6.0/property/property-list-filters.json new file mode 100644 index 0000000000..e8968dcef1 --- /dev/null +++ b/dist/schemas/2.6.0/property/property-list-filters.json @@ -0,0 +1,47 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/property-list-filters.json", + "title": "Property List Filters", + "description": "Filters that dynamically modify a property list when resolved", + "type": "object", + "properties": { + "countries_all": { + "type": "array", + "description": "Property must have feature data for ALL listed countries (ISO codes). Required.", + "items": { + "type": "string", + "pattern": "^[A-Z]{2}$" + } + }, + "channels_any": { + "type": "array", + "description": "Property must support ANY of the listed channels. Required.", + "items": { + "$ref": "/schemas/2.6.0/enums/channels.json" + } + }, + "property_types": { + "type": "array", + "description": "Filter to these property types", + "items": { + "$ref": "/schemas/2.6.0/enums/property-type.json" + } + }, + "feature_requirements": { + "type": "array", + "description": "Feature-based requirements. Property must pass ALL requirements (AND logic).", + "items": { + "$ref": "/schemas/2.6.0/property/feature-requirement.json" + } + }, + "exclude_identifiers": { + "type": "array", + "description": "Identifiers to always exclude from results", + "items": { + "$ref": "/schemas/2.6.0/core/identifier.json" + } + } + }, + "required": ["countries_all", "channels_any"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/property-list.json b/dist/schemas/2.6.0/property/property-list.json new file mode 100644 index 0000000000..647670c5cc --- /dev/null +++ b/dist/schemas/2.6.0/property/property-list.json @@ -0,0 +1,67 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/property-list.json", + "title": "Property List", + "description": "A managed property list with optional filters for dynamic evaluation. Lists are resolved at setup time and cached by orchestrators/sellers for real-time use.", + "type": "object", + "properties": { + "list_id": { + "type": "string", + "description": "Unique identifier for this property list" + }, + "name": { + "type": "string", + "description": "Human-readable name for the list" + }, + "description": { + "type": "string", + "description": "Description of the list's purpose" + }, + "principal": { + "type": "string", + "description": "Principal identity that owns this list" + }, + "base_properties": { + "type": "array", + "description": "Array of property sources to evaluate. Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers). If omitted, queries the agent's entire property database.", + "items": { + "$ref": "/schemas/2.6.0/property/base-property-source.json" + } + }, + "filters": { + "$ref": "/schemas/2.6.0/property/property-list-filters.json", + "description": "Dynamic filters applied when resolving the list" + }, + "brand_manifest": { + "$ref": "/schemas/2.6.0/core/brand-manifest.json", + "description": "Brand identity used to automatically apply appropriate rules" + }, + "webhook_url": { + "type": "string", + "format": "uri", + "description": "URL to receive notifications when the resolved list changes" + }, + "cache_duration_hours": { + "type": "integer", + "description": "Recommended cache duration for resolved list. Consumers should re-fetch after this period.", + "minimum": 1, + "default": 24 + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the list was created" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When the list was last modified" + }, + "property_count": { + "type": "integer", + "description": "Number of properties in the resolved list (at time of last resolution)" + } + }, + "required": ["list_id", "name"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/update-property-list-request.json b/dist/schemas/2.6.0/property/update-property-list-request.json new file mode 100644 index 0000000000..29368a3e11 --- /dev/null +++ b/dist/schemas/2.6.0/property/update-property-list-request.json @@ -0,0 +1,49 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/update-property-list-request.json", + "title": "Update Property List Request", + "description": "Request parameters for updating an existing property list", + "type": "object", + "properties": { + "list_id": { + "type": "string", + "description": "ID of the property list to update" + }, + "name": { + "type": "string", + "description": "New name for the list" + }, + "description": { + "type": "string", + "description": "New description" + }, + "base_properties": { + "type": "array", + "description": "Complete replacement for the base properties list (not a patch). Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers).", + "items": { + "$ref": "/schemas/2.6.0/property/base-property-source.json" + } + }, + "filters": { + "$ref": "/schemas/2.6.0/property/property-list-filters.json", + "description": "Complete replacement for the filters (not a patch)" + }, + "brand_manifest": { + "$ref": "/schemas/2.6.0/core/brand-manifest.json", + "description": "Update brand identity and requirements" + }, + "webhook_url": { + "type": "string", + "format": "uri", + "description": "Update the webhook URL for list change notifications (set to empty string to remove)" + }, + "context": { + "$ref": "/schemas/2.6.0/core/context.json" + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "required": ["list_id"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/update-property-list-response.json b/dist/schemas/2.6.0/property/update-property-list-response.json new file mode 100644 index 0000000000..435d4c7f09 --- /dev/null +++ b/dist/schemas/2.6.0/property/update-property-list-response.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/update-property-list-response.json", + "title": "Update Property List Response", + "description": "Response payload for update_property_list task", + "type": "object", + "properties": { + "list": { + "$ref": "/schemas/2.6.0/property/property-list.json", + "description": "The updated property list" + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "required": ["list"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/validate-property-delivery-request.json b/dist/schemas/2.6.0/property/validate-property-delivery-request.json new file mode 100644 index 0000000000..39638929b8 --- /dev/null +++ b/dist/schemas/2.6.0/property/validate-property-delivery-request.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/validate-property-delivery-request.json", + "title": "Validate Property Delivery Request", + "description": "Request payload for validate_property_delivery task. Validates delivery records against a property list to determine compliance.", + "type": "object", + "properties": { + "list_id": { + "type": "string", + "description": "ID of the property list to validate against" + }, + "records": { + "type": "array", + "description": "Delivery records to validate. Each record represents impressions delivered to a property identifier.", + "items": { + "$ref": "/schemas/2.6.0/property/delivery-record.json" + }, + "minItems": 1, + "maxItems": 10000 + }, + "include_compliant": { + "type": "boolean", + "default": false, + "description": "Include compliant records in results (default: only return non_compliant, unmodeled, and unidentified)" + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "required": ["list_id", "records"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/validate-property-delivery-response.json b/dist/schemas/2.6.0/property/validate-property-delivery-response.json new file mode 100644 index 0000000000..ced6ba0e0e --- /dev/null +++ b/dist/schemas/2.6.0/property/validate-property-delivery-response.json @@ -0,0 +1,167 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/validate-property-delivery-response.json", + "title": "Validate Property Delivery Response", + "description": "Response payload for validate_property_delivery task. Returns aggregate compliance statistics and per-record validation results.", + "type": "object", + "properties": { + "list_id": { + "type": "string", + "description": "ID of the property list validated against" + }, + "summary": { + "type": "object", + "description": "Aggregate validation statistics", + "properties": { + "total_records": { + "type": "integer", + "description": "Total number of records validated" + }, + "total_impressions": { + "type": "integer", + "description": "Total impressions across all records" + }, + "compliant_records": { + "type": "integer", + "description": "Number of records with compliant status" + }, + "compliant_impressions": { + "type": "integer", + "description": "Impressions from compliant records" + }, + "non_compliant_records": { + "type": "integer", + "description": "Number of records with non_compliant status" + }, + "non_compliant_impressions": { + "type": "integer", + "description": "Impressions from non_compliant records" + }, + "not_covered_records": { + "type": "integer", + "description": "Number of records where identifier was recognized but no data available" + }, + "not_covered_impressions": { + "type": "integer", + "description": "Impressions from not_covered records" + }, + "unidentified_records": { + "type": "integer", + "description": "Number of records where identifier type was not resolvable" + }, + "unidentified_impressions": { + "type": "integer", + "description": "Impressions from unidentified records" + } + }, + "required": [ + "total_records", + "total_impressions", + "compliant_records", + "compliant_impressions", + "non_compliant_records", + "non_compliant_impressions", + "not_covered_records", + "not_covered_impressions", + "unidentified_records", + "unidentified_impressions" + ], + "additionalProperties": false + }, + "aggregate": { + "type": "object", + "description": "Optional aggregate measurements computed by the governance agent. Format and meaning are agent-specific.", + "properties": { + "score": { + "type": "number", + "description": "Numeric score (0-100 scale typical, but agent-defined)" + }, + "grade": { + "type": "string", + "description": "Letter grade or category (e.g., 'A+', 'B-', 'Gold', 'Compliant')" + }, + "label": { + "type": "string", + "description": "Human-readable summary (e.g., '85% compliant', 'High quality')" + }, + "methodology_url": { + "type": "string", + "format": "uri", + "description": "URL explaining how this aggregate was calculated" + } + }, + "additionalProperties": true + }, + "authorization_summary": { + "type": "object", + "description": "Aggregate authorization statistics. Only present if any records included sales_agent_url.", + "properties": { + "records_checked": { + "type": "integer", + "description": "Number of records with sales_agent_url provided" + }, + "impressions_checked": { + "type": "integer", + "description": "Total impressions from records with sales_agent_url" + }, + "authorized_records": { + "type": "integer", + "description": "Number of records where sales agent was authorized" + }, + "authorized_impressions": { + "type": "integer", + "description": "Impressions from authorized records" + }, + "unauthorized_records": { + "type": "integer", + "description": "Number of records where sales agent was NOT authorized" + }, + "unauthorized_impressions": { + "type": "integer", + "description": "Impressions from unauthorized records" + }, + "unknown_records": { + "type": "integer", + "description": "Number of records where authorization could not be determined (adagents.json unavailable)" + }, + "unknown_impressions": { + "type": "integer", + "description": "Impressions from records where authorization could not be determined" + } + }, + "required": [ + "records_checked", + "impressions_checked", + "authorized_records", + "authorized_impressions", + "unauthorized_records", + "unauthorized_impressions", + "unknown_records", + "unknown_impressions" + ], + "additionalProperties": false + }, + "results": { + "type": "array", + "description": "Per-record validation results. By default only includes non_compliant and unknown records. Set include_compliant=true to include all records.", + "items": { + "$ref": "/schemas/2.6.0/property/validation-result.json" + } + }, + "validated_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp when validation was performed" + }, + "list_resolved_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of the property list resolution used for validation" + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "required": ["list_id", "summary", "results", "validated_at"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/property/validation-result.json b/dist/schemas/2.6.0/property/validation-result.json new file mode 100644 index 0000000000..db1845ee8c --- /dev/null +++ b/dist/schemas/2.6.0/property/validation-result.json @@ -0,0 +1,79 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.6.0/property/validation-result.json", + "title": "Validation Result", + "description": "Result of validating a single delivery record against a property list.", + "type": "object", + "properties": { + "identifier": { + "$ref": "/schemas/2.6.0/core/identifier.json", + "description": "The identifier that was validated" + }, + "record_id": { + "type": "string", + "description": "Client-provided ID from the delivery record (if provided)" + }, + "status": { + "type": "string", + "enum": ["compliant", "non_compliant", "not_covered", "unidentified"], + "description": "Validation status: compliant (in list), non_compliant (not in list), not_covered (identifier recognized but no data available), unidentified (identifier type not resolvable by this governance agent)" + }, + "impressions": { + "type": "integer", + "minimum": 0, + "description": "Number of impressions from this record" + }, + "violations": { + "type": "array", + "description": "Specific violations found (only present for non_compliant records)", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Machine-readable violation code" + }, + "message": { + "type": "string", + "description": "Human-readable violation description" + }, + "feature_id": { + "type": "string", + "description": "ID of the feature that caused the violation (only present for feature_failed violations)" + }, + "requirement": { + "type": "object", + "description": "The feature requirement that was not met (only present for feature_failed violations)", + "properties": { + "min_value": { + "type": "number", + "description": "Minimum value that was required" + }, + "max_value": { + "type": "number", + "description": "Maximum value that was allowed" + }, + "allowed_values": { + "type": "array", + "description": "Values that would have been acceptable", + "items": {} + } + }, + "additionalProperties": false + } + }, + "required": ["code", "message"], + "additionalProperties": false + } + }, + "authorization": { + "$ref": "/schemas/2.6.0/property/authorization-result.json", + "description": "Authorization validation result (only present if sales_agent_url was provided in the delivery record)" + }, + "ext": { + "$ref": "/schemas/2.6.0/core/ext.json" + } + }, + "required": ["identifier", "status", "impressions"], + "additionalProperties": false +} diff --git a/dist/schemas/2.6.0/protocols/adcp-extension.json b/dist/schemas/2.6.0/protocols/adcp-extension.json index 3d31caf104..07ca8f429e 100644 --- a/dist/schemas/2.6.0/protocols/adcp-extension.json +++ b/dist/schemas/2.6.0/protocols/adcp-extension.json @@ -1,14 +1,14 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/2.6.0/protocols/adcp-extension.json", - "title": "AdCP Agent Card Extension", - "description": "Extension metadata for agent cards (MCP and A2A) declaring AdCP version and supported protocol domains", + "title": "AdCP Agent Card Extension Params", + "description": "Parameters for declaring AdCP support in agent discovery. For A2A, use in extensions[].params. For MCP, use in _meta['adcontextprotocol.org'].", "type": "object", "properties": { "adcp_version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$", - "description": "Semantic version of the AdCP specification this agent implements (e.g., '2.4.0')" + "description": "Semantic version of the AdCP specification this agent implements (e.g., '2.5.0'). Extension schemas are versioned along with the AdCP spec." }, "protocols_supported": { "type": "array", @@ -23,6 +23,16 @@ "minItems": 1, "uniqueItems": true, "description": "AdCP protocol domains supported by this agent. At least one must be specified." + }, + "extensions_supported": { + "type": "array", + "description": "Typed extensions this agent supports. Each extension has a formal schema in /schemas/extensions/. Extension version is determined by adcp_version.", + "items": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]*$", + "description": "Extension namespace (e.g., 'sustainability'). Must be lowercase alphanumeric with underscores." + }, + "uniqueItems": true } }, "required": [ From 977a400671feb51a56c59fe54c7aff0a04961c07 Mon Sep 17 00:00:00 2001 From: BaiyuScope3 <240560747+BaiyuScope3@users.noreply.github.com> Date: Tue, 13 Jan 2026 23:10:46 +0000 Subject: [PATCH 18/49] chore: sync v2.6-rc docs and schemas from 2.6.x branch --- v2.6-rc/docs/creative/index.mdx | 2 +- .../docs/governance/brand-standards/index.mdx | 90 +++ v2.6-rc/docs/governance/index.mdx | 187 +++++ .../property}/adagents.mdx | 4 +- .../property}/authorized-properties.mdx | 3 +- v2.6-rc/docs/governance/property/index.mdx | 227 ++++++ .../governance/property/specification.mdx | 611 +++++++++++++++ .../docs/governance/property/tasks/index.mdx | 153 ++++ .../property/tasks/list_property_features.mdx | 235 ++++++ .../property/tasks/property_lists.mdx | 701 ++++++++++++++++++ .../tasks/validate_property_delivery.mdx | 402 ++++++++++ v2.6-rc/docs/intro.mdx | 30 +- .../principals-and-security.mdx | 84 +-- .../media-buy/capability-discovery/index.mdx | 2 +- v2.6-rc/docs/media-buy/index.mdx | 6 +- .../task-reference/create_media_buy.mdx | 2 + .../media-buy/task-reference/get_products.mdx | 81 ++ .../task-reference/update_media_buy.mdx | 1 + v2.6-rc/docs/protocols/a2a-guide.mdx | 39 +- v2.6-rc/docs/protocols/mcp-guide.mdx | 52 +- v2.6-rc/docs/reference/authentication.mdx | 128 +++- .../reference/creative-quick-reference.mdx | 285 +++++++ .../docs/reference/extensions-and-context.mdx | 207 ++++++ v2.6-rc/docs/reference/glossary.mdx | 4 +- .../reference/media-buy-quick-reference.mdx | 321 ++++++++ v2.6-rc/docs/reference/roadmap.mdx | 4 +- .../reference/signals-quick-reference.mdx | 200 +++++ v2.6-rc/docs/signals/overview.mdx | 4 +- 28 files changed, 3935 insertions(+), 130 deletions(-) create mode 100644 v2.6-rc/docs/governance/brand-standards/index.mdx create mode 100644 v2.6-rc/docs/governance/index.mdx rename v2.6-rc/docs/{media-buy/capability-discovery => governance/property}/adagents.mdx (98%) rename v2.6-rc/docs/{media-buy/capability-discovery => governance/property}/authorized-properties.mdx (99%) create mode 100644 v2.6-rc/docs/governance/property/index.mdx create mode 100644 v2.6-rc/docs/governance/property/specification.mdx create mode 100644 v2.6-rc/docs/governance/property/tasks/index.mdx create mode 100644 v2.6-rc/docs/governance/property/tasks/list_property_features.mdx create mode 100644 v2.6-rc/docs/governance/property/tasks/property_lists.mdx create mode 100644 v2.6-rc/docs/governance/property/tasks/validate_property_delivery.mdx create mode 100644 v2.6-rc/docs/reference/creative-quick-reference.mdx create mode 100644 v2.6-rc/docs/reference/media-buy-quick-reference.mdx create mode 100644 v2.6-rc/docs/reference/signals-quick-reference.mdx diff --git a/v2.6-rc/docs/creative/index.mdx b/v2.6-rc/docs/creative/index.mdx index d12c1fe9e3..8faf1958c6 100644 --- a/v2.6-rc/docs/creative/index.mdx +++ b/v2.6-rc/docs/creative/index.mdx @@ -3,7 +3,7 @@ title: Overview --- -This guide explains how creatives work in AdCP, from defining format requirements to assembling and delivering ads. +One upload, every format. This guide explains how creatives work in AdCP, from defining format requirements to assembling and delivering ads. ## The Four Key Concepts diff --git a/v2.6-rc/docs/governance/brand-standards/index.mdx b/v2.6-rc/docs/governance/brand-standards/index.mdx new file mode 100644 index 0000000000..15a738adc0 --- /dev/null +++ b/v2.6-rc/docs/governance/brand-standards/index.mdx @@ -0,0 +1,90 @@ +--- +sidebar_position: 1 +title: Brand Standards +--- + + +**AdCP 3.0 Proposal** - This protocol is under development for AdCP 3.0. Feedback welcome via [GitHub Discussions](https://github.com/adcontextprotocol/adcp/discussions). + + +Brand Standards governance enables buyers to declare brand requirements that governance agents use to automatically apply appropriate rules across advertising workflows. + +## Overview + +Brand Standards addresses three concerns: + +| Concern | Description | Mechanism | +|---------|-------------|-----------| +| **Brand Identity** | Who is the advertiser? | Brand manifest declarations | +| **Creative Guidelines** | What can the brand show? | Creative constraints and policies | +| **Audience Constraints** | Who should see the ads? | Industry-specific targeting rules | + +## Brand Manifest + +The brand manifest is the core declaration that buyers provide to governance agents: + +```json +{ + "brand_manifest": { + "brand_name": "ToyBrand", + "industry": "toys", + "target_audience": "children_under_13", + "prohibited_categories": ["gambling", "alcohol", "tobacco"], + "creative_guidelines": { + "max_animation_duration": 15, + "require_accessibility": true + } + } +} +``` + +When governance agents receive a brand manifest, they automatically apply: + +- **Regulatory compliance**: COPPA for children's brands, GDPR consent requirements +- **Content filtering**: Block categories inappropriate for the brand's audience +- **Creative validation**: Enforce brand-specific creative guidelines + +## Integration with Property Governance + +Brand manifests work alongside property lists. When creating a property list, include the brand manifest to get automatic rule application: + +```json +{ + "tool": "create_property_list", + "arguments": { + "name": "ToyBrand Q1 Properties", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "kidsmedia.com", + "tags": ["kids_family", "educational"] + } + ], + "filters": { + "countries_all": ["US"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "brand_safety", "min_value": 90, "max_value": 100 } + ] + }, + "brand_manifest": { + "brand_name": "ToyBrand", + "industry": "toys", + "target_audience": "children_under_13" + } + } +} +``` + +The governance agent uses the brand manifest to apply COPPA requirements, filter out non-child-safe properties, and enforce relevant industry rules. + +## Coming Soon + +Detailed specification and task definitions for Brand Standards governance are under development. Key planned features: + +- **Brand manifest validation**: Verify manifest completeness and consistency +- **Industry rule sets**: Pre-defined rules for common industries (toys, alcohol, pharmaceuticals, finance) +- **Creative compliance**: Validate creatives against brand guidelines +- **Audience verification**: Ensure targeting complies with brand audience constraints + +See the [Governance Protocol overview](/docs/governance/index) for the broader context of how Brand Standards fits into AdCP governance. diff --git a/v2.6-rc/docs/governance/index.mdx b/v2.6-rc/docs/governance/index.mdx new file mode 100644 index 0000000000..f08e368f2d --- /dev/null +++ b/v2.6-rc/docs/governance/index.mdx @@ -0,0 +1,187 @@ +--- +sidebar_position: 1 +title: Governance Protocol +--- + + +**Draft for AdCP 3.0** - These governance protocols are under active development by the AAO Governance Working Group. Feedback welcome via [GitHub Discussions](https://github.com/adcontextprotocol/adcp/discussions). + + +The Governance Protocol provides standardized mechanisms for compliance, brand safety, and quality control across advertising workflows. It enables specialized agents to evaluate, filter, and score advertising entities against configurable criteria. + +## Mission + +> Define and operate transparent, privacy-safe, brand-suitable campaign governance that encodes human heuristics in open protocol objects, making campaigns easy to launch, safe to scale, and measurable against verifiable outcomes for buyers, sellers, and auditors. + +## Protocol Domains + +The Governance Protocol covers four distinct governance domains: + +| Domain | What It Governs | Key Mechanisms | +|--------|-----------------|----------------| +| **[Property Governance](/docs/governance/property/index)** | Where ads can run | Property lists, compliance filtering, adagents.json authorization | +| **[Brand Standards](/docs/governance/brand-standards/index)** | Brand requirements | Brand manifests, creative guidelines, audience constraints | +| **Creative Governance** | What creatives are compliant | Format validation, content moderation, accessibility | +| **Campaign Governance** | What can be bought | Budget controls, approval workflows, policy compliance | + +Each domain has specialized governance agents that provide data, filtering, and scoring capabilities. + +## Working Group Areas + +The AAO Governance Working Group is developing standards across six areas that inform these protocols: + +| Area | Focus | Protocol Impact | +|------|-------|-----------------| +| Brand Safety & Suitability | Safe, appropriate ad contexts | Property Governance, Content filtering | +| Brand Manifesto & Standards Library | Machine-readable brand policies | Brand Standards | +| Creative Standards & Validation | Technical and ethical creative criteria | Creative Governance | +| Process & Human Oversight | Human-in-the-loop checkpoints | All protocols | +| Regulatory & Compliance | Legal and regional frameworks | All protocols | +| Measurement & Verification | Proving compliance | All protocols | + +## Architecture + +Governance agents operate across the **full campaign lifecycle**: + +- **Setup time**: Configure property lists, brand requirements, and compliance rules +- **Real-time**: Influence bid-time decisions through cached rules and scoring +- **Post-bid**: Measurement, verification, and reporting on compliance + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ BUYER AGENT │ +│ - Aggregates governance from specialized agents │ +│ - Issues auth_tokens for sellers to access governance data │ +│ - Source of truth for compliant lists and brand requirements │ +└───────────────────────────┬─────────────────────────────────────────────┘ + │ create_property_list, webhooks + ┌───────────────────┼───────────────────┐ + ▼ ▼ ▼ +┌───────────────┐ ┌───────────────┐ ┌───────────────┐ +│ Consent Agent │ │ Scope3 Agent │ │ Brand Safety │ +│ (Compliance) │ │ │ │ Agent │ +├───────────────┤ ├───────────────┤ ├───────────────┤ +│ consent_qual │ │ carbon_score │ │ content_cat │ +│ tcf_version │ │ climate_risk │ │ brand_risk │ +│ coppa_cert │ │ green_host │ │ sentiment │ +└───────────────┘ └───────────────┘ └───────────────┘ + │ + │ get_property_list (with auth_token) + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ SELLER AGENT (DSP/SSP) │ +│ - Caches resolved property lists for bid-time decisions │ +│ - Receives updates via webhooks when lists change │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +## Common Patterns + +### Feature Discovery + +All governance agents advertise their capabilities via discovery tasks: + +```json +{ + "tool": "list_property_features", + "arguments": {} +} +``` + +Response: +```json +{ + "features": [ + { "feature_id": "consent_quality", "type": "quantitative", "range": { "min": 0, "max": 100 } }, + { "feature_id": "coppa_certified", "type": "binary" }, + { "feature_id": "carbon_score", "type": "quantitative", "range": { "min": 0, "max": 100 } } + ] +} +``` + +### Brand Manifest + +Buyers declare their brand identity, and governance agents automatically apply appropriate rules: + +```json +{ + "brand_manifest": { + "brand_name": "ToyBrand", + "industry": "toys", + "target_audience": "children_under_13" + } +} +``` + +When a governance agent sees this manifest, it automatically applies COPPA requirements, content filtering for children's brands, and other industry-specific rules. + +### Prompt-Based Policies + +Governance protocols support **natural language prompts** for policy definitions rather than rigid keyword lists: + +```json +{ + "policy": "Avoid content about violence, controversial politics, or adult themes. Sports news is excellent. Entertainment is generally acceptable.", + "exclusions_prompt": "Block content containing hate speech, illegal activities, or ongoing litigation against our company." +} +``` + +This enables AI-powered verification agents to understand context and nuance. + +### Webhook Notifications + +Governance agents notify subscribers when evaluations change: + +```json +{ + "webhook_url": "https://buyer.example.com/webhooks/list-changed", + "events": ["list_updated", "property_removed", "score_changed"] +} +``` + +## Integration with Media Buy + +The Media Buy Protocol consumes governance data at multiple stages: + +- **Product discovery**: Pass `property_list_ref` to `get_products` to filter inventory +- **Media buy creation**: Reference property lists to constrain where ads can run +- **Authorization**: adagents.json validates agent authority to sell properties + +```json +{ + "tool": "get_products", + "arguments": { + "brief": "UK video inventory for Q1", + "property_list_ref": { + "agent_url": "https://buyer-agent.example.com", + "list_id": "pl_q1_uk_premium", + "auth_token": "..." + } + } +} +``` + +## Getting Started + +**Buyers:** +1. Subscribe to governance agents for property data and brand safety +2. Create property lists with filters and brand manifests +3. Aggregate results into final compliant lists +4. Share references with sellers (with auth tokens) + +**Governance Agent Implementers:** +1. Implement feature discovery tasks (`list_property_features`, etc.) +2. Implement stateful list management (CRUD operations) +3. Support webhooks to notify when evaluations change +4. See the [Property Protocol Specification](/docs/governance/property/specification) for detailed implementation guidance + +## Protocol Sections + + + + Control where ads can run with property lists, compliance filtering, and publisher authorization via adagents.json. + + + Define brand requirements including creative guidelines, audience constraints, and industry-specific rules. + + diff --git a/v2.6-rc/docs/media-buy/capability-discovery/adagents.mdx b/v2.6-rc/docs/governance/property/adagents.mdx similarity index 98% rename from v2.6-rc/docs/media-buy/capability-discovery/adagents.mdx rename to v2.6-rc/docs/governance/property/adagents.mdx index 442dee1805..2732d1d8f1 100644 --- a/v2.6-rc/docs/media-buy/capability-discovery/adagents.mdx +++ b/v2.6-rc/docs/governance/property/adagents.mdx @@ -1,9 +1,9 @@ --- -sidebar_position: 8 +sidebar_position: 3 title: adagents.json Tech Spec --- -The `adagents.json` file provides a standardized way for publishers to declare which sales agents are authorized to sell their advertising inventory. This specification addresses authorization transparency and helps prevent unauthorized reselling of publisher inventory. +The `adagents.json` file provides a standardized way for publishers to declare their properties and authorize sales agents. This is the foundation of Property Governance - it defines what properties exist and who can sell them. **[AdAgents.json Builder](https://adcontextprotocol.org/adagents)** - Validate existing files or create new ones with guided validation diff --git a/v2.6-rc/docs/media-buy/capability-discovery/authorized-properties.mdx b/v2.6-rc/docs/governance/property/authorized-properties.mdx similarity index 99% rename from v2.6-rc/docs/media-buy/capability-discovery/authorized-properties.mdx rename to v2.6-rc/docs/governance/property/authorized-properties.mdx index 13264accab..37981ec9e5 100644 --- a/v2.6-rc/docs/media-buy/capability-discovery/authorized-properties.mdx +++ b/v2.6-rc/docs/governance/property/authorized-properties.mdx @@ -1,5 +1,6 @@ --- -title: Authorized Properties +sidebar_position: 2.5 +title: Understanding Authorization description: Understanding property authorization in AdCP - preventing unauthorized resale and validating sales agent relationships keywords: [authorized properties, adagents.json, unauthorized resale, ads.txt, sales agent authorization, property validation] --- diff --git a/v2.6-rc/docs/governance/property/index.mdx b/v2.6-rc/docs/governance/property/index.mdx new file mode 100644 index 0000000000..d4f8468777 --- /dev/null +++ b/v2.6-rc/docs/governance/property/index.mdx @@ -0,0 +1,227 @@ +--- +sidebar_position: 1 +title: Overview +--- + + +**AdCP 3.0 Proposal** - This protocol is under development for AdCP 3.0. Feedback welcome via [GitHub Discussions](https://github.com/adcontextprotocol/adcp/discussions). + + +Property Governance standardizes how advertising properties (websites, apps, CTV, podcasts, billboards) are identified, authorized, enriched with data, and selected for campaigns. + +## Overview + +Property Governance addresses four distinct concerns: + +| Concern | Question | Owner | Mechanism | +|---------|----------|-------|-----------| +| **Property Identity** | What properties exist? | Publishers | `adagents.json` properties array | +| **Sales Authorization** | Who can sell this property? | Publishers | `adagents.json` authorized_agents | +| **Property Data** | What do we know about this property? | Data providers | Governance agents via `list_property_features` | +| **Property Selection** | Which properties meet my requirements? | Buyers | Property lists with filters | + +The first two are **publisher-side declarations** via adagents.json. The last two are **buyer-side operations** that consume property data from governance agents. + +## Publisher Side: adagents.json + +Publishers declare their properties and authorize sales agents via `/.well-known/adagents.json`: + +```json +{ + "$schema": "https://adcontextprotocol.org/schemas/v2/adagents.json", + "properties": [ + { + "property_id": "example_site", + "property_type": "website", + "name": "Example Site", + "identifiers": [{"type": "domain", "value": "example.com"}] + } + ], + "authorized_agents": [ + { + "url": "https://agent.example.com", + "authorized_for": "Official sales agent", + "authorization_type": "property_ids", + "property_ids": ["example_site"] + } + ] +} +``` + +See the [adagents.json Tech Spec](/docs/governance/property/adagents) for complete documentation. + +## Buyer Side: Property Data and Selection + +### Property Data Providers + +Governance agents provide data about properties - compliance scores, brand safety ratings, sustainability metrics, etc. They advertise their capabilities via `list_property_features`: + +```json +{ + "features": [ + { "feature_id": "consent_quality", "type": "quantitative", "range": { "min": 0, "max": 100 } }, + { "feature_id": "coppa_certified", "type": "binary" }, + { "feature_id": "carbon_score", "type": "quantitative", "range": { "min": 0, "max": 100 } } + ] +} +``` + +Buyers send property lists to these agents, and the agents filter and score the properties based on their specialized data. Different agents specialize in different data: + +- **Compliance vendors** (data integrity, consent quality) +- **Brand safety providers** (content classification, risk scoring) +- **Quality measurement** (viewability, fraud detection) +- **Sustainability providers** (carbon scoring, green hosting) + +### Property Selection via Governance Agents + +Buyers create **property lists on governance agents** - the agents manage these lists and apply their filtering logic: + +```json +{ + "tool": "create_property_list", + "arguments": { + "name": "Q1 Campaign - UK Premium", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news"] + } + ], + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 85, "max_value": 100 } + ] + }, + "brand_manifest": { + "brand_name": "ToyBrand", + "industry": "toys", + "target_audience": "children_under_13" + } + } +} +``` + +When you provide a brand manifest, governance agents automatically apply appropriate rules (COPPA for children's brands, content filtering based on industry, etc.). + +A buyer agent typically works with **multiple governance agents** (consent, brand safety, sustainability) and aggregates/intersects their results into a final compliant list. + +## How It Fits Together + +```mermaid +flowchart TB + subgraph Buyer["BUYER AGENT"] + B1[Source of truth for compliant list] + B2[Aggregates results from specialized agents] + B3[Issues auth_tokens for sellers] + end + + subgraph Governance["GOVERNANCE AGENTS"] + CA["Consent Agent
consent_quality
tcf_version
coppa_certified"] + S3["Scope3 Agent
carbon_score
climate_risk
green_hosting"] + BS["Brand Safety Agent
content_category
brand_risk
sentiment"] + end + + subgraph Seller["SELLER AGENT (DSP/SSP)"] + SE1[Caches resolved property lists] + SE2[Uses cached lists for bid-time decisions] + end + + Buyer -->|create_property_list + webhooks| CA + Buyer -->|create_property_list + webhooks| S3 + Buyer -->|create_property_list + webhooks| BS + + Buyer -->|get_property_list with auth_token| Seller +``` + +## Sharing Property Lists with Sellers + +Once a buyer has a compliant property list, they share it with sellers: + +1. **Get a list reference**: The buyer agent exposes the list via `get_property_list` +2. **Issue an auth token**: The buyer generates a token that authorizes access to the list +3. **Pass to seller**: Include `property_list_ref` with `auth_token` in product discovery or media buy requests +4. **Seller caches locally**: Sellers fetch and cache the resolved list for bid-time decisions +5. **Webhooks for updates**: When the list changes, sellers are notified to refresh their cache + +```json +{ + "property_list_ref": { + "agent_url": "https://buyer-agent.example.com", + "list_id": "pl_q1_uk_premium", + "auth_token": "eyJhbGciOiJIUzI1NiIs..." + } +} +``` + +Sellers use this reference in `get_products` to filter available inventory: + +```json +{ + "tool": "get_products", + "arguments": { + "brief": "UK video inventory for Q1", + "property_list_ref": { + "agent_url": "https://buyer-agent.example.com", + "list_id": "pl_q1_uk_premium", + "auth_token": "..." + } + } +} +``` + +## Relationship to Other Protocols + +### Property Governance + Media Buy + +The Media Buy Protocol consumes property lists at multiple stages: + +- **Product discovery**: Pass `property_list_ref` to `get_products` to filter inventory to compliant properties +- **Media buy creation**: Reference property lists to constrain where ads can run +- **Authorization**: adagents.json validates agent authority to sell + +### Property Governance + Signals + +Both protocols operate on properties but serve different purposes: + +| Signals Protocol | Property Governance | +|------------------|---------------------| +| Audience/contextual data | Property metadata and compliance | +| "Who should see this ad?" | "Where can this ad run?" | +| Signal activation | Property filtering | + +## Tasks + +### Discovery + +- **[list_property_features](/docs/governance/property/tasks/list_property_features)**: Discover what data a governance agent can provide about properties + +### Property List Management + +- **[create_property_list](/docs/governance/property/tasks/property_lists#create_property_list)**: Create a new property list on a governance agent +- **[get_property_list](/docs/governance/property/tasks/property_lists#get_property_list)**: Retrieve resolved properties (with caching guidance) +- **[update_property_list](/docs/governance/property/tasks/property_lists#update_property_list)**: Modify filters or base properties +- **[delete_property_list](/docs/governance/property/tasks/property_lists#delete_property_list)**: Remove a property list + +## Getting Started + +**Publishers:** +1. Create `/.well-known/adagents.json` with property definitions +2. Authorize sales agents for your properties + +**Buyers:** +1. Subscribe to governance agents for property data +2. Create property lists on each governance agent with filters and brand manifests +3. Aggregate results into a final compliant list +4. Share property list references with sellers (with auth tokens) + +**Governance Agent Implementers:** +1. Implement `list_property_features` to advertise your capabilities +2. Implement property list CRUD operations +3. Support webhooks to notify buyers when evaluations change +4. See the [Protocol Specification](/docs/governance/property/specification) for implementation details + +See the [Protocol Specification](/docs/governance/property/specification) for detailed implementation guidance. diff --git a/v2.6-rc/docs/governance/property/specification.mdx b/v2.6-rc/docs/governance/property/specification.mdx new file mode 100644 index 0000000000..343d1c7e32 --- /dev/null +++ b/v2.6-rc/docs/governance/property/specification.mdx @@ -0,0 +1,611 @@ +--- +sidebar_position: 2 +title: Protocol Specification +--- + + +**AdCP 3.0 Proposal** - This specification is under development for AdCP 3.0. Feedback welcome via [GitHub Discussions](https://github.com/adcontextprotocol/adcp/discussions). + + +**Status**: Request for Comments +**Last Updated**: January 2026 + +## Abstract + +The Property Protocol defines a standard Model Context Protocol (MCP) and Agent-to-Agent (A2A) interface for property identity, authorization, data provision, and selection. This protocol enables publishers to declare properties and authorized agents, data providers to offer property intelligence, and buyers to select compliant property sets. + +## Overview + +The Property Protocol addresses four distinct concerns: + +| Concern | Question | Owner | Mechanism | +|---------|----------|-------|-----------| +| **Property Identity** | What properties exist? | Publishers | `adagents.json` properties array | +| **Sales Authorization** | Who can sell this property? | Publishers | `adagents.json` authorized_agents | +| **Property Data** | What do we know about this property? | Data providers | Governance agents via `list_property_features` | +| **Property Selection** | Which properties meet my requirements? | Buyers | Property lists with filters | + +The first two are **publisher-side declarations** via adagents.json. The last two are **buyer-side operations** that consume property data from governance agents. + +### Property Data and Selection + +Property data and selection use a **stateful** model: + +- **Feature discovery**: Agents advertise what they can evaluate via `list_property_features` +- **Property list management**: CRUD operations for managed property lists with filters +- **Brand manifests**: Let agents automatically apply rules based on brand characteristics +- **Webhook notifications**: Real-time updates when resolved lists change +- **Marketplace architecture**: Multiple specialized agents as subscription services + +All evaluation (scoring, filtering, discovery) happens implicitly when property lists are resolved via `get_property_list`. + +## Core Concepts + +### Request Roles and Relationships + +Every governance request involves two key roles: + +#### Orchestrator (Buyer Agent) +The platform or system making the API request to the governance agent. In AdCP documentation, this role is often called a "buyer agent" when operating in the media buying context. +- **Examples**: DSP, trading desk platform, campaign management tool +- **Responsibilities**: Makes API calls, handles authentication, manages the technical interaction +- **Account**: Has technical credentials and API access to the governance agent + +#### Principal +The entity on whose behalf the request is being made: +- **Examples**: Advertiser (Nike), agency (Omnicom), brand team +- **Responsibilities**: Owns the campaign objectives and policy requirements +- **Policies**: May have custom thresholds, blocklists, or compliance requirements + +### Property Identification + +Properties are identified using the standard AdCP property model: + +```json +{ + "property_type": "website", + "name": "Example News", + "identifiers": [ + { "type": "domain", "value": "example.com" } + ] +} +``` + +Property types include: `website`, `mobile_app`, `ctv_app`, `dooh`, `podcast`, `radio`, `streaming_audio`. + +### Property List References + +For large property sets, use property list references instead of embedding properties: + +```json +{ + "property_list_ref": { + "agent_url": "https://lists.example.com", + "list_id": "premium_news_sites", + "auth_token": "eyJhbGciOiJIUzI1NiIs..." + } +} +``` + +The receiving agent fetches and caches the list independently, enabling: +- **Scale**: Pass 50,000+ properties without payload bloat +- **Updates**: Lists evolve without changing requests +- **Authorization**: Token controls access to the list + +### Governance Agent Types + +#### Compliance Agents +Specialized vendors providing property compliance intelligence: +- **Examples**: Data integrity scoring, consent quality measurement +- **Business Model**: Subscription or per-query pricing +- **Methodology**: Published rubrics for transparency + +#### Brand Safety Agents +Content classification and risk assessment: +- **Examples**: Content categorization, brand safety scoring +- **Coverage**: May specialize by channel or geography + +#### Quality Agents +Performance and fraud measurement: +- **Examples**: Viewability prediction, IVT detection +- **Integration**: May correlate with campaign outcomes + +### Scoring and Data Privacy + +#### Scores Are Internal + +**Critical design principle**: Raw scores are NOT shared with buyers or downstream clients. This prevents data leakage. + +Governance agents maintain internal scoring models, but the protocol is designed around **list management**, not score exposure: + +- Buyers specify **thresholds** via `feature_requirements` (e.g., `"min_value": 85`) +- Agents return **pass/fail lists** of properties that meet the thresholds +- Raw scores never leave the governance agent + +This design prevents: +- Score enumeration attacks (running lists with different thresholds to reverse-engineer scores) +- Competitive intelligence leakage +- Data arbitrage where buyers resell scoring data + +#### What Buyers Receive + +When calling `get_property_list`, buyers receive a compact list of identifiers (not full property objects) for efficiency: + +```json +{ + "list_id": "pl_abc123", + "identifiers": [ + { "type": "domain", "value": "bbc.co.uk" }, + { "type": "domain", "value": "theguardian.com" }, + { "type": "domain", "value": "ft.com" } + ], + "total_count": 847 +} +``` + +Properties that pass the threshold are included. Properties that fail are excluded. No scores or property metadata are returned - just the identifiers needed for bid-time lookups. + +#### Methodology Discovery + +The `list_property_features` task returns information about what features an agent evaluates and their methodology, but NOT the underlying scores: + +```json +{ + "features": [ + { + "feature_id": "consent_quality", + "name": "Consent Quality Score", + "type": "quantitative", + "range": { "min": 0, "max": 100 }, + "methodology": "data_integrity_index", + "methodology_version": "v2.1", + "methodology_url": "https://compliance.example.com/methodology" + } + ] +} +``` + +This allows buyers to: +- Understand what an agent measures +- Compare methodologies across agents +- Set appropriate thresholds + +But they cannot retrieve the actual scores for individual properties. + +## Tasks + +### Discovery + +#### list_property_features + +Discover what features a governance agent can evaluate. + +**Use Cases**: +- Capability discovery: Understand what an agent can evaluate +- Marketplace browsing: Compare features across agents +- Integration planning: Know what filters are available before creating lists + +### Property List Management + +#### create_property_list + +Create a new property list with filters and optional brand manifest. + +**Required Parameters**: +- At least one country in `countries_all` (ISO country code) +- At least one channel in `channels_any` (display, video, audio, etc.) + +**Base Properties**: An array of property sources to evaluate. Each entry is a discriminated union with `selection_type` as the discriminator: +- **`publisher_tags`**: `{ "selection_type": "publisher_tags", "publisher_domain": "...", "tags": [...] }` - tags scoped to publisher +- **`publisher_ids`**: `{ "selection_type": "publisher_ids", "publisher_domain": "...", "property_ids": [...] }` - property IDs scoped to publisher +- **`identifiers`**: `{ "selection_type": "identifiers", "identifiers": [...] }` - no publisher context needed +- **Omitted**: Query the agent's entire property database + +See the [base-property-source schema](https://adcontextprotocol.org/schemas/v2/property/base-property-source.json) for the full specification. + +**Filter Logic** (explicit in field names): +- `countries_all`: Property must have feature data for **ALL** listed countries +- `channels_any`: Property must support **ANY** of the listed channels +- `feature_requirements`: Property must pass **ALL** requirements (AND) + +**Use Cases**: +- Define compliant property sets with filters (country, channel, feature thresholds) +- Provide brand manifest for automatic rule application +- Register webhook URL for change notifications + +#### update_property_list + +Modify an existing property list. + +**Use Cases**: +- Add or remove properties from base list +- Adjust filters based on campaign needs +- Update webhook URL + +#### get_property_list + +Retrieve a property list with resolved properties. + +**Use Cases**: +- Get the current list of compliant properties after filters applied +- Cache resolved list for bid-time use +- Retrieve updated list after webhook notification + +#### list_property_lists + +List all property lists accessible to the authenticated principal. + +#### delete_property_list + +Remove a property list. + +### Validation + +#### validate_property_delivery + +Validates delivery records against a property list to determine compliance. Closes the loop between "what I wanted" and "what I got." + +Performs two independent validations: +1. **Property compliance**: Is the identifier in the resolved property list? +2. **Supply path authorization**: Was the sales agent authorized to sell that property? (optional, requires `sales_agent_url`) + +**Use Cases**: +- Post-campaign validation: Verify impressions landed on compliant properties +- Supply path verification: Confirm sales agents were authorized by publishers +- Real-time monitoring: Check compliance rate during campaign execution +- Audit trails: Generate compliance reports for regulatory or brand safety reviews + +**Property Validation Statuses**: +- `compliant`: Identifier is in the resolved property list +- `non_compliant`: Identifier is NOT in the resolved property list +- `not_covered`: Identifier recognized but governance agent has no data for it (e.g., property too new) +- `unidentified`: Identifier type not resolvable by this agent (e.g., detection failed, unsupported type) + +**Authorization Validation Statuses** (when `sales_agent_url` provided): +- `authorized`: Sales agent is listed in publisher's adagents.json +- `unauthorized`: Sales agent is NOT in publisher's authorized_agents list +- `unknown`: Could not fetch or parse adagents.json + +**Unverifiable Records**: Both `not_covered` and `unidentified` records should be excluded when calculating compliance rates - you cannot penalize for detection gaps or coverage limitations. The distinction helps identify whether the gap is in the agent's data coverage vs the identifier resolution. + +**Response Format**: The response returns raw counts (compliant, non_compliant, not_covered, unidentified impressions). Consumers calculate rates as needed. Governance agents may optionally include an `aggregate` field with computed metrics (score, grade, label) - the format and meaning are agent-specific. + +## Typical Flows + +### Property List Flow + +Property lists enable buyers to define and manage compliant property sets: + +1. **Create property list**: Buyer defines list on governance agent with filters +2. **Resolve and iterate**: Buyer calls `get_property_list` to see resolved properties +3. **Share list reference**: Buyer provides `list_id` to orchestrator/seller +4. **Cache locally**: Orchestrator/seller fetches and caches resolved properties +5. **Use at bid time**: Orchestrator/seller uses local cache (no governance agent calls) +6. **Refresh periodically**: Re-fetch based on `cache_valid_until` (typically 1-24 hours) + +**Important**: Governance agents are NOT in the real-time bid path. All bid-time decisions use locally cached property sets. + +### Webhook and Caching Pattern + +Webhooks provide **notification** that a property list has changed. The webhook payload contains a summary of changes, but you must call `get_property_list` to retrieve the actual updated properties. + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Webhook Flow │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. Governance agent re-evaluates properties (background) │ +│ 2. Webhook fires with change summary (added/removed counts) │ +│ 3. Recipient calls get_property_list to fetch updated list │ +│ 4. Recipient updates local cache │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +**Best Practices for Downstream Consumers**: + +Consumers of property lists (orchestrators, sellers, buyer agents) should implement **at least one** of these patterns: + +1. **Webhook-driven updates** (recommended): Register a webhook URL when creating the property list. Re-fetch via `get_property_list` when notified of changes. + +2. **Polling with cache hints**: Use `cache_valid_until` from `get_property_list` responses to schedule periodic re-fetches. Typical validity periods are 1-24 hours. + +3. **Hybrid approach**: Use webhooks for immediate updates, with polling as a fallback safety net. + +**Cache Expiry Guidance**: + +Every `get_property_list` response includes: +- `resolved_at`: When the list was evaluated +- `cache_valid_until`: When consumers should consider the cache stale + +```json +{ + "resolved_at": "2026-01-04T10:00:00Z", + "cache_valid_until": "2026-01-04T22:00:00Z" +} +``` + +Consumers MUST NOT use cached data beyond `cache_valid_until` without re-fetching. + +### Property Discovery Flow + +1. **Define filters**: Specify country, channel, quality thresholds when creating property list +2. **Resolve list**: Call `get_property_list` with `resolve=true` to get matching properties +3. **Review candidates**: Evaluate returned properties for fit +4. **Add to campaign**: Include property list reference in media buy + +## Response Structure + +All AdCP Governance responses follow a consistent structure: + +### Core Response Fields +- **message**: Human-readable summary of the operation result +- **context_id**: Session continuity identifier for follow-up requests +- **data**: Task-specific payload (varies by task) + +### Protocol Transport +- **MCP**: Returns complete response as flat JSON object +- **A2A**: Returns as structured artifacts with message in text part, data in data part +- **Data Consistency**: Both protocols contain identical AdCP data structures + +## Error Handling + +### Error Codes + +- `PROPERTY_NOT_FOUND`: Property identifier not recognized +- `PROPERTY_NOT_MONITORED`: Governance agent doesn't cover this property +- `POLICY_NOT_FOUND`: Referenced policy doesn't exist +- `LIST_ACCESS_DENIED`: Cannot access property list (auth failed) +- `LIST_NOT_FOUND`: Property list reference invalid +- `METHODOLOGY_NOT_SUPPORTED`: Requested methodology version unavailable +- `PARTIAL_RESULTS`: Some properties couldn't be evaluated + +### Partial Success + +For bulk operations, the response may include partial results: + +```json +{ + "message": "Evaluated 847 of 850 properties. 3 properties not in coverage.", + "context_id": "ctx-gov-123", + "scores": [...], + "errors": [ + { + "code": "PROPERTY_NOT_MONITORED", + "property": { "identifiers": [{ "type": "domain", "value": "unknown.com" }] }, + "message": "Property not in monitoring coverage" + } + ] +} +``` + +## Implementation Notes + +### Caching Architecture + +Governance decisions are highly cacheable: + +#### Orchestrator-Side Caching +- **Score cache**: Store scores with TTL from `valid_until` field +- **Decision cache**: Pre-compute pass/fail for campaigns +- **List cache**: Cache property lists from `property_list_ref` + +#### Agent-Side Caching +- **Profile cache**: Maintain pre-computed property profiles +- **Methodology cache**: Cache scoring algorithm results + +### Performance Requirements + +| Operation | Target Latency | +|-----------|----------------| +| Single property score | < 100ms | +| Bulk scoring (100 properties) | < 2s | +| Filter decision (cached) | < 5ms | +| Property discovery | < 5s | + +### Multi-Agent Strategies + +Orchestrators may consult multiple governance agents: + +1. **Primary + Validation**: Use primary agent, validate with secondary +2. **Specialization**: Route by property type to specialist agents +3. **Consensus**: Require multiple agents to agree +4. **Competitive**: Track agent accuracy, weight by performance + +## Agent Discovery + +Governance agents expose capabilities via `.well-known/agent-card.json`: + +```json +{ + "name": "Example Compliance Provider", + "url": "https://compliance.example.com", + "capabilities": { + "tasks": [ + "list_property_features", + "create_property_list", + "get_property_list", + "update_property_list", + "delete_property_list", + "list_property_lists", + "validate_property_delivery" + ], + "protocols": ["MCP", "A2A"], + "schema_version": "v1" + }, + "methodology": { + "documentation_url": "https://compliance.example.com/methodology", + "scoring_frameworks": ["data_integrity_index", "brand_safety_score"], + "coverage": { + "property_types": ["website", "mobile_app", "ctv_app"], + "jurisdictions": ["GDPR", "CCPA", "COPPA"] + } + } +} +``` + +Use `list_property_features` for detailed capability discovery: + +```json +{ + "tool": "list_property_features", + "arguments": {} +} +``` + +Returns the specific features the agent can evaluate (consent_quality, carbon_score, brand_risk, etc.). + +## Marketplace Architecture + +The Property Protocol enables a marketplace of specialized data agents: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ SELLER AGENT (DSP/SSP) │ +│ "Give me the compliant property list for this campaign" │ +└───────────────────────────────┬─────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ BUYER AGENT (implements Property Protocol) │ +│ - Exposes: list_property_features, get_property_list, webhooks │ +│ - Source of truth for final compliant list │ +│ - Intersects results from specialized agents │ +└───────────────────────────┬─────────────────────────────────────────────┘ + │ + ┌───────────────────┼───────────────────┐ + ▼ ▼ ▼ +┌───────────────┐ ┌───────────────┐ ┌───────────────┐ +│ Consent Agent │ │ Scope3 Agent │ │ Brand Safety │ +│ (Compliant) │ │ │ │ Agent │ +├───────────────┤ ├───────────────┤ ├───────────────┤ +│ Features: │ │ Features: │ │ Features: │ +│ consent_qual │ │ carbon_score │ │ content_cat │ +│ tcf_version │ │ climate_risk │ │ brand_risk │ +│ coppa_cert │ │ green_host │ │ sentiment │ +├───────────────┤ ├───────────────┤ ├───────────────┤ +│ Subscription │ │ Subscription │ │ Subscription │ +└───────────────┘ └───────────────┘ └───────────────┘ +``` + +### Key Principles + +1. **Buyer agent is source of truth**: The buyer agent aggregates data from multiple specialized governance agents +2. **Seller sees one interface**: Sellers interact only with the buyer agent using standard Property Protocol +3. **Subscription model**: Each specialized agent is a paid service with its own features and coverage +4. **Webhook-driven updates**: Specialized agents notify the buyer agent when property evaluations change + +### Multi-Agent Orchestration + +A buyer agent can distribute a master property list to multiple specialized agents: + +```python +# Buyer agent creates variants on each specialized agent +consent_list = consent_agent.create_property_list( + name="Q1 Campaign - Consent", + base_properties=master_list, + brand_manifest=brand_manifest +) +# Configure webhook for updates +consent_agent.update_property_list( + list_id=consent_list.list_id, + webhook_url="https://buyer.example.com/webhooks/consent" +) + +scope3_list = scope3_agent.create_property_list( + name="Q1 Campaign - Sustainability", + base_properties=master_list, + brand_manifest=brand_manifest +) +scope3_agent.update_property_list( + list_id=scope3_list.list_id, + webhook_url="https://buyer.example.com/webhooks/scope3" +) + +# Buyer agent intersects filtered results +def on_list_changed(event): + consent_props = consent_agent.get_property_list(consent_list.list_id, resolve=True) + scope3_props = scope3_agent.get_property_list(scope3_list.list_id, resolve=True) + + # Intersection = properties that pass ALL governance agents + compliant_props = intersect(consent_props, scope3_props) + + # Update buyer agent's exposed list + update_compliant_list(compliant_props) +``` + +### Brand Manifest + +Instead of specifying complex filters, buyers provide a brand manifest: + +```json +{ + "brand_manifest": { + "brand_name": "ToyBrand", + "industry": "toys", + "target_audience": "children_under_13", + "content_adjacency": ["kids_family", "education"], + "excluded_content": ["violence", "adult"] + } +} +``` + +Each governance agent interprets the manifest according to their domain expertise: +- **Consent agent**: Applies COPPA requirements for children_under_13 +- **Brand safety agent**: Filters to kids_family content, excludes violence/adult +- **Sustainability agent**: Applies any green media requirements + +The buyer doesn't need to know the specific rules - they declare who they are, and agents figure out what applies. + +## Integration with Media Buy Protocol + +### Property Lists in Media Buys + +The Media Buy Protocol accepts property list references: + +```json +{ + "task": "create_media_buy", + "arguments": { + "packages": [{ + "property_list_ref": { + "agent_url": "https://governance.example.com", + "list_id": "approved_q1_campaign", + "auth_token": "..." + } + }] + } +} +``` + +### Policy Compliance + +Media buys can reference governance policies via property list references: + +```json +{ + "compliance_requirements": { + "property_list_ref": { + "agent_url": "https://compliance.example.com", + "list_id": "pl_q1_compliant", + "auth_token": "eyJhbGciOiJIUzI1NiIs..." + } + } +} +``` + +## Best Practices + +1. **Cache aggressively**: Property scores change slowly; cache for hours/days +2. **Bulk where possible**: Use batch operations for planning, not per-property calls +3. **Pre-compute decisions**: Build pass/fail lookups before bid-time +4. **Monitor coverage**: Track which properties agents don't cover +5. **Log methodology versions**: For audit trails, record which scoring version was used +6. **Handle partial results**: Not all properties will be scorable; plan for gaps + +## Next Steps + +- See the [adagents.json Tech Spec](/docs/governance/property/adagents) for property declaration and authorization +- See the [list_property_features task reference](/docs/governance/property/tasks/list_property_features) for capability discovery +- See the [Property List Management](/docs/governance/property/tasks/property_lists) for CRUD operations and webhooks +- See the [validate_property_delivery task reference](/docs/governance/property/tasks/validate_property_delivery) for post-campaign compliance validation diff --git a/v2.6-rc/docs/governance/property/tasks/index.mdx b/v2.6-rc/docs/governance/property/tasks/index.mdx new file mode 100644 index 0000000000..4679e3704b --- /dev/null +++ b/v2.6-rc/docs/governance/property/tasks/index.mdx @@ -0,0 +1,153 @@ +--- +sidebar_position: 1 +title: Task Reference +--- + +# Property Governance Tasks + + +**AdCP 3.0 Proposal** - These tasks are under development for AdCP 3.0. + + +Property governance uses a **stateful** model where all evaluation happens through property list management. Create lists with filters and brand manifests, then resolve them to get compliant properties. + +## Discovery + +| Task | Purpose | Response Time | +|------|---------|---------------| +| [list_property_features](/docs/governance/property/tasks/list_property_features) | Discover agent capabilities | ~200ms | + +## Property List Management + +| Task | Purpose | Response Time | +|------|---------|---------------| +| [create_property_list](/docs/governance/property/tasks/property_lists#create_property_list) | Create a new property list | ~500ms | +| [update_property_list](/docs/governance/property/tasks/property_lists#update_property_list) | Modify an existing list | ~500ms | +| [get_property_list](/docs/governance/property/tasks/property_lists#get_property_list) | Retrieve list with resolved properties | ~2-5s | +| [list_property_lists](/docs/governance/property/tasks/property_lists#list_property_lists) | List all property lists | ~500ms | +| [delete_property_list](/docs/governance/property/tasks/property_lists#delete_property_list) | Delete a property list | ~200ms | + +See [Property List Management](/docs/governance/property/tasks/property_lists) for complete CRUD documentation. + +## Validation + +| Task | Purpose | Response Time | +|------|---------|---------------| +| [validate_property_delivery](/docs/governance/property/tasks/validate_property_delivery) | Validate delivery records against a list | ~1-5s | + +See [validate_property_delivery](/docs/governance/property/tasks/validate_property_delivery) for post-campaign compliance validation. + +## Task Selection Guide + +### Creating a Property List + +Use `create_property_list` with filters and brand manifest: + +```json +{ + "tool": "create_property_list", + "arguments": { + "name": "Q1 Campaign - UK Premium", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news"] + } + ], + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { + "feature_id": "consent_quality", + "min_value": 85, + "max_value": 100 + }, + { + "feature_id": "coppa_certified", + "allowed_values": [true] + } + ] + }, + "brand_manifest": { + "name": "ToyBrand", + "industry": "toys", + "target_audience": "children_under_13" + } + } +} +``` + +**Required filters**: At least one country in `countries_all` and one channel in `channels_any` must be provided. + +**Base properties**: An array of property sources to evaluate. Each entry is a discriminated union with `selection_type`: +- **`publisher_tags`**: `{ "selection_type": "publisher_tags", "publisher_domain": "...", "tags": [...] }` +- **`publisher_ids`**: `{ "selection_type": "publisher_ids", "publisher_domain": "...", "property_ids": [...] }` +- **`identifiers`**: `{ "selection_type": "identifiers", "identifiers": [...] }` +- **Omitted**: Query the agent's entire property database + +**Filter logic** (explicit in field names): +- `countries_all`: Property must have feature data for ALL listed countries +- `channels_any`: Property must support ANY of the listed channels +- `feature_requirements`: Property must pass ALL requirements (AND) + +Filters have two built-in fields (`countries_all`, `channels_any`) plus `feature_requirements` which reference features the agent provides (discovered via `list_property_features`). For quantitative features, use `min_value`/`max_value`. For binary or categorical features, use `allowed_values`. + +### Getting Resolved Properties + +Use `get_property_list` to retrieve the list with resolved identifiers: + +```json +{ + "tool": "get_property_list", + "arguments": { + "list_id": "pl_abc123", + "resolve": true + } +} +``` + +Response includes resolved identifiers. Note that **raw scores are not returned** - only identifiers that pass the filter thresholds are included: + +```json +{ + "list_id": "pl_abc123", + "identifiers": [ + { "type": "domain", "value": "bbc.co.uk" }, + { "type": "domain", "value": "news.sky.com" } + ], + "cache_valid_until": "2026-01-04T17:15:00Z" +} +``` + +The `auth_token` for sharing with sellers is returned at creation time (from `create_property_list`). Store it securely - it's only returned once. + +### Multi-Agent Integration + +Create the same property list on multiple governance agents, then configure webhooks to aggregate results: + +```python +# Create lists on specialized agents +consent_list = consent_agent.create_property_list( + name="Q1 - Consent", + base_properties=master_list, + brand_manifest=brand_manifest +) +consent_agent.update_property_list( + list_id=consent_list.list_id, + webhook_url="https://buyer.example.com/webhooks/consent" +) + +scope3_list = scope3_agent.create_property_list( + name="Q1 - Sustainability", + base_properties=master_list, + brand_manifest=brand_manifest +) +scope3_agent.update_property_list( + list_id=scope3_list.list_id, + webhook_url="https://buyer.example.com/webhooks/scope3" +) + +# Buyer agent intersects results when webhooks fire +``` diff --git a/v2.6-rc/docs/governance/property/tasks/list_property_features.mdx b/v2.6-rc/docs/governance/property/tasks/list_property_features.mdx new file mode 100644 index 0000000000..6c7dcd16a7 --- /dev/null +++ b/v2.6-rc/docs/governance/property/tasks/list_property_features.mdx @@ -0,0 +1,235 @@ +--- +sidebar_position: 1 +title: list_property_features +--- + + +**AdCP 3.0 Proposal** - This task is under development for AdCP 3.0. + + +**Task**: Discover what features a property governance agent can evaluate. + +**Response Time**: ~200ms + +**Request Schema**: [`https://adcontextprotocol.org/schemas/v1/governance/list-property-features-request.json`](https://adcontextprotocol.org/schemas/v1/governance/list-property-features-request.json) +**Response Schema**: [`https://adcontextprotocol.org/schemas/v1/governance/list-property-features-response.json`](https://adcontextprotocol.org/schemas/v1/governance/list-property-features-response.json) + +The `list_property_features` task returns the features a governance agent can evaluate. This is the discovery mechanism for understanding an agent's capabilities before using other governance tasks. + +## Request Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `property_types` | string[] | No | Filter to features available for these property types | +| `countries` | string[] | No | Filter to features available in these countries | + +## Response Structure + +All AdCP responses include: +- **message**: Human-readable summary of the operation result +- **context_id**: Session continuity identifier for follow-up requests +- **data**: Task-specific payload (see Response Data below) + +## Response Data + +```json +{ + "features": [ + { + "feature_id": "consent_quality", + "name": "Consent Quality Score", + "description": "Measures the quality of consent implementation including UX, granularity, and compliance", + "type": "quantitative", + "range": { "min": 0, "max": 100 }, + "coverage": { + "property_types": ["website", "mobile_app"], + "countries": [] + } + }, + { + "feature_id": "coppa_certified", + "name": "COPPA Certified", + "description": "Whether the property has COPPA certification for child-directed content", + "type": "binary", + "coverage": { + "property_types": ["website", "mobile_app", "ctv_app"], + "countries": ["US"] + } + }, + { + "feature_id": "content_category", + "name": "Content Category", + "description": "IAB content category classification", + "type": "categorical", + "allowed_values": ["news", "sports", "entertainment", "kids_family", "technology", "business", "adult", "violence"] + } + ] +} +``` + +### Feature Types + +| Type | Description | Schema Fields | +|------|-------------|---------------| +| `binary` | True/false values | None additional | +| `quantitative` | Numeric values within a range | `range: { min, max }` | +| `categorical` | Enumerated string values | `allowed_values: string[]` | + +### Coverage + +The `coverage` object indicates where a feature applies: +- **property_types**: Empty array means all property types +- **countries**: Empty array means all countries + +## Protocol-Specific Examples + +### MCP Request + +```json +{ + "tool": "list_property_features", + "arguments": {} +} +``` + +### MCP Response + +```json +{ + "message": "This agent evaluates 12 features across consent, brand safety, and sustainability.", + "context_id": "ctx-gov-features-123", + "features": [ + { + "feature_id": "consent_quality", + "name": "Consent Quality Score", + "description": "Measures the quality of consent implementation", + "type": "quantitative", + "range": { "min": 0, "max": 100 } + }, + { + "feature_id": "tcf_version", + "name": "TCF Version", + "description": "IAB Transparency & Consent Framework version supported", + "type": "categorical", + "allowed_values": ["none", "tcf_1.1", "tcf_2.0", "tcf_2.2"] + }, + { + "feature_id": "coppa_certified", + "name": "COPPA Certified", + "description": "Whether property has COPPA certification", + "type": "binary", + "coverage": { + "property_types": ["website", "mobile_app", "ctv_app"], + "countries": ["US"] + } + } + ] +} +``` + +### MCP Request - Filtered by Property Type + +```json +{ + "tool": "list_property_features", + "arguments": { + "property_types": ["mobile_app"] + } +} +``` + +### A2A Request + +```javascript +await a2a.send({ + message: { + parts: [{ + kind: "data", + data: { + skill: "list_property_features", + parameters: {} + } + }] + } +}); +``` + +## Example Feature Sets by Agent Type + +### Consent/Compliance Agent + +```json +{ + "features": [ + { "feature_id": "consent_quality", "type": "quantitative", "range": { "min": 0, "max": 100 } }, + { "feature_id": "tcf_version", "type": "categorical", "allowed_values": ["none", "tcf_2.0", "tcf_2.2"] }, + { "feature_id": "coppa_certified", "type": "binary" }, + { "feature_id": "gpp_supported", "type": "binary" }, + { "feature_id": "vendor_count", "type": "quantitative", "range": { "min": 0, "max": 1000 } } + ] +} +``` + +### Sustainability Agent (e.g., Scope3) + +```json +{ + "features": [ + { "feature_id": "carbon_score", "type": "quantitative", "range": { "min": 0, "max": 100 } }, + { "feature_id": "green_certified", "type": "binary" }, + { "feature_id": "renewable_hosting", "type": "binary" }, + { "feature_id": "emissions_per_impression", "type": "quantitative", "range": { "min": 0, "max": 100 } } + ] +} +``` + +### Brand Safety Agent + +```json +{ + "features": [ + { "feature_id": "content_category", "type": "categorical", "allowed_values": ["news", "sports", ...] }, + { "feature_id": "brand_risk_score", "type": "quantitative", "range": { "min": 0, "max": 100 } }, + { "feature_id": "sentiment", "type": "categorical", "allowed_values": ["positive", "neutral", "negative"] }, + { "feature_id": "made_for_advertising", "type": "binary" } + ] +} +``` + +## Integration Pattern + +Use `list_property_features` to understand agent capabilities before creating property lists: + +```python +# Step 1: Discover agent capabilities +features_response = governance_agent.list_property_features() + +# Step 2: Build feature_requirements using available features +feature_requirements = [] +for feature in features_response.features: + if feature.feature_id == "consent_quality" and feature.type == "quantitative": + feature_requirements.append({ + "feature_id": "consent_quality", + "min_value": 80, + "max_value": 100 + }) + if feature.feature_id == "coppa_certified" and feature.type == "binary": + feature_requirements.append({ + "feature_id": "coppa_certified", + "allowed_values": [True] + }) + +# Step 3: Create property list with feature requirements +governance_agent.create_property_list( + name="Q1 Campaign", + filters={"feature_requirements": feature_requirements}, + brand_manifest=my_brand_manifest +) +``` + +## Usage Notes + +1. **Call first**: Use this task to discover capabilities before other governance tasks +2. **Cache results**: Feature definitions change infrequently; cache for hours/days +3. **Check coverage**: Not all features apply to all property types or countries +4. **Marketplace discovery**: Use this to understand what different governance agents offer diff --git a/v2.6-rc/docs/governance/property/tasks/property_lists.mdx b/v2.6-rc/docs/governance/property/tasks/property_lists.mdx new file mode 100644 index 0000000000..a46e54545f --- /dev/null +++ b/v2.6-rc/docs/governance/property/tasks/property_lists.mdx @@ -0,0 +1,701 @@ +--- +sidebar_position: 5 +title: Property List Management +--- + + +**AdCP 3.0 Proposal** - These tasks are under development for AdCP 3.0. + + +**Tasks**: Create, update, get, list, and delete property lists. + +Property lists are managed resources that combine static property sets with dynamic filters. When resolved, filters are applied to produce the final property set. + +## Architecture: Setup Time, Not Real-Time + +Property lists are designed for **setup-time** operations, not real-time bid decisions: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ SETUP TIME (Campaign Planning) │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. Buyer creates/updates property list on governance agent │ +│ 2. Buyer resolves list to get current properties │ +│ 3. Buyer provides list_id to orchestrator/seller │ +│ 4. Orchestrator/seller fetches and caches resolved list │ +│ 5. Campaign targets only cached compliant properties │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ BID TIME (Milliseconds) │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ • Orchestrator/seller uses LOCAL cache only │ +│ • NO calls to governance agent │ +│ • Pass/fail from cached property set │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ REFRESH (Periodic) │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ • Orchestrator/seller re-fetches list on schedule │ +│ • Frequency based on cache_valid_until │ +│ • Typically every 1-24 hours │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +This enables: + +- **Static lists**: Curated sets of approved properties +- **Dynamic lists**: Properties matching criteria (country, channel, score thresholds) +- **Hybrid lists**: Base set modified by filters + +## Tasks Overview + +| Task | Purpose | Response Time | +|------|---------|---------------| +| `create_property_list` | Create a new property list | ~500ms | +| `update_property_list` | Modify an existing list | ~500ms | +| `get_property_list` | Retrieve list with resolved properties | ~2-5s (depending on size) | +| `list_property_lists` | List all property lists | ~500ms | +| `delete_property_list` | Delete a property list | ~200ms | + +## Property List Structure + +A property list contains: + +```json +{ + "list_id": "uk_premium_news_q1", + "name": "UK Premium News Sites Q1 2026", + "description": "High-quality UK news sites for Q1 campaign", + "principal": "did:principal:brand-x", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news", "uk_tier1"] + } + ], + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 90, "max_value": 100 } + ] + }, + "brand_manifest": { + "industry": "retail", + "target_audience": "general" + }, + "created_at": "2026-01-03T10:00:00Z", + "updated_at": "2026-01-03T10:00:00Z", + "property_count": 847 +} +``` + +## Brand Manifest + +Instead of manually specifying all filters, provide a brand manifest and let the governance agent apply appropriate rules based on who you are: + +```json +{ + "brand_manifest": { + "name": "ToyBrand", + "industry": "toys", + "target_audience": "children under 13", + "tone": "playful and safe" + } +} +``` + +The agent interprets the manifest based on its domain expertise: +- A consent agent applies COPPA requirements based on `target_audience` +- A brand safety agent infers content categories from `industry` +- A sustainability agent applies requirements from the brand's profile + +The brand manifest uses the standard [core/brand-manifest](https://adcontextprotocol.org/schemas/v2/core/brand-manifest.json) schema which includes fields for `industry`, `target_audience`, `tone`, and other brand identity information. + +## Webhooks + +Configure webhooks via `update_property_list` to receive notifications when the resolved list changes. + +**Important**: Webhooks provide **notification only**. They tell you that the list has changed, but do not stream the updated properties. After receiving a webhook, you must call `get_property_list` to retrieve the updated property set. + +### Webhook Flow + +``` +1. Governance agent re-evaluates properties (periodically or on trigger) +2. Agent detects changes to the resolved property list +3. Webhook fires with change summary (counts, not full list) +4. Recipient calls get_property_list(list_id, resolve=true) +5. Recipient updates local cache with new properties +``` + +### Webhook Payload + +```json +{ + "event": "property_list_changed", + "list_id": "uk_premium_news_q1", + "list_name": "UK Premium News Sites Q1 2026", + "change_summary": { + "properties_added": 12, + "properties_removed": 3, + "total_properties": 856 + }, + "resolved_at": "2026-01-03T18:00:00Z", + "cache_valid_until": "2026-01-04T18:00:00Z" +} +``` + +The webhook payload includes counts but NOT the actual properties. This keeps payloads small and avoids redundant data transfer when recipients may not need the full list immediately. + +### Webhook Use Cases + +1. **Buyer agent aggregation**: Receive updates from multiple specialized agents, intersect results +2. **Seller cache invalidation**: Know when to re-fetch the compliant property list +3. **Alerting**: Notify when significant changes occur to compliance status + +## Filters + +Filters are applied when the list is resolved (via `get_property_list`): + +| Filter | Type | Description | +|--------|------|-------------| +| `countries_all` | string[] | ISO country codes - property must have feature data for ALL (required) | +| `channels_any` | string[] | Advertising channels - property must support ANY (required) | +| `property_types` | string[] | Property types (website, mobile_app, ctv_app, etc.) | +| `feature_requirements` | FeatureRequirement[] | Requirements based on agent-provided features | +| `exclude_identifiers` | Identifier[] | Identifiers to always exclude | + +### Feature Requirements + +Feature requirements reference features discovered via `list_property_features`. Each agent exposes different features (consent_quality, carbon_score, coppa_certified, etc.). + +For **quantitative** features (scores, ranges): +```json +{ "feature_id": "consent_quality", "min_value": 85, "max_value": 100 } +``` + +For **binary** features (true/false): +```json +{ "feature_id": "coppa_certified", "allowed_values": [true] } +``` + +For **categorical** features (enum values): +```json +{ "feature_id": "content_category", "allowed_values": ["news", "sports", "technology"] } +``` + +### Handling Missing Coverage + +When a property doesn't have data for a required feature, you can control the behavior with `if_not_covered`: + +```json +{ + "feature_id": "viewability_score", + "min_value": 70, + "if_not_covered": "include" +} +``` + +| Value | Behavior | Use Case | +|-------|----------|----------| +| `exclude` (default) | Property is removed from the list | Strict enforcement - only include properties with verified data | +| `include` | Property passes this requirement | Lenient enforcement - don't penalize for coverage gaps | + +When `if_not_covered: "include"` is used, the response includes a `coverage_gaps` field showing which properties were included despite missing data: + +```json +{ + "identifiers": [...], + "coverage_gaps": { + "viewability_score": [ + { "type": "domain", "value": "app.example.com" }, + { "type": "domain", "value": "ctv.example.com" } + ] + } +} +``` + +This transparency helps agencies distinguish between properties that passed a requirement vs. those that couldn't be evaluated. + +### Required Filters + +Every property list must include at least: +- One country in `countries_all` (ISO country code) +- One channel in `channels_any` (display, video, audio, etc.) + +These are required because governance agents need to know which jurisdiction and context to evaluate properties against. + +### Filter Logic + +The filter field names make the logic explicit: + +- **`countries_all`**: Property must have feature data for **ALL** listed countries. +- **`channels_any`**: Property must support **ANY** of the listed channels. +- **`feature_requirements`**: Property must pass **ALL** requirements (AND). + +### Base Properties + +`base_properties` is an array of property sources to evaluate. Each entry is a **discriminated union** with `selection_type` as the discriminator: + +```json +{ + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news", "tier1"] + }, + { + "selection_type": "publisher_tags", + "publisher_domain": "mediavine.com", + "tags": ["lifestyle"] + }, + { + "selection_type": "identifiers", + "identifiers": [ + { "type": "domain", "value": "bbc.co.uk" }, + { "type": "domain", "value": "ft.com" } + ] + } + ] +} +``` + +Each entry must include `selection_type`: + +| selection_type | Required Fields | Description | +|-------------|-----------------|-------------| +| `publisher_tags` | `publisher_domain`, `tags` | All properties matching these tags within the publisher | +| `publisher_ids` | `publisher_domain`, `property_ids` | Specific property IDs within the publisher | +| `identifiers` | `identifiers` | Direct domain/app identifiers (no publisher context) | + +If `base_properties` is omitted, the agent queries its entire property database for properties matching the filters. + +See the [base-property-source schema](https://adcontextprotocol.org/schemas/v2/property/base-property-source.json) for the full specification. + +--- + +## create_property_list + +Create a new property list. + +### Request + +```json +{ + "tool": "create_property_list", + "arguments": { + "name": "UK Premium News Q1", + "description": "High-quality UK news sites for Q1 campaign", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news"] + } + ], + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 85, "max_value": 100 } + ] + } + } +} +``` + +### Response + +```json +{ + "message": "Created property list 'UK Premium News Q1'.", + "context_id": "ctx-gov-list-123", + "list": { + "list_id": "pl_abc123", + "name": "UK Premium News Q1", + "description": "High-quality UK news sites for Q1 campaign", + "principal": "did:principal:brand-x", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news"] + } + ], + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 85, "max_value": 100 } + ] + }, + "created_at": "2026-01-03T16:30:00Z", + "updated_at": "2026-01-03T16:30:00Z", + "property_count": 847 + }, + "auth_token": "eyJhbGciOiJIUzI1NiIs..." +} +``` + +### Dynamic List (Filters Only) + +Create a list that dynamically queries the governance agent's database (no base_properties - uses agent's full coverage): + +```json +{ + "tool": "create_property_list", + "arguments": { + "name": "GDPR-Compliant DE Video", + "description": "All DE properties supporting video with strong consent", + "filters": { + "countries_all": ["DE"], + "channels_any": ["video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 90, "max_value": 100 } + ] + } + } +} +``` + +--- + +## update_property_list + +Modify an existing property list. + +### Request - Update Filters + +```json +{ + "tool": "update_property_list", + "arguments": { + "list_id": "pl_abc123", + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 80, "max_value": 100 } + ] + } + } +} +``` + +### Request - Replace Base Properties + +```json +{ + "tool": "update_property_list", + "arguments": { + "list_id": "pl_abc123", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news", "uk_tier1"] + } + ] + } +} +``` + +### Request - Add Exclusions + +```json +{ + "tool": "update_property_list", + "arguments": { + "list_id": "pl_abc123", + "filters": { + "exclude_identifiers": [ + { "type": "domain", "value": "excluded-site.com" } + ] + } + } +} +``` + +### Response + +```json +{ + "message": "Updated property list 'UK Premium News Q1'.", + "context_id": "ctx-gov-list-456", + "list": { + "list_id": "pl_abc123", + "name": "UK Premium News Q1", + "updated_at": "2026-01-03T17:00:00Z", + "property_count": 845 + } +} +``` + +--- + +## get_property_list + +Retrieve a property list with optional resolution of filters. + +### Request - Get Resolved Properties + +```json +{ + "tool": "get_property_list", + "arguments": { + "list_id": "pl_abc123", + "resolve": true, + "max_results": 100 + } +} +``` + +### Response + +The response returns a compact list of **identifiers only** (not full property objects) for efficiency. Only identifiers that pass the feature requirements are included - no scores or metadata. + +```json +{ + "message": "Retrieved property list 'UK Premium News Q1' with 847 resolved identifiers.", + "context_id": "ctx-gov-list-789", + "list_id": "pl_abc123", + "identifiers": [ + { "type": "domain", "value": "bbc.co.uk" }, + { "type": "domain", "value": "news.sky.com" }, + { "type": "domain", "value": "ft.com" }, + { "type": "domain", "value": "theguardian.com" } + ], + "total_count": 847, + "returned_count": 100, + "pagination": { + "has_more": true, + "cursor": "eyJvZmZzZXQiOjEwMH0=" + }, + "resolved_at": "2026-01-03T17:15:00Z", + "cache_valid_until": "2026-01-04T17:15:00Z" +} +``` + + +The `auth_token` is only returned when the list is created via `create_property_list`. Store it securely - you'll need it to share access with sellers. + + +### Request - Get Metadata Only + +```json +{ + "tool": "get_property_list", + "arguments": { + "list_id": "pl_abc123", + "resolve": false + } +} +``` + +### Response (Metadata Only) + +```json +{ + "message": "Retrieved property list 'UK Premium News Q1' metadata.", + "context_id": "ctx-gov-list-790", + "list": { + "list_id": "pl_abc123", + "name": "UK Premium News Q1", + "description": "High-quality UK news sites for Q1 campaign", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news"] + } + ], + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 85, "max_value": 100 } + ] + }, + "created_at": "2026-01-03T16:30:00Z", + "updated_at": "2026-01-03T17:00:00Z", + "property_count": 847 + } +} +``` + +--- + +## list_property_lists + +List all property lists accessible to the authenticated principal. + +### Request + +```json +{ + "tool": "list_property_lists", + "arguments": { + "name_contains": "UK", + "max_results": 50 + } +} +``` + +### Response + +```json +{ + "message": "Found 3 property lists matching 'UK'.", + "context_id": "ctx-gov-list-list-123", + "lists": [ + { + "list_id": "pl_abc123", + "name": "UK Premium News Q1", + "description": "High-quality UK news sites for Q1 campaign", + "created_at": "2026-01-03T16:30:00Z", + "updated_at": "2026-01-03T17:00:00Z", + "property_count": 847 + }, + { + "list_id": "pl_def456", + "name": "UK Sports Sites", + "description": "UK sports content for sponsorship", + "created_at": "2026-01-02T10:00:00Z", + "updated_at": "2026-01-02T10:00:00Z", + "property_count": 156 + } + ], + "total_count": 3, + "returned_count": 3, + "pagination": { + "has_more": false + } +} +``` + +--- + +## delete_property_list + +Delete a property list. + +### Request + +```json +{ + "tool": "delete_property_list", + "arguments": { + "list_id": "pl_abc123" + } +} +``` + +### Response + +```json +{ + "message": "Deleted property list 'UK Premium News Q1'.", + "context_id": "ctx-gov-list-del-123", + "deleted": true, + "list_id": "pl_abc123" +} +``` + +--- + +## Integration with Other Tasks + +### Using Lists in score_properties + +Reference a property list instead of passing properties inline: + +```json +{ + "tool": "score_properties", + "arguments": { + "property_list_ref": { + "agent_url": "https://governance.example.com", + "list_id": "pl_abc123" + }, + "scoring_context": { + "jurisdiction": "GDPR" + } + } +} +``` + +### Using Lists in Media Buys + +Pass property lists to media buy creation: + +```json +{ + "tool": "create_media_buy", + "arguments": { + "packages": [{ + "property_list_ref": { + "agent_url": "https://governance.example.com", + "list_id": "pl_abc123" + } + }] + } +} +``` + +--- + +## Error Codes + +| Code | Description | +|------|-------------| +| `LIST_NOT_FOUND` | Property list ID doesn't exist | +| `LIST_ACCESS_DENIED` | Principal doesn't have access to this list | +| `INVALID_FILTER` | Filter configuration is invalid | +| `LIST_NAME_EXISTS` | A list with this name already exists | + +## Caching and Refresh + +The `get_property_list` response includes caching guidance: + +| Field | Description | +|-------|-------------| +| `resolved_at` | When filters were applied and properties resolved | +| `cache_valid_until` | When consumers should re-fetch the list | + +**Typical flow for orchestrators/sellers:** + +```python +# Initial setup +response = governance_agent.get_property_list(list_id, resolve=True) +local_cache = build_property_lookup(response.properties) +cache_expiry = response.cache_valid_until + +# Periodic refresh (background job) +if now() >= cache_expiry: + response = governance_agent.get_property_list(list_id, resolve=True) + local_cache = build_property_lookup(response.properties) + cache_expiry = response.cache_valid_until + +# Bid time (no external calls) +def should_bid(property_domain): + return property_domain in local_cache +``` + +## Usage Notes + +1. **Setup time only**: Governance agents are not in the real-time bid path; resolve lists during campaign setup +2. **Local caching**: Orchestrators/sellers must cache resolved properties locally for bid-time decisions +3. **Refresh on schedule**: Re-fetch lists based on `cache_valid_until` (typically every 1-24 hours) +4. **Dynamic vs Static**: Use filters-only lists when you want the agent to maintain the property set; use base_properties when you need explicit control +5. **Pagination**: Large lists may require multiple requests with cursor-based pagination +6. **No score leakage**: Raw scores are kept internal to governance agents; responses contain pass/fail lists, not scores diff --git a/v2.6-rc/docs/governance/property/tasks/validate_property_delivery.mdx b/v2.6-rc/docs/governance/property/tasks/validate_property_delivery.mdx new file mode 100644 index 0000000000..7f9acc8c42 --- /dev/null +++ b/v2.6-rc/docs/governance/property/tasks/validate_property_delivery.mdx @@ -0,0 +1,402 @@ +--- +sidebar_position: 4 +title: validate_property_delivery +--- + +# validate_property_delivery + + +**AdCP 3.0 Proposal** - This task is under development for AdCP 3.0. + + +Validates delivery records against a property list to determine compliance. Answers two questions: +1. **Property compliance**: Did my impressions land on properties in my list? +2. **Supply path authorization**: Was the sales agent authorized to sell that inventory? + +## Use Cases + +- **Post-campaign validation**: Verify that impressions were delivered to compliant properties +- **Supply path verification**: Confirm sales agents were authorized by publishers +- **Real-time monitoring**: Check compliance rate during campaign execution +- **Audit trails**: Generate compliance reports for regulatory or brand safety reviews + +## Request + +```json +{ + "$schema": "/schemas/property/validate-property-delivery-request.json", + "list_id": "pl_abc123", + "records": [ + { + "identifier": { "type": "domain", "value": "www.nytimes.com" }, + "impressions": 103 + }, + { + "identifier": { "type": "domain", "value": "sketchy-site.example" }, + "impressions": 47 + }, + { + "identifier": { "type": "android_package", "value": "com.unknown.app" }, + "impressions": 25 + } + ], + "include_compliant": false +} +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `list_id` | string | Yes | ID of the property list to validate against | +| `records` | array | Yes | Delivery records to validate (1-10,000 records) | +| `records[].identifier` | object | Yes | Property identifier (`type` and `value`) | +| `records[].impressions` | integer | Yes | Number of impressions delivered | +| `records[].record_id` | string | No | Client-provided ID for correlation | +| `records[].sales_agent_url` | string | No | Sales agent URL to validate authorization against adagents.json | +| `include_compliant` | boolean | No | Include compliant records in results (default: false) | + +## Response + +```json +{ + "$schema": "/schemas/property/validate-property-delivery-response.json", + "list_id": "pl_abc123", + "summary": { + "total_records": 4, + "total_impressions": 200, + "compliant_records": 1, + "compliant_impressions": 103, + "non_compliant_records": 1, + "non_compliant_impressions": 47, + "not_covered_records": 1, + "not_covered_impressions": 25, + "unidentified_records": 1, + "unidentified_impressions": 25 + }, + "aggregate": { + "score": 68.7, + "grade": "C+", + "label": "68.7% compliant", + "methodology_url": "https://governance.example.com/methodology/compliance-scoring" + }, + "results": [ + { + "identifier": { "type": "domain", "value": "sketchy-site.example" }, + "status": "non_compliant", + "impressions": 47, + "violations": [ + { + "code": "not_in_list", + "message": "Identifier not found in resolved property list" + } + ] + }, + { + "identifier": { "type": "domain", "value": "new-site.example" }, + "status": "not_covered", + "impressions": 25 + }, + { + "identifier": { "type": "android_package", "value": "com.unknown.app" }, + "status": "unidentified", + "impressions": 25 + } + ], + "validated_at": "2026-01-04T19:00:00Z", + "list_resolved_at": "2026-01-04T12:00:00Z" +} +``` + +### Response Fields + +| Field | Type | Description | +|-------|------|-------------| +| `list_id` | string | ID of the property list validated against | +| `summary` | object | Raw counts for property compliance validation | +| `aggregate` | object | Optional computed metrics from the governance agent | +| `results` | array | Per-record validation results | +| `validated_at` | datetime | When validation was performed | +| `list_resolved_at` | datetime | Resolution timestamp of the property list used | + +### Summary Fields + +The summary provides raw counts - consumers calculate rates as needed: + +| Field | Description | +|-------|-------------| +| `total_records` | Total records validated | +| `total_impressions` | Total impressions across all records | +| `compliant_records` / `compliant_impressions` | Records/impressions in the property list | +| `non_compliant_records` / `non_compliant_impressions` | Records/impressions NOT in the property list | +| `not_covered_records` / `not_covered_impressions` | Identifier recognized but no data available | +| `unidentified_records` / `unidentified_impressions` | Identifier type not resolvable | + +### Validation Statuses + +| Status | Meaning | +|--------|---------| +| `compliant` | Identifier is in the resolved property list | +| `non_compliant` | Identifier is NOT in the resolved property list | +| `not_covered` | Identifier recognized but governance agent has no data for it | +| `unidentified` | Identifier type not resolvable by this agent | + +## Understanding not_covered vs unidentified + +These two statuses distinguish different types of "unknown" scenarios: + +**`not_covered`** - The governance agent recognized the identifier (e.g., it's a valid domain) but doesn't have data for that specific property. This happens when: +- A property is too new to be in the agent's database +- The property exists but hasn't been evaluated yet +- The agent's coverage doesn't include that property category + +**`unidentified`** - The governance agent couldn't recognize the identifier at all. This happens when: +- Client-side detection failed to capture the property +- The identifier type isn't supported (e.g., agent handles domains but received an app ID) +- The identifier value is malformed or invalid + +Both statuses should be excluded from compliance rate calculations - you cannot penalize for detection or coverage gaps. + +## Optional Aggregate Metrics + +Governance agents can optionally return computed metrics in the `aggregate` field: + +```json +"aggregate": { + "score": 68.7, + "grade": "C+", + "label": "68.7% compliant", + "methodology_url": "https://governance.example.com/methodology" +} +``` + +| Field | Description | +|-------|-------------| +| `score` | Numeric score (scale is agent-defined, typically 0-100) | +| `grade` | Letter grade or category (e.g., "A+", "B-", "Gold") | +| `label` | Human-readable summary (e.g., "85% compliant") | +| `methodology_url` | URL explaining how the aggregate was calculated | + +The `aggregate` field is optional and agent-specific. Consumers should not assume a particular format - always check `methodology_url` for interpretation. + +## Calculating Your Own Rates + +The response always includes raw counts. Calculate rates as needed: + +```python +# Compliance rate (exclude unverifiable from denominator) +unverifiable = summary.not_covered_impressions + summary.unidentified_impressions +verifiable = summary.total_impressions - unverifiable +compliance_rate = summary.compliant_impressions / verifiable if verifiable > 0 else None +``` + +In the example above: +- Compliant impressions: 103 +- Non-compliant impressions: 47 +- not_covered + unidentified impressions: 50 (excluded) +- Compliance rate: 103 / (200 - 50) = 103 / 150 = 68.7% + +## Violation Codes + +| Code | Description | +|------|-------------| +| `not_in_list` | Identifier not found in the resolved property list | +| `excluded` | Identifier explicitly excluded via `exclude_identifiers` filter | +| `country_mismatch` | Property lacks feature data for required countries | +| `channel_mismatch` | Property doesn't support required channels | +| `feature_failed` | Property failed a `feature_requirements` filter | + +### Feature Violation Details + +When a property fails a feature requirement, the violation includes the feature details: + +```json +{ + "identifier": { "type": "domain", "value": "low-quality-site.example" }, + "status": "non_compliant", + "impressions": 150, + "violations": [ + { + "code": "feature_failed", + "message": "Property failed consent_quality requirement (min: 85)", + "feature_id": "consent_quality", + "requirement": { + "min_value": 85 + } + } + ] +} +``` + +The `feature_id` and `requirement` fields are optional - they're included when the violation is due to a feature requirement filter. + +## Supply Path Authorization + +When `sales_agent_url` is provided in delivery records, the governance agent validates that the sales agent is authorized to sell the property by checking the publisher's `adagents.json`. + +### Request with Authorization + +```json +{ + "list_id": "pl_abc123", + "records": [ + { + "identifier": { "type": "domain", "value": "www.nytimes.com" }, + "impressions": 103, + "sales_agent_url": "https://legitimate-ssp.example.com" + }, + { + "identifier": { "type": "domain", "value": "www.nytimes.com" }, + "impressions": 50, + "sales_agent_url": "https://unauthorized-reseller.example.com" + } + ] +} +``` + +### Response with Authorization + +When authorization is validated, each result includes an `authorization` field: + +```json +{ + "list_id": "pl_abc123", + "summary": { + "total_records": 2, + "total_impressions": 153, + "compliant_records": 2, + "compliant_impressions": 153, + "non_compliant_records": 0, + "non_compliant_impressions": 0, + "unknown_records": 0, + "unknown_impressions": 0 + }, + "authorization_summary": { + "records_checked": 2, + "impressions_checked": 153, + "authorized_records": 1, + "authorized_impressions": 103, + "unauthorized_records": 1, + "unauthorized_impressions": 50, + "unknown_records": 0, + "unknown_impressions": 0 + }, + "results": [ + { + "identifier": { "type": "domain", "value": "www.nytimes.com" }, + "status": "compliant", + "impressions": 50, + "authorization": { + "status": "unauthorized", + "publisher_domain": "nytimes.com", + "sales_agent_url": "https://unauthorized-reseller.example.com", + "violation": { + "code": "agent_not_authorized", + "message": "Sales agent not listed in nytimes.com/.well-known/adagents.json" + } + } + } + ], + "validated_at": "2026-01-04T19:00:00Z" +} +``` + +### Authorization Statuses + +| Status | Meaning | Included in authorization_rate? | +|--------|---------|--------------------------------| +| `authorized` | Sales agent is listed in publisher's adagents.json | Yes (numerator) | +| `unauthorized` | Sales agent is NOT listed in publisher's adagents.json | Yes (denominator only) | +| `unknown` | Could not fetch or parse adagents.json | No (excluded) | + +### Authorization Violation Codes + +| Code | Description | +|------|-------------| +| `agent_not_authorized` | Sales agent URL not found in publisher's authorized_agents list | +| `adagents_not_found` | Publisher's adagents.json could not be fetched (404, timeout, etc.) | +| `adagents_invalid` | Publisher's adagents.json exists but is malformed | +| `property_not_declared` | Property identifier not declared in publisher's adagents.json | + +### Two Independent Checks + +Property compliance and authorization are **independent checks**. A record can be: + +| Property Status | Authorization Status | Meaning | +|-----------------|---------------------|---------| +| compliant | authorized | Fully valid - property in list, sold by authorized agent | +| compliant | unauthorized | Property is approved but sold by unauthorized reseller | +| non_compliant | authorized | Authorized agent sold property outside your list | +| non_compliant | unauthorized | Neither property nor agent validated | + +Both checks use the same "unknown excludes from rate" pattern - you cannot penalize for detection gaps. + +## Best Practices + +### Batch Validation + +For large-scale validation, batch records up to the 10,000 limit: + +```python +def validate_delivery_batch(records, list_id, governance_agent): + """Validate delivery records in batches.""" + batch_size = 10000 + all_results = [] + + for i in range(0, len(records), batch_size): + batch = records[i:i + batch_size] + response = governance_agent.validate_property_delivery( + list_id=list_id, + records=batch + ) + all_results.extend(response.results) + + return all_results +``` + +### Sampling Strategy + +For real-time monitoring during campaign execution, validate a statistical sample rather than all records: + +```python +import random + +def sample_and_validate(records, sample_size=1000): + """Validate a random sample for real-time monitoring.""" + sample = random.sample(records, min(sample_size, len(records))) + return governance_agent.validate_property_delivery( + list_id=list_id, + records=sample + ) +``` + +### Handling Unknown Records + +Track unknown rates separately to identify detection gaps: + +```python +def analyze_validation(response): + """Analyze validation results with unknown handling.""" + summary = response.summary + + # Core compliance metric + compliance_rate = summary.compliance_rate + + # Detection quality metric + unknown_rate = summary.unknown_impressions / summary.total_impressions + + if unknown_rate > 0.1: + print(f"Warning: {unknown_rate:.1%} of impressions unresolvable") + + return { + "compliance_rate": compliance_rate, + "unknown_rate": unknown_rate, + "non_compliant_impressions": summary.non_compliant_impressions + } +``` + +## Related Tasks + +- [create_property_list](/docs/governance/property/tasks/property_lists#create_property_list) - Create the list to validate against +- [get_property_list](/docs/governance/property/tasks/property_lists#get_property_list) - Retrieve current list membership +- [list_property_features](/docs/governance/property/tasks/list_property_features) - Discover available filter features diff --git a/v2.6-rc/docs/intro.mdx b/v2.6-rc/docs/intro.mdx index fa5dd7389a..a2c45f6fa3 100644 --- a/v2.6-rc/docs/intro.mdx +++ b/v2.6-rc/docs/intro.mdx @@ -1,7 +1,7 @@ --- sidebar_position: 1 title: Getting Started -description: AdCP is an open standard for advertising automation built on Model Context Protocol (MCP). Learn how to integrate AI-powered advertising workflows. +description: AdCP is an open standard for advertising automation that works over MCP and A2A protocols. Learn how to integrate AI-powered advertising workflows. keywords: [advertising automation protocol, programmatic advertising API, MCP advertising integration, AI advertising workflows, unified advertising platform API] --- @@ -12,17 +12,23 @@ keywords: [advertising automation protocol, programmatic advertising API, MCP ad Asset discovery release featuring unified `assets` field in format schema for discovering both required and optional assets. Buyers and AI agents can now see ALL assets a format supports. [See what's new →](/docs/reference/release-notes) -Welcome to the Ad Context Protocol (AdCP) documentation. AdCP is an **open standard for advertising automation** that enables AI assistants to interact with advertising platforms through unified, standardized interfaces. +Welcome to the Ad Context Protocol (AdCP) documentation—the open standard for agentic advertising. -## What is Ad Context Protocol? +## The Opportunity -Ad Context Protocol (AdCP) is an **open standard for advertising automation** that enables AI-powered programmatic advertising workflows through: +RTB unlocked programmatic. AdCP unlocks the rest. -- **Unified Advertising API**: Single interface for all advertising platforms -- **AI-Powered Automation**: Built on Model Context Protocol (MCP) for seamless AI integration -- **Multi-Protocol Support**: Access through MCP, A2A, or future protocols +90% of ad spend never touches RTB—it lives in walled gardens, direct deals, and premium inventory. Execution costs limit how many media partners advertisers can work with. The opportunity isn't optimizing existing platforms better—it's expanding to more partners without scaling headcount. + +AI agents collapse this complexity cost. AdCP gives them a standard way to buy media, build creatives, and activate audiences across any platform. + +## What is AdCP? + +AdCP is an **open standard for advertising automation** that enables AI agents to interact with advertising platforms through unified interfaces: + +- **One Protocol**: Single interface for all advertising platforms +- **AI-Native**: Works over MCP and A2A protocols for seamless agent integration - **Platform Agnostic**: Works with any compatible advertising platform -- **Programmatic Advertising Made Simple**: Standardized workflows across all ad tech AdCP uses a task-first architecture where core advertising tasks (like creating media buys or discovering signals) can be accessed through multiple protocols: - **MCP (Model Context Protocol)**: For direct AI assistant integration @@ -232,13 +238,15 @@ Generate and optimize creative assets using AI-powered agents. ## For Platform Providers -If you operate a signal platform, DSP, or ad tech solution: +AI is buying ads. Make sure it can buy yours. -1. [Review the Protocol Specifications](/docs/signals/specification) +If you operate a signal platform, DSP, or ad tech solution, AdCP lets AI agents discover and purchase your inventory. [Review the Protocol Specifications](/docs/signals/specification) to get started. ## For Advertisers & Agencies -If you want to use AdCP with your AI assistant: +AI that sells products and builds brands. + +AdCP-enabled AI assistants can work across all your media partners through a single interface: 1. Check if your platforms support AdCP 2. Configure your AI assistant with AdCP-enabled platforms diff --git a/v2.6-rc/docs/media-buy/advanced-topics/principals-and-security.mdx b/v2.6-rc/docs/media-buy/advanced-topics/principals-and-security.mdx index 913944f3f8..abe747a6ca 100644 --- a/v2.6-rc/docs/media-buy/advanced-topics/principals-and-security.mdx +++ b/v2.6-rc/docs/media-buy/advanced-topics/principals-and-security.mdx @@ -3,80 +3,52 @@ title: Principals & Security --- -A critical concept in AdCP is the **Principal**. A Principal represents a distinct client or buyer. The protocol is designed to be multi-tenant, and security is enforced through bearer token authentication. +A **Principal** represents a distinct client or buyer in AdCP. Sales agents use principals to identify who is making requests and enforce data isolation between different buyers. ## Authentication -All MCP requests must be authenticated using a bearer token. The client must include an `x-adcp-auth` header with each request: +All requests must be authenticated using a bearer token in the standard `Authorization` header: -`x-adcp-auth: ` +``` +Authorization: Bearer +``` -The server validates this token and associates it with both a specific `tenant_id` and `principal_id`. All subsequent operations within that request are scoped to that authenticated tenant and principal. +The server validates this token and associates it with a specific `principal_id`. All subsequent operations within that request are scoped to that authenticated principal. + +See [Authentication](/docs/reference/authentication) for details on obtaining credentials and authentication methods. ### The Principal Model -On the server, a Principal is defined by: -- **`principal_id`** (string): A unique identifier for the client (e.g., `"purina"`). -- **`platform_mappings`** (dict): A JSON object that maps the `principal_id` to identifiers in various ad serving platforms (e.g., `{"gam_advertiser_id": 12345}`). +A Principal is defined by: +- **`principal_id`** (string): A unique identifier for the client (e.g., `"acme_corp"`). +- **`platform_mappings`** (object): Maps the `principal_id` to identifiers in ad serving platforms (e.g., `{"gam_advertiser_id": 12345}`). ## Data Isolation -Authentication provides the foundation for strict data isolation. The server **MUST** enforce the following rules: +Authentication provides the foundation for strict data isolation. Sales agents **MUST** enforce the following rules: -1. When an object like a `MediaBuy` is created, it **MUST** be permanently associated with the `principal_id` from the authenticated request context. -2. For any subsequent request to read or modify that object, the server **MUST** verify that the `principal_id` from the new request's context matches the `principal_id` stored with the object. -3. If the IDs do not match, the server **MUST** return a permission denied error. +1. When an object like a `MediaBuy` is created, it **MUST** be permanently associated with the `principal_id` from the authenticated request context. +2. For any subsequent request to read or modify that object, the server **MUST** verify that the `principal_id` from the request matches the `principal_id` stored with the object. +3. If the IDs do not match, the server **MUST** return a permission denied error. This model ensures that one principal can never view or modify another principal's data, as they will not possess the correct bearer token to do so. Passing a `principal_id` in the request body is not required or respected; the identity is based solely on the validated token. -## Multi-Tenant Architecture - -AdCP supports full multi-tenant deployment, allowing a single instance to serve multiple publishers: - -### Tenant Model - -Each tenant represents a publisher with: -- **`tenant_id`**: Unique identifier for the publisher -- **`subdomain`**: Optional subdomain for routing (e.g., `sports.example.com`) -- **`config`**: JSON configuration including adapter settings, features, and limits -- **`admin_token`**: Special token for administrative operations - -### Tenant Isolation - -1. **Data Isolation**: All data (principals, products, media buys, creatives) is scoped by `tenant_id` -2. **Configuration Isolation**: Each tenant has independent adapter configuration -3. **Token Namespace**: Authentication tokens are unique within each tenant - -### Admin Operations - -Some tools are restricted to admin users with the tenant's admin token: -- `review_pending_creatives`: Approve/reject creative submissions -- `list_human_tasks`: View manual approval queue -- `complete_human_task`: Process manual approvals -- `get_all_media_buy_delivery`: View all media buys across principals +## Security Requirements -## Security Boundaries +### Required Security Measures -### Adapter Security +Sales agent implementations **MUST**: -Each ad server adapter enforces its own security perimeter: -- **Read vs Write**: Some adapters may have read-only access -- **Scope Limitations**: Access may be limited to specific accounts/networks -- **API Quotas**: Platform-specific rate limits and quotas +- Validate bearer tokens on every authenticated request +- Enforce principal-based data isolation +- Use TLS for all communications +- Log authentication failures for security monitoring -### Audit Logging +### Recommended Security Measures -All operations are logged to the database with: -- Timestamp -- Principal and tenant context -- Operation type and parameters -- Success/failure status -- Security-relevant events (auth failures, permission denials) -- Full request/response details for compliance +Sales agent implementations **SHOULD**: -The audit logging system provides: -- **Database Persistence**: All logs stored in `audit_logs` table with tenant isolation -- **File Backup**: Redundant file-based logging for disaster recovery -- **Real-time Monitoring**: Operations dashboard shows audit trail with filtering -- **Security Alerts**: Highlights authentication failures and permission violations -- **Compliance Ready**: Full audit trail for regulatory requirements +- Implement rate limiting per principal +- Support token expiration and refresh +- Provide audit logging for compliance +- Support IP allowlisting for high-security principals diff --git a/v2.6-rc/docs/media-buy/capability-discovery/index.mdx b/v2.6-rc/docs/media-buy/capability-discovery/index.mdx index 214806a992..297984b4c1 100644 --- a/v2.6-rc/docs/media-buy/capability-discovery/index.mdx +++ b/v2.6-rc/docs/media-buy/capability-discovery/index.mdx @@ -21,7 +21,7 @@ Learn how sales agents can support standard creative formats through the referen - Leverage the Standard Creative Agent for standard formats - Work with publisher-specific creative agents for custom formats -### [Authorized Properties](/docs/governance/property/authorized-properties) 🔐 +### [Understanding Authorization](/docs/governance/property/authorized-properties) 🔐 Learn how AdCP prevents unauthorized resale and ensures sales agents are legitimate. Understand: - The problem of unauthorized resale in digital advertising diff --git a/v2.6-rc/docs/media-buy/index.mdx b/v2.6-rc/docs/media-buy/index.mdx index fcaed419b0..5afaa30219 100644 --- a/v2.6-rc/docs/media-buy/index.mdx +++ b/v2.6-rc/docs/media-buy/index.mdx @@ -5,7 +5,9 @@ keywords: [media buy protocol, advertising automation, AI advertising workflows, --- -The Media Buy protocol is AdCP's core advertising automation interface, providing 8 standardized tasks for managing the complete advertising lifecycle - from inventory discovery through campaign optimization. +AI is buying ads. The Media Buy protocol ensures it can buy yours. + +This is AdCP's core advertising automation interface, providing 8 standardized tasks for managing the complete advertising lifecycle—from inventory discovery through campaign optimization. ## Protocol Access @@ -232,7 +234,7 @@ Choose your path based on your role and needs: 4. **Explore [Optimization & Reporting](/docs/media-buy/media-buys/optimization-reporting)** - Learn performance management ### **For Publishers/Sales Agents** -1. **Learn [Authorized Properties](/docs/governance/property/authorized-properties)** - Understand authorization requirements +1. **Learn [Understanding Authorization](/docs/governance/property/authorized-properties)** - Understand authorization requirements 2. **Review [Creative Formats](/docs/creative/formats)** - See supported creative specifications 3. **Study [Advanced Topics](/docs/media-buy/advanced-topics/)** - Deep dive into technical implementation diff --git a/v2.6-rc/docs/media-buy/task-reference/create_media_buy.mdx b/v2.6-rc/docs/media-buy/task-reference/create_media_buy.mdx index f5dfdeae9b..80001c91ba 100644 --- a/v2.6-rc/docs/media-buy/task-reference/create_media_buy.mdx +++ b/v2.6-rc/docs/media-buy/task-reference/create_media_buy.mdx @@ -177,6 +177,8 @@ npx adcp \ | `pricing_option_id` | string | Yes | Pricing option ID from product's `pricing_options` array | | `format_ids` | FormatID[] | Yes | Format IDs that will be used - must be supported by product | | `budget` | number | Yes | Budget in currency specified by pricing option | +| `impressions` | number | No | Impression goal for this package | +| `paused` | boolean | No | Create package in paused state (default: `false`) | | `pacing` | string | No | `"even"` (default), `"asap"`, or `"front_loaded"` | | `bid_price` | number | No | Bid price for auction pricing (required when `is_fixed` is false) | | `targeting_overlay` | TargetingOverlay | No | Additional targeting criteria (see [Targeting](/docs/media-buy/advanced-topics/targeting)) | diff --git a/v2.6-rc/docs/media-buy/task-reference/get_products.mdx b/v2.6-rc/docs/media-buy/task-reference/get_products.mdx index 9a877d146e..fa00a940b7 100644 --- a/v2.6-rc/docs/media-buy/task-reference/get_products.mdx +++ b/v2.6-rc/docs/media-buy/task-reference/get_products.mdx @@ -129,6 +129,7 @@ asyncio.run(discover_with_filters()) | `brief` | string | No | Natural language description of campaign requirements | | `brand_manifest` | BrandManifest \| string | No | Brand information (inline object or URL). See [Brand Manifest](/docs/creative/brand-manifest) | | `filters` | Filters | No | Structured filters (see below) | +| `property_list` | PropertyListRef | No | [AdCP 3.0] Reference to a property list for filtering. See [Property Lists](/docs/governance/property/tasks/property_lists) | ### Filters Object @@ -172,6 +173,12 @@ Returns an array of `products`, each containing: | `pricing_options` | PricingOption[] | Available pricing models (CPM, CPCV, etc.) | | `brief_relevance` | string | Why this product matches the brief (when brief provided) | +### Response Metadata + +| Field | Type | Description | +|-------|------|-------------| +| `property_list_applied` | boolean | [AdCP 3.0] `true` if the agent filtered products based on the provided `property_list`. Absent or `false` if not provided or not supported. | + **See schema for complete field list**: [`get-products-response.json`](https://adcontextprotocol.org/schemas/v2/media-buy/get-products-response.json) ## Common Scenarios @@ -476,6 +483,80 @@ asyncio.run(discover_standard_formats()) +### Property List Filtering + + +**AdCP 3.0** - Property list filtering requires governance agent support. + + +Filter products to only those available on properties in your approved list: + + + +```javascript JavaScript +import { testAgent } from '@adcp/client/testing'; + +// Filter products by property list from governance agent +const result = await testAgent.getProducts({ + brief: 'Brand-safe inventory for family brand', + brand_manifest: { + name: 'Nike', + url: 'https://nike.com' + }, + property_list: { + agent_url: 'https://governance.example.com', + list_id: 'pl_brand_safe_2024' + } +}); + +if (result.success && result.data) { + // Check if filtering was actually applied + if (result.data.property_list_applied) { + console.log(`Found ${result.data.products.length} products on approved properties`); + } else { + console.log('Agent does not support property list filtering'); + console.log(`Found ${result.data.products.length} products (unfiltered)`); + } +} +``` + +```python Python +import asyncio +from adcp import test_agent + +async def discover_with_property_list(): + # Filter products by property list from governance agent + result = await test_agent.simple.get_products( + brief='Brand-safe inventory for family brand', + brand_manifest={ + 'name': 'Nike', + 'url': 'https://nike.com' + }, + property_list={ + 'agent_url': 'https://governance.example.com', + 'list_id': 'pl_brand_safe_2024' + } + ) + + # Check if filtering was actually applied + if result.get('property_list_applied'): + print(f"Found {len(result['products'])} products on approved properties") + else: + print("Agent does not support property list filtering") + print(f"Found {len(result['products'])} products (unfiltered)") + +asyncio.run(discover_with_property_list()) +``` + + + +**Note**: If `property_list_applied` is absent or `false`, the sales agent did not filter products. This can happen if: +- The agent doesn't support property governance features +- The agent couldn't access the property list +- The property list had no effect on the available inventory + +See [Property Governance](/docs/governance/property/specification) for more on property lists. + ## Error Handling | Error Code | Description | Resolution | diff --git a/v2.6-rc/docs/media-buy/task-reference/update_media_buy.mdx b/v2.6-rc/docs/media-buy/task-reference/update_media_buy.mdx index 9524bebb37..237b6847e7 100644 --- a/v2.6-rc/docs/media-buy/task-reference/update_media_buy.mdx +++ b/v2.6-rc/docs/media-buy/task-reference/update_media_buy.mdx @@ -162,6 +162,7 @@ asyncio.run(create_and_pause_campaign()) | `buyer_ref` | string | Your reference for the package to update | | `paused` | boolean | Pause/resume specific package (`true` = paused, `false` = active) | | `budget` | number | Updated budget allocation | +| `impressions` | number | Updated impression goal for this package | | `pacing` | string | Updated pacing strategy | | `bid_price` | number | Updated bid price (auction products only) | | `targeting_overlay` | TargetingOverlay | Updated targeting restrictions | diff --git a/v2.6-rc/docs/protocols/a2a-guide.mdx b/v2.6-rc/docs/protocols/a2a-guide.mdx index 8bfe9b4f54..f66867dc49 100644 --- a/v2.6-rc/docs/protocols/a2a-guide.mdx +++ b/v2.6-rc/docs/protocols/a2a-guide.mdx @@ -711,32 +711,51 @@ console.log('Examples:', getProductsSkill.examples); ] } ], - "extensions": { - "adcp": { - "adcp_version": "2.4.0", - "protocols_supported": ["media_buy"] + "extensions": [ + { + "uri": "https://adcontextprotocol.org/extensions/adcp", + "description": "AdCP media buying protocol support", + "required": false, + "params": { + "adcp_version": "2.6.0", + "protocols_supported": ["media_buy"], + "extensions_supported": ["sustainability"] + } } - } + ] } ``` ### AdCP Extension -**Recommended**: Include the `extensions.adcp` field in your agent card to declare AdCP support programmatically. +**Recommended**: Include the AdCP extension in your agent card's `extensions` array to declare AdCP support programmatically. + +The A2A protocol uses an `extensions` array where each extension has: +- **`uri`**: Extension identifier (use `https://adcontextprotocol.org/extensions/adcp`) +- **`description`**: Human-readable description of how you use AdCP +- **`required`**: Whether clients must support this extension (typically `false` for AdCP) +- **`params`**: AdCP-specific configuration (see schema below) ```javascript // Check if agent supports AdCP const agentCard = await fetch('https://sales.example.com/.well-known/agent.json') .then(r => r.json()); -if (agentCard.extensions?.adcp) { - console.log('AdCP Version:', agentCard.extensions.adcp.adcp_version); - console.log('Supported domains:', agentCard.extensions.adcp.protocols_supported); +// Find the AdCP extension in the extensions array +const adcpExt = agentCard.extensions?.find( + ext => ext.uri === 'https://adcontextprotocol.org/extensions/adcp' +); + +if (adcpExt) { + console.log('AdCP Version:', adcpExt.params.adcp_version); + console.log('Supported domains:', adcpExt.params.protocols_supported); // ["media_buy", "creative", "signals"] + console.log('Typed extensions:', adcpExt.params.extensions_supported); + // ["sustainability"] } ``` -**Extension Structure**: See the [AdCP extension schema](https://adcontextprotocol.org/schemas/v2/protocols/adcp-extension.json) for complete specification. +**Extension Params Schema**: See the [AdCP extension schema](https://adcontextprotocol.org/schemas/v2/protocols/adcp-extension.json) for the complete `params` specification. **Benefits**: - Clients can discover AdCP capabilities without making test calls diff --git a/v2.6-rc/docs/protocols/mcp-guide.mdx b/v2.6-rc/docs/protocols/mcp-guide.mdx index c3303454dd..a8581b776e 100644 --- a/v2.6-rc/docs/protocols/mcp-guide.mdx +++ b/v2.6-rc/docs/protocols/mcp-guide.mdx @@ -632,22 +632,60 @@ const adcpTools = tools.filter(t => t.name.startsWith('adcp_') || ['get_products', 'create_media_buy'].includes(t.name)); ``` -### AdCP Extension (Future) +### AdCP Extension via MCP Server Card -**Status**: MCP server cards are expected in a future MCP release. When available, AdCP servers will include the AdCP extension. +MCP servers can declare AdCP support via a server card at `/.well-known/mcp.json` (or `/.well-known/server.json`). AdCP-specific metadata goes in the `_meta` field using the `adcontextprotocol.org` namespace. ```json { - "extensions": { - "adcp": { - "adcp_version": "2.4.0", - "protocols_supported": ["media_buy", "creative", "signals"] + "name": "io.adcontextprotocol/media-buy-agent", + "version": "1.0.0", + "title": "AdCP Media Buy Agent", + "description": "AI-powered media buying agent implementing AdCP", + "tools": [ + { "name": "get_products" }, + { "name": "create_media_buy" }, + { "name": "list_creative_formats" } + ], + "_meta": { + "adcontextprotocol.org": { + "adcp_version": "2.6.0", + "protocols_supported": ["media_buy"], + "extensions_supported": ["sustainability"] } } } ``` -This will allow clients to programmatically discover which AdCP version and protocol domains an MCP server implements. See the [AdCP extension schema](https://adcontextprotocol.org/schemas/v2/protocols/adcp-extension.json) for specification details. +**Discovering AdCP support:** + +```javascript +// Check both possible locations for MCP server card +const serverCard = await fetch('https://sales.example.com/.well-known/mcp.json') + .then(r => r.ok ? r.json() : null) + .catch(() => null) + || await fetch('https://sales.example.com/.well-known/server.json') + .then(r => r.json()); + +// Check for AdCP metadata +const adcpMeta = serverCard?._meta?.['adcontextprotocol.org']; + +if (adcpMeta) { + console.log('AdCP Version:', adcpMeta.adcp_version); + console.log('Supported domains:', adcpMeta.protocols_supported); + // ["media_buy", "creative", "signals"] + console.log('Typed extensions:', adcpMeta.extensions_supported); + // ["sustainability"] +} +``` + +**Benefits:** +- Clients can discover AdCP capabilities without making test calls +- Declare which protocol domains you implement (media_buy, creative, signals) +- Declare which [typed extensions](/docs/reference/extensions-and-context#typed-extensions) you support +- Enable compatibility checks based on version + +**Note:** The `_meta` field uses reverse DNS namespacing per the [MCP server.json spec](https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/generic-server-json.md). AdCP servers should support both `/.well-known/mcp.json` and `/.well-known/server.json` locations. ### Parameter Validation ```javascript diff --git a/v2.6-rc/docs/reference/authentication.mdx b/v2.6-rc/docs/reference/authentication.mdx index 02247e0d7b..0f1e4aafa4 100644 --- a/v2.6-rc/docs/reference/authentication.mdx +++ b/v2.6-rc/docs/reference/authentication.mdx @@ -38,36 +38,31 @@ These operations require valid credentials: **Rationale**: These operations involve financial commitments, access to proprietary data, or modifications to active campaigns. -## Authentication Methods +## Authentication Method -AdCP supports multiple authentication methods. Implementations must support at least one method: +AdCP uses Bearer token authentication, consistent with the MCP specification: -### JWT Bearer Token -```http -Authorization: Bearer ``` +Authorization: Bearer +``` + +Tokens may be: +- **Opaque tokens**: Server-validated strings mapped to principals +- **JWT tokens**: Self-contained tokens with embedded claims + +### JWT Token Claims + +When using JWT tokens, include these standard claims: -JWT tokens must include standard claims: ```json { "sub": "principal_123", "exp": 1706745600, - "iat": 1706742000, - "permissions": { - "products": ["read"], - "media_buys": ["read", "write"], - "creatives": ["read", "write"], - "reports": ["read"] - } + "iat": 1706742000 } ``` -### API Key -```http -X-API-Key: -``` - -API keys are mapped to principals and their associated permissions. +Sales agents may require additional claims for authorization. ## Principal Model @@ -85,26 +80,85 @@ interface Principal { type Permission = 'read' | 'write' | 'delete' | 'approve'; ``` -## Required Headers by Protocol +## Protocol Configuration + +Both MCP and A2A protocols use the same authentication header. Configure your client with: -### MCP ```json { - "headers": { - "Authorization": "Bearer " + "auth": { + "type": "bearer", + "token": "" } } ``` -### A2A -```json -{ - "headers": { - "Authorization": "Bearer " +The client library handles adding the `Authorization: Bearer ` header to requests. + +## MCP Client Configuration + +When using the MCP protocol, authentication is handled by the transport layer, not by adding HTTP headers manually. + +### Using MCP Client Libraries + +The recommended approach is to use an MCP client library: + + + +```typescript TypeScript +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; + +const transport = new StreamableHTTPClientTransport( + new URL('https://test-agent.adcontextprotocol.org/mcp'), + { + requestInit: { + headers: { + 'Authorization': 'Bearer YOUR_TOKEN_HERE' + } + } } -} +); + +const client = new Client({ name: 'my-client', version: '1.0.0' }); +await client.connect(transport); +``` + +```python Python +from mcp import ClientSession +from mcp.client.streamable_http import streamablehttp_client + +async with streamablehttp_client( + "https://test-agent.adcontextprotocol.org/mcp", + headers={"Authorization": "Bearer YOUR_TOKEN_HERE"} +) as (read, write): + async with ClientSession(read, write) as session: + await session.initialize() +``` + + + +### Common Mistake: Raw HTTP Headers + +A common mistake is trying to add authentication headers to raw HTTP requests: + +```http +# This won't work for MCP endpoints +GET /mcp HTTP/1.1 +Authorization: Bearer YOUR_TOKEN ``` +MCP uses a streaming protocol over HTTP. The authentication must be configured in the MCP client transport layer, which handles the protocol negotiation and message framing. + +### Troubleshooting Authentication + +If you're getting "authentication required" errors: + +1. **Verify you're using an MCP client library** - not making raw HTTP calls +2. **Check the token format** - should be passed to the transport configuration +3. **Test with the public test agent** - verify your setup works before testing custom agents +4. **Check protocol version** - ensure client and server protocol versions are compatible + ## Obtaining Credentials ### Account Setup Process @@ -189,11 +243,17 @@ Consider using aggregation platforms (like Scope3) that manage credentials and r ## Testing Authentication -Use dry run mode to test authenticated operations without affecting production: +Use the public test agent to validate your authentication setup: -```http -X-Dry-Run: true -Authorization: Bearer +```json +{ + "agent_uri": "https://test-agent.adcontextprotocol.org/mcp", + "protocol": "mcp", + "auth": { + "type": "bearer", + "token": "1v8tAhASaUYYp4odoQ1PnMpdqNaMiTrCRqYo9OJp6IQ" + } +} ``` -See [Testing & Development Guide](/docs/media-buy/advanced-topics/testing) for complete testing capabilities. \ No newline at end of file +See [Testing & Development Guide](/docs/media-buy/advanced-topics/testing) for complete testing capabilities including dry run mode and time simulation. \ No newline at end of file diff --git a/v2.6-rc/docs/reference/creative-quick-reference.mdx b/v2.6-rc/docs/reference/creative-quick-reference.mdx new file mode 100644 index 0000000000..6fe5ccb9d0 --- /dev/null +++ b/v2.6-rc/docs/reference/creative-quick-reference.mdx @@ -0,0 +1,285 @@ +--- +title: Creative Quick Reference +description: Compact reference for executing AdCP Creative Protocol tasks. Use this when interacting with creative agents via call_adcp_agent. +keywords: [adcp tasks, creative api, creative agent, build_creative, preview_creative, quick reference] +--- + +Use the `call_adcp_agent` tool to execute these tasks against any AdCP creative agent. + +## Task Overview + +| Task | Purpose | Response Time | +|------|---------|---------------| +| `list_creative_formats` | View format specifications | ~1s | +| `build_creative` | Generate or transform creatives | ~30s-5m | +| `preview_creative` | Get visual previews | ~5s | + +## Typical Workflow + +1. **Discover formats**: `list_creative_formats` to see available format specs +2. **Build creative**: `build_creative` to generate or transform a manifest +3. **Preview**: `preview_creative` to see how it renders +4. **Sync**: Use `sync_creatives` (media-buy task) to traffic the creative + +--- + +## Task Reference + +### list_creative_formats + +Discover creative formats and their specifications. + +```json +{ + "type": "video", + "asset_types": ["image", "text"] +} +``` + +**Key fields:** +- `format_ids` (array, optional): Request specific format IDs +- `type` (string, optional): Filter by type: `video`, `display`, `audio`, `dooh` +- `asset_types` (array, optional): Filter by accepted asset types +- `max_width`, `max_height` (integer, optional): Dimension constraints +- `is_responsive` (boolean, optional): Filter for responsive formats +- `name_search` (string, optional): Search formats by name + +**Response contains:** +- `formats`: Array of format definitions with `format_id`, `name`, `type`, `assets_required`, `renders` +- `creative_agents`: Optional array of other creative agents providing additional formats + +--- + +### build_creative + +Generate a creative from scratch or transform an existing creative to a different format. + +**Pure Generation (from brief):** +```json +{ + "message": "Create a banner promoting our winter sale with a warm, inviting feel", + "target_format_id": { + "agent_url": "https://creative.adcontextprotocol.org", + "id": "display_300x250_generative" + }, + "creative_manifest": { + "format_id": { + "agent_url": "https://creative.adcontextprotocol.org", + "id": "display_300x250_generative" + }, + "assets": { + "promoted_offerings": { + "brand_manifest": { + "url": "https://mybrand.com", + "name": "My Brand", + "colors": { "primary": "#FF5733" } + }, + "inline_offerings": [ + { + "name": "Winter Sale Collection", + "description": "50% off all winter items" + } + ] + } + } + } +} +``` + +**Transformation (resize/reformat):** +```json +{ + "message": "Adapt this leaderboard to a 300x250 banner", + "creative_manifest": { + "format_id": { + "agent_url": "https://creative.adcontextprotocol.org", + "id": "display_728x90" + }, + "assets": { + "banner_image": { + "asset_type": "image", + "url": "https://cdn.mybrand.com/leaderboard.png", + "width": 728, + "height": 90 + }, + "headline": { + "asset_type": "text", + "content": "Spring Sale - 30% Off" + } + } + }, + "target_format_id": { + "agent_url": "https://creative.adcontextprotocol.org", + "id": "display_300x250" + } +} +``` + +**Key fields:** +- `message` (string, optional): Natural language instructions for generation/transformation +- `creative_manifest` (object, optional): Source manifest - minimal for generation, complete for transformation +- `target_format_id` (object, required): Format to generate - `{ agent_url, id }` + +**Response contains:** +- `creative_manifest`: Complete manifest ready for `preview_creative` or `sync_creatives` + +--- + +### preview_creative + +Generate visual previews of creative manifests. + +**Single preview:** +```json +{ + "request_type": "single", + "format_id": { + "agent_url": "https://creative.adcontextprotocol.org", + "id": "display_300x250" + }, + "creative_manifest": { + "format_id": { + "agent_url": "https://creative.adcontextprotocol.org", + "id": "display_300x250" + }, + "assets": { + "banner_image": { + "asset_type": "image", + "url": "https://cdn.example.com/banner.png", + "width": 300, + "height": 250 + } + } + } +} +``` + +**With device variants:** +```json +{ + "request_type": "single", + "format_id": { "agent_url": "...", "id": "native_responsive" }, + "creative_manifest": { }, + "inputs": [ + { "name": "Desktop", "macros": { "DEVICE_TYPE": "desktop" } }, + { "name": "Mobile", "macros": { "DEVICE_TYPE": "mobile" } } + ] +} +``` + +**Batch preview (5-10x faster):** +```json +{ + "request_type": "batch", + "requests": [ + { "format_id": {}, "creative_manifest": { } }, + { "format_id": {}, "creative_manifest": { } } + ] +} +``` + +**Key fields:** +- `request_type` (string, required): `"single"` or `"batch"` +- `format_id` (object, required for single): Format identifier +- `creative_manifest` (object, required): Complete creative manifest +- `inputs` (array, optional): Generate variants with different macros/contexts +- `output_format` (string, optional): `"url"` (default) or `"html"` + +**Response contains:** +- `previews`: Array of preview objects with `preview_url` or `preview_html` +- `expires_at`: When preview URLs expire + +--- + +## Key Concepts + +### Format IDs + +All format references use structured objects: +```json +{ + "format_id": { + "agent_url": "https://creative.adcontextprotocol.org", + "id": "display_300x250" + } +} +``` + +The `agent_url` specifies the creative agent authoritative for this format. + +### Creative Manifests + +Manifests pair format specifications with actual assets: +```json +{ + "format_id": { + "agent_url": "https://creative.adcontextprotocol.org", + "id": "display_300x250" + }, + "assets": { + "banner_image": { + "asset_type": "image", + "url": "https://cdn.example.com/banner.png", + "width": 300, + "height": 250 + }, + "headline": { + "asset_type": "text", + "content": "Shop Now" + }, + "clickthrough_url": { + "asset_type": "url", + "url": "https://brand.com/sale" + } + } +} +``` + +### Asset Types + +Common asset types: +- `image`: Static images (JPEG, PNG, WebP) +- `video`: Video files (MP4, WebM) or VAST tags +- `audio`: Audio files (MP3, M4A) or DAAST tags +- `text`: Headlines, descriptions, CTAs +- `html`: HTML5 creatives or third-party tags +- `javascript`: JavaScript tags +- `url`: Tracking pixels, clickthrough URLs + +### Brand Manifest + +For generative creatives, provide brand context: +```json +{ + "brand_manifest": { + "url": "https://brand.com", + "name": "Brand Name", + "colors": { "primary": "#FF0000", "secondary": "#0000FF" } + } +} +``` + +### Generative vs Transformation + +- **Pure Generation**: Minimal manifest with `promoted_offerings` in assets. Creative agent generates all output assets from scratch. +- **Transformation**: Complete manifest with existing assets. Creative agent adapts to target format, following `message` guidance. + +--- + +## Error Handling + +Common error patterns: + +- **400 Bad Request**: Invalid manifest or format_id +- **404 Not Found**: Format not supported by this agent +- **422 Validation Error**: Manifest doesn't match format requirements + +Error responses include: +```json +{ + "error": { + "code": "INVALID_FORMAT_ID", + "message": "format_id must be a structured object with 'agent_url' and 'id' fields" + } +} +``` diff --git a/v2.6-rc/docs/reference/extensions-and-context.mdx b/v2.6-rc/docs/reference/extensions-and-context.mdx index 48484c0201..e29ec563c3 100644 --- a/v2.6-rc/docs/reference/extensions-and-context.mdx +++ b/v2.6-rc/docs/reference/extensions-and-context.mdx @@ -181,6 +181,213 @@ Without namespacing: - Impossible to support multiple platforms simultaneously - Harder to deprecate platform-specific features +## Typed Extensions + +AdCP supports formal JSON schemas for extensions, enabling type safety and validation. Typed extensions are published in the `/schemas/extensions/` directory and can be declared in agent cards. + +### How Typed Extensions Work + +Typed extensions provide a middle ground between untyped vendor extensions and core AdCP fields: + +- **Untyped extensions** (`ext.vendor.*`) - Any JSON, no validation, vendor-specific +- **Typed extensions** (`ext.sustainability.*`) - Formal schema, validated, cross-vendor +- **Core fields** - Part of AdCP specification, required for interoperability + +Typed extensions enable SDK code generation, schema validation, and cross-vendor interoperability while remaining optional. + +### Extension Schema Registry + +The registry of available typed extensions is auto-generated from extension files: +- **Registry**: `/schemas/v1/extensions/index.json` +- **Extension schemas**: `/schemas/v1/extensions/{namespace}.json` +- **Meta schema**: `/schemas/v1/extensions/extension-meta.json` + +Each versioned schema path (`/schemas/v2.5/`, `/schemas/v2.6/`) includes only extensions valid for that version, based on the `valid_from` and `valid_until` fields in each extension. + +### Extension Versioning + +Extensions are versioned independently of the AdCP specification: + +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/extensions/sustainability.json", + "title": "Sustainability Extension", + "description": "Carbon footprint and green certification data", + "valid_from": "2.5", + "valid_until": "4.0", + "docs_url": "https://adcontextprotocol.org/docs/extensions/sustainability", + "type": "object", + "properties": { + "carbon_kg_per_impression": { "type": "number" }, + "certified_green": { "type": "boolean" } + } +} +``` + +- **`valid_from`** (required): Minimum AdCP version this extension works with (e.g., `"2.5"`) +- **`valid_until`** (optional): Last AdCP version this extension works with. Omit if still valid. + +This allows extensions to be backported to earlier AdCP versions and deprecated gracefully. + +### Declaring Extension Support + +Agents declare which typed extensions they support in their discovery metadata. The location depends on the protocol: + +#### A2A Agent Cards + +A2A agents declare AdCP support in the `extensions` array at `/.well-known/agent.json`: + +```json +{ + "name": "AdCP Media Buy Agent", + "skills": [{ "name": "get_products" }, { "name": "create_media_buy" }], + "extensions": [ + { + "uri": "https://adcontextprotocol.org/extensions/adcp", + "description": "AdCP media buying protocol support", + "required": false, + "params": { + "adcp_version": "2.6.0", + "protocols_supported": ["media_buy"], + "extensions_supported": ["sustainability"] + } + } + ] +} +``` + +See the [A2A Guide](/docs/protocols/a2a-guide#adcp-extension) for complete details. + +#### MCP Server Cards + +MCP servers declare AdCP support via the `_meta` field at `/.well-known/mcp.json` or `/.well-known/server.json`: + +```json +{ + "name": "io.adcontextprotocol/media-buy-agent", + "tools": [{ "name": "get_products" }, { "name": "create_media_buy" }], + "_meta": { + "adcontextprotocol.org": { + "adcp_version": "2.6.0", + "protocols_supported": ["media_buy"], + "extensions_supported": ["sustainability"] + } + } +} +``` + +See the [MCP Guide](/docs/protocols/mcp-guide#adcp-extension-via-mcp-server-card) for complete details. + +#### Extension Params Schema + +Both protocols use the same params structure, defined in the [AdCP extension schema](https://adcontextprotocol.org/schemas/v2/protocols/adcp-extension.json): +- **`adcp_version`** (required): Semantic version of AdCP implemented (e.g., "2.6.0") +- **`protocols_supported`** (required): Array of protocol domains (media_buy, creative, signals) +- **`extensions_supported`** (optional): Array of typed extension namespaces + +When an agent declares support for an extension, clients can expect: +- The agent will accept and populate `ext.{namespace}` fields conforming to that extension's schema +- Data in `ext.{namespace}` is strongly typed and can be validated +- SDK code generation can produce type-safe interfaces for the extension + +### Using Typed Extensions + +When an agent supports a typed extension, you can include and expect data in the corresponding namespace: + +```json +{ + "product_id": "premium_ctv", + "name": "Premium CTV Package", + "ext": { + "sustainability": { + "carbon_kg_per_impression": 0.0012, + "certified_green": true + } + } +} +``` + +### Creating a Typed Extension + +To create a typed extension: + +1. **Create the extension file** at `/schemas/extensions/{namespace}.json` +2. **Follow the meta schema** defined in `/schemas/extensions/extension-meta.json` +3. **Set `valid_from`** to the minimum AdCP version your extension requires +4. **Add documentation** via the `docs_url` field + +Example extension file: + +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/extensions/sustainability.json", + "title": "Sustainability Extension", + "description": "Carbon footprint and green certification data for ad products", + "valid_from": "2.5", + "docs_url": "https://adcontextprotocol.org/docs/extensions/sustainability", + "type": "object", + "properties": { + "carbon_kg_per_impression": { + "type": "number", + "description": "Estimated CO2 emissions per impression in kilograms" + }, + "certified_green": { + "type": "boolean", + "description": "Whether the inventory is certified by a green advertising program" + }, + "certification_provider": { + "type": "string", + "description": "Name of the green certification provider" + } + } +} +``` + +The build process auto-discovers extension files and includes them in the appropriate versioned schema builds. + +### Proposing Typed Extensions + +To propose a typed extension for inclusion in AdCP: + +1. **Validate in production first** - Use vendor-namespaced extensions (`ext.yourcompany.*`) to prove value +2. **Document the schema** - Define clear property names, types, and descriptions +3. **Gather adoption evidence** - Show multiple implementations or strong demand +4. **Submit an RFC** - Propose the extension with schema and use cases +5. **If accepted** - Extension is added to `/schemas/extensions/` with appropriate `valid_from` + +### Reserved Namespaces + +The following namespaces are reserved and cannot be used for typed extensions: +- `adcp`, `core`, `protocol`, `schema`, `meta`, `ext`, `context` + +These are reserved to prevent confusion with core AdCP concepts. + +### Extension Deprecation Policy + +#### When Extensions May Be Deprecated + +1. **Superseded** - A better extension exists with a documented migration path +2. **Promoted to core** - The extension becomes a core AdCP field +3. **Low adoption** - Insufficient usage across implementations +4. **Incompatible** - Breaking changes in AdCP require removal + +#### Deprecation Process + +1. Set `valid_until` to current version + 1 minor release (minimum grace period) +2. Announce deprecation in CHANGELOG +3. Publish migration guide if replacement exists +4. Add deprecation warning to extension docs +5. Extension removed from builds after `valid_until` version + +#### Emergency Deprecation + +Security issues may require immediate deprecation: +- `valid_until` set to current version +- Security advisory published +- Patch release issued + ## Proposing Spec Additions If your extension field represents **common ad tech functionality** that would benefit all AdCP implementations: diff --git a/v2.6-rc/docs/reference/glossary.mdx b/v2.6-rc/docs/reference/glossary.mdx index 81859db6ad..e069d90d63 100644 --- a/v2.6-rc/docs/reference/glossary.mdx +++ b/v2.6-rc/docs/reference/glossary.mdx @@ -12,8 +12,8 @@ Classification of MCP session credentials as either "platform" (aggregator) or " **Activation** The process of making a signal available for targeting on a specific platform and seat. -**Ad Context Protocol (AdCP)** -An open standard based on Model Context Protocol (MCP) that enables AI-powered advertising workflows through natural language interfaces. +**Ad Context Protocol (AdCP)** +An open standard for AI-powered advertising workflows. AdCP defines domain-specific tasks and schemas that work over MCP and A2A as transports, enabling natural language interfaces for advertising operations. **Agentic eXecution Engine (AXE)** The real-time execution layer that sits between orchestrators and decisioning platforms, handling dynamic audience targeting, brand safety enforcement, frequency management, and first-party data activation at impression time. See [AXE documentation](/docs/media-buy/advanced-topics/agentic-execution-engine) for details. diff --git a/v2.6-rc/docs/reference/media-buy-quick-reference.mdx b/v2.6-rc/docs/reference/media-buy-quick-reference.mdx new file mode 100644 index 0000000000..595bd8efca --- /dev/null +++ b/v2.6-rc/docs/reference/media-buy-quick-reference.mdx @@ -0,0 +1,321 @@ +--- +title: Media Buy Quick Reference +description: Compact reference for executing AdCP Media Buy Protocol tasks. Use this when interacting with sales agents via call_adcp_agent. +keywords: [adcp tasks, media buy api, sales agent, get_products, create_media_buy, sync_creatives, quick reference] +--- + +Use the `call_adcp_agent` tool to execute these tasks against any AdCP sales agent. + +## Task Overview + +| Task | Purpose | Response Time | +|------|---------|---------------| +| `get_products` | Discover inventory using natural language | ~60s | +| `list_authorized_properties` | See publisher properties | ~1s | +| `list_creative_formats` | View creative specifications | ~1s | +| `create_media_buy` | Create campaigns | Minutes-Days | +| `update_media_buy` | Modify campaigns | Minutes-Days | +| `sync_creatives` | Upload creative assets | Minutes-Days | +| `list_creatives` | Query creative library | ~1s | +| `get_media_buy_delivery` | Get performance data | ~60s | + +## Typical Workflow + +1. **Discover products**: `get_products` with a natural language brief +2. **Review formats**: `list_creative_formats` to understand creative requirements +3. **Create campaign**: `create_media_buy` with selected products and budget +4. **Upload creatives**: `sync_creatives` to add creative assets +5. **Monitor delivery**: `get_media_buy_delivery` to track performance + +--- + +## Task Reference + +### get_products + +Discover advertising products using natural language briefs. + +```json +{ + "brief": "Looking for premium video inventory for a tech brand targeting developers", + "brand_manifest": { + "url": "https://example.com" + }, + "filters": { + "channels": ["video", "ctv"], + "budget_range": { "min": 5000, "max": 50000 } + } +} +``` + +**Key fields:** +- `brief` (string): Natural language description of campaign requirements +- `brand_manifest` (object): Brand context - can be `{ "url": "https://..." }` or inline manifest +- `filters` (object, optional): Filter by channels, budget, delivery_type, format_types + +**Response contains:** +- `products`: Array of matching products with `product_id`, `name`, `description`, `pricing_options` +- Each product includes `format_ids` (supported creative formats) and `targeting` (available targeting) + +--- + +### list_authorized_properties + +Get the list of publisher properties this agent can sell. + +```json +{} +``` + +No parameters required. + +**Response contains:** +- `publisher_domains`: Array of domain strings the agent is authorized to sell + +--- + +### list_creative_formats + +View supported creative specifications. + +```json +{ + "format_types": ["video", "display"] +} +``` + +**Key fields:** +- `format_types` (array, optional): Filter to specific format categories + +**Response contains:** +- `formats`: Array of format specifications with dimensions, requirements, and asset schemas + +--- + +### create_media_buy + +Create an advertising campaign from selected products. + +```json +{ + "buyer_ref": "campaign-2026-q1-001", + "brand_manifest": { + "url": "https://acme.com", + "name": "Acme Corporation" + }, + "packages": [ + { + "buyer_ref": "pkg-video-001", + "product_id": "premium_video_30s", + "pricing_option_id": "cpm-standard", + "budget": 10000 + } + ], + "start_time": { + "type": "asap" + }, + "end_time": "2026-03-31T23:59:59Z" +} +``` + +**Key fields:** +- `buyer_ref` (string, required): Your unique identifier for this campaign +- `brand_manifest` (object, required): Brand identity - URL or inline manifest +- `packages` (array, required): Products to purchase, each with: + - `buyer_ref`: Your identifier for this package + - `product_id`: From `get_products` response + - `pricing_option_id`: From product's `pricing_options` + - `budget`: Amount in dollars + - `bid_price`: Required for auction pricing + - `targeting_overlay`: Additional targeting constraints + - `creative_ids` or `creatives`: Creative assignments +- `start_time` (object, required): `{ "type": "asap" }` or `{ "type": "scheduled", "datetime": "..." }` +- `end_time` (string, required): ISO 8601 datetime + +**Response contains:** +- `media_buy_id`: The created campaign identifier +- `status`: Current state (often `pending` for async approval) +- `packages`: Created packages with their IDs + +--- + +### update_media_buy + +Modify an existing campaign. + +```json +{ + "media_buy_id": "mb_abc123", + "updates": { + "budget_change": 5000, + "end_time": "2026-04-30T23:59:59Z", + "status": "paused" + } +} +``` + +**Key fields:** +- `media_buy_id` (string, required): The campaign to update +- `updates` (object): Changes to apply - budget_change, end_time, status, targeting, etc. + +--- + +### sync_creatives + +Upload and manage creative assets. + +```json +{ + "creatives": [ + { + "creative_id": "hero_video_30s", + "name": "Brand Hero Video", + "format_id": { + "agent_url": "https://creative.adcontextprotocol.org", + "id": "video_standard_30s" + }, + "assets": { + "video": { + "url": "https://cdn.example.com/hero.mp4", + "width": 1920, + "height": 1080, + "duration_ms": 30000 + } + } + } + ], + "assignments": { + "hero_video_30s": ["pkg_001", "pkg_002"] + } +} +``` + +**Key fields:** +- `creatives` (array, required): Creative assets to sync + - `creative_id`: Your unique identifier + - `format_id`: Object with `agent_url` and `id` from format specifications + - `assets`: Asset content (video, image, html, etc.) +- `assignments` (object, optional): Map creative_id to package IDs +- `dry_run` (boolean): Preview changes without applying +- `delete_missing` (boolean): Archive creatives not in this sync + +--- + +### list_creatives + +Query the creative library with filtering. + +```json +{ + "filters": { + "status": ["active"], + "format_types": ["video"] + }, + "limit": 20 +} +``` + +--- + +### get_media_buy_delivery + +Retrieve performance metrics for a campaign. + +```json +{ + "media_buy_id": "mb_abc123", + "granularity": "daily", + "date_range": { + "start": "2026-01-01", + "end": "2026-01-31" + } +} +``` + +**Response contains:** +- `delivery`: Aggregated metrics (impressions, spend, clicks, etc.) +- `by_package`: Breakdown by package +- `timeseries`: Data points over time if granularity specified + +--- + +## Key Concepts + +### Brand Manifest + +Brand context can be provided in two ways: + +**URL reference** (recommended): +```json +{ + "brand_manifest": { + "url": "https://brand.com" + } +} +``` + +**Inline manifest**: +```json +{ + "brand_manifest": { + "name": "Brand Name", + "url": "https://brand.com", + "tagline": "Brand tagline", + "colors": { "primary": "#FF0000" } + } +} +``` + +### Format IDs + +Creative format identifiers are structured objects: +```json +{ + "format_id": { + "agent_url": "https://creative.adcontextprotocol.org", + "id": "display_300x250" + } +} +``` + +The `agent_url` specifies which creative agent defines the format. Use `https://creative.adcontextprotocol.org` for standard IAB formats. + +### Pricing Options + +Products include `pricing_options` array. Each option has: +- `pricing_option_id`: Use this in `create_media_buy` +- `pricing_model`: "cpm", "cpm-auction", "flat-fee", etc. +- `price`: Base price (for fixed pricing) +- `floor`: Minimum bid (for auction) + +For auction pricing, include `bid_price` in your package. + +### Asynchronous Operations + +Operations like `create_media_buy` and `sync_creatives` may require human approval. The response includes: +- `status: "pending"` - Operation awaiting approval +- `task_id` - For tracking async progress + +Poll or use webhooks to check completion status. + +--- + +## Error Handling + +Common error patterns: + +- **400 Bad Request**: Invalid parameters - check required fields +- **401 Unauthorized**: Invalid or missing authentication token +- **404 Not Found**: Invalid product_id, media_buy_id, or creative_id +- **422 Validation Error**: Schema validation failure - check field types + +Error responses include: +```json +{ + "error": { + "code": "VALIDATION_ERROR", + "message": "budget must be greater than 0", + "field": "packages[0].budget" + } +} +``` diff --git a/v2.6-rc/docs/reference/roadmap.mdx b/v2.6-rc/docs/reference/roadmap.mdx index 35b1ec2d61..137ddeb29e 100644 --- a/v2.6-rc/docs/reference/roadmap.mdx +++ b/v2.6-rc/docs/reference/roadmap.mdx @@ -59,9 +59,9 @@ These features are in active development but may not make the 3.0 release: The following features are in the working group planning stages: -### Governance Protocol +### Governance Protocol [3.0] -A framework for multi-party approval workflows and delegated authority management. See the [Governance Protocol Working Document](https://docs.google.com/document/d/1osokTr5Xk2PyLBUHbx2jpPad0TLrQIDh2I6ZWzpHMmY/edit?tab=t.0) for details. +Property governance (property lists, filters, adagents.json authorization), brand standards (brand manifests, creative guidelines), and compliance controls. See the [Governance Protocol](/docs/governance/index) for current specification. ### Private Marketplace (PMP) Support - Deal ID integration diff --git a/v2.6-rc/docs/reference/signals-quick-reference.mdx b/v2.6-rc/docs/reference/signals-quick-reference.mdx new file mode 100644 index 0000000000..084b7bd974 --- /dev/null +++ b/v2.6-rc/docs/reference/signals-quick-reference.mdx @@ -0,0 +1,200 @@ +--- +title: Signals Quick Reference +description: Compact reference for executing AdCP Signals Protocol tasks. Use this when interacting with signal agents via call_adcp_agent. +keywords: [adcp tasks, signals api, signal agent, get_signals, activate_signal, audience targeting, quick reference] +--- + +Use the `call_adcp_agent` tool to execute these tasks against any AdCP signal agent. + +## Task Overview + +| Task | Purpose | Response Time | +|------|---------|---------------| +| `get_signals` | Discover signals using natural language | ~60s | +| `activate_signal` | Activate a signal on a platform/agent | Minutes-Hours | + +## Typical Workflow + +1. **Discover signals**: `get_signals` with a natural language description of targeting needs +2. **Review options**: Evaluate signals by coverage, pricing, and deployment status +3. **Activate if needed**: `activate_signal` for signals not yet live on your platform +4. **Use in campaigns**: Reference the activation key in your media buy targeting + +--- + +## Task Reference + +### get_signals + +Discover signals based on natural language description, with deployment status across platforms. + +```json +{ + "signal_spec": "High-income households interested in luxury goods", + "deliver_to": { + "deployments": [ + { + "type": "platform", + "platform": "the-trade-desk", + "account": "agency-123" + } + ], + "countries": ["US"] + }, + "filters": { + "max_cpm": 5.0, + "catalog_types": ["marketplace"] + }, + "max_results": 5 +} +``` + +**Key fields:** +- `signal_spec` (string, required): Natural language description of desired signals +- `deliver_to` (object, required): Where signals will be used + - `deployments` (array): Target platforms/agents with `type`, `platform`/`agent_url`, and optional `account` + - `countries` (array): ISO country codes where signals will be used +- `filters` (object, optional): Filter by `catalog_types`, `data_providers`, `max_cpm`, `min_coverage_percentage` +- `max_results` (number, optional): Limit number of results + +**Deployment types:** +```json +// DSP platform +{ "type": "platform", "platform": "the-trade-desk", "account": "agency-123" } + +// Sales agent +{ "type": "agent", "agent_url": "https://salesagent.example.com" } +``` + +**Response contains:** +- `signals`: Array of matching signals with: + - `signal_agent_segment_id`: Use this in `activate_signal` + - `name`, `description`: Human-readable signal info + - `data_provider`: Source of the signal data + - `coverage_percentage`: Reach relative to agent's population + - `deployments`: Status per platform with `is_live`, `activation_key`, `estimated_activation_duration_minutes` + - `pricing`: CPM and currency + +--- + +### activate_signal + +Activate a signal for use on a specific platform or agent. + +```json +{ + "signal_agent_segment_id": "luxury_auto_intenders", + "deployments": [ + { + "type": "platform", + "platform": "the-trade-desk", + "account": "agency-123-ttd" + } + ] +} +``` + +**Key fields:** +- `signal_agent_segment_id` (string, required): From `get_signals` response +- `deployments` (array, required): Target deployment(s) with `type`, `platform`/`agent_url`, and optional `account` + +**Response contains:** +- `deployments`: Array with activation results per target + - `activation_key`: The key to use for targeting (segment ID or key-value pair) + - `deployed_at`: ISO timestamp when activation completed + - `estimated_activation_duration_minutes`: Time remaining if async +- `errors`: Any warnings or errors encountered + +--- + +## Key Concepts + +### Deployment Targets + +Signals can be activated on two types of targets: + +**DSP Platforms:** +```json +{ + "type": "platform", + "platform": "the-trade-desk", + "account": "agency-123" +} +``` + +**Sales Agents:** +```json +{ + "type": "agent", + "agent_url": "https://wonderstruck.salesagents.com" +} +``` + +### Activation Keys + +When signals are live, the response includes an activation key for targeting: + +**Segment ID format (typical for DSPs):** +```json +{ + "type": "segment_id", + "segment_id": "ttd_segment_12345" +} +``` + +**Key-Value format (typical for sales agents):** +```json +{ + "type": "key_value", + "key": "audience_segment", + "value": "luxury_auto_intenders" +} +``` + +### Signal Types + +- **marketplace**: Licensed from data providers (CPM pricing) +- **custom**: Built for specific principal accounts +- **owned**: Private signals from your own data (no cost) + +### Coverage Percentage + +Indicates signal reach relative to the agent's population: +- 99%: Very broad signal (matches most identifiers) +- 50%: Medium signal +- 1%: Very niche signal + +### Asynchronous Operations + +Signal activation may take time. Check the response: +- `is_live: true` + `activation_key`: Ready to use immediately +- `is_live: false` + `estimated_activation_duration_minutes`: Activation in progress + +Poll or use webhooks to check completion status. + +--- + +## Error Handling + +Common error codes: + +- `SIGNAL_AGENT_SEGMENT_NOT_FOUND`: Invalid signal_agent_segment_id +- `ACTIVATION_FAILED`: Could not activate signal +- `ALREADY_ACTIVATED`: Signal already active on target +- `DEPLOYMENT_UNAUTHORIZED`: Not authorized for platform/account +- `AGENT_NOT_FOUND`: Private agent not visible to this principal +- `AGENT_ACCESS_DENIED`: Not authorized for this signal agent + +Error responses include: +```json +{ + "errors": [ + { + "code": "DEPLOYMENT_UNAUTHORIZED", + "message": "Account not authorized for this data provider", + "field": "deployment.account", + "suggestion": "Contact your account manager to enable access" + } + ] +} +``` diff --git a/v2.6-rc/docs/signals/overview.mdx b/v2.6-rc/docs/signals/overview.mdx index c6604448b7..f3388b18c0 100644 --- a/v2.6-rc/docs/signals/overview.mdx +++ b/v2.6-rc/docs/signals/overview.mdx @@ -4,7 +4,9 @@ title: Overview --- -The Signals Activation Protocol enables AI assistants to discover, activate, and manage data signals through natural language—making it practical to navigate and utilize the vast universe of available segments. +Signal providers have data. AI agents need access. The Signals Activation Protocol connects them. + +This protocol enables AI assistants to discover, activate, and manage data signals through natural language—making it practical to navigate and utilize the vast universe of available segments. ## The Challenge: Navigating Hundreds of Thousands of Segments From 98eedf2dcbdad471016137a09eb8df4887af3d64 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 22:53:44 +0000 Subject: [PATCH 19/49] Version Packages --- .changeset/account-lifecycle-stage.md | 4 - .changeset/activity-user-names.md | 4 - .changeset/addie-bot-token-fix.md | 8 - .changeset/addie-dm-no-threading.md | 4 - .changeset/addie-email-domain-linking.md | 3 - .changeset/addie-first-dm-fix.md | 3 - .changeset/addie-home-nav-fix.md | 4 - .changeset/addie-knowledge-improvements.md | 4 - .changeset/addie-newline-fix.md | 4 - .changeset/addie-news-feeds.md | 4 - .changeset/addie-role-suggestions.md | 3 - .changeset/addie-slack-endpoint.md | 18 - .changeset/addie-thread-reply-fix.md | 4 - .changeset/addie-thread-sharing.md | 3 - .changeset/addie-user-simulation.md | 4 - .changeset/afraid-ends-mate.md | 4 - .changeset/afraid-hairs-add.md | 2 - .changeset/afraid-pigs-sit.md | 8 - .changeset/all-dolls-beam.md | 2 - .changeset/all-llamas-sin.md | 2 - .changeset/allow-extra-fields.md | 7 - .changeset/anthropic-timeout.md | 4 - .changeset/better-papayas-attack.md | 2 - .changeset/big-lies-end.md | 6 - .changeset/block-gmail-corp-domain.md | 4 - .changeset/block-personal-workspace-merge.md | 2 - .changeset/blue-tables-agree.md | 2 - .changeset/bold-hornets-behave.md | 2 - .changeset/brave-emus-invite.md | 2 - .changeset/brave-forks-crash.md | 4 - .changeset/breezy-cats-own.md | 2 - .changeset/breezy-pens-kick.md | 2 - .changeset/breezy-zoos-hope.md | 2 - .changeset/bright-kids-say.md | 4 - .changeset/brown-doors-fly.md | 2 - .changeset/bumpy-carrots-change.md | 2 - .changeset/bumpy-cycles-hear.md | 2 - .changeset/bumpy-kings-follow.md | 4 - .changeset/busy-swans-judge.md | 2 - .changeset/calm-lamps-doubt.md | 2 - .changeset/chatty-tools-lead.md | 2 - .changeset/chubby-baboons-kick.md | 4 - .changeset/chubby-books-film.md | 2 - .changeset/chubby-pumas-smoke.md | 2 - .changeset/clean-ghosts-beam.md | 4 - .changeset/clean-groups-press.md | 2 - .changeset/clean-jokes-invent.md | 10 - .changeset/clean-pumas-open.md | 2 - .changeset/clear-colts-wink.md | 2 - .changeset/clever-pets-brush.md | 2 - .changeset/clickable-user-name.md | 3 - .changeset/cold-shrimps-taste.md | 2 - .changeset/common-cougars-unite.md | 2 - .changeset/common-geckos-wash.md | 2 - .changeset/cool-memes-wear.md | 2 - .changeset/crisp-moments-wear.md | 2 - .changeset/crisp-pets-tease.md | 2 - .changeset/crisp-readers-rule.md | 2 - .changeset/cuddly-pets-sort.md | 2 - .changeset/cuddly-windows-accept.md | 2 - .changeset/curvy-rice-bake.md | 2 - .changeset/cute-taxes-boil.md | 2 - .changeset/cyan-cooks-hang.md | 2 - .changeset/dashboard-user-menu.md | 2 - .changeset/david-event-mgmt-fix.md | 4 - .changeset/dirty-kiwis-cheer.md | 46 - .changeset/dirty-meteors-sell.md | 2 - .changeset/dirty-spies-burn.md | 2 - .changeset/docs-versioning.md | 3 - .changeset/dry-singers-pull.md | 3 - .changeset/dull-dogs-live.md | 4 - .changeset/dull-rooms-battle.md | 2 - .changeset/dull-singers-mate.md | 2 - .changeset/eager-eels-sneeze.md | 2 - .changeset/eager-humans-change.md | 2 - .changeset/easy-lizards-hug.md | 2 - .changeset/eighty-onions-leave.md | 2 - .changeset/eighty-sloths-feel.md | 2 - .changeset/eleven-rabbits-enter.md | 2 - .changeset/eleven-wolves-return.md | 2 - .changeset/email-tracking-logging.md | 4 - .changeset/enable-addie-public.md | 3 - .changeset/engagement-scoring-job.md | 3 - .changeset/error-details-type.md | 7 - .changeset/event-attendance-tracking.md | 2 - .changeset/event-visibility-fix.md | 4 - .changeset/every-falcons-peel.md | 4 - .changeset/every-moose-kick.md | 2 - .changeset/fair-plants-reply.md | 2 - .changeset/fair-turkeys-sell.md | 2 - .changeset/famous-rockets-report.md | 2 - .changeset/famous-sides-hide.md | 7 - .changeset/fast-hounds-lick.md | 2 - .changeset/fast-sloths-press.md | 2 - .changeset/fiery-actors-show.md | 4 - .changeset/fiery-days-roll.md | 2 - .changeset/fiery-maps-tan.md | 12 - .changeset/fiery-sides-allow.md | 4 - .changeset/fifty-bars-pump.md | 4 - .changeset/fifty-pandas-sleep.md | 2 - .changeset/fix-addie-github-link.md | 3 - .changeset/fix-admin-checks-mobile-pane.md | 4 - .changeset/fix-admin-member-links.md | 4 - .changeset/fix-analytics-views.md | 4 - .changeset/fix-broken-readme-links.md | 3 - .changeset/fix-company-type-validation.md | 3 - .changeset/fix-daily-digest.md | 3 - .changeset/fix-domain-health-orgs.md | 4 - .changeset/fix-duplicate-members.md | 2 - .changeset/fix-email-preferences-table.md | 4 - .changeset/fix-feed-email-slugs.md | 4 - .changeset/fix-feedback-modal.md | 4 - .changeset/fix-html-entities-news.md | 4 - .../fix-industry-alerts-channel-filter.md | 3 - .changeset/fix-industry-alerts-token.md | 3 - .changeset/fix-interaction-analyzer-column.md | 4 - .changeset/fix-invoice-products.md | 4 - .changeset/fix-login-display-and-stats.md | 3 - .changeset/fix-member-name-click.md | 4 - .changeset/fix-migration-151.md | 5 - .changeset/fix-org-memberships-table.md | 4 - .changeset/fix-outreach-stats.md | 4 - .changeset/fix-package-request-fields.md | 22 - .changeset/fix-pending-articles.md | 4 - .changeset/fix-perspectives-body.md | 3 - .changeset/fix-perspectives-link.md | 4 - .changeset/fix-planner-column-names.md | 4 - .changeset/fix-resend-inbound-webhook.md | 4 - .changeset/fix-slack-auth-security.md | 4 - .changeset/fix-slack-leader-display.md | 12 - .changeset/flat-candies-crash.md | 2 - .changeset/flat-friends-learn.md | 2 - .changeset/flat-frogs-go.md | 2 - .changeset/flat-turkeys-tell.md | 2 - .changeset/floppy-apples-live.md | 4 - .changeset/floppy-crews-greet.md | 2 - .changeset/forty-emus-sniff.md | 2 - .changeset/forty-symbols-kick.md | 2 - .changeset/four-forks-go.md | 2 - .changeset/four-ways-yawn.md | 2 - .changeset/frank-pets-tan.md | 4 - .changeset/free-chicken-write.md | 2 - .changeset/free-terms-send.md | 2 - .changeset/fresh-crews-wash.md | 2 - .changeset/fresh-numbers-hide.md | 2 - .changeset/fruity-papayas-act.md | 2 - .changeset/fruity-states-spend.md | 2 - .changeset/fruity-wasps-hope.md | 2 - .changeset/funky-bars-chew.md | 9 - .changeset/funny-jeans-attack.md | 2 - .changeset/funny-sheep-remain.md | 2 - .changeset/fuzzy-showers-know.md | 2 - .changeset/fuzzy-streets-call.md | 2 - .changeset/gentle-coins-live.md | 2 - .changeset/gentle-moose-greet.md | 2 - .changeset/gentle-pianos-spend.md | 2 - .changeset/gentle-rivers-add.md | 2 - .changeset/giant-eggs-stare.md | 2 - .changeset/giant-parts-heal.md | 4 - .changeset/giant-tires-argue.md | 2 - .changeset/github-issue-offer.md | 4 - .changeset/gold-spies-wait.md | 2 - .changeset/good-ads-rule.md | 2 - .changeset/good-cougars-serve.md | 2 - .changeset/good-kiwis-tie.md | 2 - .changeset/good-lemons-accept.md | 2 - .changeset/good-steaks-slide.md | 2 - .changeset/goofy-deer-pick.md | 2 - .changeset/great-cobras-wave.md | 2 - .changeset/great-singers-greet.md | 2 - .changeset/green-banks-juggle.md | 4 - .changeset/green-bottles-fly.md | 2 - .changeset/green-cooks-double.md | 4 - .changeset/happy-llamas-hope.md | 2 - .changeset/happy-nights-talk.md | 4 - .changeset/heavy-dots-doubt.md | 2 - .changeset/heavy-tips-visit.md | 2 - .changeset/hip-bugs-jam.md | 2 - .changeset/honest-spiders-tickle.md | 2 - .changeset/hot-teams-leave.md | 2 - .changeset/huge-taxes-sin.md | 2 - .changeset/humble-clouds-smell.md | 4 - .changeset/hungry-bars-jump.md | 2 - .changeset/hungry-feet-jump.md | 4 - .changeset/icy-toes-pay.md | 2 - .changeset/industry-gathering-slug-fix.md | 3 - .changeset/jolly-chefs-knock.md | 2 - .changeset/jolly-houses-shake.md | 2 - .changeset/jolly-spoons-jump.md | 2 - .changeset/kind-horses-fry.md | 2 - .changeset/late-candles-invite.md | 2 - .changeset/late-teeth-divide.md | 4 - .changeset/lazy-doors-read.md | 2 - .changeset/legal-dodos-rush.md | 2 - .changeset/lemon-cats-brake.md | 2 - .changeset/link-org-direct.md | 4 - .changeset/loose-feet-live.md | 4 - .changeset/loud-ducks-cough.md | 4 - .changeset/loud-hairs-repeat.md | 4 - .changeset/loud-pigs-heal.md | 2 - .changeset/lovely-banks-argue.md | 2 - .changeset/lovely-dragons-know.md | 4 - .changeset/many-cups-teach.md | 2 - .changeset/many-doors-shine.md | 10 - .changeset/member-search-intros.md | 4 - .changeset/metal-ravens-argue.md | 2 - .changeset/mighty-needles-count.md | 2 - .changeset/mighty-rings-add.md | 2 - .changeset/modern-maps-happen.md | 2 - .changeset/modern-planets-marry.md | 2 - .changeset/neat-things-lay.md | 2 - .changeset/new-coins-think.md | 2 - .changeset/new-meals-battle.md | 2 - .changeset/new-olives-play.md | 2 - .changeset/new-suns-study.md | 4 - .changeset/nice-camels-serve.md | 4 - .changeset/nice-meals-help.md | 2 - .changeset/nine-lies-raise.md | 2 - .changeset/nine-rings-beam.md | 2 - .changeset/nine-views-clap.md | 4 - .changeset/ninety-oranges-rush.md | 4 - .changeset/old-geese-dress.md | 4 - .changeset/old-shirts-hammer.md | 4 - .changeset/olive-buses-doubt.md | 2 - .changeset/olive-coats-bathe.md | 2 - .changeset/open-rooms-tap.md | 2 - .changeset/open-squids-flow.md | 2 - .changeset/orange-bottles-reply.md | 4 - .changeset/orange-coins-wish.md | 2 - .changeset/perky-deserts-matter.md | 2 - .changeset/personal-workspace-enforcement.md | 3 - .changeset/petite-eels-fly.md | 2 - .changeset/pink-corners-allow.md | 2 - .changeset/plain-needles-smell.md | 4 - .changeset/plain-terms-sip.md | 2 - .changeset/plenty-trees-relate.md | 28 - .changeset/polite-candies-rescue.md | 2 - .changeset/polite-candles-walk.md | 2 - .changeset/polite-cycles-hang.md | 2 - .changeset/polite-socks-swim.md | 2 - .changeset/polite-tables-fail.md | 11 - .changeset/polite-webs-cheat.md | 2 - .changeset/posthog-analytics.md | 4 - .changeset/pretty-cameras-jog.md | 4 - .changeset/pretty-crabs-create.md | 2 - .changeset/pretty-horses-add.md | 2 - .changeset/proud-jars-attack.md | 2 - .changeset/proud-pugs-punch.md | 2 - .changeset/public-needles-argue.md | 2 - .changeset/puny-terms-start.md | 4 - .changeset/purple-rice-unite.md | 2 - .changeset/quick-pandas-invent.md | 2 - .changeset/ready-breads-matter.md | 2 - .changeset/ready-kids-teach.md | 2 - .changeset/real-cases-join.md | 2 - .changeset/real-places-float.md | 4 - .changeset/red-mails-do.md | 2 - .changeset/release-relaxed-schemas.md | 9 - .changeset/ripe-ghosts-bet.md | 9 - .changeset/ripe-zebras-sell.md | 2 - .changeset/salty-cooks-grin.md | 2 - .changeset/seven-deer-marry.md | 2 - .changeset/seven-heads-smile.md | 2 - .changeset/seven-rules-invent.md | 4 - .changeset/seven-squids-carry.md | 2 - .changeset/shaggy-rocks-train.md | 2 - .changeset/shaky-paths-attend.md | 2 - .changeset/shiny-points-spend.md | 2 - .changeset/short-clouds-feel.md | 2 - .changeset/shy-bats-run.md | 2 - .changeset/silent-cars-watch.md | 2 - .changeset/silent-crews-lose.md | 2 - .changeset/silly-readers-hunt.md | 2 - .changeset/silver-goats-exist.md | 4 - .changeset/silver-suns-sell.md | 2 - .changeset/sixty-colts-bathe.md | 4 - .changeset/sixty-ends-ask.md | 2 - .changeset/sixty-paws-punch.md | 4 - .changeset/skip-welcome-engaged-users.md | 6 - .changeset/slack-engagement-scores.md | 4 - .changeset/slick-camels-hide.md | 2 - .changeset/slick-singers-prove.md | 2 - .changeset/slick-tigers-invite.md | 2 - .changeset/slimy-meteors-grab.md | 2 - .changeset/slimy-singers-care.md | 2 - .changeset/slow-snails-stand.md | 2 - .changeset/slow-trains-agree.md | 2 - .changeset/small-geese-leave.md | 2 - .changeset/smart-olives-sort.md | 2 - .changeset/smooth-rice-chew.md | 2 - .changeset/soft-apples-play.md | 4 - .changeset/soft-rooms-spend.md | 4 - .changeset/solid-hotels-occur.md | 2 - .changeset/solid-ideas-shake.md | 2 - .changeset/some-phones-study.md | 2 - .changeset/some-teeth-judge.md | 2 - .changeset/sparkly-camels-dream.md | 4 - .changeset/sparkly-onions-hug.md | 4 - .changeset/spicy-animals-fail.md | 2 - .changeset/spicy-doors-flow.md | 2 - .changeset/spicy-plants-rescue.md | 2 - .changeset/spicy-tigers-double.md | 2 - .changeset/stale-cobras-smile.md | 2 - .changeset/stale-hats-dance.md | 2 - .changeset/stale-tips-drive.md | 2 - .changeset/strict-foxes-wash.md | 8 - .changeset/strict-lamps-turn.md | 4 - .changeset/sunny-apples-jam.md | 4 - .changeset/sunny-bears-stare.md | 2 - .changeset/sweet-brooms-give.md | 2 - .changeset/sweet-planes-follow.md | 10 - .changeset/sweet-queens-send.md | 2 - .changeset/sweet-taxis-act.md | 2 - .changeset/swift-points-sniff.md | 2 - .changeset/tame-hornets-peel.md | 2 - .changeset/tame-hounds-swim.md | 2 - .changeset/tame-llamas-shake.md | 2 - .changeset/tasty-sails-tie.md | 2 - .changeset/ten-bikes-shout.md | 2 - .changeset/ten-crews-joke.md | 2 - .changeset/ten-experts-fall.md | 2 - .changeset/ten-groups-heal.md | 2 - .changeset/ten-towns-admire.md | 2 - .changeset/tender-experts-repeat.md | 4 - .changeset/tender-jeans-double.md | 2 - .changeset/tender-needles-beg.md | 2 - .changeset/tender-pants-glow.md | 2 - .changeset/thick-cows-add.md | 2 - .changeset/thin-lions-trade.md | 2 - .changeset/thirty-dots-feel.md | 2 - .changeset/thirty-papayas-rescue.md | 2 - .changeset/thirty-regions-dance.md | 2 - .changeset/thirty-snails-kneel.md | 4 - .changeset/thirty-snakes-fix.md | 2 - .changeset/tiny-games-decide.md | 2 - .changeset/tiny-news-smash.md | 4 - .changeset/tired-rockets-lie.md | 2 - .changeset/tricky-baths-obey.md | 2 - .changeset/tricky-worms-push.md | 2 - .changeset/twelve-baboons-smell.md | 2 - .changeset/twelve-books-cross.md | 2 - .changeset/twenty-ads-lay.md | 2 - .changeset/twenty-bobcats-fall.md | 2 - .changeset/wacky-seals-eat.md | 2 - .changeset/wacky-things-refuse.md | 2 - .changeset/wacky-worms-speak.md | 4 - .changeset/warm-jars-attack.md | 2 - .changeset/warm-lamps-accept.md | 2 - .changeset/wet-keys-wash.md | 4 - .changeset/wicked-islands-tickle.md | 2 - .changeset/wicked-memes-act.md | 4 - .changeset/wide-kiwis-say.md | 11 - .changeset/wide-shirts-attack.md | 2 - .changeset/wise-bags-invite.md | 2 - .changeset/wise-places-invent.md | 4 - .changeset/wise-tigers-lose.md | 2 - .changeset/witty-teeth-live.md | 2 - .changeset/yellow-ghosts-appear.md | 2 - .changeset/yellow-knives-warn.md | 2 - .changeset/yellow-llamas-lie.md | 2 - .changeset/young-baths-kiss.md | 2 - .changeset/young-masks-care.md | 2 - .changeset/yummy-areas-bet.md | 9 - CHANGELOG.md | 47 + dist/schemas/2.5.3/adagents.json | 494 ++++ .../media-buy/build-creative-request.json | 1349 +++++++++++ .../media-buy/build-creative-response.json | 1383 +++++++++++ .../media-buy/create-media-buy-request.json | 2025 +++++++++++++++++ .../media-buy/create-media-buy-response.json | 356 +++ .../get-media-buy-delivery-request.json | 92 + .../get-media-buy-delivery-response.json | 700 ++++++ .../media-buy/get-products-request.json | 657 ++++++ .../media-buy/get-products-response.json | 1313 +++++++++++ .../list-authorized-properties-request.json | 36 + .../list-authorized-properties-response.json | 128 ++ .../list-creative-formats-request.json | 137 ++ .../list-creative-formats-response.json | 646 ++++++ .../media-buy/list-creatives-request.json | 294 +++ .../media-buy/list-creatives-response.json | 1654 ++++++++++++++ .../bundled/media-buy/package-request.json | 1433 ++++++++++++ .../provide-performance-feedback-request.json | 116 + ...provide-performance-feedback-response.json | 117 + .../media-buy/sync-creatives-request.json | 1475 ++++++++++++ .../media-buy/sync-creatives-response.json | 208 ++ .../media-buy/update-media-buy-request.json | 1530 +++++++++++++ .../media-buy/update-media-buy-response.json | 358 +++ .../signals/activate-signal-request.json | 90 + .../signals/activate-signal-response.json | 295 +++ .../bundled/signals/get-signals-request.json | 154 ++ .../bundled/signals/get-signals-response.json | 327 +++ dist/schemas/2.5.3/core/activation-key.json | 50 + .../2.5.3/core/assets/audio-asset.json | 32 + dist/schemas/2.5.3/core/assets/css-asset.json | 21 + .../2.5.3/core/assets/daast-asset.json | 87 + .../schemas/2.5.3/core/assets/html-asset.json | 21 + .../2.5.3/core/assets/image-asset.json | 38 + .../2.5.3/core/assets/javascript-asset.json | 21 + .../2.5.3/core/assets/markdown-asset.json | 31 + .../schemas/2.5.3/core/assets/text-asset.json | 21 + dist/schemas/2.5.3/core/assets/url-asset.json | 26 + .../schemas/2.5.3/core/assets/vast-asset.json | 87 + .../2.5.3/core/assets/video-asset.json | 44 + .../2.5.3/core/assets/webhook-asset.json | 71 + .../2.5.3/core/async-response-data.json | 88 + .../2.5.3/core/brand-manifest-ref.json | 33 + dist/schemas/2.5.3/core/brand-manifest.json | 409 ++++ dist/schemas/2.5.3/core/context.json | 8 + dist/schemas/2.5.3/core/creative-asset.json | 125 + .../2.5.3/core/creative-assignment.json | 31 + dist/schemas/2.5.3/core/creative-filters.json | 111 + .../schemas/2.5.3/core/creative-manifest.json | 72 + dist/schemas/2.5.3/core/creative-policy.json | 27 + dist/schemas/2.5.3/core/delivery-metrics.json | 171 ++ dist/schemas/2.5.3/core/deployment.json | 93 + dist/schemas/2.5.3/core/destination.json | 53 + dist/schemas/2.5.3/core/error.json | 40 + dist/schemas/2.5.3/core/ext.json | 8 + dist/schemas/2.5.3/core/format-id.json | 47 + dist/schemas/2.5.3/core/format.json | 324 +++ dist/schemas/2.5.3/core/frequency-cap.json | 18 + .../2.5.3/core/mcp-webhook-payload.json | 152 ++ dist/schemas/2.5.3/core/measurement.json | 48 + dist/schemas/2.5.3/core/media-buy.json | 62 + dist/schemas/2.5.3/core/package.json | 72 + .../2.5.3/core/performance-feedback.json | 90 + dist/schemas/2.5.3/core/placement.json | 34 + dist/schemas/2.5.3/core/pricing-option.json | 35 + dist/schemas/2.5.3/core/product-filters.json | 102 + dist/schemas/2.5.3/core/product.json | 153 ++ .../2.5.3/core/promoted-offerings.json | 115 + .../schemas/2.5.3/core/promoted-products.json | 67 + dist/schemas/2.5.3/core/property-id.json | 14 + dist/schemas/2.5.3/core/property-tag.json | 16 + dist/schemas/2.5.3/core/property.json | 62 + .../schemas/2.5.3/core/protocol-envelope.json | 146 ++ .../core/publisher-property-selector.json | 92 + .../2.5.3/core/push-notification-config.json | 48 + .../2.5.3/core/reporting-capabilities.json | 71 + dist/schemas/2.5.3/core/response.json | 24 + dist/schemas/2.5.3/core/signal-filters.json | 35 + dist/schemas/2.5.3/core/start-timing.json | 18 + dist/schemas/2.5.3/core/sub-asset.json | 79 + dist/schemas/2.5.3/core/targeting.json | 50 + .../schemas/2.5.3/core/tasks-get-request.json | 43 + .../2.5.3/core/tasks-get-response.json | 166 ++ .../2.5.3/core/tasks-list-request.json | 192 ++ .../2.5.3/core/tasks-list-response.json | 183 ++ .../2.5.3/creative/asset-types/index.json | 106 + .../list-creative-formats-request.json | 73 + .../list-creative-formats-response.json | 61 + .../creative/preview-creative-request.json | 164 ++ .../creative/preview-creative-response.json | 245 ++ .../2.5.3/creative/preview-render.json | 225 ++ dist/schemas/2.5.3/enums/adcp-domain.json | 11 + .../2.5.3/enums/asset-content-type.json | 22 + dist/schemas/2.5.3/enums/auth-scheme.json | 11 + .../schemas/2.5.3/enums/available-metric.json | 18 + dist/schemas/2.5.3/enums/channels.json | 18 + .../2.5.3/enums/co-branding-requirement.json | 12 + dist/schemas/2.5.3/enums/creative-action.json | 14 + .../enums/creative-agent-capability.json | 13 + .../2.5.3/enums/creative-sort-field.json | 15 + dist/schemas/2.5.3/enums/creative-status.json | 19 + .../2.5.3/enums/daast-tracking-event.json | 20 + dist/schemas/2.5.3/enums/daast-version.json | 11 + dist/schemas/2.5.3/enums/delivery-type.json | 15 + dist/schemas/2.5.3/enums/dimension-unit.json | 13 + dist/schemas/2.5.3/enums/feed-format.json | 12 + dist/schemas/2.5.3/enums/feedback-source.json | 13 + dist/schemas/2.5.3/enums/format-category.json | 16 + .../2.5.3/enums/format-id-parameter.json | 11 + .../2.5.3/enums/frequency-cap-scope.json | 13 + .../2.5.3/enums/history-entry-type.json | 11 + dist/schemas/2.5.3/enums/http-method.json | 11 + .../schemas/2.5.3/enums/identifier-types.json | 34 + .../2.5.3/enums/javascript-module-type.json | 12 + .../2.5.3/enums/landing-page-requirement.json | 12 + dist/schemas/2.5.3/enums/markdown-flavor.json | 11 + .../schemas/2.5.3/enums/media-buy-status.json | 19 + dist/schemas/2.5.3/enums/metric-type.json | 17 + .../2.5.3/enums/notification-type.json | 13 + dist/schemas/2.5.3/enums/pacing.json | 17 + .../2.5.3/enums/preview-output-format.json | 11 + dist/schemas/2.5.3/enums/pricing-model.json | 25 + dist/schemas/2.5.3/enums/property-type.json | 16 + .../enums/publisher-identifier-types.json | 19 + .../2.5.3/enums/reporting-frequency.json | 12 + .../2.5.3/enums/signal-catalog-type.json | 12 + dist/schemas/2.5.3/enums/sort-direction.json | 11 + .../2.5.3/enums/standard-format-ids.json | 103 + dist/schemas/2.5.3/enums/task-status.json | 29 + dist/schemas/2.5.3/enums/task-type.json | 27 + .../schemas/2.5.3/enums/update-frequency.json | 13 + dist/schemas/2.5.3/enums/url-asset-type.json | 12 + dist/schemas/2.5.3/enums/validation-mode.json | 11 + .../2.5.3/enums/vast-tracking-event.json | 25 + dist/schemas/2.5.3/enums/vast-version.json | 14 + .../2.5.3/enums/webhook-response-type.json | 13 + .../2.5.3/enums/webhook-security-method.json | 12 + dist/schemas/2.5.3/index.json | 604 +++++ .../media-buy/build-creative-request.json | 31 + .../media-buy/build-creative-response.json | 65 + ...dia-buy-async-response-input-required.json | 31 + ...te-media-buy-async-response-submitted.json | 16 + ...eate-media-buy-async-response-working.json | 36 + .../media-buy/create-media-buy-request.json | 126 + .../media-buy/create-media-buy-response.json | 97 + .../get-media-buy-delivery-request.json | 54 + .../get-media-buy-delivery-response.json | 285 +++ ...roducts-async-response-input-required.json | 38 + ...get-products-async-response-submitted.json | 21 + .../get-products-async-response-working.json | 34 + .../2.5.3/media-buy/get-products-request.json | 28 + .../media-buy/get-products-response.json | 33 + .../list-authorized-properties-request.json | 26 + .../list-authorized-properties-response.json | 70 + .../list-creative-formats-request.json | 58 + .../list-creative-formats-response.json | 61 + .../media-buy/list-creatives-request.json | 137 ++ .../media-buy/list-creatives-response.json | 437 ++++ .../2.5.3/media-buy/package-request.json | 80 + .../provide-performance-feedback-request.json | 88 + ...provide-performance-feedback-response.json | 66 + ...eatives-async-response-input-required.json | 25 + ...nc-creatives-async-response-submitted.json | 16 + ...sync-creatives-async-response-working.json | 46 + .../media-buy/sync-creatives-request.json | 178 ++ .../media-buy/sync-creatives-response.json | 149 ++ ...dia-buy-async-response-input-required.json | 24 + ...te-media-buy-async-response-submitted.json | 16 + ...date-media-buy-async-response-working.json | 36 + .../media-buy/update-media-buy-request.json | 129 ++ .../media-buy/update-media-buy-response.json | 99 + .../2.5.3/pricing-options/cpc-option.json | 52 + .../2.5.3/pricing-options/cpcv-option.json | 52 + .../pricing-options/cpm-auction-option.json | 81 + .../pricing-options/cpm-fixed-option.json | 52 + .../2.5.3/pricing-options/cpp-option.json | 73 + .../2.5.3/pricing-options/cpv-option.json | 88 + .../pricing-options/flat-rate-option.json | 93 + .../pricing-options/vcpm-auction-option.json | 81 + .../pricing-options/vcpm-fixed-option.json | 52 + .../2.5.3/protocols/adcp-extension.json | 33 + .../signals/activate-signal-request.json | 32 + .../signals/activate-signal-response.json | 68 + .../2.5.3/signals/get-signals-request.json | 59 + .../2.5.3/signals/get-signals-response.json | 100 + 547 files changed, 27620 insertions(+), 1163 deletions(-) delete mode 100644 .changeset/account-lifecycle-stage.md delete mode 100644 .changeset/activity-user-names.md delete mode 100644 .changeset/addie-bot-token-fix.md delete mode 100644 .changeset/addie-dm-no-threading.md delete mode 100644 .changeset/addie-email-domain-linking.md delete mode 100644 .changeset/addie-first-dm-fix.md delete mode 100644 .changeset/addie-home-nav-fix.md delete mode 100644 .changeset/addie-knowledge-improvements.md delete mode 100644 .changeset/addie-newline-fix.md delete mode 100644 .changeset/addie-news-feeds.md delete mode 100644 .changeset/addie-role-suggestions.md delete mode 100644 .changeset/addie-slack-endpoint.md delete mode 100644 .changeset/addie-thread-reply-fix.md delete mode 100644 .changeset/addie-thread-sharing.md delete mode 100644 .changeset/addie-user-simulation.md delete mode 100644 .changeset/afraid-ends-mate.md delete mode 100644 .changeset/afraid-hairs-add.md delete mode 100644 .changeset/afraid-pigs-sit.md delete mode 100644 .changeset/all-dolls-beam.md delete mode 100644 .changeset/all-llamas-sin.md delete mode 100644 .changeset/allow-extra-fields.md delete mode 100644 .changeset/anthropic-timeout.md delete mode 100644 .changeset/better-papayas-attack.md delete mode 100644 .changeset/big-lies-end.md delete mode 100644 .changeset/block-gmail-corp-domain.md delete mode 100644 .changeset/block-personal-workspace-merge.md delete mode 100644 .changeset/blue-tables-agree.md delete mode 100644 .changeset/bold-hornets-behave.md delete mode 100644 .changeset/brave-emus-invite.md delete mode 100644 .changeset/brave-forks-crash.md delete mode 100644 .changeset/breezy-cats-own.md delete mode 100644 .changeset/breezy-pens-kick.md delete mode 100644 .changeset/breezy-zoos-hope.md delete mode 100644 .changeset/bright-kids-say.md delete mode 100644 .changeset/brown-doors-fly.md delete mode 100644 .changeset/bumpy-carrots-change.md delete mode 100644 .changeset/bumpy-cycles-hear.md delete mode 100644 .changeset/bumpy-kings-follow.md delete mode 100644 .changeset/busy-swans-judge.md delete mode 100644 .changeset/calm-lamps-doubt.md delete mode 100644 .changeset/chatty-tools-lead.md delete mode 100644 .changeset/chubby-baboons-kick.md delete mode 100644 .changeset/chubby-books-film.md delete mode 100644 .changeset/chubby-pumas-smoke.md delete mode 100644 .changeset/clean-ghosts-beam.md delete mode 100644 .changeset/clean-groups-press.md delete mode 100644 .changeset/clean-jokes-invent.md delete mode 100644 .changeset/clean-pumas-open.md delete mode 100644 .changeset/clear-colts-wink.md delete mode 100644 .changeset/clever-pets-brush.md delete mode 100644 .changeset/clickable-user-name.md delete mode 100644 .changeset/cold-shrimps-taste.md delete mode 100644 .changeset/common-cougars-unite.md delete mode 100644 .changeset/common-geckos-wash.md delete mode 100644 .changeset/cool-memes-wear.md delete mode 100644 .changeset/crisp-moments-wear.md delete mode 100644 .changeset/crisp-pets-tease.md delete mode 100644 .changeset/crisp-readers-rule.md delete mode 100644 .changeset/cuddly-pets-sort.md delete mode 100644 .changeset/cuddly-windows-accept.md delete mode 100644 .changeset/curvy-rice-bake.md delete mode 100644 .changeset/cute-taxes-boil.md delete mode 100644 .changeset/cyan-cooks-hang.md delete mode 100644 .changeset/dashboard-user-menu.md delete mode 100644 .changeset/david-event-mgmt-fix.md delete mode 100644 .changeset/dirty-kiwis-cheer.md delete mode 100644 .changeset/dirty-meteors-sell.md delete mode 100644 .changeset/dirty-spies-burn.md delete mode 100644 .changeset/docs-versioning.md delete mode 100644 .changeset/dry-singers-pull.md delete mode 100644 .changeset/dull-dogs-live.md delete mode 100644 .changeset/dull-rooms-battle.md delete mode 100644 .changeset/dull-singers-mate.md delete mode 100644 .changeset/eager-eels-sneeze.md delete mode 100644 .changeset/eager-humans-change.md delete mode 100644 .changeset/easy-lizards-hug.md delete mode 100644 .changeset/eighty-onions-leave.md delete mode 100644 .changeset/eighty-sloths-feel.md delete mode 100644 .changeset/eleven-rabbits-enter.md delete mode 100644 .changeset/eleven-wolves-return.md delete mode 100644 .changeset/email-tracking-logging.md delete mode 100644 .changeset/enable-addie-public.md delete mode 100644 .changeset/engagement-scoring-job.md delete mode 100644 .changeset/error-details-type.md delete mode 100644 .changeset/event-attendance-tracking.md delete mode 100644 .changeset/event-visibility-fix.md delete mode 100644 .changeset/every-falcons-peel.md delete mode 100644 .changeset/every-moose-kick.md delete mode 100644 .changeset/fair-plants-reply.md delete mode 100644 .changeset/fair-turkeys-sell.md delete mode 100644 .changeset/famous-rockets-report.md delete mode 100644 .changeset/famous-sides-hide.md delete mode 100644 .changeset/fast-hounds-lick.md delete mode 100644 .changeset/fast-sloths-press.md delete mode 100644 .changeset/fiery-actors-show.md delete mode 100644 .changeset/fiery-days-roll.md delete mode 100644 .changeset/fiery-maps-tan.md delete mode 100644 .changeset/fiery-sides-allow.md delete mode 100644 .changeset/fifty-bars-pump.md delete mode 100644 .changeset/fifty-pandas-sleep.md delete mode 100644 .changeset/fix-addie-github-link.md delete mode 100644 .changeset/fix-admin-checks-mobile-pane.md delete mode 100644 .changeset/fix-admin-member-links.md delete mode 100644 .changeset/fix-analytics-views.md delete mode 100644 .changeset/fix-broken-readme-links.md delete mode 100644 .changeset/fix-company-type-validation.md delete mode 100644 .changeset/fix-daily-digest.md delete mode 100644 .changeset/fix-domain-health-orgs.md delete mode 100644 .changeset/fix-duplicate-members.md delete mode 100644 .changeset/fix-email-preferences-table.md delete mode 100644 .changeset/fix-feed-email-slugs.md delete mode 100644 .changeset/fix-feedback-modal.md delete mode 100644 .changeset/fix-html-entities-news.md delete mode 100644 .changeset/fix-industry-alerts-channel-filter.md delete mode 100644 .changeset/fix-industry-alerts-token.md delete mode 100644 .changeset/fix-interaction-analyzer-column.md delete mode 100644 .changeset/fix-invoice-products.md delete mode 100644 .changeset/fix-login-display-and-stats.md delete mode 100644 .changeset/fix-member-name-click.md delete mode 100644 .changeset/fix-migration-151.md delete mode 100644 .changeset/fix-org-memberships-table.md delete mode 100644 .changeset/fix-outreach-stats.md delete mode 100644 .changeset/fix-package-request-fields.md delete mode 100644 .changeset/fix-pending-articles.md delete mode 100644 .changeset/fix-perspectives-body.md delete mode 100644 .changeset/fix-perspectives-link.md delete mode 100644 .changeset/fix-planner-column-names.md delete mode 100644 .changeset/fix-resend-inbound-webhook.md delete mode 100644 .changeset/fix-slack-auth-security.md delete mode 100644 .changeset/fix-slack-leader-display.md delete mode 100644 .changeset/flat-candies-crash.md delete mode 100644 .changeset/flat-friends-learn.md delete mode 100644 .changeset/flat-frogs-go.md delete mode 100644 .changeset/flat-turkeys-tell.md delete mode 100644 .changeset/floppy-apples-live.md delete mode 100644 .changeset/floppy-crews-greet.md delete mode 100644 .changeset/forty-emus-sniff.md delete mode 100644 .changeset/forty-symbols-kick.md delete mode 100644 .changeset/four-forks-go.md delete mode 100644 .changeset/four-ways-yawn.md delete mode 100644 .changeset/frank-pets-tan.md delete mode 100644 .changeset/free-chicken-write.md delete mode 100644 .changeset/free-terms-send.md delete mode 100644 .changeset/fresh-crews-wash.md delete mode 100644 .changeset/fresh-numbers-hide.md delete mode 100644 .changeset/fruity-papayas-act.md delete mode 100644 .changeset/fruity-states-spend.md delete mode 100644 .changeset/fruity-wasps-hope.md delete mode 100644 .changeset/funky-bars-chew.md delete mode 100644 .changeset/funny-jeans-attack.md delete mode 100644 .changeset/funny-sheep-remain.md delete mode 100644 .changeset/fuzzy-showers-know.md delete mode 100644 .changeset/fuzzy-streets-call.md delete mode 100644 .changeset/gentle-coins-live.md delete mode 100644 .changeset/gentle-moose-greet.md delete mode 100644 .changeset/gentle-pianos-spend.md delete mode 100644 .changeset/gentle-rivers-add.md delete mode 100644 .changeset/giant-eggs-stare.md delete mode 100644 .changeset/giant-parts-heal.md delete mode 100644 .changeset/giant-tires-argue.md delete mode 100644 .changeset/github-issue-offer.md delete mode 100644 .changeset/gold-spies-wait.md delete mode 100644 .changeset/good-ads-rule.md delete mode 100644 .changeset/good-cougars-serve.md delete mode 100644 .changeset/good-kiwis-tie.md delete mode 100644 .changeset/good-lemons-accept.md delete mode 100644 .changeset/good-steaks-slide.md delete mode 100644 .changeset/goofy-deer-pick.md delete mode 100644 .changeset/great-cobras-wave.md delete mode 100644 .changeset/great-singers-greet.md delete mode 100644 .changeset/green-banks-juggle.md delete mode 100644 .changeset/green-bottles-fly.md delete mode 100644 .changeset/green-cooks-double.md delete mode 100644 .changeset/happy-llamas-hope.md delete mode 100644 .changeset/happy-nights-talk.md delete mode 100644 .changeset/heavy-dots-doubt.md delete mode 100644 .changeset/heavy-tips-visit.md delete mode 100644 .changeset/hip-bugs-jam.md delete mode 100644 .changeset/honest-spiders-tickle.md delete mode 100644 .changeset/hot-teams-leave.md delete mode 100644 .changeset/huge-taxes-sin.md delete mode 100644 .changeset/humble-clouds-smell.md delete mode 100644 .changeset/hungry-bars-jump.md delete mode 100644 .changeset/hungry-feet-jump.md delete mode 100644 .changeset/icy-toes-pay.md delete mode 100644 .changeset/industry-gathering-slug-fix.md delete mode 100644 .changeset/jolly-chefs-knock.md delete mode 100644 .changeset/jolly-houses-shake.md delete mode 100644 .changeset/jolly-spoons-jump.md delete mode 100644 .changeset/kind-horses-fry.md delete mode 100644 .changeset/late-candles-invite.md delete mode 100644 .changeset/late-teeth-divide.md delete mode 100644 .changeset/lazy-doors-read.md delete mode 100644 .changeset/legal-dodos-rush.md delete mode 100644 .changeset/lemon-cats-brake.md delete mode 100644 .changeset/link-org-direct.md delete mode 100644 .changeset/loose-feet-live.md delete mode 100644 .changeset/loud-ducks-cough.md delete mode 100644 .changeset/loud-hairs-repeat.md delete mode 100644 .changeset/loud-pigs-heal.md delete mode 100644 .changeset/lovely-banks-argue.md delete mode 100644 .changeset/lovely-dragons-know.md delete mode 100644 .changeset/many-cups-teach.md delete mode 100644 .changeset/many-doors-shine.md delete mode 100644 .changeset/member-search-intros.md delete mode 100644 .changeset/metal-ravens-argue.md delete mode 100644 .changeset/mighty-needles-count.md delete mode 100644 .changeset/mighty-rings-add.md delete mode 100644 .changeset/modern-maps-happen.md delete mode 100644 .changeset/modern-planets-marry.md delete mode 100644 .changeset/neat-things-lay.md delete mode 100644 .changeset/new-coins-think.md delete mode 100644 .changeset/new-meals-battle.md delete mode 100644 .changeset/new-olives-play.md delete mode 100644 .changeset/new-suns-study.md delete mode 100644 .changeset/nice-camels-serve.md delete mode 100644 .changeset/nice-meals-help.md delete mode 100644 .changeset/nine-lies-raise.md delete mode 100644 .changeset/nine-rings-beam.md delete mode 100644 .changeset/nine-views-clap.md delete mode 100644 .changeset/ninety-oranges-rush.md delete mode 100644 .changeset/old-geese-dress.md delete mode 100644 .changeset/old-shirts-hammer.md delete mode 100644 .changeset/olive-buses-doubt.md delete mode 100644 .changeset/olive-coats-bathe.md delete mode 100644 .changeset/open-rooms-tap.md delete mode 100644 .changeset/open-squids-flow.md delete mode 100644 .changeset/orange-bottles-reply.md delete mode 100644 .changeset/orange-coins-wish.md delete mode 100644 .changeset/perky-deserts-matter.md delete mode 100644 .changeset/personal-workspace-enforcement.md delete mode 100644 .changeset/petite-eels-fly.md delete mode 100644 .changeset/pink-corners-allow.md delete mode 100644 .changeset/plain-needles-smell.md delete mode 100644 .changeset/plain-terms-sip.md delete mode 100644 .changeset/plenty-trees-relate.md delete mode 100644 .changeset/polite-candies-rescue.md delete mode 100644 .changeset/polite-candles-walk.md delete mode 100644 .changeset/polite-cycles-hang.md delete mode 100644 .changeset/polite-socks-swim.md delete mode 100644 .changeset/polite-tables-fail.md delete mode 100644 .changeset/polite-webs-cheat.md delete mode 100644 .changeset/posthog-analytics.md delete mode 100644 .changeset/pretty-cameras-jog.md delete mode 100644 .changeset/pretty-crabs-create.md delete mode 100644 .changeset/pretty-horses-add.md delete mode 100644 .changeset/proud-jars-attack.md delete mode 100644 .changeset/proud-pugs-punch.md delete mode 100644 .changeset/public-needles-argue.md delete mode 100644 .changeset/puny-terms-start.md delete mode 100644 .changeset/purple-rice-unite.md delete mode 100644 .changeset/quick-pandas-invent.md delete mode 100644 .changeset/ready-breads-matter.md delete mode 100644 .changeset/ready-kids-teach.md delete mode 100644 .changeset/real-cases-join.md delete mode 100644 .changeset/real-places-float.md delete mode 100644 .changeset/red-mails-do.md delete mode 100644 .changeset/release-relaxed-schemas.md delete mode 100644 .changeset/ripe-ghosts-bet.md delete mode 100644 .changeset/ripe-zebras-sell.md delete mode 100644 .changeset/salty-cooks-grin.md delete mode 100644 .changeset/seven-deer-marry.md delete mode 100644 .changeset/seven-heads-smile.md delete mode 100644 .changeset/seven-rules-invent.md delete mode 100644 .changeset/seven-squids-carry.md delete mode 100644 .changeset/shaggy-rocks-train.md delete mode 100644 .changeset/shaky-paths-attend.md delete mode 100644 .changeset/shiny-points-spend.md delete mode 100644 .changeset/short-clouds-feel.md delete mode 100644 .changeset/shy-bats-run.md delete mode 100644 .changeset/silent-cars-watch.md delete mode 100644 .changeset/silent-crews-lose.md delete mode 100644 .changeset/silly-readers-hunt.md delete mode 100644 .changeset/silver-goats-exist.md delete mode 100644 .changeset/silver-suns-sell.md delete mode 100644 .changeset/sixty-colts-bathe.md delete mode 100644 .changeset/sixty-ends-ask.md delete mode 100644 .changeset/sixty-paws-punch.md delete mode 100644 .changeset/skip-welcome-engaged-users.md delete mode 100644 .changeset/slack-engagement-scores.md delete mode 100644 .changeset/slick-camels-hide.md delete mode 100644 .changeset/slick-singers-prove.md delete mode 100644 .changeset/slick-tigers-invite.md delete mode 100644 .changeset/slimy-meteors-grab.md delete mode 100644 .changeset/slimy-singers-care.md delete mode 100644 .changeset/slow-snails-stand.md delete mode 100644 .changeset/slow-trains-agree.md delete mode 100644 .changeset/small-geese-leave.md delete mode 100644 .changeset/smart-olives-sort.md delete mode 100644 .changeset/smooth-rice-chew.md delete mode 100644 .changeset/soft-apples-play.md delete mode 100644 .changeset/soft-rooms-spend.md delete mode 100644 .changeset/solid-hotels-occur.md delete mode 100644 .changeset/solid-ideas-shake.md delete mode 100644 .changeset/some-phones-study.md delete mode 100644 .changeset/some-teeth-judge.md delete mode 100644 .changeset/sparkly-camels-dream.md delete mode 100644 .changeset/sparkly-onions-hug.md delete mode 100644 .changeset/spicy-animals-fail.md delete mode 100644 .changeset/spicy-doors-flow.md delete mode 100644 .changeset/spicy-plants-rescue.md delete mode 100644 .changeset/spicy-tigers-double.md delete mode 100644 .changeset/stale-cobras-smile.md delete mode 100644 .changeset/stale-hats-dance.md delete mode 100644 .changeset/stale-tips-drive.md delete mode 100644 .changeset/strict-foxes-wash.md delete mode 100644 .changeset/strict-lamps-turn.md delete mode 100644 .changeset/sunny-apples-jam.md delete mode 100644 .changeset/sunny-bears-stare.md delete mode 100644 .changeset/sweet-brooms-give.md delete mode 100644 .changeset/sweet-planes-follow.md delete mode 100644 .changeset/sweet-queens-send.md delete mode 100644 .changeset/sweet-taxis-act.md delete mode 100644 .changeset/swift-points-sniff.md delete mode 100644 .changeset/tame-hornets-peel.md delete mode 100644 .changeset/tame-hounds-swim.md delete mode 100644 .changeset/tame-llamas-shake.md delete mode 100644 .changeset/tasty-sails-tie.md delete mode 100644 .changeset/ten-bikes-shout.md delete mode 100644 .changeset/ten-crews-joke.md delete mode 100644 .changeset/ten-experts-fall.md delete mode 100644 .changeset/ten-groups-heal.md delete mode 100644 .changeset/ten-towns-admire.md delete mode 100644 .changeset/tender-experts-repeat.md delete mode 100644 .changeset/tender-jeans-double.md delete mode 100644 .changeset/tender-needles-beg.md delete mode 100644 .changeset/tender-pants-glow.md delete mode 100644 .changeset/thick-cows-add.md delete mode 100644 .changeset/thin-lions-trade.md delete mode 100644 .changeset/thirty-dots-feel.md delete mode 100644 .changeset/thirty-papayas-rescue.md delete mode 100644 .changeset/thirty-regions-dance.md delete mode 100644 .changeset/thirty-snails-kneel.md delete mode 100644 .changeset/thirty-snakes-fix.md delete mode 100644 .changeset/tiny-games-decide.md delete mode 100644 .changeset/tiny-news-smash.md delete mode 100644 .changeset/tired-rockets-lie.md delete mode 100644 .changeset/tricky-baths-obey.md delete mode 100644 .changeset/tricky-worms-push.md delete mode 100644 .changeset/twelve-baboons-smell.md delete mode 100644 .changeset/twelve-books-cross.md delete mode 100644 .changeset/twenty-ads-lay.md delete mode 100644 .changeset/twenty-bobcats-fall.md delete mode 100644 .changeset/wacky-seals-eat.md delete mode 100644 .changeset/wacky-things-refuse.md delete mode 100644 .changeset/wacky-worms-speak.md delete mode 100644 .changeset/warm-jars-attack.md delete mode 100644 .changeset/warm-lamps-accept.md delete mode 100644 .changeset/wet-keys-wash.md delete mode 100644 .changeset/wicked-islands-tickle.md delete mode 100644 .changeset/wicked-memes-act.md delete mode 100644 .changeset/wide-kiwis-say.md delete mode 100644 .changeset/wide-shirts-attack.md delete mode 100644 .changeset/wise-bags-invite.md delete mode 100644 .changeset/wise-places-invent.md delete mode 100644 .changeset/wise-tigers-lose.md delete mode 100644 .changeset/witty-teeth-live.md delete mode 100644 .changeset/yellow-ghosts-appear.md delete mode 100644 .changeset/yellow-knives-warn.md delete mode 100644 .changeset/yellow-llamas-lie.md delete mode 100644 .changeset/young-baths-kiss.md delete mode 100644 .changeset/young-masks-care.md delete mode 100644 .changeset/yummy-areas-bet.md create mode 100644 dist/schemas/2.5.3/adagents.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/build-creative-request.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/build-creative-response.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/create-media-buy-request.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/create-media-buy-response.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/get-media-buy-delivery-request.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/get-media-buy-delivery-response.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/get-products-request.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/get-products-response.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/list-authorized-properties-request.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/list-authorized-properties-response.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/list-creative-formats-request.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/list-creative-formats-response.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/list-creatives-request.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/list-creatives-response.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/package-request.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/provide-performance-feedback-request.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/provide-performance-feedback-response.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/sync-creatives-request.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/sync-creatives-response.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/update-media-buy-request.json create mode 100644 dist/schemas/2.5.3/bundled/media-buy/update-media-buy-response.json create mode 100644 dist/schemas/2.5.3/bundled/signals/activate-signal-request.json create mode 100644 dist/schemas/2.5.3/bundled/signals/activate-signal-response.json create mode 100644 dist/schemas/2.5.3/bundled/signals/get-signals-request.json create mode 100644 dist/schemas/2.5.3/bundled/signals/get-signals-response.json create mode 100644 dist/schemas/2.5.3/core/activation-key.json create mode 100644 dist/schemas/2.5.3/core/assets/audio-asset.json create mode 100644 dist/schemas/2.5.3/core/assets/css-asset.json create mode 100644 dist/schemas/2.5.3/core/assets/daast-asset.json create mode 100644 dist/schemas/2.5.3/core/assets/html-asset.json create mode 100644 dist/schemas/2.5.3/core/assets/image-asset.json create mode 100644 dist/schemas/2.5.3/core/assets/javascript-asset.json create mode 100644 dist/schemas/2.5.3/core/assets/markdown-asset.json create mode 100644 dist/schemas/2.5.3/core/assets/text-asset.json create mode 100644 dist/schemas/2.5.3/core/assets/url-asset.json create mode 100644 dist/schemas/2.5.3/core/assets/vast-asset.json create mode 100644 dist/schemas/2.5.3/core/assets/video-asset.json create mode 100644 dist/schemas/2.5.3/core/assets/webhook-asset.json create mode 100644 dist/schemas/2.5.3/core/async-response-data.json create mode 100644 dist/schemas/2.5.3/core/brand-manifest-ref.json create mode 100644 dist/schemas/2.5.3/core/brand-manifest.json create mode 100644 dist/schemas/2.5.3/core/context.json create mode 100644 dist/schemas/2.5.3/core/creative-asset.json create mode 100644 dist/schemas/2.5.3/core/creative-assignment.json create mode 100644 dist/schemas/2.5.3/core/creative-filters.json create mode 100644 dist/schemas/2.5.3/core/creative-manifest.json create mode 100644 dist/schemas/2.5.3/core/creative-policy.json create mode 100644 dist/schemas/2.5.3/core/delivery-metrics.json create mode 100644 dist/schemas/2.5.3/core/deployment.json create mode 100644 dist/schemas/2.5.3/core/destination.json create mode 100644 dist/schemas/2.5.3/core/error.json create mode 100644 dist/schemas/2.5.3/core/ext.json create mode 100644 dist/schemas/2.5.3/core/format-id.json create mode 100644 dist/schemas/2.5.3/core/format.json create mode 100644 dist/schemas/2.5.3/core/frequency-cap.json create mode 100644 dist/schemas/2.5.3/core/mcp-webhook-payload.json create mode 100644 dist/schemas/2.5.3/core/measurement.json create mode 100644 dist/schemas/2.5.3/core/media-buy.json create mode 100644 dist/schemas/2.5.3/core/package.json create mode 100644 dist/schemas/2.5.3/core/performance-feedback.json create mode 100644 dist/schemas/2.5.3/core/placement.json create mode 100644 dist/schemas/2.5.3/core/pricing-option.json create mode 100644 dist/schemas/2.5.3/core/product-filters.json create mode 100644 dist/schemas/2.5.3/core/product.json create mode 100644 dist/schemas/2.5.3/core/promoted-offerings.json create mode 100644 dist/schemas/2.5.3/core/promoted-products.json create mode 100644 dist/schemas/2.5.3/core/property-id.json create mode 100644 dist/schemas/2.5.3/core/property-tag.json create mode 100644 dist/schemas/2.5.3/core/property.json create mode 100644 dist/schemas/2.5.3/core/protocol-envelope.json create mode 100644 dist/schemas/2.5.3/core/publisher-property-selector.json create mode 100644 dist/schemas/2.5.3/core/push-notification-config.json create mode 100644 dist/schemas/2.5.3/core/reporting-capabilities.json create mode 100644 dist/schemas/2.5.3/core/response.json create mode 100644 dist/schemas/2.5.3/core/signal-filters.json create mode 100644 dist/schemas/2.5.3/core/start-timing.json create mode 100644 dist/schemas/2.5.3/core/sub-asset.json create mode 100644 dist/schemas/2.5.3/core/targeting.json create mode 100644 dist/schemas/2.5.3/core/tasks-get-request.json create mode 100644 dist/schemas/2.5.3/core/tasks-get-response.json create mode 100644 dist/schemas/2.5.3/core/tasks-list-request.json create mode 100644 dist/schemas/2.5.3/core/tasks-list-response.json create mode 100644 dist/schemas/2.5.3/creative/asset-types/index.json create mode 100644 dist/schemas/2.5.3/creative/list-creative-formats-request.json create mode 100644 dist/schemas/2.5.3/creative/list-creative-formats-response.json create mode 100644 dist/schemas/2.5.3/creative/preview-creative-request.json create mode 100644 dist/schemas/2.5.3/creative/preview-creative-response.json create mode 100644 dist/schemas/2.5.3/creative/preview-render.json create mode 100644 dist/schemas/2.5.3/enums/adcp-domain.json create mode 100644 dist/schemas/2.5.3/enums/asset-content-type.json create mode 100644 dist/schemas/2.5.3/enums/auth-scheme.json create mode 100644 dist/schemas/2.5.3/enums/available-metric.json create mode 100644 dist/schemas/2.5.3/enums/channels.json create mode 100644 dist/schemas/2.5.3/enums/co-branding-requirement.json create mode 100644 dist/schemas/2.5.3/enums/creative-action.json create mode 100644 dist/schemas/2.5.3/enums/creative-agent-capability.json create mode 100644 dist/schemas/2.5.3/enums/creative-sort-field.json create mode 100644 dist/schemas/2.5.3/enums/creative-status.json create mode 100644 dist/schemas/2.5.3/enums/daast-tracking-event.json create mode 100644 dist/schemas/2.5.3/enums/daast-version.json create mode 100644 dist/schemas/2.5.3/enums/delivery-type.json create mode 100644 dist/schemas/2.5.3/enums/dimension-unit.json create mode 100644 dist/schemas/2.5.3/enums/feed-format.json create mode 100644 dist/schemas/2.5.3/enums/feedback-source.json create mode 100644 dist/schemas/2.5.3/enums/format-category.json create mode 100644 dist/schemas/2.5.3/enums/format-id-parameter.json create mode 100644 dist/schemas/2.5.3/enums/frequency-cap-scope.json create mode 100644 dist/schemas/2.5.3/enums/history-entry-type.json create mode 100644 dist/schemas/2.5.3/enums/http-method.json create mode 100644 dist/schemas/2.5.3/enums/identifier-types.json create mode 100644 dist/schemas/2.5.3/enums/javascript-module-type.json create mode 100644 dist/schemas/2.5.3/enums/landing-page-requirement.json create mode 100644 dist/schemas/2.5.3/enums/markdown-flavor.json create mode 100644 dist/schemas/2.5.3/enums/media-buy-status.json create mode 100644 dist/schemas/2.5.3/enums/metric-type.json create mode 100644 dist/schemas/2.5.3/enums/notification-type.json create mode 100644 dist/schemas/2.5.3/enums/pacing.json create mode 100644 dist/schemas/2.5.3/enums/preview-output-format.json create mode 100644 dist/schemas/2.5.3/enums/pricing-model.json create mode 100644 dist/schemas/2.5.3/enums/property-type.json create mode 100644 dist/schemas/2.5.3/enums/publisher-identifier-types.json create mode 100644 dist/schemas/2.5.3/enums/reporting-frequency.json create mode 100644 dist/schemas/2.5.3/enums/signal-catalog-type.json create mode 100644 dist/schemas/2.5.3/enums/sort-direction.json create mode 100644 dist/schemas/2.5.3/enums/standard-format-ids.json create mode 100644 dist/schemas/2.5.3/enums/task-status.json create mode 100644 dist/schemas/2.5.3/enums/task-type.json create mode 100644 dist/schemas/2.5.3/enums/update-frequency.json create mode 100644 dist/schemas/2.5.3/enums/url-asset-type.json create mode 100644 dist/schemas/2.5.3/enums/validation-mode.json create mode 100644 dist/schemas/2.5.3/enums/vast-tracking-event.json create mode 100644 dist/schemas/2.5.3/enums/vast-version.json create mode 100644 dist/schemas/2.5.3/enums/webhook-response-type.json create mode 100644 dist/schemas/2.5.3/enums/webhook-security-method.json create mode 100644 dist/schemas/2.5.3/index.json create mode 100644 dist/schemas/2.5.3/media-buy/build-creative-request.json create mode 100644 dist/schemas/2.5.3/media-buy/build-creative-response.json create mode 100644 dist/schemas/2.5.3/media-buy/create-media-buy-async-response-input-required.json create mode 100644 dist/schemas/2.5.3/media-buy/create-media-buy-async-response-submitted.json create mode 100644 dist/schemas/2.5.3/media-buy/create-media-buy-async-response-working.json create mode 100644 dist/schemas/2.5.3/media-buy/create-media-buy-request.json create mode 100644 dist/schemas/2.5.3/media-buy/create-media-buy-response.json create mode 100644 dist/schemas/2.5.3/media-buy/get-media-buy-delivery-request.json create mode 100644 dist/schemas/2.5.3/media-buy/get-media-buy-delivery-response.json create mode 100644 dist/schemas/2.5.3/media-buy/get-products-async-response-input-required.json create mode 100644 dist/schemas/2.5.3/media-buy/get-products-async-response-submitted.json create mode 100644 dist/schemas/2.5.3/media-buy/get-products-async-response-working.json create mode 100644 dist/schemas/2.5.3/media-buy/get-products-request.json create mode 100644 dist/schemas/2.5.3/media-buy/get-products-response.json create mode 100644 dist/schemas/2.5.3/media-buy/list-authorized-properties-request.json create mode 100644 dist/schemas/2.5.3/media-buy/list-authorized-properties-response.json create mode 100644 dist/schemas/2.5.3/media-buy/list-creative-formats-request.json create mode 100644 dist/schemas/2.5.3/media-buy/list-creative-formats-response.json create mode 100644 dist/schemas/2.5.3/media-buy/list-creatives-request.json create mode 100644 dist/schemas/2.5.3/media-buy/list-creatives-response.json create mode 100644 dist/schemas/2.5.3/media-buy/package-request.json create mode 100644 dist/schemas/2.5.3/media-buy/provide-performance-feedback-request.json create mode 100644 dist/schemas/2.5.3/media-buy/provide-performance-feedback-response.json create mode 100644 dist/schemas/2.5.3/media-buy/sync-creatives-async-response-input-required.json create mode 100644 dist/schemas/2.5.3/media-buy/sync-creatives-async-response-submitted.json create mode 100644 dist/schemas/2.5.3/media-buy/sync-creatives-async-response-working.json create mode 100644 dist/schemas/2.5.3/media-buy/sync-creatives-request.json create mode 100644 dist/schemas/2.5.3/media-buy/sync-creatives-response.json create mode 100644 dist/schemas/2.5.3/media-buy/update-media-buy-async-response-input-required.json create mode 100644 dist/schemas/2.5.3/media-buy/update-media-buy-async-response-submitted.json create mode 100644 dist/schemas/2.5.3/media-buy/update-media-buy-async-response-working.json create mode 100644 dist/schemas/2.5.3/media-buy/update-media-buy-request.json create mode 100644 dist/schemas/2.5.3/media-buy/update-media-buy-response.json create mode 100644 dist/schemas/2.5.3/pricing-options/cpc-option.json create mode 100644 dist/schemas/2.5.3/pricing-options/cpcv-option.json create mode 100644 dist/schemas/2.5.3/pricing-options/cpm-auction-option.json create mode 100644 dist/schemas/2.5.3/pricing-options/cpm-fixed-option.json create mode 100644 dist/schemas/2.5.3/pricing-options/cpp-option.json create mode 100644 dist/schemas/2.5.3/pricing-options/cpv-option.json create mode 100644 dist/schemas/2.5.3/pricing-options/flat-rate-option.json create mode 100644 dist/schemas/2.5.3/pricing-options/vcpm-auction-option.json create mode 100644 dist/schemas/2.5.3/pricing-options/vcpm-fixed-option.json create mode 100644 dist/schemas/2.5.3/protocols/adcp-extension.json create mode 100644 dist/schemas/2.5.3/signals/activate-signal-request.json create mode 100644 dist/schemas/2.5.3/signals/activate-signal-response.json create mode 100644 dist/schemas/2.5.3/signals/get-signals-request.json create mode 100644 dist/schemas/2.5.3/signals/get-signals-response.json diff --git a/.changeset/account-lifecycle-stage.md b/.changeset/account-lifecycle-stage.md deleted file mode 100644 index 1a3d295713..0000000000 --- a/.changeset/account-lifecycle-stage.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Internal tooling: Add unified account lifecycle stage to admin tools diff --git a/.changeset/activity-user-names.md b/.changeset/activity-user-names.md deleted file mode 100644 index 3b1026e46e..0000000000 --- a/.changeset/activity-user-names.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Display user names instead of Slack IDs in proactive outreach history diff --git a/.changeset/addie-bot-token-fix.md b/.changeset/addie-bot-token-fix.md deleted file mode 100644 index b527df12db..0000000000 --- a/.changeset/addie-bot-token-fix.md +++ /dev/null @@ -1,8 +0,0 @@ ---- ---- - -Server-only: Fix Addie using wrong bot token to send messages. - -Addie was using SLACK_BOT_TOKEN (AAO bot) to send messages, which -failed with channel_not_found because Addie's DM channels are only -accessible via ADDIE_BOT_TOKEN. diff --git a/.changeset/addie-dm-no-threading.md b/.changeset/addie-dm-no-threading.md deleted file mode 100644 index 7c2dd05f25..0000000000 --- a/.changeset/addie-dm-no-threading.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix DM response posting as thread instead of regular message diff --git a/.changeset/addie-email-domain-linking.md b/.changeset/addie-email-domain-linking.md deleted file mode 100644 index ec380ec43f..0000000000 --- a/.changeset/addie-email-domain-linking.md +++ /dev/null @@ -1,3 +0,0 @@ ---- ---- - diff --git a/.changeset/addie-first-dm-fix.md b/.changeset/addie-first-dm-fix.md deleted file mode 100644 index ec380ec43f..0000000000 --- a/.changeset/addie-first-dm-fix.md +++ /dev/null @@ -1,3 +0,0 @@ ---- ---- - diff --git a/.changeset/addie-home-nav-fix.md b/.changeset/addie-home-nav-fix.md deleted file mode 100644 index c9428e1ddd..0000000000 --- a/.changeset/addie-home-nav-fix.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix Addie chat home button navigation - clicking Home now properly shows the dashboard diff --git a/.changeset/addie-knowledge-improvements.md b/.changeset/addie-knowledge-improvements.md deleted file mode 100644 index 75e77a5735..0000000000 --- a/.changeset/addie-knowledge-improvements.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Addie admin improvements: Unified Knowledge Base tab, Slack message indexing, and curated content system. Server-only changes, no protocol impact. diff --git a/.changeset/addie-newline-fix.md b/.changeset/addie-newline-fix.md deleted file mode 100644 index 1658dde4dd..0000000000 --- a/.changeset/addie-newline-fix.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix missing newlines between context sections in Addie message formatting diff --git a/.changeset/addie-news-feeds.md b/.changeset/addie-news-feeds.md deleted file mode 100644 index babf21bb3d..0000000000 --- a/.changeset/addie-news-feeds.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add get_recent_news tool to Addie for browsing curated industry feeds diff --git a/.changeset/addie-role-suggestions.md b/.changeset/addie-role-suggestions.md deleted file mode 100644 index ec380ec43f..0000000000 --- a/.changeset/addie-role-suggestions.md +++ /dev/null @@ -1,3 +0,0 @@ ---- ---- - diff --git a/.changeset/addie-slack-endpoint.md b/.changeset/addie-slack-endpoint.md deleted file mode 100644 index ce0693d8b8..0000000000 --- a/.changeset/addie-slack-endpoint.md +++ /dev/null @@ -1,18 +0,0 @@ ---- ---- - -Server-only changes: Refactor Slack endpoints with consistent URL structure. - -**BREAKING CHANGE:** Slack endpoint URLs changed: -- AAO bot: `/api/slack/events` → `/api/slack/aaobot/events` -- AAO bot: `/api/slack/commands` → `/api/slack/aaobot/commands` -- Addie: Added `/api/slack/addie/events` - -Update Slack app Event Subscription URLs in production. - -Also: -- Extract Slack middleware to `server/src/middleware/slack.ts` -- Extract public Slack routes to `server/src/routes/slack.ts` -- Extract admin Slack routes to `server/src/routes/admin/slack.ts` -- Extract admin Email routes to `server/src/routes/admin/email.ts` -- Create shared unified users cache in `server/src/cache/unified-users.ts` diff --git a/.changeset/addie-thread-reply-fix.md b/.changeset/addie-thread-reply-fix.md deleted file mode 100644 index 88e73d82fb..0000000000 --- a/.changeset/addie-thread-reply-fix.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix Addie mentions to reply in threads with full context diff --git a/.changeset/addie-thread-sharing.md b/.changeset/addie-thread-sharing.md deleted file mode 100644 index ec380ec43f..0000000000 --- a/.changeset/addie-thread-sharing.md +++ /dev/null @@ -1,3 +0,0 @@ ---- ---- - diff --git a/.changeset/addie-user-simulation.md b/.changeset/addie-user-simulation.md deleted file mode 100644 index 2e2228725e..0000000000 --- a/.changeset/addie-user-simulation.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add admin user simulation support for Addie web chat debugging (server-side implementation only, no protocol changes). diff --git a/.changeset/afraid-ends-mate.md b/.changeset/afraid-ends-mate.md deleted file mode 100644 index 6dcf0b4089..0000000000 --- a/.changeset/afraid-ends-mate.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix missing column references in admin stats queries (engagement_reasons, next_step, subscription_status) diff --git a/.changeset/afraid-hairs-add.md b/.changeset/afraid-hairs-add.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/afraid-hairs-add.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/afraid-pigs-sit.md b/.changeset/afraid-pigs-sit.md deleted file mode 100644 index c4d6ca9058..0000000000 --- a/.changeset/afraid-pigs-sit.md +++ /dev/null @@ -1,8 +0,0 @@ ---- ---- - -Add member welcome and user signup email notifications via Resend. - -- New member thank you email sent after Stripe subscription created -- User signup email with conditional content based on org subscription status -- Updated naming from "Alliance for Agentic Advertising" to "AgenticAdvertising.org" diff --git a/.changeset/all-dolls-beam.md b/.changeset/all-dolls-beam.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/all-dolls-beam.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/all-llamas-sin.md b/.changeset/all-llamas-sin.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/all-llamas-sin.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/allow-extra-fields.md b/.changeset/allow-extra-fields.md deleted file mode 100644 index 85eea668ec..0000000000 --- a/.changeset/allow-extra-fields.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"adcontextprotocol": patch ---- - -Allow additional properties in all JSON schemas for forward compatibility - -Changes all schemas from `"additionalProperties": false` to `"additionalProperties": true`. This enables clients running older schema versions to accept responses from servers with newer schemas without breaking validation - a standard practice for protocol evolution in distributed systems. diff --git a/.changeset/anthropic-timeout.md b/.changeset/anthropic-timeout.md deleted file mode 100644 index 18d0d43420..0000000000 --- a/.changeset/anthropic-timeout.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add timeout to Anthropic API calls in webhook handler (no protocol changes) diff --git a/.changeset/better-papayas-attack.md b/.changeset/better-papayas-attack.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/better-papayas-attack.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/big-lies-end.md b/.changeset/big-lies-end.md deleted file mode 100644 index b916c68583..0000000000 --- a/.changeset/big-lies-end.md +++ /dev/null @@ -1,6 +0,0 @@ ---- ---- - -Fix duplicate migration version 039 and add CI check. - -This is an internal fix for database migration numbering and does not affect the protocol specification or public API. diff --git a/.changeset/block-gmail-corp-domain.md b/.changeset/block-gmail-corp-domain.md deleted file mode 100644 index aefb92e1e1..0000000000 --- a/.changeset/block-gmail-corp-domain.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix domain health page to exclude free email providers (gmail.com, etc.) from "Related Organizations" section diff --git a/.changeset/block-personal-workspace-merge.md b/.changeset/block-personal-workspace-merge.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/block-personal-workspace-merge.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/blue-tables-agree.md b/.changeset/blue-tables-agree.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/blue-tables-agree.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/bold-hornets-behave.md b/.changeset/bold-hornets-behave.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/bold-hornets-behave.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/brave-emus-invite.md b/.changeset/brave-emus-invite.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/brave-emus-invite.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/brave-forks-crash.md b/.changeset/brave-forks-crash.md deleted file mode 100644 index 186b5d34c5..0000000000 --- a/.changeset/brave-forks-crash.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix Addie's response to empty mentions - when mentioned with just "@Addie" (no question), she no longer incorrectly welcomes the mentioner as if they were new to the channel. diff --git a/.changeset/breezy-cats-own.md b/.changeset/breezy-cats-own.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/breezy-cats-own.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/breezy-pens-kick.md b/.changeset/breezy-pens-kick.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/breezy-pens-kick.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/breezy-zoos-hope.md b/.changeset/breezy-zoos-hope.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/breezy-zoos-hope.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/bright-kids-say.md b/.changeset/bright-kids-say.md deleted file mode 100644 index d8ebeebfc6..0000000000 --- a/.changeset/bright-kids-say.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -fix: correct GitHub issue drafting repo and add confidentiality guidelines for Addie diff --git a/.changeset/brown-doors-fly.md b/.changeset/brown-doors-fly.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/brown-doors-fly.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/bumpy-carrots-change.md b/.changeset/bumpy-carrots-change.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/bumpy-carrots-change.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/bumpy-cycles-hear.md b/.changeset/bumpy-cycles-hear.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/bumpy-cycles-hear.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/bumpy-kings-follow.md b/.changeset/bumpy-kings-follow.md deleted file mode 100644 index 2a9f4be3f0..0000000000 --- a/.changeset/bumpy-kings-follow.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix Docker image to include docs directory and git for Addie knowledge search and external repo indexing. diff --git a/.changeset/busy-swans-judge.md b/.changeset/busy-swans-judge.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/busy-swans-judge.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/calm-lamps-doubt.md b/.changeset/calm-lamps-doubt.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/calm-lamps-doubt.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/chatty-tools-lead.md b/.changeset/chatty-tools-lead.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/chatty-tools-lead.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/chubby-baboons-kick.md b/.changeset/chubby-baboons-kick.md deleted file mode 100644 index 4d017f0a0e..0000000000 --- a/.changeset/chubby-baboons-kick.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix invoice modal positioning: modals now appear centered on screen instead of at page bottom. diff --git a/.changeset/chubby-books-film.md b/.changeset/chubby-books-film.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/chubby-books-film.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/chubby-pumas-smoke.md b/.changeset/chubby-pumas-smoke.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/chubby-pumas-smoke.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/clean-ghosts-beam.md b/.changeset/clean-ghosts-beam.md deleted file mode 100644 index 87a9ee43f8..0000000000 --- a/.changeset/clean-ghosts-beam.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Documentation cleanup - streamlined CLAUDE.md to workspace-specific guidance only. No protocol changes. diff --git a/.changeset/clean-groups-press.md b/.changeset/clean-groups-press.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/clean-groups-press.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/clean-jokes-invent.md b/.changeset/clean-jokes-invent.md deleted file mode 100644 index 3d7d2144d3..0000000000 --- a/.changeset/clean-jokes-invent.md +++ /dev/null @@ -1,10 +0,0 @@ ---- ---- - -Add multi-select company types with "Data & Measurement" and "AI & Tech Platforms" categories (registry only) - -- Organizations can now have multiple company types (e.g., Microsoft can be both "brand" and "ai") -- Added new company type categories: "data" (Data & Measurement) and "ai" (AI & Tech Platforms) -- Renamed "AI Infrastructure" to "AI & Tech Platforms" to include agent builders -- Created centralized company-types config for frontend and backend consistency -- Migration preserves existing single-value data while enabling multi-select diff --git a/.changeset/clean-pumas-open.md b/.changeset/clean-pumas-open.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/clean-pumas-open.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/clear-colts-wink.md b/.changeset/clear-colts-wink.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/clear-colts-wink.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/clever-pets-brush.md b/.changeset/clever-pets-brush.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/clever-pets-brush.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/clickable-user-name.md b/.changeset/clickable-user-name.md deleted file mode 100644 index ec380ec43f..0000000000 --- a/.changeset/clickable-user-name.md +++ /dev/null @@ -1,3 +0,0 @@ ---- ---- - diff --git a/.changeset/cold-shrimps-taste.md b/.changeset/cold-shrimps-taste.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/cold-shrimps-taste.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/common-cougars-unite.md b/.changeset/common-cougars-unite.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/common-cougars-unite.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/common-geckos-wash.md b/.changeset/common-geckos-wash.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/common-geckos-wash.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/cool-memes-wear.md b/.changeset/cool-memes-wear.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/cool-memes-wear.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/crisp-moments-wear.md b/.changeset/crisp-moments-wear.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/crisp-moments-wear.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/crisp-pets-tease.md b/.changeset/crisp-pets-tease.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/crisp-pets-tease.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/crisp-readers-rule.md b/.changeset/crisp-readers-rule.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/crisp-readers-rule.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/cuddly-pets-sort.md b/.changeset/cuddly-pets-sort.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/cuddly-pets-sort.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/cuddly-windows-accept.md b/.changeset/cuddly-windows-accept.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/cuddly-windows-accept.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/curvy-rice-bake.md b/.changeset/curvy-rice-bake.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/curvy-rice-bake.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/cute-taxes-boil.md b/.changeset/cute-taxes-boil.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/cute-taxes-boil.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/cyan-cooks-hang.md b/.changeset/cyan-cooks-hang.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/cyan-cooks-hang.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/dashboard-user-menu.md b/.changeset/dashboard-user-menu.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/dashboard-user-menu.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/david-event-mgmt-fix.md b/.changeset/david-event-mgmt-fix.md deleted file mode 100644 index b0344a2e57..0000000000 --- a/.changeset/david-event-mgmt-fix.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix chapter leader event management permission checks for users added via Slack diff --git a/.changeset/dirty-kiwis-cheer.md b/.changeset/dirty-kiwis-cheer.md deleted file mode 100644 index 172b54291e..0000000000 --- a/.changeset/dirty-kiwis-cheer.md +++ /dev/null @@ -1,46 +0,0 @@ ---- ---- - -Improve Addie router intelligence and search observability - -**Router improvements:** -- Add `usage_hints` field to AddieTool interface for router-specific guidance -- Router now builds tool descriptions from tool definitions (no duplication) -- Distinguish "how does X work?" (search_docs) from "validate my X" (validate_adagents) -- Separate expertise areas for validation vs learning questions - -**Docs indexing:** -- Extract markdown headings as separate searchable artifacts -- Generate anchor links for deep linking to specific sections -- Build headings index alongside docs index (1659 headings from 100 docs) - -**Search tracking:** -- Log all search queries for pattern analysis -- Track results count, latency, and tool used -- Enable content gap detection via zero-result query analysis - -**Prompt improvements:** -- Strengthen GitHub issue drafting instructions - users cannot see tool outputs -- Add conversation context maintenance guidance to prevent entity substitution - -**Bug fixes:** -- Fix DM Assistant thread context loss - now fetches conversation history from database -- Previous messages are passed to Claude so it maintains context across turns - -**Member insights integration:** -- Router now uses member insights (role, interests, pain points) for smarter tool selection -- Fetch member context and insights in parallel for better performance -- Add in-memory cache with 30-minute TTL (long since we invalidate on writes) -- Prefetch insights when user opens Addie (before first message) -- Auto-invalidate cache when new insights are extracted or added via admin API - -**Performance optimizations:** -- Add 30-minute cache for admin status checks (isSlackUserAdmin) -- Admin status rarely changes and was being checked multiple times per conversation -- Add 30-minute cache for active insight goals (only 2 possible variants: mapped/unmapped) -- Auto-invalidate goals cache on goal create/update/delete via admin API -- Add 30-minute cache for Slack channel info (names/purposes rarely change) - -**Previous work (already in PR):** -- Log router decisions to unified thread messages -- Add config versioning for feedback analysis by configuration diff --git a/.changeset/dirty-meteors-sell.md b/.changeset/dirty-meteors-sell.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/dirty-meteors-sell.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/dirty-spies-burn.md b/.changeset/dirty-spies-burn.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/dirty-spies-burn.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/docs-versioning.md b/.changeset/docs-versioning.md deleted file mode 100644 index ec380ec43f..0000000000 --- a/.changeset/docs-versioning.md +++ /dev/null @@ -1,3 +0,0 @@ ---- ---- - diff --git a/.changeset/dry-singers-pull.md b/.changeset/dry-singers-pull.md deleted file mode 100644 index ec380ec43f..0000000000 --- a/.changeset/dry-singers-pull.md +++ /dev/null @@ -1,3 +0,0 @@ ---- ---- - diff --git a/.changeset/dull-dogs-live.md b/.changeset/dull-dogs-live.md deleted file mode 100644 index 724918eea8..0000000000 --- a/.changeset/dull-dogs-live.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add organization_domains table and WorkOS webhook handlers for domain sync diff --git a/.changeset/dull-rooms-battle.md b/.changeset/dull-rooms-battle.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/dull-rooms-battle.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/dull-singers-mate.md b/.changeset/dull-singers-mate.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/dull-singers-mate.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/eager-eels-sneeze.md b/.changeset/eager-eels-sneeze.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/eager-eels-sneeze.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/eager-humans-change.md b/.changeset/eager-humans-change.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/eager-humans-change.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/easy-lizards-hug.md b/.changeset/easy-lizards-hug.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/easy-lizards-hug.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/eighty-onions-leave.md b/.changeset/eighty-onions-leave.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/eighty-onions-leave.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/eighty-sloths-feel.md b/.changeset/eighty-sloths-feel.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/eighty-sloths-feel.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/eleven-rabbits-enter.md b/.changeset/eleven-rabbits-enter.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/eleven-rabbits-enter.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/eleven-wolves-return.md b/.changeset/eleven-wolves-return.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/eleven-wolves-return.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/email-tracking-logging.md b/.changeset/email-tracking-logging.md deleted file mode 100644 index 431f9dde3d..0000000000 --- a/.changeset/email-tracking-logging.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add diagnostic logging to email webhook handler (no protocol changes) diff --git a/.changeset/enable-addie-public.md b/.changeset/enable-addie-public.md deleted file mode 100644 index ec380ec43f..0000000000 --- a/.changeset/enable-addie-public.md +++ /dev/null @@ -1,3 +0,0 @@ ---- ---- - diff --git a/.changeset/engagement-scoring-job.md b/.changeset/engagement-scoring-job.md deleted file mode 100644 index ec380ec43f..0000000000 --- a/.changeset/engagement-scoring-job.md +++ /dev/null @@ -1,3 +0,0 @@ ---- ---- - diff --git a/.changeset/error-details-type.md b/.changeset/error-details-type.md deleted file mode 100644 index c9ee80212e..0000000000 --- a/.changeset/error-details-type.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"adcontextprotocol": patch ---- - -Add explicit type definition to error.json details property - -The `details` property in core/error.json now explicitly declares `"type": "object"` and `"additionalProperties": true`, consistent with other error details definitions in the codebase. This addresses issue #343 where the data type was unspecified. diff --git a/.changeset/event-attendance-tracking.md b/.changeset/event-attendance-tracking.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/event-attendance-tracking.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/event-visibility-fix.md b/.changeset/event-visibility-fix.md deleted file mode 100644 index 17c91e4e55..0000000000 --- a/.changeset/event-visibility-fix.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Internal: Fix event visibility and registration matching (no protocol changes) diff --git a/.changeset/every-falcons-peel.md b/.changeset/every-falcons-peel.md deleted file mode 100644 index a0791a3b6b..0000000000 --- a/.changeset/every-falcons-peel.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Update Addie's core thesis to incorporate the allocation vs efficiency framing. Advertisers are stuck at 3-5 platforms due to execution costs - the opportunity is scaling to 20+ partners, not optimizing the existing 3-5. Walled gardens benefit from AdCP because it lets them capture new allocation budgets without commoditizing their differentiation. diff --git a/.changeset/every-moose-kick.md b/.changeset/every-moose-kick.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/every-moose-kick.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/fair-plants-reply.md b/.changeset/fair-plants-reply.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/fair-plants-reply.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/fair-turkeys-sell.md b/.changeset/fair-turkeys-sell.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/fair-turkeys-sell.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/famous-rockets-report.md b/.changeset/famous-rockets-report.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/famous-rockets-report.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/famous-sides-hide.md b/.changeset/famous-sides-hide.md deleted file mode 100644 index 25bd707397..0000000000 --- a/.changeset/famous-sides-hide.md +++ /dev/null @@ -1,7 +0,0 @@ ---- ---- - -feat: Add membership metrics to admin analytics dashboard - -Adds new admin analytics endpoints and UI tables showing membership counts -broken down by company type and revenue tier. Includes CSV export functionality. diff --git a/.changeset/fast-hounds-lick.md b/.changeset/fast-hounds-lick.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/fast-hounds-lick.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/fast-sloths-press.md b/.changeset/fast-sloths-press.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/fast-sloths-press.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/fiery-actors-show.md b/.changeset/fiery-actors-show.md deleted file mode 100644 index 450cd1d6c0..0000000000 --- a/.changeset/fiery-actors-show.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add unified user counts (members + Slack-only) to accounts list and detail pages. Add Addie tools for listing organizations by user count and listing Slack users by organization. diff --git a/.changeset/fiery-days-roll.md b/.changeset/fiery-days-roll.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/fiery-days-roll.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/fiery-maps-tan.md b/.changeset/fiery-maps-tan.md deleted file mode 100644 index 79826f3d12..0000000000 --- a/.changeset/fiery-maps-tan.md +++ /dev/null @@ -1,12 +0,0 @@ ---- ---- - -Add call_adcp_agent tool and Claude Skills for full AdCP protocol access. - -This enables clients to execute the full AdCP spec (not just testing API) via: -- `call_adcp_agent` tool: Low-level proxy to any AdCP-compliant sales agent -- Claude Skills: Protocol knowledge for media-buy, signals, and creative - -Skills teach Claude the protocol schemas and workflows; the tool routes to -whatever agent the user specifies. Auth tokens are looked up from saved -agent context or can be provided directly. diff --git a/.changeset/fiery-sides-allow.md b/.changeset/fiery-sides-allow.md deleted file mode 100644 index c4472c0b66..0000000000 --- a/.changeset/fiery-sides-allow.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Admin AI analysis UX improvements - no protocol changes diff --git a/.changeset/fifty-bars-pump.md b/.changeset/fifty-bars-pump.md deleted file mode 100644 index d6ac9145fb..0000000000 --- a/.changeset/fifty-bars-pump.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Remove insight_goals table and consolidate goal management into outreach_goals. Admin insight goals CRUD endpoints and UI have been removed. The insight extractor and passive extraction now read from outreach_goals. diff --git a/.changeset/fifty-pandas-sleep.md b/.changeset/fifty-pandas-sleep.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/fifty-pandas-sleep.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/fix-addie-github-link.md b/.changeset/fix-addie-github-link.md deleted file mode 100644 index ec380ec43f..0000000000 --- a/.changeset/fix-addie-github-link.md +++ /dev/null @@ -1,3 +0,0 @@ ---- ---- - diff --git a/.changeset/fix-admin-checks-mobile-pane.md b/.changeset/fix-admin-checks-mobile-pane.md deleted file mode 100644 index 8c994eaea1..0000000000 --- a/.changeset/fix-admin-checks-mobile-pane.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -No protocol changes - internal admin check consistency fix and mobile CSS improvement. diff --git a/.changeset/fix-admin-member-links.md b/.changeset/fix-admin-member-links.md deleted file mode 100644 index 7a519b2209..0000000000 --- a/.changeset/fix-admin-member-links.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix broken admin organization links and make member names clickable. diff --git a/.changeset/fix-analytics-views.md b/.changeset/fix-analytics-views.md deleted file mode 100644 index e28feb4dae..0000000000 --- a/.changeset/fix-analytics-views.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix analytics dashboard: rename "Revenue by Month" to "Bookings by Month" and fix empty "Revenue by Product" view by querying revenue_events instead of subscription_line_items. diff --git a/.changeset/fix-broken-readme-links.md b/.changeset/fix-broken-readme-links.md deleted file mode 100644 index ec380ec43f..0000000000 --- a/.changeset/fix-broken-readme-links.md +++ /dev/null @@ -1,3 +0,0 @@ ---- ---- - diff --git a/.changeset/fix-company-type-validation.md b/.changeset/fix-company-type-validation.md deleted file mode 100644 index ec380ec43f..0000000000 --- a/.changeset/fix-company-type-validation.md +++ /dev/null @@ -1,3 +0,0 @@ ---- ---- - diff --git a/.changeset/fix-daily-digest.md b/.changeset/fix-daily-digest.md deleted file mode 100644 index ec380ec43f..0000000000 --- a/.changeset/fix-daily-digest.md +++ /dev/null @@ -1,3 +0,0 @@ ---- ---- - diff --git a/.changeset/fix-domain-health-orgs.md b/.changeset/fix-domain-health-orgs.md deleted file mode 100644 index 233db1edd7..0000000000 --- a/.changeset/fix-domain-health-orgs.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Improve admin domain health page to show existing organizations for unlinked domains instead of only proposing new prospects. diff --git a/.changeset/fix-duplicate-members.md b/.changeset/fix-duplicate-members.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/fix-duplicate-members.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/fix-email-preferences-table.md b/.changeset/fix-email-preferences-table.md deleted file mode 100644 index 02940fd408..0000000000 --- a/.changeset/fix-email-preferences-table.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix table name in getMemberCapabilities: email_preferences → user_email_preferences diff --git a/.changeset/fix-feed-email-slugs.md b/.changeset/fix-feed-email-slugs.md deleted file mode 100644 index 21b8491247..0000000000 --- a/.changeset/fix-feed-email-slugs.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix feed email slug lookup - prepend 'feed-' prefix to seeded email slugs to match webhook extraction diff --git a/.changeset/fix-feedback-modal.md b/.changeset/fix-feedback-modal.md deleted file mode 100644 index aa53c2615c..0000000000 --- a/.changeset/fix-feedback-modal.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix feedback modal not displaying saved feedback when viewing threads diff --git a/.changeset/fix-html-entities-news.md b/.changeset/fix-html-entities-news.md deleted file mode 100644 index 745e2fe647..0000000000 --- a/.changeset/fix-html-entities-news.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix HTML entity encoding in news feed API responses. RSS feed titles with entities like `'` are now properly decoded before being returned in API responses. diff --git a/.changeset/fix-industry-alerts-channel-filter.md b/.changeset/fix-industry-alerts-channel-filter.md deleted file mode 100644 index ec380ec43f..0000000000 --- a/.changeset/fix-industry-alerts-channel-filter.md +++ /dev/null @@ -1,3 +0,0 @@ ---- ---- - diff --git a/.changeset/fix-industry-alerts-token.md b/.changeset/fix-industry-alerts-token.md deleted file mode 100644 index ec380ec43f..0000000000 --- a/.changeset/fix-industry-alerts-token.md +++ /dev/null @@ -1,3 +0,0 @@ ---- ---- - diff --git a/.changeset/fix-interaction-analyzer-column.md b/.changeset/fix-interaction-analyzer-column.md deleted file mode 100644 index faab1dc733..0000000000 --- a/.changeset/fix-interaction-analyzer-column.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix organization_domains column name in interaction-analyzer query diff --git a/.changeset/fix-invoice-products.md b/.changeset/fix-invoice-products.md deleted file mode 100644 index aaa9496d8e..0000000000 --- a/.changeset/fix-invoice-products.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Filter invoice modal dropdown to show only subscription products, excluding legacy one-time invoice products. diff --git a/.changeset/fix-login-display-and-stats.md b/.changeset/fix-login-display-and-stats.md deleted file mode 100644 index ec380ec43f..0000000000 --- a/.changeset/fix-login-display-and-stats.md +++ /dev/null @@ -1,3 +0,0 @@ ---- ---- - diff --git a/.changeset/fix-member-name-click.md b/.changeset/fix-member-name-click.md deleted file mode 100644 index 9aa2754cbc..0000000000 --- a/.changeset/fix-member-name-click.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -fix: escape apostrophes in user names for onclick handlers diff --git a/.changeset/fix-migration-151.md b/.changeset/fix-migration-151.md deleted file mode 100644 index 4ec327c8b6..0000000000 --- a/.changeset/fix-migration-151.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"adcontextprotocol": patch ---- - -Fix migration 151 to delete duplicates before updating Slack IDs to WorkOS IDs diff --git a/.changeset/fix-org-memberships-table.md b/.changeset/fix-org-memberships-table.md deleted file mode 100644 index fbe7f3984a..0000000000 --- a/.changeset/fix-org-memberships-table.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix organization member lookup table name in inbound email webhook diff --git a/.changeset/fix-outreach-stats.md b/.changeset/fix-outreach-stats.md deleted file mode 100644 index 8a7bfeefe1..0000000000 --- a/.changeset/fix-outreach-stats.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -fix: align frontend outreach stats field names with backend API response diff --git a/.changeset/fix-package-request-fields.md b/.changeset/fix-package-request-fields.md deleted file mode 100644 index d8b68492b7..0000000000 --- a/.changeset/fix-package-request-fields.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -"adcontextprotocol": patch ---- - -Add missing fields to package request schemas for consistency with core/package.json. - -**Schema Changes:** - -- `media-buy/package-request.json`: Added `impressions` and `paused` fields -- `media-buy/update-media-buy-request.json`: Added `impressions` field to package updates - -**Details:** - -- `impressions`: Impression goal for the package (optional, minimum: 0) -- `paused`: Create package in paused state (optional, default: false) - -These fields were defined in `core/package.json` but missing from the request schemas, making it impossible to set impression goals or initial paused state when creating/updating media buys. - -**Documentation:** - -- Updated `create_media_buy` task reference with new package parameters -- Updated `update_media_buy` task reference with impressions parameter diff --git a/.changeset/fix-pending-articles.md b/.changeset/fix-pending-articles.md deleted file mode 100644 index 122252461e..0000000000 --- a/.changeset/fix-pending-articles.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix industry feeds pending article count mismatch diff --git a/.changeset/fix-perspectives-body.md b/.changeset/fix-perspectives-body.md deleted file mode 100644 index ec380ec43f..0000000000 --- a/.changeset/fix-perspectives-body.md +++ /dev/null @@ -1,3 +0,0 @@ ---- ---- - diff --git a/.changeset/fix-perspectives-link.md b/.changeset/fix-perspectives-link.md deleted file mode 100644 index 6c8028d3d0..0000000000 --- a/.changeset/fix-perspectives-link.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix broken working group post links in Slack notifications and frontend diff --git a/.changeset/fix-planner-column-names.md b/.changeset/fix-planner-column-names.md deleted file mode 100644 index 3f9e4cd8d0..0000000000 --- a/.changeset/fix-planner-column-names.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix column name mismatch in getMemberCapabilities query (messages_sent → message_count, date → activity_date) diff --git a/.changeset/fix-resend-inbound-webhook.md b/.changeset/fix-resend-inbound-webhook.md deleted file mode 100644 index 678a402d5e..0000000000 --- a/.changeset/fix-resend-inbound-webhook.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix Resend inbound email webhook timeout by skipping global JSON parser diff --git a/.changeset/fix-slack-auth-security.md b/.changeset/fix-slack-auth-security.md deleted file mode 100644 index dd0727322d..0000000000 --- a/.changeset/fix-slack-auth-security.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Update dependencies to address security vulnerabilities. diff --git a/.changeset/fix-slack-leader-display.md b/.changeset/fix-slack-leader-display.md deleted file mode 100644 index 3620cb18ae..0000000000 --- a/.changeset/fix-slack-leader-display.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"adcontextprotocol": patch ---- - -fix: display Slack profile name for chapter leaders without WorkOS accounts - -Leaders added via Slack ID that haven't linked their WorkOS account now display -their Slack profile name (real_name or display_name) instead of the raw Slack -user ID (e.g., U09BEKNJ3GB). - -The getLeaders and getLeadersBatch queries now include slack_user_mappings as an -additional name source in the COALESCE chain. diff --git a/.changeset/flat-candies-crash.md b/.changeset/flat-candies-crash.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/flat-candies-crash.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/flat-friends-learn.md b/.changeset/flat-friends-learn.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/flat-friends-learn.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/flat-frogs-go.md b/.changeset/flat-frogs-go.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/flat-frogs-go.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/flat-turkeys-tell.md b/.changeset/flat-turkeys-tell.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/flat-turkeys-tell.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/floppy-apples-live.md b/.changeset/floppy-apples-live.md deleted file mode 100644 index 8134d5058f..0000000000 --- a/.changeset/floppy-apples-live.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Update @adcp/client to 3.5.2 to fix schema validation for empty publisher_domains arrays. diff --git a/.changeset/floppy-crews-greet.md b/.changeset/floppy-crews-greet.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/floppy-crews-greet.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/forty-emus-sniff.md b/.changeset/forty-emus-sniff.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/forty-emus-sniff.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/forty-symbols-kick.md b/.changeset/forty-symbols-kick.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/forty-symbols-kick.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/four-forks-go.md b/.changeset/four-forks-go.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/four-forks-go.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/four-ways-yawn.md b/.changeset/four-ways-yawn.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/four-ways-yawn.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/frank-pets-tan.md b/.changeset/frank-pets-tan.md deleted file mode 100644 index 7d477a6dad..0000000000 --- a/.changeset/frank-pets-tan.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -No protocol changes - website improvements only. diff --git a/.changeset/free-chicken-write.md b/.changeset/free-chicken-write.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/free-chicken-write.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/free-terms-send.md b/.changeset/free-terms-send.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/free-terms-send.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/fresh-crews-wash.md b/.changeset/fresh-crews-wash.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/fresh-crews-wash.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/fresh-numbers-hide.md b/.changeset/fresh-numbers-hide.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/fresh-numbers-hide.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/fruity-papayas-act.md b/.changeset/fruity-papayas-act.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/fruity-papayas-act.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/fruity-states-spend.md b/.changeset/fruity-states-spend.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/fruity-states-spend.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/fruity-wasps-hope.md b/.changeset/fruity-wasps-hope.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/fruity-wasps-hope.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/funky-bars-chew.md b/.changeset/funky-bars-chew.md deleted file mode 100644 index 92afc14bf7..0000000000 --- a/.changeset/funky-bars-chew.md +++ /dev/null @@ -1,9 +0,0 @@ ---- ---- - -Improve industry news alert format for engagement - -- Replace priority headers with article title in Slack alerts -- Add "Addie's take" with emoji and discussion prompt to drive engagement -- Decode HTML entities in RSS feed titles -- Reduce false positives in agentic mention detection by only checking original content diff --git a/.changeset/funny-jeans-attack.md b/.changeset/funny-jeans-attack.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/funny-jeans-attack.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/funny-sheep-remain.md b/.changeset/funny-sheep-remain.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/funny-sheep-remain.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/fuzzy-showers-know.md b/.changeset/fuzzy-showers-know.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/fuzzy-showers-know.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/fuzzy-streets-call.md b/.changeset/fuzzy-streets-call.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/fuzzy-streets-call.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/gentle-coins-live.md b/.changeset/gentle-coins-live.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/gentle-coins-live.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/gentle-moose-greet.md b/.changeset/gentle-moose-greet.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/gentle-moose-greet.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/gentle-pianos-spend.md b/.changeset/gentle-pianos-spend.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/gentle-pianos-spend.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/gentle-rivers-add.md b/.changeset/gentle-rivers-add.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/gentle-rivers-add.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/giant-eggs-stare.md b/.changeset/giant-eggs-stare.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/giant-eggs-stare.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/giant-parts-heal.md b/.changeset/giant-parts-heal.md deleted file mode 100644 index 8dfffdc2cb..0000000000 --- a/.changeset/giant-parts-heal.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add Addie Desktop app with native OAuth flow and multi-thread chat UI for web. diff --git a/.changeset/giant-tires-argue.md b/.changeset/giant-tires-argue.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/giant-tires-argue.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/github-issue-offer.md b/.changeset/github-issue-offer.md deleted file mode 100644 index a780d5e737..0000000000 --- a/.changeset/github-issue-offer.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add GitHub issue offer for open-source agent test failures diff --git a/.changeset/gold-spies-wait.md b/.changeset/gold-spies-wait.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/gold-spies-wait.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/good-ads-rule.md b/.changeset/good-ads-rule.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/good-ads-rule.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/good-cougars-serve.md b/.changeset/good-cougars-serve.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/good-cougars-serve.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/good-kiwis-tie.md b/.changeset/good-kiwis-tie.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/good-kiwis-tie.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/good-lemons-accept.md b/.changeset/good-lemons-accept.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/good-lemons-accept.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/good-steaks-slide.md b/.changeset/good-steaks-slide.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/good-steaks-slide.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/goofy-deer-pick.md b/.changeset/goofy-deer-pick.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/goofy-deer-pick.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/great-cobras-wave.md b/.changeset/great-cobras-wave.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/great-cobras-wave.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/great-singers-greet.md b/.changeset/great-singers-greet.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/great-singers-greet.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/green-banks-juggle.md b/.changeset/green-banks-juggle.md deleted file mode 100644 index 26569a2f78..0000000000 --- a/.changeset/green-banks-juggle.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add static admin API key for programmatic access (ADMIN_API_KEY env var) diff --git a/.changeset/green-bottles-fly.md b/.changeset/green-bottles-fly.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/green-bottles-fly.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/green-cooks-double.md b/.changeset/green-cooks-double.md deleted file mode 100644 index bed9202737..0000000000 --- a/.changeset/green-cooks-double.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Consolidate check_agent_health and get_agent_capabilities into probe_adcp_agent tool diff --git a/.changeset/happy-llamas-hope.md b/.changeset/happy-llamas-hope.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/happy-llamas-hope.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/happy-nights-talk.md b/.changeset/happy-nights-talk.md deleted file mode 100644 index 4c3ad70f2f..0000000000 --- a/.changeset/happy-nights-talk.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add committee leadership management tools for Addie and display industry gatherings in the dashboard committees section. diff --git a/.changeset/heavy-dots-doubt.md b/.changeset/heavy-dots-doubt.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/heavy-dots-doubt.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/heavy-tips-visit.md b/.changeset/heavy-tips-visit.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/heavy-tips-visit.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/hip-bugs-jam.md b/.changeset/hip-bugs-jam.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/hip-bugs-jam.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/honest-spiders-tickle.md b/.changeset/honest-spiders-tickle.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/honest-spiders-tickle.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/hot-teams-leave.md b/.changeset/hot-teams-leave.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/hot-teams-leave.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/huge-taxes-sin.md b/.changeset/huge-taxes-sin.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/huge-taxes-sin.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/humble-clouds-smell.md b/.changeset/humble-clouds-smell.md deleted file mode 100644 index f4fc7b5d9b..0000000000 --- a/.changeset/humble-clouds-smell.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix chat page showing logged-out state for authenticated users. diff --git a/.changeset/hungry-bars-jump.md b/.changeset/hungry-bars-jump.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/hungry-bars-jump.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/hungry-feet-jump.md b/.changeset/hungry-feet-jump.md deleted file mode 100644 index fff40c1626..0000000000 --- a/.changeset/hungry-feet-jump.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Ignore Slackbot system messages in Addie handlers (no protocol changes) diff --git a/.changeset/icy-toes-pay.md b/.changeset/icy-toes-pay.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/icy-toes-pay.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/industry-gathering-slug-fix.md b/.changeset/industry-gathering-slug-fix.md deleted file mode 100644 index ec380ec43f..0000000000 --- a/.changeset/industry-gathering-slug-fix.md +++ /dev/null @@ -1,3 +0,0 @@ ---- ---- - diff --git a/.changeset/jolly-chefs-knock.md b/.changeset/jolly-chefs-knock.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/jolly-chefs-knock.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/jolly-houses-shake.md b/.changeset/jolly-houses-shake.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/jolly-houses-shake.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/jolly-spoons-jump.md b/.changeset/jolly-spoons-jump.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/jolly-spoons-jump.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/kind-horses-fry.md b/.changeset/kind-horses-fry.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/kind-horses-fry.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/late-candles-invite.md b/.changeset/late-candles-invite.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/late-candles-invite.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/late-teeth-divide.md b/.changeset/late-teeth-divide.md deleted file mode 100644 index 836280767f..0000000000 --- a/.changeset/late-teeth-divide.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Improve domain health admin page UX: inline domain linking, www normalization, and verify button for existing domains. diff --git a/.changeset/lazy-doors-read.md b/.changeset/lazy-doors-read.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/lazy-doors-read.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/legal-dodos-rush.md b/.changeset/legal-dodos-rush.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/legal-dodos-rush.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/lemon-cats-brake.md b/.changeset/lemon-cats-brake.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/lemon-cats-brake.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/link-org-direct.md b/.changeset/link-org-direct.md deleted file mode 100644 index 6a38402a25..0000000000 --- a/.changeset/link-org-direct.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Link domain directly when clicking "Link to Org" on domain health page instead of showing modal diff --git a/.changeset/loose-feet-live.md b/.changeset/loose-feet-live.md deleted file mode 100644 index a9f04a1ea0..0000000000 --- a/.changeset/loose-feet-live.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add heading-level indexing for external repos (Addie knowledge base improvement - no protocol changes) diff --git a/.changeset/loud-ducks-cough.md b/.changeset/loud-ducks-cough.md deleted file mode 100644 index 0e7d225748..0000000000 --- a/.changeset/loud-ducks-cough.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix ambiguous ORDER BY column in getMembershipsByWorkingGroup query. diff --git a/.changeset/loud-hairs-repeat.md b/.changeset/loud-hairs-repeat.md deleted file mode 100644 index 76db2d2794..0000000000 --- a/.changeset/loud-hairs-repeat.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Filter RSS and email content from research section to show only member perspectives. diff --git a/.changeset/loud-pigs-heal.md b/.changeset/loud-pigs-heal.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/loud-pigs-heal.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/lovely-banks-argue.md b/.changeset/lovely-banks-argue.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/lovely-banks-argue.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/lovely-dragons-know.md b/.changeset/lovely-dragons-know.md deleted file mode 100644 index 74fa3d2804..0000000000 --- a/.changeset/lovely-dragons-know.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix profile modal Continue button not working on dashboard. Added proper change event listeners to radio buttons for more reliable selection handling across browsers and mobile devices. diff --git a/.changeset/many-cups-teach.md b/.changeset/many-cups-teach.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/many-cups-teach.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/many-doors-shine.md b/.changeset/many-doors-shine.md deleted file mode 100644 index 8973ad0967..0000000000 --- a/.changeset/many-doors-shine.md +++ /dev/null @@ -1,10 +0,0 @@ ---- ---- - -Add unified account management system with action items, user stakeholders, and momentum tracking. - -- New migration for user_stakeholders and action_items tables -- Database service for managing account assignments and action items -- Momentum check job for analyzing outreach history and creating action items -- Admin UI with action items panel and My Accounts tab -- Reorganized admin sidebar with Account Management section diff --git a/.changeset/member-search-intros.md b/.changeset/member-search-intros.md deleted file mode 100644 index 11dfde4a52..0000000000 --- a/.changeset/member-search-intros.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Member search analytics and introduction email improvements (no protocol changes) diff --git a/.changeset/metal-ravens-argue.md b/.changeset/metal-ravens-argue.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/metal-ravens-argue.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/mighty-needles-count.md b/.changeset/mighty-needles-count.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/mighty-needles-count.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/mighty-rings-add.md b/.changeset/mighty-rings-add.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/mighty-rings-add.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/modern-maps-happen.md b/.changeset/modern-maps-happen.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/modern-maps-happen.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/modern-planets-marry.md b/.changeset/modern-planets-marry.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/modern-planets-marry.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/neat-things-lay.md b/.changeset/neat-things-lay.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/neat-things-lay.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/new-coins-think.md b/.changeset/new-coins-think.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/new-coins-think.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/new-meals-battle.md b/.changeset/new-meals-battle.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/new-meals-battle.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/new-olives-play.md b/.changeset/new-olives-play.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/new-olives-play.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/new-suns-study.md b/.changeset/new-suns-study.md deleted file mode 100644 index 781b121132..0000000000 --- a/.changeset/new-suns-study.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Server-only audit logging changes - adds comprehensive audit logging for organization lifecycle events and admin viewer. diff --git a/.changeset/nice-camels-serve.md b/.changeset/nice-camels-serve.md deleted file mode 100644 index 4c5176df57..0000000000 --- a/.changeset/nice-camels-serve.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add collapsible sidebar to chat interface with localStorage persistence. Fix external link handling in desktop app iframe. diff --git a/.changeset/nice-meals-help.md b/.changeset/nice-meals-help.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/nice-meals-help.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/nine-lies-raise.md b/.changeset/nine-lies-raise.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/nine-lies-raise.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/nine-rings-beam.md b/.changeset/nine-rings-beam.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/nine-rings-beam.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/nine-views-clap.md b/.changeset/nine-views-clap.md deleted file mode 100644 index bd889bacfb..0000000000 --- a/.changeset/nine-views-clap.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add committee types system: Evolves working groups into multi-type committees (Working Groups, Industry Councils, Regional Chapters, Governance). Includes new navigation dropdown, landing pages for councils/chapters, admin panel updates, and seed data for initial committees. diff --git a/.changeset/ninety-oranges-rush.md b/.changeset/ninety-oranges-rush.md deleted file mode 100644 index 8bb529999c..0000000000 --- a/.changeset/ninety-oranges-rush.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Consolidate Contacts page into Users page - engagement scores, goals, and insights now shown in Users table and context modal. Remove redundant Contacts page and APIs. diff --git a/.changeset/old-geese-dress.md b/.changeset/old-geese-dress.md deleted file mode 100644 index 7bd09068db..0000000000 --- a/.changeset/old-geese-dress.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add industry gathering management for Addie admin and improve admin form UX. diff --git a/.changeset/old-shirts-hammer.md b/.changeset/old-shirts-hammer.md deleted file mode 100644 index 2e662e7335..0000000000 --- a/.changeset/old-shirts-hammer.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix outreach preview showing Slack-linking messages for already-linked users and remove guilt-inducing language from outreach variants. diff --git a/.changeset/olive-buses-doubt.md b/.changeset/olive-buses-doubt.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/olive-buses-doubt.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/olive-coats-bathe.md b/.changeset/olive-coats-bathe.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/olive-coats-bathe.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/open-rooms-tap.md b/.changeset/open-rooms-tap.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/open-rooms-tap.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/open-squids-flow.md b/.changeset/open-squids-flow.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/open-squids-flow.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/orange-bottles-reply.md b/.changeset/orange-bottles-reply.md deleted file mode 100644 index 741650006f..0000000000 --- a/.changeset/orange-bottles-reply.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Clarify AdCP/MCP relationship in docs: AdCP works *over* MCP and A2A as transports, not "built on" MCP. diff --git a/.changeset/orange-coins-wish.md b/.changeset/orange-coins-wish.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/orange-coins-wish.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/perky-deserts-matter.md b/.changeset/perky-deserts-matter.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/perky-deserts-matter.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/personal-workspace-enforcement.md b/.changeset/personal-workspace-enforcement.md deleted file mode 100644 index ec380ec43f..0000000000 --- a/.changeset/personal-workspace-enforcement.md +++ /dev/null @@ -1,3 +0,0 @@ ---- ---- - diff --git a/.changeset/petite-eels-fly.md b/.changeset/petite-eels-fly.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/petite-eels-fly.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/pink-corners-allow.md b/.changeset/pink-corners-allow.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/pink-corners-allow.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/plain-needles-smell.md b/.changeset/plain-needles-smell.md deleted file mode 100644 index d55d877750..0000000000 --- a/.changeset/plain-needles-smell.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix ON CONFLICT clause in domain insert queries to match database schema constraint. diff --git a/.changeset/plain-terms-sip.md b/.changeset/plain-terms-sip.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/plain-terms-sip.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/plenty-trees-relate.md b/.changeset/plenty-trees-relate.md deleted file mode 100644 index a10b808acf..0000000000 --- a/.changeset/plenty-trees-relate.md +++ /dev/null @@ -1,28 +0,0 @@ ---- ---- - -Server/dashboard changes: Add Luma integration for AAO events management. - -**Luma API Client** (`server/src/luma/client.ts`): -- Full Luma API client with typed interfaces -- Event operations: create, get, update, delete -- Guest/registration operations: list guests, approve, decline, check-in -- Calendar operations: list calendars, list calendar events -- Webhook payload parsing and validation - -**Luma Webhook Handler** (`POST /api/webhooks/luma`): -- Handles `guest.created` - Syncs new registrations from Luma to AAO database -- Handles `guest.updated` - Updates registration status (approved/declined/checked-in) -- Handles `event.updated` - Syncs event changes from Luma back to AAO - -**Addie Event Tools** for natural language event management: -- `create_event` - Create events in both Luma and AAO database -- `list_upcoming_events` - List upcoming events with filtering -- `get_event_details` - Get event details with registration counts -- `manage_event_registrations` - List, approve waitlist, export registrations -- `update_event` - Update event details - -**Admin Navigation**: -- Added "Events" link to admin sidebar in the Community section - -Requires `LUMA_API_KEY` environment variable for Luma integration. diff --git a/.changeset/polite-candies-rescue.md b/.changeset/polite-candies-rescue.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/polite-candies-rescue.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/polite-candles-walk.md b/.changeset/polite-candles-walk.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/polite-candles-walk.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/polite-cycles-hang.md b/.changeset/polite-cycles-hang.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/polite-cycles-hang.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/polite-socks-swim.md b/.changeset/polite-socks-swim.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/polite-socks-swim.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/polite-tables-fail.md b/.changeset/polite-tables-fail.md deleted file mode 100644 index 8469f93f36..0000000000 --- a/.changeset/polite-tables-fail.md +++ /dev/null @@ -1,11 +0,0 @@ ---- ---- - -Add comprehensive prospect ownership and management system: - -- Database migration to convert legacy prospect_owner text to org_stakeholders records -- 5 new Addie admin tools: my_engaged_prospects, my_followups_needed, unassigned_prospects, claim_prospect, suggest_prospects -- "My Prospects" section at top of admin dashboard with hot/followup lists -- Prospect stats in Slack App Home admin panel -- mine=true filter on prospects API -- Fix org owner display to fall back to legacy prospect_owner field when no stakeholder owner exists diff --git a/.changeset/polite-webs-cheat.md b/.changeset/polite-webs-cheat.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/polite-webs-cheat.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/posthog-analytics.md b/.changeset/posthog-analytics.md deleted file mode 100644 index 4f21593d06..0000000000 --- a/.changeset/posthog-analytics.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add PostHog analytics integration for tracking rage clicks, session recordings, and heatmaps. diff --git a/.changeset/pretty-cameras-jog.md b/.changeset/pretty-cameras-jog.md deleted file mode 100644 index 3c864ac716..0000000000 --- a/.changeset/pretty-cameras-jog.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Admin UI improvements: dynamic owner dropdowns with "(me)" indicator, quick follow-up form. diff --git a/.changeset/pretty-crabs-create.md b/.changeset/pretty-crabs-create.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/pretty-crabs-create.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/pretty-horses-add.md b/.changeset/pretty-horses-add.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/pretty-horses-add.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/proud-jars-attack.md b/.changeset/proud-jars-attack.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/proud-jars-attack.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/proud-pugs-punch.md b/.changeset/proud-pugs-punch.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/proud-pugs-punch.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/public-needles-argue.md b/.changeset/public-needles-argue.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/public-needles-argue.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/puny-terms-start.md b/.changeset/puny-terms-start.md deleted file mode 100644 index bafe4fd5eb..0000000000 --- a/.changeset/puny-terms-start.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add AXE (Agentic eXecution Engine) documentation with real-time targeting, brand safety, and frequency management details. diff --git a/.changeset/purple-rice-unite.md b/.changeset/purple-rice-unite.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/purple-rice-unite.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/quick-pandas-invent.md b/.changeset/quick-pandas-invent.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/quick-pandas-invent.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/ready-breads-matter.md b/.changeset/ready-breads-matter.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/ready-breads-matter.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/ready-kids-teach.md b/.changeset/ready-kids-teach.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/ready-kids-teach.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/real-cases-join.md b/.changeset/real-cases-join.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/real-cases-join.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/real-places-float.md b/.changeset/real-places-float.md deleted file mode 100644 index 23458d9db1..0000000000 --- a/.changeset/real-places-float.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -CI fix: Add publish step to release workflow to enable GitHub release creation. diff --git a/.changeset/red-mails-do.md b/.changeset/red-mails-do.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/red-mails-do.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/release-relaxed-schemas.md b/.changeset/release-relaxed-schemas.md deleted file mode 100644 index fb0609cfce..0000000000 --- a/.changeset/release-relaxed-schemas.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"adcontextprotocol": patch ---- - -Release schemas with `additionalProperties: true` for forward compatibility - -This releases `dist/schemas/2.5.2/` containing the relaxed schema validation -introduced in #646. Clients can now safely ignore unknown fields when parsing -API responses, allowing the API to evolve without breaking existing integrations. diff --git a/.changeset/ripe-ghosts-bet.md b/.changeset/ripe-ghosts-bet.md deleted file mode 100644 index 50f3463080..0000000000 --- a/.changeset/ripe-ghosts-bet.md +++ /dev/null @@ -1,9 +0,0 @@ ---- ---- - -Add escalation and learning capture tools for Addie - -- New `escalate_to_admin` tool for capability gaps and requests needing human action -- New `capture_learning` tool to flag valuable user insights for synthesis -- Admin API endpoints for managing escalations -- System prompt updates to prevent Addie from promising actions without tools diff --git a/.changeset/ripe-zebras-sell.md b/.changeset/ripe-zebras-sell.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/ripe-zebras-sell.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/salty-cooks-grin.md b/.changeset/salty-cooks-grin.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/salty-cooks-grin.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/seven-deer-marry.md b/.changeset/seven-deer-marry.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/seven-deer-marry.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/seven-heads-smile.md b/.changeset/seven-heads-smile.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/seven-heads-smile.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/seven-rules-invent.md b/.changeset/seven-rules-invent.md deleted file mode 100644 index 37a3d4f1bc..0000000000 --- a/.changeset/seven-rules-invent.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Allow working group leaders to schedule meetings for groups they lead via Addie. Added meeting and event tools documentation to Addie's system prompt. diff --git a/.changeset/seven-squids-carry.md b/.changeset/seven-squids-carry.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/seven-squids-carry.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/shaggy-rocks-train.md b/.changeset/shaggy-rocks-train.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/shaggy-rocks-train.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/shaky-paths-attend.md b/.changeset/shaky-paths-attend.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/shaky-paths-attend.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/shiny-points-spend.md b/.changeset/shiny-points-spend.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/shiny-points-spend.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/short-clouds-feel.md b/.changeset/short-clouds-feel.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/short-clouds-feel.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/shy-bats-run.md b/.changeset/shy-bats-run.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/shy-bats-run.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/silent-cars-watch.md b/.changeset/silent-cars-watch.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/silent-cars-watch.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/silent-crews-lose.md b/.changeset/silent-crews-lose.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/silent-crews-lose.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/silly-readers-hunt.md b/.changeset/silly-readers-hunt.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/silly-readers-hunt.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/silver-goats-exist.md b/.changeset/silver-goats-exist.md deleted file mode 100644 index e454a20f63..0000000000 --- a/.changeset/silver-goats-exist.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Internal server improvements: static fs import and proper 404 handling for HTML serving. diff --git a/.changeset/silver-suns-sell.md b/.changeset/silver-suns-sell.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/silver-suns-sell.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/sixty-colts-bathe.md b/.changeset/sixty-colts-bathe.md deleted file mode 100644 index a9682143b2..0000000000 --- a/.changeset/sixty-colts-bathe.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add company type and revenue tier collection during onboarding flow. diff --git a/.changeset/sixty-ends-ask.md b/.changeset/sixty-ends-ask.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/sixty-ends-ask.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/sixty-paws-punch.md b/.changeset/sixty-paws-punch.md deleted file mode 100644 index 9105fb5231..0000000000 --- a/.changeset/sixty-paws-punch.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add OpenTelemetry logging integration for PostHog and fix client-side JavaScript errors. diff --git a/.changeset/skip-welcome-engaged-users.md b/.changeset/skip-welcome-engaged-users.md deleted file mode 100644 index 01554d803f..0000000000 --- a/.changeset/skip-welcome-engaged-users.md +++ /dev/null @@ -1,6 +0,0 @@ ---- ---- - -No protocol impact - internal behavior change only. - -Skip intro/welcome goals for highly engaged users (committee leaders, working group members, council members, active Slack users). diff --git a/.changeset/slack-engagement-scores.md b/.changeset/slack-engagement-scores.md deleted file mode 100644 index 2a2fb92cb5..0000000000 --- a/.changeset/slack-engagement-scores.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Website only: Compute engagement scores for Slack-only contacts in admin dashboard diff --git a/.changeset/slick-camels-hide.md b/.changeset/slick-camels-hide.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/slick-camels-hide.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/slick-singers-prove.md b/.changeset/slick-singers-prove.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/slick-singers-prove.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/slick-tigers-invite.md b/.changeset/slick-tigers-invite.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/slick-tigers-invite.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/slimy-meteors-grab.md b/.changeset/slimy-meteors-grab.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/slimy-meteors-grab.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/slimy-singers-care.md b/.changeset/slimy-singers-care.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/slimy-singers-care.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/slow-snails-stand.md b/.changeset/slow-snails-stand.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/slow-snails-stand.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/slow-trains-agree.md b/.changeset/slow-trains-agree.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/slow-trains-agree.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/small-geese-leave.md b/.changeset/small-geese-leave.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/small-geese-leave.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/smart-olives-sort.md b/.changeset/smart-olives-sort.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/smart-olives-sort.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/smooth-rice-chew.md b/.changeset/smooth-rice-chew.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/smooth-rice-chew.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/soft-apples-play.md b/.changeset/soft-apples-play.md deleted file mode 100644 index 6bf8a359b8..0000000000 --- a/.changeset/soft-apples-play.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix profile completion goal to only show for paid members, handle personal workspaces diff --git a/.changeset/soft-rooms-spend.md b/.changeset/soft-rooms-spend.md deleted file mode 100644 index 968502484e..0000000000 --- a/.changeset/soft-rooms-spend.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Internal improvements to industry news page: HTML entity parsing, article sorting by publication date, and URL deduplication. diff --git a/.changeset/solid-hotels-occur.md b/.changeset/solid-hotels-occur.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/solid-hotels-occur.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/solid-ideas-shake.md b/.changeset/solid-ideas-shake.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/solid-ideas-shake.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/some-phones-study.md b/.changeset/some-phones-study.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/some-phones-study.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/some-teeth-judge.md b/.changeset/some-teeth-judge.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/some-teeth-judge.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/sparkly-camels-dream.md b/.changeset/sparkly-camels-dream.md deleted file mode 100644 index 648b907b34..0000000000 --- a/.changeset/sparkly-camels-dream.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Homepage layout refinement: tightened hero sections, moved Go Agentic section to prominent position after release banner, updated prompts to be more actionable, and removed mission statement band from org-index.html. diff --git a/.changeset/sparkly-onions-hug.md b/.changeset/sparkly-onions-hug.md deleted file mode 100644 index d9927220e2..0000000000 --- a/.changeset/sparkly-onions-hug.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add Addie email response capability - allows Addie to reply to email threads when CC'd and explicitly invoked diff --git a/.changeset/spicy-animals-fail.md b/.changeset/spicy-animals-fail.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/spicy-animals-fail.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/spicy-doors-flow.md b/.changeset/spicy-doors-flow.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/spicy-doors-flow.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/spicy-plants-rescue.md b/.changeset/spicy-plants-rescue.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/spicy-plants-rescue.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/spicy-tigers-double.md b/.changeset/spicy-tigers-double.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/spicy-tigers-double.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/stale-cobras-smile.md b/.changeset/stale-cobras-smile.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/stale-cobras-smile.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/stale-hats-dance.md b/.changeset/stale-hats-dance.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/stale-hats-dance.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/stale-tips-drive.md b/.changeset/stale-tips-drive.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/stale-tips-drive.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/strict-foxes-wash.md b/.changeset/strict-foxes-wash.md deleted file mode 100644 index 64c4740cc9..0000000000 --- a/.changeset/strict-foxes-wash.md +++ /dev/null @@ -1,8 +0,0 @@ ---- ---- - -fix: simplify engagement scoring and fix working group leader detection - -- Simplified engagement scoring: 10 points per Slack action + 20 points per event (capped at 100) -- Fixed working group count to include leaders as implicit members -- Committee leaders no longer receive "Discover Working Groups" recommendations diff --git a/.changeset/strict-lamps-turn.md b/.changeset/strict-lamps-turn.md deleted file mode 100644 index aab5a8b79f..0000000000 --- a/.changeset/strict-lamps-turn.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix Slack user linking in bulk prospect creation from domain discovery. diff --git a/.changeset/sunny-apples-jam.md b/.changeset/sunny-apples-jam.md deleted file mode 100644 index 3146a532c7..0000000000 --- a/.changeset/sunny-apples-jam.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Improve Addie's GitHub issue detection for client library bugs vs agent bugs diff --git a/.changeset/sunny-bears-stare.md b/.changeset/sunny-bears-stare.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/sunny-bears-stare.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/sweet-brooms-give.md b/.changeset/sweet-brooms-give.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/sweet-brooms-give.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/sweet-planes-follow.md b/.changeset/sweet-planes-follow.md deleted file mode 100644 index 6aa9556f09..0000000000 --- a/.changeset/sweet-planes-follow.md +++ /dev/null @@ -1,10 +0,0 @@ ---- ---- - -Improve Addie's Slack message understanding - -- Fix Addie not seeing forwarded Slack messages (content is in `attachments`, not `text`) -- Add reaction-based confirmations: thumbs up on "should I proceed?" means yes -- Add file share awareness: Addie now sees file metadata when users share files -- Add `fetch_url` tool: Addie can read content from URLs shared in messages -- Add `read_slack_file` tool: Addie can read text files shared in Slack diff --git a/.changeset/sweet-queens-send.md b/.changeset/sweet-queens-send.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/sweet-queens-send.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/sweet-taxis-act.md b/.changeset/sweet-taxis-act.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/sweet-taxis-act.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/swift-points-sniff.md b/.changeset/swift-points-sniff.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/swift-points-sniff.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/tame-hornets-peel.md b/.changeset/tame-hornets-peel.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/tame-hornets-peel.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/tame-hounds-swim.md b/.changeset/tame-hounds-swim.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/tame-hounds-swim.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/tame-llamas-shake.md b/.changeset/tame-llamas-shake.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/tame-llamas-shake.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/tasty-sails-tie.md b/.changeset/tasty-sails-tie.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/tasty-sails-tie.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/ten-bikes-shout.md b/.changeset/ten-bikes-shout.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/ten-bikes-shout.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/ten-crews-joke.md b/.changeset/ten-crews-joke.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/ten-crews-joke.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/ten-experts-fall.md b/.changeset/ten-experts-fall.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/ten-experts-fall.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/ten-groups-heal.md b/.changeset/ten-groups-heal.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/ten-groups-heal.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/ten-towns-admire.md b/.changeset/ten-towns-admire.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/ten-towns-admire.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/tender-experts-repeat.md b/.changeset/tender-experts-repeat.md deleted file mode 100644 index 4ceb3aa470..0000000000 --- a/.changeset/tender-experts-repeat.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Remove insight_goal_id references following migration 166 column drop. diff --git a/.changeset/tender-jeans-double.md b/.changeset/tender-jeans-double.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/tender-jeans-double.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/tender-needles-beg.md b/.changeset/tender-needles-beg.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/tender-needles-beg.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/tender-pants-glow.md b/.changeset/tender-pants-glow.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/tender-pants-glow.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/thick-cows-add.md b/.changeset/thick-cows-add.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/thick-cows-add.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/thin-lions-trade.md b/.changeset/thin-lions-trade.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/thin-lions-trade.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/thirty-dots-feel.md b/.changeset/thirty-dots-feel.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/thirty-dots-feel.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/thirty-papayas-rescue.md b/.changeset/thirty-papayas-rescue.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/thirty-papayas-rescue.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/thirty-regions-dance.md b/.changeset/thirty-regions-dance.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/thirty-regions-dance.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/thirty-snails-kneel.md b/.changeset/thirty-snails-kneel.md deleted file mode 100644 index efbd790a09..0000000000 --- a/.changeset/thirty-snails-kneel.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add token-aware context limiting for Addie to prevent prompt overflow errors in long conversations. diff --git a/.changeset/thirty-snakes-fix.md b/.changeset/thirty-snakes-fix.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/thirty-snakes-fix.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/tiny-games-decide.md b/.changeset/tiny-games-decide.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/tiny-games-decide.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/tiny-news-smash.md b/.changeset/tiny-news-smash.md deleted file mode 100644 index d453bc8770..0000000000 --- a/.changeset/tiny-news-smash.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add mobile app-like experience with slide-in navigation, safe area handling, touch-optimized targets, and PWA support. diff --git a/.changeset/tired-rockets-lie.md b/.changeset/tired-rockets-lie.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/tired-rockets-lie.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/tricky-baths-obey.md b/.changeset/tricky-baths-obey.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/tricky-baths-obey.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/tricky-worms-push.md b/.changeset/tricky-worms-push.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/tricky-worms-push.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/twelve-baboons-smell.md b/.changeset/twelve-baboons-smell.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/twelve-baboons-smell.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/twelve-books-cross.md b/.changeset/twelve-books-cross.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/twelve-books-cross.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/twenty-ads-lay.md b/.changeset/twenty-ads-lay.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/twenty-ads-lay.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/twenty-bobcats-fall.md b/.changeset/twenty-bobcats-fall.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/twenty-bobcats-fall.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/wacky-seals-eat.md b/.changeset/wacky-seals-eat.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/wacky-seals-eat.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/wacky-things-refuse.md b/.changeset/wacky-things-refuse.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/wacky-things-refuse.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/wacky-worms-speak.md b/.changeset/wacky-worms-speak.md deleted file mode 100644 index 42ecbe41cd..0000000000 --- a/.changeset/wacky-worms-speak.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Fix admin tools not being available in Slack DM threads, add timeframe filtering to conversation stats, and fix feedback UI re-rendering after save diff --git a/.changeset/warm-jars-attack.md b/.changeset/warm-jars-attack.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/warm-jars-attack.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/warm-lamps-accept.md b/.changeset/warm-lamps-accept.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/warm-lamps-accept.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/wet-keys-wash.md b/.changeset/wet-keys-wash.md deleted file mode 100644 index 2405357f90..0000000000 --- a/.changeset/wet-keys-wash.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Add brand_manifest parameter to test_adcp_agent tool, allowing users to specify custom brands when testing agents. Defaults to Nike if not provided. diff --git a/.changeset/wicked-islands-tickle.md b/.changeset/wicked-islands-tickle.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/wicked-islands-tickle.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/wicked-memes-act.md b/.changeset/wicked-memes-act.md deleted file mode 100644 index 7596795353..0000000000 --- a/.changeset/wicked-memes-act.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Admin collaboration features for outreach: add insight button, year reference cleanup, vendor membership goal, admin nudge/override diff --git a/.changeset/wide-kiwis-say.md b/.changeset/wide-kiwis-say.md deleted file mode 100644 index 34a6a9b184..0000000000 --- a/.changeset/wide-kiwis-say.md +++ /dev/null @@ -1,11 +0,0 @@ ---- ---- - -Consolidate rating system to thumbs up/down and add rating_source to distinguish user vs admin feedback. - -Add eval framework for testing rule changes against historical interactions: -- New tables: addie_eval_runs, addie_eval_results -- Re-execute historical messages with proposed rules using real Claude calls -- Compare original vs new responses (routing, tools, response text) -- Human review with verdicts (improved/same/worse/uncertain) -- API endpoints: POST/GET /api/admin/addie/eval/runs, GET /results, PUT /review diff --git a/.changeset/wide-shirts-attack.md b/.changeset/wide-shirts-attack.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/wide-shirts-attack.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/wise-bags-invite.md b/.changeset/wise-bags-invite.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/wise-bags-invite.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/wise-places-invent.md b/.changeset/wise-places-invent.md deleted file mode 100644 index 1a9d8eea67..0000000000 --- a/.changeset/wise-places-invent.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Documentation improvements: Added testable JSON examples with $schema declarations to formats.mdx, consolidated list_creative_formats docs, and split preview_creative.mdx into core reference and advanced guide for better readability. diff --git a/.changeset/wise-tigers-lose.md b/.changeset/wise-tigers-lose.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/wise-tigers-lose.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/witty-teeth-live.md b/.changeset/witty-teeth-live.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/witty-teeth-live.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/yellow-ghosts-appear.md b/.changeset/yellow-ghosts-appear.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/yellow-ghosts-appear.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/yellow-knives-warn.md b/.changeset/yellow-knives-warn.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/yellow-knives-warn.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/yellow-llamas-lie.md b/.changeset/yellow-llamas-lie.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/yellow-llamas-lie.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/young-baths-kiss.md b/.changeset/young-baths-kiss.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/young-baths-kiss.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/young-masks-care.md b/.changeset/young-masks-care.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/young-masks-care.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/yummy-areas-bet.md b/.changeset/yummy-areas-bet.md deleted file mode 100644 index 99bd059314..0000000000 --- a/.changeset/yummy-areas-bet.md +++ /dev/null @@ -1,9 +0,0 @@ ---- ---- - -Add regional chapters and industry event presence features: -- User location tracking (city, country) for chapter matching -- Event groups (committee_type: 'event') linked to industry events -- Slack channel auto-sync: join channel = join group -- Admin UI for event groups and chapters -- Addie tools for member-driven chapter creation diff --git a/CHANGELOG.md b/CHANGELOG.md index fc9abadea1..39b2f0d568 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,53 @@ **Migration:** Non-breaking change. `assets_required` is deprecated but still supported. New implementations should use `assets`. +## 2.5.3 + +### Patch Changes + +- 309a880: Allow additional properties in all JSON schemas for forward compatibility + + Changes all schemas from `"additionalProperties": false` to `"additionalProperties": true`. This enables clients running older schema versions to accept responses from servers with newer schemas without breaking validation - a standard practice for protocol evolution in distributed systems. + +- 5d0ce75: Add explicit type definition to error.json details property + + The `details` property in core/error.json now explicitly declares `"type": "object"` and `"additionalProperties": true`, consistent with other error details definitions in the codebase. This addresses issue #343 where the data type was unspecified. + +- cdcd70f: Fix migration 151 to delete duplicates before updating Slack IDs to WorkOS IDs +- 39abf79: Add missing fields to package request schemas for consistency with core/package.json. + + **Schema Changes:** + + - `media-buy/package-request.json`: Added `impressions` and `paused` fields + - `media-buy/update-media-buy-request.json`: Added `impressions` field to package updates + + **Details:** + + - `impressions`: Impression goal for the package (optional, minimum: 0) + - `paused`: Create package in paused state (optional, default: false) + + These fields were defined in `core/package.json` but missing from the request schemas, making it impossible to set impression goals or initial paused state when creating/updating media buys. + + **Documentation:** + + - Updated `create_media_buy` task reference with new package parameters + - Updated `update_media_buy` task reference with impressions parameter + +- fa68588: fix: display Slack profile name for chapter leaders without WorkOS accounts + + Leaders added via Slack ID that haven't linked their WorkOS account now display + their Slack profile name (real_name or display_name) instead of the raw Slack + user ID (e.g., U09BEKNJ3GB). + + The getLeaders and getLeadersBatch queries now include slack_user_mappings as an + additional name source in the COALESCE chain. + +- 9315247: Release schemas with `additionalProperties: true` for forward compatibility + + This releases `dist/schemas/2.5.2/` containing the relaxed schema validation + introduced in #646. Clients can now safely ignore unknown fields when parsing + API responses, allowing the API to evolve without breaking existing integrations. + ## 2.5.2 ### Patch Changes diff --git a/dist/schemas/2.5.3/adagents.json b/dist/schemas/2.5.3/adagents.json new file mode 100644 index 0000000000..f36134db23 --- /dev/null +++ b/dist/schemas/2.5.3/adagents.json @@ -0,0 +1,494 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.5.3/adagents.json", + "title": "Authorized Sales Agents", + "description": "Declaration of authorized sales agents for advertising inventory. Hosted at /.well-known/adagents.json on publisher domains. Can either contain the full structure inline or reference an authoritative URL.", + "oneOf": [ + { + "type": "object", + "description": "URL reference variant - points to the authoritative location of the adagents.json file", + "properties": { + "$schema": { + "type": "string", + "description": "JSON Schema identifier for this adagents.json file" + }, + "authoritative_location": { + "type": "string", + "format": "uri", + "pattern": "^https://", + "description": "HTTPS URL of the authoritative adagents.json file. When present, this file is a reference and the authoritative location contains the actual agent authorization data." + }, + "last_updated": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp indicating when this reference was last updated" + } + }, + "required": [ + "authoritative_location" + ], + "additionalProperties": true + }, + { + "type": "object", + "description": "Inline structure variant - contains full agent authorization data", + "properties": { + "$schema": { + "type": "string", + "description": "JSON Schema identifier for this adagents.json file" + }, + "contact": { + "type": "object", + "description": "Contact information for the entity managing this adagents.json file (may be publisher or third-party operator)", + "properties": { + "name": { + "type": "string", + "description": "Name of the entity managing this file (e.g., 'Meta Advertising Operations', 'Clear Channel Digital')", + "minLength": 1, + "maxLength": 255 + }, + "email": { + "type": "string", + "format": "email", + "description": "Contact email for questions or issues with this authorization file", + "minLength": 1, + "maxLength": 255 + }, + "domain": { + "type": "string", + "description": "Primary domain of the entity managing this file", + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$" + }, + "seller_id": { + "type": "string", + "description": "Seller ID from IAB Tech Lab sellers.json (if applicable)", + "minLength": 1, + "maxLength": 255 + }, + "tag_id": { + "type": "string", + "description": "TAG Certified Against Fraud ID for verification (if applicable)", + "minLength": 1, + "maxLength": 100 + } + }, + "required": [ + "name" + ], + "additionalProperties": true + }, + "properties": { + "type": "array", + "description": "Array of all properties covered by this adagents.json file. Defines the canonical property list that authorized agents reference.", + "items": { + "$ref": "/schemas/2.5.3/core/property.json" + }, + "minItems": 1 + }, + "tags": { + "type": "object", + "description": "Metadata for each tag referenced by properties. Provides human-readable context for property tag values.", + "additionalProperties": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Human-readable name for this tag" + }, + "description": { + "type": "string", + "description": "Description of what this tag represents" + } + }, + "required": [ + "name", + "description" + ], + "additionalProperties": true + } + }, + "authorized_agents": { + "type": "array", + "description": "Array of sales agents authorized to sell inventory for properties in this file", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "The authorized agent's API endpoint URL" + }, + "authorized_for": { + "type": "string", + "description": "Human-readable description of what this agent is authorized to sell", + "minLength": 1, + "maxLength": 500 + }, + "authorization_type": { + "type": "string", + "const": "property_ids", + "description": "Discriminator indicating authorization by specific property IDs" + }, + "property_ids": { + "type": "array", + "description": "Property IDs this agent is authorized for. Resolved against the top-level properties array in this file", + "items": { + "$ref": "/schemas/2.5.3/core/property-id.json" + }, + "minItems": 1 + } + }, + "required": [ + "url", + "authorized_for", + "authorization_type", + "property_ids" + ], + "additionalProperties": true + }, + { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "The authorized agent's API endpoint URL" + }, + "authorized_for": { + "type": "string", + "description": "Human-readable description of what this agent is authorized to sell", + "minLength": 1, + "maxLength": 500 + }, + "authorization_type": { + "type": "string", + "const": "property_tags", + "description": "Discriminator indicating authorization by property tags" + }, + "property_tags": { + "type": "array", + "description": "Tags identifying which properties this agent is authorized for. Resolved against the top-level properties array in this file using tag matching", + "items": { + "$ref": "/schemas/2.5.3/core/property-tag.json" + }, + "minItems": 1 + } + }, + "required": [ + "url", + "authorized_for", + "authorization_type", + "property_tags" + ], + "additionalProperties": true + }, + { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "The authorized agent's API endpoint URL" + }, + "authorized_for": { + "type": "string", + "description": "Human-readable description of what this agent is authorized to sell", + "minLength": 1, + "maxLength": 500 + }, + "authorization_type": { + "type": "string", + "const": "inline_properties", + "description": "Discriminator indicating authorization by inline property definitions" + }, + "properties": { + "type": "array", + "description": "Specific properties this agent is authorized for (alternative to property_ids/property_tags)", + "items": { + "$ref": "/schemas/2.5.3/core/property.json" + }, + "minItems": 1 + } + }, + "required": [ + "url", + "authorized_for", + "authorization_type", + "properties" + ], + "additionalProperties": true + }, + { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "The authorized agent's API endpoint URL" + }, + "authorized_for": { + "type": "string", + "description": "Human-readable description of what this agent is authorized to sell", + "minLength": 1, + "maxLength": 500 + }, + "authorization_type": { + "type": "string", + "const": "publisher_properties", + "description": "Discriminator indicating authorization for properties from other publisher domains" + }, + "publisher_properties": { + "type": "array", + "description": "Properties from other publisher domains this agent is authorized for. Each entry specifies a publisher domain and which of their properties this agent can sell", + "items": { + "$ref": "/schemas/2.5.3/core/publisher-property-selector.json" + }, + "minItems": 1 + } + }, + "required": [ + "url", + "authorized_for", + "authorization_type", + "publisher_properties" + ], + "additionalProperties": true + } + ] + }, + "minItems": 1 + }, + "last_updated": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp indicating when this file was last updated" + } + }, + "required": [ + "authorized_agents" + ], + "additionalProperties": true + } + ], + "examples": [ + { + "$schema": "/schemas/2.5.3/adagents.json", + "authoritative_location": "https://cdn.example.com/adagents/v2/adagents.json", + "last_updated": "2025-01-15T10:00:00Z" + }, + { + "$schema": "/schemas/2.5.3/adagents.json", + "properties": [ + { + "property_type": "website", + "name": "Example Site", + "identifiers": [ + { + "type": "domain", + "value": "example.com" + } + ], + "publisher_domain": "example.com" + } + ], + "authorized_agents": [ + { + "url": "https://agent.example.com", + "authorized_for": "Official sales agent", + "authorization_type": "property_tags", + "property_tags": [ + "all" + ] + } + ], + "tags": { + "all": { + "name": "All Properties", + "description": "All properties in this file" + } + }, + "last_updated": "2025-01-10T12:00:00Z" + }, + { + "$schema": "/schemas/2.5.3/adagents.json", + "contact": { + "name": "Meta Advertising Operations", + "email": "adops@meta.com", + "domain": "meta.com", + "seller_id": "pub-meta-12345", + "tag_id": "12345" + }, + "properties": [ + { + "property_type": "mobile_app", + "name": "Instagram", + "identifiers": [ + { + "type": "ios_bundle", + "value": "com.burbn.instagram" + }, + { + "type": "android_package", + "value": "com.instagram.android" + } + ], + "tags": [ + "meta_network", + "social_media" + ], + "publisher_domain": "instagram.com" + }, + { + "property_type": "mobile_app", + "name": "Facebook", + "identifiers": [ + { + "type": "ios_bundle", + "value": "com.facebook.Facebook" + }, + { + "type": "android_package", + "value": "com.facebook.katana" + } + ], + "tags": [ + "meta_network", + "social_media" + ], + "publisher_domain": "facebook.com" + }, + { + "property_type": "mobile_app", + "name": "WhatsApp", + "identifiers": [ + { + "type": "ios_bundle", + "value": "net.whatsapp.WhatsApp" + }, + { + "type": "android_package", + "value": "com.whatsapp" + } + ], + "tags": [ + "meta_network", + "messaging" + ], + "publisher_domain": "whatsapp.com" + } + ], + "tags": { + "meta_network": { + "name": "Meta Network", + "description": "All Meta-owned properties" + }, + "social_media": { + "name": "Social Media Apps", + "description": "Social networking applications" + }, + "messaging": { + "name": "Messaging Apps", + "description": "Messaging and communication apps" + } + }, + "authorized_agents": [ + { + "url": "https://meta-ads.com", + "authorized_for": "All Meta properties", + "authorization_type": "property_tags", + "property_tags": [ + "meta_network" + ] + } + ], + "last_updated": "2025-01-10T15:30:00Z" + }, + { + "$schema": "/schemas/2.5.3/adagents.json", + "contact": { + "name": "Tumblr Advertising" + }, + "properties": [ + { + "property_type": "website", + "name": "Tumblr Corporate", + "identifiers": [ + { + "type": "domain", + "value": "tumblr.com" + } + ], + "tags": [ + "corporate" + ], + "publisher_domain": "tumblr.com" + } + ], + "tags": { + "corporate": { + "name": "Corporate Properties", + "description": "Tumblr-owned corporate properties (not user blogs)" + } + }, + "authorized_agents": [ + { + "url": "https://tumblr-sales.com", + "authorized_for": "Tumblr corporate properties only", + "authorization_type": "property_tags", + "property_tags": [ + "corporate" + ] + } + ], + "last_updated": "2025-01-10T16:00:00Z" + }, + { + "$schema": "/schemas/2.5.3/adagents.json", + "contact": { + "name": "Example Third-Party Sales Agent", + "email": "sales@agent.example", + "domain": "agent.example" + }, + "authorized_agents": [ + { + "url": "https://agent.example/api", + "authorized_for": "CNN CTV properties via publisher authorization", + "authorization_type": "publisher_properties", + "publisher_properties": [ + { + "publisher_domain": "cnn.com", + "selection_type": "by_id", + "property_ids": [ + "cnn_ctv_app" + ] + } + ] + }, + { + "url": "https://agent.example/api", + "authorized_for": "All CTV properties from multiple publishers", + "authorization_type": "publisher_properties", + "publisher_properties": [ + { + "publisher_domain": "cnn.com", + "selection_type": "by_tag", + "property_tags": [ + "ctv" + ] + }, + { + "publisher_domain": "espn.com", + "selection_type": "by_tag", + "property_tags": [ + "ctv" + ] + } + ] + } + ], + "last_updated": "2025-01-10T17:00:00Z" + } + ] +} diff --git a/dist/schemas/2.5.3/bundled/media-buy/build-creative-request.json b/dist/schemas/2.5.3/bundled/media-buy/build-creative-request.json new file mode 100644 index 0000000000..f915a27362 --- /dev/null +++ b/dist/schemas/2.5.3/bundled/media-buy/build-creative-request.json @@ -0,0 +1,1349 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/2.5.3/bundled/media-buy/build-creative-request.json", + "title": "Build Creative Request", + "description": "Request to transform or generate a creative manifest. Takes a source manifest (which may be minimal for pure generation) and produces a target manifest in the specified format. The source manifest should include all assets required by the target format (e.g., promoted_offerings for generative formats).", + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Natural language instructions for the transformation or generation. For pure generation, this is the creative brief. For transformation, this provides guidance on how to adapt the creative." + }, + "creative_manifest": { + "title": "Creative Manifest", + "description": "Creative manifest to transform or generate from. For pure generation, this should include the target format_id and any required input assets (e.g., promoted_offerings for generative formats). For transformation (e.g., resizing, reformatting), this is the complete creative to adapt.", + "type": "object", + "properties": { + "format_id": { + "title": "Format ID", + "description": "Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height/unit in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250, unit: 'px'}).", + "type": "object", + "properties": { + "agent_url": { + "type": "string", + "format": "uri", + "description": "URL of the agent that defines this format (e.g., 'https://creatives.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats)" + }, + "id": { + "type": "string", + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant." + }, + "width": { + "type": "integer", + "minimum": 1, + "description": "Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants." + }, + "height": { + "type": "integer", + "minimum": 1, + "description": "Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants." + }, + "duration_ms": { + "type": "number", + "minimum": 1, + "description": "Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters." + } + }, + "required": [ + "agent_url", + "id" + ], + "additionalProperties": true, + "dependencies": { + "width": [ + "height" + ], + "height": [ + "width" + ] + } + }, + "promoted_offering": { + "type": "string", + "description": "Product name or offering being advertised. Maps to promoted_offerings in create_media_buy request to associate creative with the product being promoted." + }, + "assets": { + "type": "object", + "description": "Map of asset IDs to actual asset content. Each key MUST match an asset_id from the format's assets_required array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). The asset_id is the technical identifier used to match assets to format requirements.\n\nIMPORTANT: Creative manifest validation MUST be performed in the context of the format specification. The format defines what type each asset_id should be, which eliminates any validation ambiguity.", + "patternProperties": { + "^[a-z0-9_]+$": { + "oneOf": [ + { + "title": "Image Asset", + "description": "Image asset with URL and dimensions", + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "URL to the image asset" + }, + "width": { + "type": "integer", + "minimum": 1, + "description": "Width in pixels" + }, + "height": { + "type": "integer", + "minimum": 1, + "description": "Height in pixels" + }, + "format": { + "type": "string", + "description": "Image file format (jpg, png, gif, webp, etc.)" + }, + "alt_text": { + "type": "string", + "description": "Alternative text for accessibility" + } + }, + "required": [ + "url", + "width", + "height" + ], + "additionalProperties": true + }, + { + "title": "Video Asset", + "description": "Video asset with URL and specifications", + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "URL to the video asset" + }, + "width": { + "type": "integer", + "minimum": 1, + "description": "Width in pixels" + }, + "height": { + "type": "integer", + "minimum": 1, + "description": "Height in pixels" + }, + "duration_ms": { + "type": "integer", + "description": "Video duration in milliseconds", + "minimum": 1 + }, + "format": { + "type": "string", + "description": "Video file format (mp4, webm, mov, etc.)" + }, + "bitrate_kbps": { + "type": "integer", + "description": "Video bitrate in kilobits per second", + "minimum": 1 + } + }, + "required": [ + "url", + "width", + "height" + ], + "additionalProperties": true + }, + { + "title": "Audio Asset", + "description": "Audio asset with URL and specifications", + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "URL to the audio asset" + }, + "duration_ms": { + "type": "integer", + "description": "Audio duration in milliseconds", + "minimum": 0 + }, + "format": { + "type": "string", + "description": "Audio file format (mp3, wav, aac, etc.)" + }, + "bitrate_kbps": { + "type": "integer", + "description": "Audio bitrate in kilobits per second", + "minimum": 1 + } + }, + "required": [ + "url" + ], + "additionalProperties": true + }, + { + "title": "VAST Asset", + "description": "VAST (Video Ad Serving Template) tag for third-party video ad serving", + "oneOf": [ + { + "type": "object", + "properties": { + "delivery_type": { + "type": "string", + "const": "url", + "description": "Discriminator indicating VAST is delivered via URL endpoint" + }, + "url": { + "type": "string", + "format": "uri", + "description": "URL endpoint that returns VAST XML" + }, + "vast_version": { + "title": "VAST Version", + "description": "VAST specification version", + "type": "string", + "enum": [ + "2.0", + "3.0", + "4.0", + "4.1", + "4.2" + ] + }, + "vpaid_enabled": { + "type": "boolean", + "description": "Whether VPAID (Video Player-Ad Interface Definition) is supported" + }, + "duration_ms": { + "type": "integer", + "description": "Expected video duration in milliseconds (if known)", + "minimum": 0 + }, + "tracking_events": { + "type": "array", + "items": { + "title": "VAST Tracking Event", + "description": "Standard VAST tracking events for video ad playback and interaction", + "type": "string", + "enum": [ + "start", + "firstQuartile", + "midpoint", + "thirdQuartile", + "complete", + "impression", + "click", + "pause", + "resume", + "skip", + "mute", + "unmute", + "fullscreen", + "exitFullscreen", + "playerExpand", + "playerCollapse" + ] + }, + "description": "Tracking events supported by this VAST tag" + } + }, + "required": [ + "delivery_type", + "url" + ], + "additionalProperties": true + }, + { + "type": "object", + "properties": { + "delivery_type": { + "type": "string", + "const": "inline", + "description": "Discriminator indicating VAST is delivered as inline XML content" + }, + "content": { + "type": "string", + "description": "Inline VAST XML content" + }, + "vast_version": { + "title": "VAST Version", + "description": "VAST specification version", + "type": "string", + "enum": [ + "2.0", + "3.0", + "4.0", + "4.1", + "4.2" + ] + }, + "vpaid_enabled": { + "type": "boolean", + "description": "Whether VPAID (Video Player-Ad Interface Definition) is supported" + }, + "duration_ms": { + "type": "integer", + "description": "Expected video duration in milliseconds (if known)", + "minimum": 0 + }, + "tracking_events": { + "type": "array", + "items": { + "title": "VAST Tracking Event", + "description": "Standard VAST tracking events for video ad playback and interaction", + "type": "string", + "enum": [ + "start", + "firstQuartile", + "midpoint", + "thirdQuartile", + "complete", + "impression", + "click", + "pause", + "resume", + "skip", + "mute", + "unmute", + "fullscreen", + "exitFullscreen", + "playerExpand", + "playerCollapse" + ] + }, + "description": "Tracking events supported by this VAST tag" + } + }, + "required": [ + "delivery_type", + "content" + ], + "additionalProperties": true + } + ] + }, + { + "title": "Text Asset", + "description": "Text content asset", + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "Text content" + }, + "language": { + "type": "string", + "description": "Language code (e.g., 'en', 'es', 'fr')" + } + }, + "required": [ + "content" + ], + "additionalProperties": true + }, + { + "title": "URL Asset", + "description": "URL reference asset", + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "URL reference" + }, + "url_type": { + "title": "URL Asset Type", + "description": "Type of URL asset: 'clickthrough' for user click destination (landing page), 'tracker_pixel' for impression/event tracking via HTTP request (fires GET, expects pixel/204 response), 'tracker_script' for measurement SDKs that must load as + + + + + +
+ +
+ Loading... +
+ + + + + + diff --git a/server/public/admin-settings.html b/server/public/admin-settings.html index d7b9e6ce67..e639b4a6e4 100644 --- a/server/public/admin-settings.html +++ b/server/public/admin-settings.html @@ -192,6 +192,32 @@

Billing Notifications Channel

+ +
+

Escalation Notifications Channel

+

+ Choose which Slack channel receives escalation notifications when Addie cannot fulfill a request. + This allows admins to be notified when users need human assistance. +

+ +
+ Loading... +
+ +
+
+ + + Select a channel where admins can see and respond to escalations. View escalations +
+ +
+
+ @@ -214,6 +240,7 @@

Billing Notifications Channel

currentSettings = await response.json(); updateCurrentChannelDisplay(); + updateCurrentEscalationChannelDisplay(); document.getElementById('loading').style.display = 'none'; document.getElementById('content').style.display = 'block'; @@ -229,7 +256,8 @@

Billing Notifications Channel

// Load Slack channels for picker async function loadSlackChannels() { - const select = document.getElementById('billingChannel'); + const billingSelect = document.getElementById('billingChannel'); + const escalationSelect = document.getElementById('escalationChannel'); try { const response = await fetch('/api/admin/settings/slack-channels'); @@ -241,24 +269,34 @@

Billing Notifications Channel

const data = await response.json(); slackChannels = data.channels; - // Build options - let html = ''; + // Build options for both dropdowns + let billingHtml = ''; + let escalationHtml = ''; + if (slackChannels.length === 0) { - html = ''; - select.innerHTML = html; - showStatusMessage('Addie is not in any private channels. Invite @Addie to your billing channel and refresh.', 'error'); + const emptyOption = ''; + billingSelect.innerHTML = emptyOption; + escalationSelect.innerHTML = emptyOption; + showStatusMessage('Addie is not in any private channels. Invite @Addie to your channels and refresh.', 'error'); } else { slackChannels.forEach(ch => { - const selected = currentSettings?.billing_channel?.channel_id === ch.id ? 'selected' : ''; - html += ``; + const billingSelected = currentSettings?.billing_channel?.channel_id === ch.id ? 'selected' : ''; + const escalationSelected = currentSettings?.escalation_channel?.channel_id === ch.id ? 'selected' : ''; + billingHtml += ``; + escalationHtml += ``; }); - select.innerHTML = html; - select.disabled = false; + billingSelect.innerHTML = billingHtml; + billingSelect.disabled = false; document.getElementById('saveBtn').disabled = false; + + escalationSelect.innerHTML = escalationHtml; + escalationSelect.disabled = false; + document.getElementById('saveEscalationBtn').disabled = false; } } catch (error) { console.error('Error loading Slack channels:', error); - select.innerHTML = ''; + billingSelect.innerHTML = ''; + escalationSelect.innerHTML = ''; showStatusMessage('Failed to load Slack channels: ' + error.message, 'error'); } } @@ -277,6 +315,20 @@

Billing Notifications Channel

} } + // Update the current escalation channel display + function updateCurrentEscalationChannelDisplay() { + const el = document.getElementById('currentEscalationChannel'); + const channel = currentSettings?.escalation_channel; + + if (channel?.channel_id && channel?.channel_name) { + el.className = 'current-value'; + el.innerHTML = `Current: #${escapeHtml(channel.channel_name)}`; + } else { + el.className = 'current-value not-set'; + el.innerHTML = 'Current: Not configured (escalation notifications disabled)'; + } + } + // Save billing channel async function saveBillingChannel() { const select = document.getElementById('billingChannel'); @@ -326,6 +378,55 @@

Billing Notifications Channel

} } + // Save escalation channel + async function saveEscalationChannel() { + const select = document.getElementById('escalationChannel'); + const saveBtn = document.getElementById('saveEscalationBtn'); + const channelId = select.value || null; + + // Find channel name + let channelName = null; + if (channelId) { + const channel = slackChannels.find(c => c.id === channelId); + channelName = channel?.name || null; + } + + saveBtn.disabled = true; + saveBtn.textContent = 'Saving...'; + + try { + const response = await fetch('/api/admin/settings/escalation-channel', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ channel_id: channelId, channel_name: channelName }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.message || 'Failed to save'); + } + + const data = await response.json(); + currentSettings.escalation_channel = data.escalation_channel; + updateCurrentEscalationChannelDisplay(); + + saveBtn.textContent = 'Saved!'; + saveBtn.classList.add('btn-success'); + showStatusMessage('Escalation channel updated successfully', 'success'); + + setTimeout(() => { + saveBtn.textContent = 'Save'; + saveBtn.classList.remove('btn-success'); + saveBtn.disabled = false; + }, 2000); + } catch (error) { + console.error('Error saving escalation channel:', error); + showStatusMessage('Failed to save: ' + error.message, 'error'); + saveBtn.textContent = 'Save'; + saveBtn.disabled = false; + } + } + // Status message helpers function showStatusMessage(message, type) { const el = document.getElementById('statusMessage'); diff --git a/server/public/admin-sidebar.js b/server/public/admin-sidebar.js index 3dc75dce5c..9f650fe630 100644 --- a/server/public/admin-sidebar.js +++ b/server/public/admin-sidebar.js @@ -46,6 +46,7 @@ { href: '/admin/agreements', label: 'Agreements', icon: '📋' }, { href: '/admin/email', label: 'Email', icon: '📧' }, { href: '/admin/addie', label: 'Addie', icon: '🤖' }, + { href: '/admin/escalations', label: 'Escalations', icon: '🚨' }, { href: '/admin/feeds', label: 'Industry Feeds', icon: '📰' }, { href: '/admin/notification-channels', label: 'Alert Channels', icon: '📢' }, { href: '/admin/api-keys', label: 'API Keys', icon: '🔑' }, diff --git a/server/src/addie/mcp/escalation-tools.ts b/server/src/addie/mcp/escalation-tools.ts index d27b5343b3..abb1518661 100644 --- a/server/src/addie/mcp/escalation-tools.ts +++ b/server/src/addie/mcp/escalation-tools.ts @@ -17,7 +17,7 @@ import { } from '../../db/escalation-db.js'; import { getThreadService } from '../thread-service.js'; import { sendChannelMessage } from '../../slack/client.js'; -import { getActiveChannels } from '../../db/notification-channels-db.js'; +import { getEscalationChannel } from '../../db/system-settings-db.js'; import { AddieDatabase } from '../../db/addie-db.js'; const logger = createLogger('addie-escalation-tools'); @@ -112,15 +112,11 @@ DO NOT use for: ]; /** - * Find the escalation notification channel + * Get the configured escalation notification channel from system settings */ async function getEscalationChannelId(): Promise { - const channels = await getActiveChannels(); - // Look for a channel named "Addie Escalations" or similar - const escalationChannel = channels.find( - (c) => c.name.toLowerCase().includes('escalation') || c.name.toLowerCase().includes('addie-admin') - ); - return escalationChannel?.slack_channel_id || null; + const setting = await getEscalationChannel(); + return setting.channel_id; } /** diff --git a/server/src/db/system-settings-db.ts b/server/src/db/system-settings-db.ts index d63af1a3c7..1a0521da2f 100644 --- a/server/src/db/system-settings-db.ts +++ b/server/src/db/system-settings-db.ts @@ -20,10 +20,16 @@ export interface BillingChannelSetting { channel_name: string | null; } +export interface EscalationChannelSetting { + channel_id: string | null; + channel_name: string | null; +} + // ============== Setting Keys ============== export const SETTING_KEYS = { BILLING_SLACK_CHANNEL: 'billing_slack_channel', + ESCALATION_SLACK_CHANNEL: 'escalation_slack_channel', } as const; // ============== Generic Operations ============== @@ -90,3 +96,28 @@ export async function setBillingChannel( updatedBy ); } + +// ============== Escalation Channel Operations ============== + +/** + * Get the configured escalation notification Slack channel + */ +export async function getEscalationChannel(): Promise { + const result = await getSetting(SETTING_KEYS.ESCALATION_SLACK_CHANNEL); + return result ?? { channel_id: null, channel_name: null }; +} + +/** + * Set the escalation notification Slack channel + */ +export async function setEscalationChannel( + channelId: string | null, + channelName: string | null, + updatedBy?: string +): Promise { + await setSetting( + SETTING_KEYS.ESCALATION_SLACK_CHANNEL, + { channel_id: channelId, channel_name: channelName }, + updatedBy + ); +} diff --git a/server/src/http.ts b/server/src/http.ts index 244cd4da30..ed2e1f978d 100644 --- a/server/src/http.ts +++ b/server/src/http.ts @@ -3529,6 +3529,10 @@ Disallow: /api/admin/ await this.serveHtmlWithConfig(req, res, 'admin-settings.html'); }); + this.app.get('/admin/escalations', requireAuth, requireAdmin, async (req, res) => { + await this.serveHtmlWithConfig(req, res, 'admin-escalations.html'); + }); + // Registry API endpoints (consolidated agents, publishers, lookups) this.setupRegistryRoutes(); } diff --git a/server/src/routes/admin/settings.ts b/server/src/routes/admin/settings.ts index 08a8ec539b..ac95e81e47 100644 --- a/server/src/routes/admin/settings.ts +++ b/server/src/routes/admin/settings.ts @@ -13,6 +13,8 @@ import { getAllSettings, getBillingChannel, setBillingChannel, + getEscalationChannel, + setEscalationChannel, } from '../../db/system-settings-db.js'; import { getSlackChannels, getChannelInfo, isSlackConfigured } from '../../slack/client.js'; @@ -26,10 +28,12 @@ export function createAdminSettingsRouter(): Router { try { const settings = await getAllSettings(); const billingChannel = await getBillingChannel(); + const escalationChannel = await getEscalationChannel(); res.json({ settings, billing_channel: billingChannel, + escalation_channel: escalationChannel, }); } catch (error) { logger.error({ err: error }, 'Failed to get system settings'); @@ -136,5 +140,64 @@ export function createAdminSettingsRouter(): Router { } }); + // PUT /api/admin/settings/escalation-channel - Update escalation notification channel + router.put('/escalation-channel', requireAuth, requireAdmin, async (req: Request, res: Response) => { + try { + const { channel_id, channel_name } = req.body; + + // Allow null to clear the channel + if (channel_id !== null && channel_id !== undefined) { + // Validate channel ID format + if (typeof channel_id !== 'string' || !/^[CG][A-Z0-9]+$/.test(channel_id)) { + res.status(400).json({ + error: 'Invalid channel ID format', + message: 'Channel ID should start with C or G followed by alphanumeric characters', + }); + return; + } + + // Verify the channel is private (escalation info may contain sensitive user data) + if (isSlackConfigured()) { + const channelInfo = await getChannelInfo(channel_id); + if (channelInfo && !channelInfo.is_private) { + res.status(400).json({ + error: 'Invalid channel', + message: 'Only private channels are allowed for escalation notifications', + }); + return; + } + } + } + + // Validate channel name if provided + if (channel_name !== null && channel_name !== undefined) { + if (typeof channel_name !== 'string' || channel_name.length > 200) { + res.status(400).json({ + error: 'Invalid channel name', + message: 'Channel name must be a string under 200 characters', + }); + return; + } + } + + const userId = req.user?.id; + await setEscalationChannel(channel_id ?? null, channel_name ?? null, userId); + + logger.info({ channel_id, channel_name, userId }, 'Escalation channel updated'); + + const updated = await getEscalationChannel(); + res.json({ + success: true, + escalation_channel: updated, + }); + } catch (error) { + logger.error({ err: error }, 'Failed to update escalation channel'); + res.status(500).json({ + error: 'Failed to update escalation channel', + message: error instanceof Error ? error.message : 'Unknown error', + }); + } + }); + return router; } From 1b40d7c0c18d7cc6817b2baa665aa7c599cc2df4 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Thu, 15 Jan 2026 19:47:30 -0500 Subject: [PATCH 31/49] docs: clarify inline creative handling (Option B) - create_media_buy and update_media_buy creatives array creates NEW creatives only - Add CREATIVE_ID_EXISTS error code when creative_id already exists in library - Add CREATIVE_IN_ACTIVE_DELIVERY error code to sync_creatives - Clarify that sync_creatives is the canonical way to update/delete creatives - Document upsert semantics with active delivery protection Co-Authored-By: Claude Opus 4.5 --- .changeset/loud-bushes-train.md | 8 ++++++++ docs/media-buy/task-reference/create_media_buy.mdx | 4 +++- docs/media-buy/task-reference/sync_creatives.mdx | 6 ++++-- docs/media-buy/task-reference/update_media_buy.mdx | 4 +++- 4 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 .changeset/loud-bushes-train.md diff --git a/.changeset/loud-bushes-train.md b/.changeset/loud-bushes-train.md new file mode 100644 index 0000000000..b02953c8f7 --- /dev/null +++ b/.changeset/loud-bushes-train.md @@ -0,0 +1,8 @@ +--- +--- + +Clarify inline creative handling in media buy operations (Option B): +- `create_media_buy` and `update_media_buy` inline `creatives` array creates NEW creatives only +- Add `CREATIVE_ID_EXISTS` error code for duplicate creative IDs +- Add `CREATIVE_IN_ACTIVE_DELIVERY` error code for sync_creatives +- Document that existing creatives should be managed via `sync_creatives` diff --git a/docs/media-buy/task-reference/create_media_buy.mdx b/docs/media-buy/task-reference/create_media_buy.mdx index 80001c91ba..cac328d1dc 100644 --- a/docs/media-buy/task-reference/create_media_buy.mdx +++ b/docs/media-buy/task-reference/create_media_buy.mdx @@ -183,7 +183,7 @@ npx adcp \ | `bid_price` | number | No | Bid price for auction pricing (required when `is_fixed` is false) | | `targeting_overlay` | TargetingOverlay | No | Additional targeting criteria (see [Targeting](/docs/media-buy/advanced-topics/targeting)) | | `creative_ids` | string[] | No | Existing library creative IDs to assign | -| `creatives` | CreativeAsset[] | No | Full creative objects to upload and assign | +| `creatives` | CreativeAsset[] | No | New creative assets to upload and assign (`creative_id` must not already exist in library) | ## Response @@ -550,6 +550,7 @@ Common errors and resolutions: | `TARGETING_TOO_NARROW` | Targeting yields zero inventory | Broaden geographic or audience criteria | | `POLICY_VIOLATION` | Brand/product violates policy | Review publisher's content policies | | `INVALID_PRICING_OPTION` | pricing_option_id not found | Use ID from product's `pricing_options` | +| `CREATIVE_ID_EXISTS` | Creative ID already exists in library | Use a different `creative_id`, assign existing creatives via `creative_ids`, or update via `sync_creatives` | Example error response: @@ -1025,6 +1026,7 @@ For complete async handling patterns, see [Task Management](/docs/protocols/task - AXE segments enable advanced audience targeting - Pending states (`working`, `submitted`) are normal, not errors - Orchestrators MUST handle pending states as part of normal workflow +- **Inline creatives**: The `creatives` array creates NEW creatives only. To update existing creatives, use [`sync_creatives`](/docs/media-buy/task-reference/sync_creatives). To assign existing creatives, use `creative_ids` instead. ## Policy Compliance diff --git a/docs/media-buy/task-reference/sync_creatives.mdx b/docs/media-buy/task-reference/sync_creatives.mdx index e5133c59d2..0f066f9cbb 100644 --- a/docs/media-buy/task-reference/sync_creatives.mdx +++ b/docs/media-buy/task-reference/sync_creatives.mdx @@ -651,11 +651,11 @@ The operation status (`completed`) means the review process finished. Individual | `PACKAGE_NOT_FOUND` | Package ID doesn't exist in media buy | Verify `package_id` from `create_media_buy` response | | `BRAND_SAFETY_VIOLATION` | Creative failed brand safety scan | Review content against publisher's brand safety guidelines | | `FORMAT_MISMATCH` | Assets don't match format requirements | Verify asset types and specifications match format definition | -| `DUPLICATE_CREATIVE_ID` | Creative ID already exists in different media buy | Use unique `creative_id` or sync to correct media buy | +| `CREATIVE_IN_ACTIVE_DELIVERY` | Creative is assigned to an active, non-paused package | Pause the package first, or create a new creative version | ## Best Practices -1. **Use upsert semantics** - Same `creative_id` updates existing creative rather than creating duplicates. This allows iterative creative development. +1. **Use upsert semantics** - Same `creative_id` updates existing creative rather than creating duplicates. This allows iterative creative development. Note: updates are blocked for creatives in active delivery (see #7). 2. **Validate first** - Use `dry_run: true` to catch errors before actual upload. This saves bandwidth and processing time. @@ -667,6 +667,8 @@ The operation status (`completed`) means the review process finished. Individual 6. **Check format support** - Use `list_creative_formats` to verify product supports your creative formats before uploading. +7. **Active delivery protection** - Creatives assigned to active, non-paused packages cannot be updated. Pause the package first or create a new creative with a different `creative_id`. + ## Next Steps - [list_creative_formats](/docs/media-buy/task-reference/list_creative_formats) - Check supported formats before upload diff --git a/docs/media-buy/task-reference/update_media_buy.mdx b/docs/media-buy/task-reference/update_media_buy.mdx index 237b6847e7..5b4d4ae571 100644 --- a/docs/media-buy/task-reference/update_media_buy.mdx +++ b/docs/media-buy/task-reference/update_media_buy.mdx @@ -149,7 +149,7 @@ asyncio.run(create_and_pause_campaign()) | `end_time` | string | No | Updated campaign end time | | `paused` | boolean | No | Pause/resume entire media buy (`true` = paused, `false` = active) | | `packages` | PackageUpdate[] | No | Package-level updates (see below) | -| `creatives` | CreativeAsset[] | No | Upload and assign new creative assets inline | +| `creatives` | CreativeAsset[] | No | Upload and assign new creative assets inline (`creative_id` must not already exist in library) | | `creative_assignments` | CreativeAssignment[] | No | Update creative rotation weights and placement targeting | *Either `media_buy_id` OR `buyer_ref` is required (not both) @@ -548,6 +548,7 @@ Common errors and resolutions: | `BUDGET_INSUFFICIENT` | New budget below minimum | Increase budget amount | | `POLICY_VIOLATION` | Update violates content policy | Review policy requirements | | `INVALID_STATE` | Operation not allowed in current state | Check campaign status | +| `CREATIVE_ID_EXISTS` | Creative ID already exists in library | Use a different `creative_id`, assign existing creatives via `creative_ids`, or update via `sync_creatives` | Example error response: @@ -678,6 +679,7 @@ Check `affected_packages` in response to confirm changes were applied correctly. - Pending states (`working`, `submitted`) are normal, not errors - Orchestrators MUST handle pending states as part of normal workflow - `implementation_date` indicates when changes take effect (null if pending approval) +- **Inline creatives**: The `creatives` array creates NEW creatives only. To update existing creatives, use [`sync_creatives`](/docs/media-buy/task-reference/sync_creatives). To assign existing creatives, use `creative_ids` in the Package Update object. ## Next Steps From 26af926a68720d38e4032d525dc64361f35e7e0a Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Fri, 16 Jan 2026 05:47:24 -0500 Subject: [PATCH 32/49] refactor: replace creative_ids with creative_assignments - Remove creative_ids from package-request.json and update-media-buy-request.json - Use creative_assignments for assigning existing library creatives (supports optional weight/placement_ids) - Update documentation and examples to use creative_assignments - Clarify delete_missing in sync_creatives cannot delete creatives in active delivery Co-Authored-By: Claude Opus 4.5 --- .changeset/loud-bushes-train.md | 17 ++++++++++---- .../task-reference/create_media_buy.mdx | 6 ++--- .../task-reference/sync_creatives.mdx | 6 ++--- .../task-reference/update_media_buy.mdx | 23 +++++++++++++------ .../source/media-buy/package-request.json | 8 +++---- .../media-buy/update-media-buy-request.json | 15 ++++-------- 6 files changed, 42 insertions(+), 33 deletions(-) diff --git a/.changeset/loud-bushes-train.md b/.changeset/loud-bushes-train.md index b02953c8f7..78db9bd2ba 100644 --- a/.changeset/loud-bushes-train.md +++ b/.changeset/loud-bushes-train.md @@ -1,8 +1,15 @@ --- +"adcontextprotocol": minor --- -Clarify inline creative handling in media buy operations (Option B): -- `create_media_buy` and `update_media_buy` inline `creatives` array creates NEW creatives only -- Add `CREATIVE_ID_EXISTS` error code for duplicate creative IDs -- Add `CREATIVE_IN_ACTIVE_DELIVERY` error code for sync_creatives -- Document that existing creatives should be managed via `sync_creatives` +Clarify creative handling in media buy operations: + +**Breaking:** Replace `creative_ids` with `creative_assignments` in `create_media_buy` and `update_media_buy` +- `creative_assignments` supports optional `weight` and `placement_ids` for granular control +- Simple assignment: `{ "creative_id": "my_creative" }` (weight/placement optional) +- Advanced assignment: `{ "creative_id": "my_creative", "weight": 60, "placement_ids": ["p1"] }` + +**Clarifications:** +- `creatives` array creates NEW creatives only (add `CREATIVE_ID_EXISTS` error) +- `delete_missing` in sync_creatives cannot delete creatives in active delivery (`CREATIVE_IN_ACTIVE_DELIVERY` error) +- Document that existing library creatives should be managed via `sync_creatives` diff --git a/docs/media-buy/task-reference/create_media_buy.mdx b/docs/media-buy/task-reference/create_media_buy.mdx index cac328d1dc..cf75dda219 100644 --- a/docs/media-buy/task-reference/create_media_buy.mdx +++ b/docs/media-buy/task-reference/create_media_buy.mdx @@ -182,8 +182,8 @@ npx adcp \ | `pacing` | string | No | `"even"` (default), `"asap"`, or `"front_loaded"` | | `bid_price` | number | No | Bid price for auction pricing (required when `is_fixed` is false) | | `targeting_overlay` | TargetingOverlay | No | Additional targeting criteria (see [Targeting](/docs/media-buy/advanced-topics/targeting)) | -| `creative_ids` | string[] | No | Existing library creative IDs to assign | -| `creatives` | CreativeAsset[] | No | New creative assets to upload and assign (`creative_id` must not already exist in library) | +| `creative_assignments` | CreativeAssignment[] | No | Assign existing library creatives with optional weights and placement targeting | +| `creatives` | CreativeAsset[] | No | Upload new creative assets and assign (`creative_id` must not already exist in library) | ## Response @@ -1026,7 +1026,7 @@ For complete async handling patterns, see [Task Management](/docs/protocols/task - AXE segments enable advanced audience targeting - Pending states (`working`, `submitted`) are normal, not errors - Orchestrators MUST handle pending states as part of normal workflow -- **Inline creatives**: The `creatives` array creates NEW creatives only. To update existing creatives, use [`sync_creatives`](/docs/media-buy/task-reference/sync_creatives). To assign existing creatives, use `creative_ids` instead. +- **Inline creatives**: The `creatives` array creates NEW creatives only. To update existing creatives, use [`sync_creatives`](/docs/media-buy/task-reference/sync_creatives). To assign existing library creatives, use `creative_assignments` instead. ## Policy Compliance diff --git a/docs/media-buy/task-reference/sync_creatives.mdx b/docs/media-buy/task-reference/sync_creatives.mdx index 0f066f9cbb..30a5182714 100644 --- a/docs/media-buy/task-reference/sync_creatives.mdx +++ b/docs/media-buy/task-reference/sync_creatives.mdx @@ -106,7 +106,7 @@ asyncio.run(main()) | `assignments` | object | No | Map of creative_id to array of package_ids for bulk assignment | | `dry_run` | boolean | No | When true, preview changes without applying them (default: false) | | `validation_mode` | string | No | Validation strictness: `"strict"` (default) or `"lenient"` | -| `delete_missing` | boolean | No | When true, creatives not in this sync are archived (default: false) | +| `delete_missing` | boolean | No | When true, creatives not in this sync are archived (default: false). Cannot delete creatives assigned to active, non-paused packages. | ### Creative Object @@ -651,7 +651,7 @@ The operation status (`completed`) means the review process finished. Individual | `PACKAGE_NOT_FOUND` | Package ID doesn't exist in media buy | Verify `package_id` from `create_media_buy` response | | `BRAND_SAFETY_VIOLATION` | Creative failed brand safety scan | Review content against publisher's brand safety guidelines | | `FORMAT_MISMATCH` | Assets don't match format requirements | Verify asset types and specifications match format definition | -| `CREATIVE_IN_ACTIVE_DELIVERY` | Creative is assigned to an active, non-paused package | Pause the package first, or create a new creative version | +| `CREATIVE_IN_ACTIVE_DELIVERY` | Creative is assigned to an active, non-paused package (blocks updates and `delete_missing` deletions) | Pause the package first, or create a new creative version | ## Best Practices @@ -667,7 +667,7 @@ The operation status (`completed`) means the review process finished. Individual 6. **Check format support** - Use `list_creative_formats` to verify product supports your creative formats before uploading. -7. **Active delivery protection** - Creatives assigned to active, non-paused packages cannot be updated. Pause the package first or create a new creative with a different `creative_id`. +7. **Active delivery protection** - Creatives assigned to active, non-paused packages cannot be updated or deleted via `delete_missing`. Pause the package first, unassign the creative via `update_media_buy`, or create a new creative with a different `creative_id`. ## Next Steps diff --git a/docs/media-buy/task-reference/update_media_buy.mdx b/docs/media-buy/task-reference/update_media_buy.mdx index 5b4d4ae571..177f35aacc 100644 --- a/docs/media-buy/task-reference/update_media_buy.mdx +++ b/docs/media-buy/task-reference/update_media_buy.mdx @@ -166,7 +166,7 @@ asyncio.run(create_and_pause_campaign()) | `pacing` | string | Updated pacing strategy | | `bid_price` | number | Updated bid price (auction products only) | | `targeting_overlay` | TargetingOverlay | Updated targeting restrictions | -| `creative_ids` | string[] | Replace assigned creatives | +| `creative_assignments` | CreativeAssignment[] | Replace assigned creatives with optional weights and placement targeting | *Either `package_id` OR `buyer_ref` is required for each package update @@ -372,7 +372,7 @@ asyncio.run(update_targeting()) ### Replace Creatives -Swap out creative assets for a package: +Swap out creative assignments for a package: @@ -384,7 +384,10 @@ const result = await testAgent.updateMediaBuy({ media_buy_id: 'mb_12345', packages: [{ buyer_ref: 'ctv_package', - creative_ids: ['creative_video_v2', 'creative_display_v2'] + creative_assignments: [ + { creative_id: 'creative_video_v2' }, + { creative_id: 'creative_display_v2', weight: 60 } + ] }] }); @@ -415,7 +418,10 @@ async def replace_creatives(): packages=[ { 'buyer_ref': 'ctv_package', - 'creative_ids': ['creative_video_v2', 'creative_display_v2'] + 'creative_assignments': [ + {'creative_id': 'creative_video_v2'}, + {'creative_id': 'creative_display_v2', 'weight': 60} + ] } ] ) @@ -597,13 +603,16 @@ Only specified fields are updated - omitted fields remain unchanged: } ``` -**Array replacement**: When updating arrays (like `creative_ids`), provide the complete new array: +**Array replacement**: When updating arrays (like `creative_assignments`), provide the complete new array: ```json { "packages": [{ "buyer_ref": "ctv_package", - "creative_ids": ["creative_video_v2", "creative_display_v2"] + "creative_assignments": [ + { "creative_id": "creative_video_v2" }, + { "creative_id": "creative_display_v2", "weight": 60 } + ] }] } ``` @@ -679,7 +688,7 @@ Check `affected_packages` in response to confirm changes were applied correctly. - Pending states (`working`, `submitted`) are normal, not errors - Orchestrators MUST handle pending states as part of normal workflow - `implementation_date` indicates when changes take effect (null if pending approval) -- **Inline creatives**: The `creatives` array creates NEW creatives only. To update existing creatives, use [`sync_creatives`](/docs/media-buy/task-reference/sync_creatives). To assign existing creatives, use `creative_ids` in the Package Update object. +- **Inline creatives**: The `creatives` array creates NEW creatives only. To update existing creatives, use [`sync_creatives`](/docs/media-buy/task-reference/sync_creatives). To assign existing library creatives, use `creative_assignments` in the Package Update object. ## Next Steps diff --git a/static/schemas/source/media-buy/package-request.json b/static/schemas/source/media-buy/package-request.json index c09f6fa5f0..a1e94bcaa2 100644 --- a/static/schemas/source/media-buy/package-request.json +++ b/static/schemas/source/media-buy/package-request.json @@ -51,16 +51,16 @@ "targeting_overlay": { "$ref": "/schemas/core/targeting.json" }, - "creative_ids": { + "creative_assignments": { "type": "array", - "description": "Creative IDs to assign to this package at creation time (references existing library creatives)", + "description": "Assign existing library creatives to this package with optional weights and placement targeting", "items": { - "type": "string" + "$ref": "/schemas/core/creative-assignment.json" } }, "creatives": { "type": "array", - "description": "Full creative objects to upload and assign to this package at creation time (alternative to creative_ids - creatives will be added to library). Supports both static and generative creatives.", + "description": "Upload new creative assets and assign to this package (creatives will be added to library). Use creative_assignments instead for existing library creatives.", "items": { "$ref": "/schemas/core/creative-asset.json" }, diff --git a/static/schemas/source/media-buy/update-media-buy-request.json b/static/schemas/source/media-buy/update-media-buy-request.json index e88dd17b5e..bbba5d833a 100644 --- a/static/schemas/source/media-buy/update-media-buy-request.json +++ b/static/schemas/source/media-buy/update-media-buy-request.json @@ -64,27 +64,20 @@ "targeting_overlay": { "$ref": "/schemas/core/targeting.json" }, - "creative_ids": { + "creative_assignments": { "type": "array", - "description": "Update creative assignments (references existing library creatives)", + "description": "Replace creative assignments for this package with optional weights and placement targeting. Uses replacement semantics - omit to leave assignments unchanged.", "items": { - "type": "string" + "$ref": "/schemas/core/creative-assignment.json" } }, "creatives": { "type": "array", - "description": "Full creative objects to upload and assign to this package (alternative to creative_ids - creatives will be added to library). Supports both static and generative creatives.", + "description": "Upload new creative assets and assign to this package (creatives will be added to library). Use creative_assignments instead for existing library creatives.", "items": { "$ref": "/schemas/core/creative-asset.json" }, "maxItems": 100 - }, - "creative_assignments": { - "type": "array", - "description": "Full creative assignment objects with weights and placement targeting (alternative to creative_ids - provides granular control over weights and placement targeting). Uses replacement semantics like creative_ids.", - "items": { - "$ref": "/schemas/core/creative-assignment.json" - } } }, "oneOf": [ From 565cfc76a919d694e4ee1c93ff4e1fc7ecd56293 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Thu, 15 Jan 2026 08:06:12 -0500 Subject: [PATCH 33/49] feat: Zoom meeting webhooks and user timezone support (#775) * feat: Add Zoom meeting webhooks and user timezone support - Handle meeting.started/ended webhooks to update meeting status - Handle recording.completed to store transcripts and Zoom AI summaries - Send Slack notifications when meetings start/end - Add user timezone column with migration - Add helper functions for timezone management Co-Authored-By: Claude Opus 4.5 * fix: Rename user_timezone migration to 171 to avoid conflict Migration 170 already exists in main (slack_activity_index). Co-Authored-By: Claude Opus 4.5 * fix: Use empty changeset for server-only changes Server implementation changes (webhooks, migrations) don't require a protocol version bump. Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Claude Opus 4.5 --- .changeset/nice-kings-add.md | 12 +- server/src/db/meetings-db.ts | 11 ++ .../src/db/migrations/171_user_timezone.sql | 11 ++ server/src/db/users-db.ts | 54 +++++++ server/src/integrations/zoom.ts | 98 ++++++++++++ server/src/notifications/slack.ts | 135 +++++++++++++++++ server/src/routes/webhooks.ts | 26 +++- server/src/services/meeting-service.ts | 142 +++++++++++++++--- server/src/types.ts | 1 + 9 files changed, 466 insertions(+), 24 deletions(-) create mode 100644 server/src/db/migrations/171_user_timezone.sql diff --git a/.changeset/nice-kings-add.md b/.changeset/nice-kings-add.md index 794035520f..80516bcb48 100644 --- a/.changeset/nice-kings-add.md +++ b/.changeset/nice-kings-add.md @@ -1,4 +1,14 @@ --- --- -Exclude Zoom webhook from JSON body parser to enable signature verification +### Zoom Meeting Webhooks & User Timezone + +**Zoom Webhook Integration:** +- Handle `meeting.started` and `meeting.ended` webhooks to update meeting status +- Handle `recording.completed` webhook to store transcripts and Zoom AI Companion summaries +- Send Slack notifications to working group channels when meetings start/end +- Fix JSON body parsing to allow webhook signature verification + +**User Timezone:** +- Add timezone column to users table for scheduling preferences +- Add helper functions for timezone management (getUserTimezone, updateUserTimezone, etc.) diff --git a/server/src/db/meetings-db.ts b/server/src/db/meetings-db.ts index c2ef08b425..42997d6dda 100644 --- a/server/src/db/meetings-db.ts +++ b/server/src/db/meetings-db.ts @@ -201,6 +201,17 @@ export class MeetingsDatabase { return result.rows[0] || null; } + /** + * Get meeting by Zoom meeting ID + */ + async getMeetingByZoomId(zoomMeetingId: string): Promise { + const result = await query( + 'SELECT * FROM meetings WHERE zoom_meeting_id = $1', + [zoomMeetingId] + ); + return result.rows[0] || null; + } + /** * Get meeting with working group info */ diff --git a/server/src/db/migrations/171_user_timezone.sql b/server/src/db/migrations/171_user_timezone.sql new file mode 100644 index 0000000000..a800b14849 --- /dev/null +++ b/server/src/db/migrations/171_user_timezone.sql @@ -0,0 +1,11 @@ +-- Add timezone column to users table +-- This stores the user's preferred timezone for scheduling meetings and notifications +-- Standard IANA timezone format (e.g., 'America/New_York', 'Europe/London') + +ALTER TABLE users +ADD COLUMN IF NOT EXISTS timezone VARCHAR(100); + +-- Index for finding users by timezone (useful for scheduling) +CREATE INDEX IF NOT EXISTS idx_users_timezone ON users(timezone) WHERE timezone IS NOT NULL; + +COMMENT ON COLUMN users.timezone IS 'User preferred timezone in IANA format (e.g., America/New_York)'; diff --git a/server/src/db/users-db.ts b/server/src/db/users-db.ts index 9ba7b3ae52..c5cb0cdee0 100644 --- a/server/src/db/users-db.ts +++ b/server/src/db/users-db.ts @@ -17,6 +17,7 @@ export interface User { country?: string; location_source?: string; location_updated_at?: Date; + timezone?: string; primary_slack_user_id?: string; primary_organization_id?: string; created_at: Date; @@ -121,4 +122,57 @@ export class UsersDatabase { ); return result.rows; } + + /** + * Get user's timezone + */ + async getUserTimezone(workosUserId: string): Promise { + const result = await query<{ timezone: string }>( + `SELECT timezone FROM users WHERE workos_user_id = $1`, + [workosUserId] + ); + return result.rows[0]?.timezone || null; + } + + /** + * Update user's timezone + */ + async updateUserTimezone(workosUserId: string, timezone: string): Promise { + const result = await query( + `UPDATE users + SET timezone = $2, + updated_at = NOW() + WHERE workos_user_id = $1 + RETURNING *`, + [workosUserId, timezone] + ); + return result.rows[0] || null; + } + + /** + * Find users by timezone + */ + async findUsersByTimezone(timezone: string): Promise { + const result = await query( + `SELECT * FROM users + WHERE timezone = $1 + ORDER BY engagement_score DESC`, + [timezone] + ); + return result.rows; + } + + /** + * Get users without timezone set (useful for prompting them to set it) + */ + async findUsersWithoutTimezone(limit = 100): Promise { + const result = await query( + `SELECT * FROM users + WHERE timezone IS NULL + ORDER BY engagement_score DESC + LIMIT $1`, + [limit] + ); + return result.rows; + } } diff --git a/server/src/integrations/zoom.ts b/server/src/integrations/zoom.ts index 8104a5986b..0b297a284f 100644 --- a/server/src/integrations/zoom.ts +++ b/server/src/integrations/zoom.ts @@ -329,6 +329,104 @@ export function parseVttToText(vttContent: string): string { return textLines.join(' '); } +/** + * Zoom AI Companion meeting summary response + */ +export interface ZoomMeetingSummary { + meeting_host_id: string; + meeting_host_email: string; + meeting_uuid: string; + meeting_id: number; + meeting_topic: string; + meeting_start_time: string; + meeting_end_time: string; + summary_start_time: string; + summary_end_time: string; + summary_overview: string; + summary_details: Array<{ + summary_title?: string; + summary_content?: string; + }>; + next_steps?: string[]; + edited_summary?: { + summary_overview?: string; + summary_details?: Array<{ + summary_title?: string; + summary_content?: string; + }>; + next_steps?: string[]; + }; +} + +/** + * Get meeting summary from Zoom AI Companion + * Requires Meeting Summary with AI Companion feature enabled + */ +export async function getMeetingSummary(meetingId: string | number): Promise { + try { + // Double-encode UUID if it contains slashes (Zoom API quirk) + const encodedId = String(meetingId).includes('/') + ? encodeURIComponent(encodeURIComponent(String(meetingId))) + : String(meetingId); + + const summary = await zoomRequest( + 'GET', + `/meetings/${encodedId}/meeting_summary` + ); + + logger.info({ meetingId, hasSummary: !!summary.summary_overview }, 'Retrieved Zoom meeting summary'); + return summary; + } catch (error) { + // 404 means no summary available (meeting hasn't ended, AI Companion not enabled, etc.) + if (error instanceof Error && error.message.includes('404')) { + logger.info({ meetingId }, 'No Zoom meeting summary available'); + return null; + } + logger.error({ err: error, meetingId }, 'Error getting Zoom meeting summary'); + return null; + } +} + +/** + * Format Zoom meeting summary to markdown + */ +export function formatMeetingSummaryAsMarkdown(summary: ZoomMeetingSummary): string { + const parts: string[] = []; + + // Use edited summary if available, otherwise use original + const overview = summary.edited_summary?.summary_overview || summary.summary_overview; + const details = summary.edited_summary?.summary_details || summary.summary_details; + const nextSteps = summary.edited_summary?.next_steps || summary.next_steps; + + if (overview) { + parts.push('## Overview'); + parts.push(overview); + parts.push(''); + } + + if (details && details.length > 0) { + for (const detail of details) { + if (detail.summary_title) { + parts.push(`## ${detail.summary_title}`); + } + if (detail.summary_content) { + parts.push(detail.summary_content); + } + parts.push(''); + } + } + + if (nextSteps && nextSteps.length > 0) { + parts.push('## Next Steps'); + for (const step of nextSteps) { + parts.push(`- ${step}`); + } + parts.push(''); + } + + return parts.join('\n').trim(); +} + /** * Zoom webhook event types we handle */ diff --git a/server/src/notifications/slack.ts b/server/src/notifications/slack.ts index b29126cfb0..57dcda3ce0 100644 --- a/server/src/notifications/slack.ts +++ b/server/src/notifications/slack.ts @@ -516,3 +516,138 @@ export async function notifyPublishedPost(data: { category: data.category, }); } + +/** + * Notify a working group's Slack channel when a meeting starts + */ +export async function notifyMeetingStarted(data: { + slackChannelId: string; + meetingTitle: string; + workingGroupName: string; + zoomJoinUrl?: string; +}): Promise { + if (!isSlackConfigured()) { + logger.debug('Slack not configured, skipping meeting started notification'); + return false; + } + + const message: SlackBlockMessage = { + text: `📹 Meeting starting now: ${data.meetingTitle}`, + blocks: [ + { + type: 'header', + text: { + type: 'plain_text' as const, + text: '📹 Meeting Starting Now', + emoji: true, + }, + }, + { + type: 'section', + text: { + type: 'mrkdwn' as const, + text: `*${data.meetingTitle}*\n${data.workingGroupName}`, + }, + }, + ...(data.zoomJoinUrl ? [{ + type: 'section', + text: { + type: 'mrkdwn' as const, + text: `<${data.zoomJoinUrl}|Join Zoom Meeting>`, + }, + }] : []), + ], + }; + + try { + const result = await sendChannelMessage(data.slackChannelId, message); + if (result.ok) { + logger.info( + { channelId: data.slackChannelId, meetingTitle: data.meetingTitle }, + 'Sent meeting started notification to Slack channel' + ); + return true; + } else { + logger.warn( + { channelId: data.slackChannelId, error: result.error }, + 'Failed to send meeting started notification' + ); + return false; + } + } catch (error) { + logger.error({ error, channelId: data.slackChannelId }, 'Error sending meeting started notification'); + return false; + } +} + +/** + * Notify a working group's Slack channel when a meeting ends + */ +export async function notifyMeetingEnded(data: { + slackChannelId: string; + meetingTitle: string; + workingGroupName: string; + durationMinutes?: number; +}): Promise { + if (!isSlackConfigured()) { + logger.debug('Slack not configured, skipping meeting ended notification'); + return false; + } + + const durationText = data.durationMinutes + ? `Duration: ${Math.floor(data.durationMinutes / 60)}h ${data.durationMinutes % 60}m` + : ''; + + const message: SlackBlockMessage = { + text: `✅ Meeting ended: ${data.meetingTitle}`, + blocks: [ + { + type: 'header', + text: { + type: 'plain_text' as const, + text: '✅ Meeting Ended', + emoji: true, + }, + }, + { + type: 'section', + text: { + type: 'mrkdwn' as const, + text: `*${data.meetingTitle}*\n${data.workingGroupName}${durationText ? `\n${durationText}` : ''}`, + }, + }, + { + type: 'context', + elements: [ + { + type: 'mrkdwn', + text: { + type: 'mrkdwn' as const, + text: 'Recording and transcript will be available soon.', + }, + }, + ], + }, + ], + }; + + try { + const result = await sendChannelMessage(data.slackChannelId, message); + if (result.ok) { + logger.info( + { channelId: data.slackChannelId, meetingTitle: data.meetingTitle }, + 'Sent meeting ended notification to Slack channel' + ); + return true; + } else { + logger.warn( + { channelId: data.slackChannelId, error: result.error }, + 'Failed to send meeting ended notification' + ); + return false; + } + } catch (error) { + logger.error({ error, channelId: data.slackChannelId }, 'Error sending meeting ended notification'); + return false; + } +} diff --git a/server/src/routes/webhooks.ts b/server/src/routes/webhooks.ts index b37c4cbeb8..bf9bef9899 100644 --- a/server/src/routes/webhooks.ts +++ b/server/src/routes/webhooks.ts @@ -13,7 +13,11 @@ import { createLogger } from '../logger.js'; import { getPool } from '../db/client.js'; import { ModelConfig } from '../config/models.js'; import { verifyWebhookSignature as verifyZoomSignature } from '../integrations/zoom.js'; -import { handleRecordingCompleted } from '../services/meeting-service.js'; +import { + handleRecordingCompleted, + handleMeetingStarted, + handleMeetingEnded, +} from '../services/meeting-service.js'; import { getFeedByEmailSlug, createEmailPerspective, @@ -1344,21 +1348,31 @@ export function createWebhooksRouter(): Router { case 'recording.completed': case 'recording.transcript_completed': { const meetingUuid = body.payload?.object?.uuid; + const meetingId = body.payload?.object?.id; if (meetingUuid && typeof meetingUuid === 'string' && meetingUuid.length < 256) { - await handleRecordingCompleted(meetingUuid); + // Pass both UUID and numeric ID - we store the numeric ID in our DB + await handleRecordingCompleted(meetingUuid, meetingId?.toString()); } else if (meetingUuid) { logger.warn({ meetingUuidType: typeof meetingUuid }, 'Invalid meetingUuid format'); } break; } - case 'meeting.started': - logger.info({ meetingId: body.payload?.object?.id }, 'Meeting started'); + case 'meeting.started': { + const startedMeetingId = body.payload?.object?.id; + if (startedMeetingId) { + await handleMeetingStarted(startedMeetingId.toString()); + } break; + } - case 'meeting.ended': - logger.info({ meetingId: body.payload?.object?.id }, 'Meeting ended'); + case 'meeting.ended': { + const endedMeetingId = body.payload?.object?.id; + if (endedMeetingId) { + await handleMeetingEnded(endedMeetingId.toString()); + } break; + } default: logger.debug({ event: body.event }, 'Unhandled Zoom webhook event'); diff --git a/server/src/services/meeting-service.ts b/server/src/services/meeting-service.ts index ad7ec349bb..e226fa44b2 100644 --- a/server/src/services/meeting-service.ts +++ b/server/src/services/meeting-service.ts @@ -13,6 +13,10 @@ import { MeetingsDatabase } from '../db/meetings-db.js'; import { WorkingGroupDatabase } from '../db/working-group-db.js'; import * as zoom from '../integrations/zoom.js'; import * as calendar from '../integrations/google-calendar.js'; +import { + notifyMeetingStarted, + notifyMeetingEnded, +} from '../notifications/slack.js'; import type { CreateMeetingInput, UpdateMeetingInput, @@ -163,6 +167,8 @@ export async function scheduleMeeting(options: ScheduleMeetingOptions): Promise< timeZone: timezone, }, attendees: attendeeEmails, + // Set location to Zoom join URL so it appears in calendar invite + location: zoomMeeting?.join_url, }; // Add Zoom link as conference data @@ -291,31 +297,51 @@ export async function addAttendeesToMeeting( /** * Handle Zoom recording completed webhook - * Stores transcript and generates summary + * Stores transcript and fetches Zoom AI Companion summary */ -export async function handleRecordingCompleted(meetingUuid: string): Promise { - logger.info({ meetingUuid }, 'Processing recording completed'); +export async function handleRecordingCompleted(meetingUuid: string, zoomMeetingId?: string): Promise { + logger.info({ meetingUuid, zoomMeetingId }, 'Processing recording completed'); - // Find meeting in database by Zoom meeting ID - // Note: We store the numeric ID, but webhooks use UUID - // For now, we'd need to query by zoom_meeting_id + // Find meeting in database - try zoom_meeting_id first (numeric ID), fall back to UUID lookup + let meeting: Meeting | null = null; + if (zoomMeetingId) { + meeting = await meetingsDb.getMeetingByZoomId(zoomMeetingId); + } - // Get transcript - const transcriptText = await zoom.getTranscriptText(meetingUuid); - if (!transcriptText) { - logger.info({ meetingUuid }, 'No transcript available'); + if (!meeting) { + logger.warn({ meetingUuid, zoomMeetingId }, 'Meeting not found in database - transcript will not be stored'); return; } - // Parse VTT to plain text - const plainText = zoom.parseVttToText(transcriptText); + // Get transcript + const transcriptText = await zoom.getTranscriptText(meetingUuid); + if (transcriptText) { + // Parse VTT to plain text and store + const plainText = zoom.parseVttToText(transcriptText); + logger.info({ meetingId: meeting.id, transcriptLength: plainText.length }, 'Transcript retrieved'); - // TODO: Find the meeting in our database by zoom_meeting_id - // TODO: Store transcript_text - // TODO: Generate AI summary using Claude - // TODO: Store summary + await meetingsDb.updateMeeting(meeting.id, { + transcript_text: plainText, + }); + } else { + logger.info({ meetingUuid, meetingId: meeting.id }, 'No transcript available'); + } + + // Fetch Zoom AI Companion meeting summary + try { + const zoomSummary = await zoom.getMeetingSummary(meetingUuid); + if (zoomSummary) { + const summary = zoom.formatMeetingSummaryAsMarkdown(zoomSummary); + await meetingsDb.updateMeeting(meeting.id, { summary }); + logger.info({ meetingId: meeting.id }, 'Zoom AI Companion summary stored'); + } else { + logger.info({ meetingId: meeting.id }, 'No Zoom AI Companion summary available'); + } + } catch (error) { + logger.error({ err: error, meetingId: meeting.id }, 'Failed to fetch Zoom meeting summary'); + } - logger.info({ meetingUuid, transcriptLength: plainText.length }, 'Transcript processed'); + logger.info({ meetingUuid, meetingId: meeting.id }, 'Recording processing completed'); } /** @@ -496,3 +522,85 @@ function getNextOccurrence(from: Date, rule: RecurrenceRule): Date { return next; } + +/** + * Handle Zoom meeting started webhook + * Updates meeting status and sends Slack notification + */ +export async function handleMeetingStarted(zoomMeetingId: string): Promise { + logger.info({ zoomMeetingId }, 'Processing meeting started'); + + const meeting = await meetingsDb.getMeetingByZoomId(zoomMeetingId); + if (!meeting) { + logger.warn({ zoomMeetingId }, 'Meeting not found in database - skipping started notification'); + return; + } + + // Update meeting status + await meetingsDb.updateMeeting(meeting.id, { status: 'in_progress' }); + + // Get working group for Slack channel + const workingGroup = await workingGroupDb.getWorkingGroupById(meeting.working_group_id); + if (!workingGroup) { + logger.warn({ meetingId: meeting.id }, 'Working group not found - skipping Slack notification'); + return; + } + + // Send Slack notification if channel is configured + if (workingGroup.slack_channel_id) { + await notifyMeetingStarted({ + slackChannelId: workingGroup.slack_channel_id, + meetingTitle: meeting.title, + workingGroupName: workingGroup.name, + zoomJoinUrl: meeting.zoom_join_url, + }); + } + + logger.info({ meetingId: meeting.id, zoomMeetingId }, 'Meeting started processed'); +} + +/** + * Handle Zoom meeting ended webhook + * Updates meeting status and sends Slack notification + */ +export async function handleMeetingEnded(zoomMeetingId: string): Promise { + logger.info({ zoomMeetingId }, 'Processing meeting ended'); + + const meeting = await meetingsDb.getMeetingByZoomId(zoomMeetingId); + if (!meeting) { + logger.warn({ zoomMeetingId }, 'Meeting not found in database - skipping ended notification'); + return; + } + + // Calculate duration if we have start time + let durationMinutes: number | undefined; + if (meeting.start_time) { + const now = new Date(); + durationMinutes = Math.round((now.getTime() - meeting.start_time.getTime()) / 60000); + } + + // Update meeting status + await meetingsDb.updateMeeting(meeting.id, { + status: 'completed', + end_time: new Date(), + }); + + // Get working group for Slack channel + const workingGroup = await workingGroupDb.getWorkingGroupById(meeting.working_group_id); + if (!workingGroup) { + logger.warn({ meetingId: meeting.id }, 'Working group not found - skipping Slack notification'); + return; + } + + // Send Slack notification if channel is configured + if (workingGroup.slack_channel_id) { + await notifyMeetingEnded({ + slackChannelId: workingGroup.slack_channel_id, + meetingTitle: meeting.title, + workingGroupName: workingGroup.name, + durationMinutes, + }); + } + + logger.info({ meetingId: meeting.id, zoomMeetingId, durationMinutes }, 'Meeting ended processed'); +} diff --git a/server/src/types.ts b/server/src/types.ts index 2a5c660af7..d313231c77 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -323,6 +323,7 @@ export type LocationSource = 'manual' | 'outreach' | 'inferred'; export interface UserLocation { city?: string; country?: string; + timezone?: string; location_source?: LocationSource; location_updated_at?: Date; } From 16638da5ab07e7065e152f38b5b6cb8a11895713 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Thu, 15 Jan 2026 09:39:49 -0500 Subject: [PATCH 34/49] fix: Meeting scheduling timezone comparison bug (#776) * fix: Meeting scheduling timezone comparison bug When Claude provides a datetime like "2026-01-15T09:15:00" without timezone info, JavaScript's Date parses it as server local time (UTC). This caused Addie to incorrectly reject meeting times as "already passed" when users specified times in their local timezone. The fix compares times within the specified timezone context by: 1. Getting current time formatted in the target timezone 2. Comparing ISO strings directly (lexicographically sortable) This ensures "9:15 AM ET" is compared against "current time in ET", not UTC. Co-Authored-By: Claude Opus 4.5 * chore: Add changeset for timezone fix --------- Co-authored-by: Claude Opus 4.5 --- .changeset/timezone-meeting-fix.md | 8 +++++++ server/src/addie/mcp/meeting-tools.ts | 34 ++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 .changeset/timezone-meeting-fix.md diff --git a/.changeset/timezone-meeting-fix.md b/.changeset/timezone-meeting-fix.md new file mode 100644 index 0000000000..e9ce99d6fd --- /dev/null +++ b/.changeset/timezone-meeting-fix.md @@ -0,0 +1,8 @@ +--- +--- + +fix: Meeting scheduling timezone comparison bug + +Fixed Addie incorrectly rejecting meeting times as "already passed" when users +specified times in their local timezone. The datetime comparison now correctly +interprets times in the specified timezone context. diff --git a/server/src/addie/mcp/meeting-tools.ts b/server/src/addie/mcp/meeting-tools.ts index 3bb7503d1a..f32fc22eeb 100644 --- a/server/src/addie/mcp/meeting-tools.ts +++ b/server/src/addie/mcp/meeting-tools.ts @@ -71,6 +71,33 @@ function formatTime(date: Date, timezone = 'America/New_York'): string { }); } +/** + * Get the current time as an ISO string in a specific timezone (for comparison). + * Returns format like "2026-01-15T09:30:00" + */ +function getNowInTimezone(timezone: string): string { + // 'sv-SE' locale gives us ISO-like format: "2026-01-15 09:30:00" + return new Date().toLocaleString('sv-SE', { timeZone: timezone }).replace(' ', 'T'); +} + +/** + * Check if a datetime string (without timezone) represents a future time + * in the specified timezone. + */ +function isFutureTimeInTimezone(isoString: string, timezone: string): boolean { + // If the string has timezone info, convert to the target timezone for comparison + if (/Z|[+-]\d{2}:\d{2}$/.test(isoString)) { + const date = new Date(isoString); + const dateInTz = date.toLocaleString('sv-SE', { timeZone: timezone }).replace(' ', 'T'); + return dateInTz > getNowInTimezone(timezone); + } + + // No timezone info - treat the string as already being in the target timezone + // Just compare strings directly (ISO format is lexicographically sortable) + const normalizedInput = isoString.substring(0, 19); // Take just "YYYY-MM-DDTHH:MM:SS" + return normalizedInput > getNowInTimezone(timezone); +} + /** * Format recurrence rule for display */ @@ -440,9 +467,10 @@ export function createMeetingToolHandlers( return `❌ Invalid start time format. Please use ISO 8601 format (e.g., "2026-01-15T14:00:00").`; } - // Check if meeting is in the future - if (startTime <= new Date()) { - return `❌ Meeting time must be in the future.`; + // Check if meeting is in the future (comparing in the specified timezone) + if (!isFutureTimeInTimezone(startTimeStr, timezone)) { + const nowInTz = getNowInTimezone(timezone); + return `❌ Meeting time must be in the future. Current time in ${timezone}: ${formatTime(new Date(), timezone)}`; } const durationMinutes = (input.duration_minutes as number) || 60; From 65290525ab9c5022a5d110f954627d2c50c23984 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Thu, 15 Jan 2026 09:56:43 -0500 Subject: [PATCH 35/49] fix: Clarify start_time format - no Z suffix (#777) * fix: Clarify start_time format - no Z suffix Claude was interpreting "11 AM ET" as "11 AM UTC" and adding Z suffix, causing meetings to be rejected. Updated schema description to explicitly tell Claude to NOT add Z or timezone offsets - the timezone parameter handles that. Co-Authored-By: Claude Opus 4.5 * fix: Remove CONCURRENTLY from migration 170 CREATE INDEX CONCURRENTLY cannot run inside a transaction block. Migrations run in transactions, so we need to use regular CREATE INDEX. The slack_user_mappings table is small enough that the brief lock is fine. Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Claude Opus 4.5 --- .changeset/fix-meeting-z-suffix.md | 8 ++++++++ server/src/addie/mcp/meeting-tools.ts | 6 ++++-- server/src/db/migrations/170_slack_activity_index.sql | 4 +++- 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 .changeset/fix-meeting-z-suffix.md diff --git a/.changeset/fix-meeting-z-suffix.md b/.changeset/fix-meeting-z-suffix.md new file mode 100644 index 0000000000..f086904417 --- /dev/null +++ b/.changeset/fix-meeting-z-suffix.md @@ -0,0 +1,8 @@ +--- +--- + +fix: Clarify start_time format in meeting scheduling schema + +Updated schema description to explicitly tell Claude NOT to add Z suffix to +start_time. Claude was interpreting "11 AM ET" as "11 AM UTC" with Z suffix, +causing meetings to be rejected as "already passed". diff --git a/server/src/addie/mcp/meeting-tools.ts b/server/src/addie/mcp/meeting-tools.ts index f32fc22eeb..caae338d51 100644 --- a/server/src/addie/mcp/meeting-tools.ts +++ b/server/src/addie/mcp/meeting-tools.ts @@ -179,9 +179,11 @@ If the user is in a channel associated with a working group, you can omit workin For recurring meetings, use the recurrence parameter with freq, interval, count, and byDay. -Required: title, start_time (ISO format) +Required: title, start_time (ISO format without timezone suffix - use timezone parameter for that) Optional: working_group_slug (auto-detected from channel), description, agenda, duration_minutes, timezone, topic_slugs, recurrence +IMPORTANT: For start_time, provide the time in the user's timezone WITHOUT a Z suffix. For example, if user says "2pm ET", use "2026-01-15T14:00:00" (not "2026-01-15T14:00:00Z"). The timezone parameter (default: America/New_York) specifies what timezone the start_time is in. + Example prompts this handles: - "Schedule a technical working group call for next Tuesday at 2pm ET" - "Set up a bylaws subcommittee meeting for Jan 15 at 3pm PT" @@ -208,7 +210,7 @@ Example prompts this handles: }, start_time: { type: 'string', - description: 'Start time in ISO 8601 format (e.g., "2026-01-15T14:00:00")', + description: 'Start time in ISO 8601 format WITHOUT timezone suffix. The time should be in the timezone specified by the timezone parameter (default ET). Example: "2026-01-15T14:00:00" for 2pm. Do NOT add "Z" or timezone offsets - the timezone parameter handles that.', }, duration_minutes: { type: 'number', diff --git a/server/src/db/migrations/170_slack_activity_index.sql b/server/src/db/migrations/170_slack_activity_index.sql index b2bb9748c1..9a5b778657 100644 --- a/server/src/db/migrations/170_slack_activity_index.sql +++ b/server/src/db/migrations/170_slack_activity_index.sql @@ -5,6 +5,8 @@ -- comparison to identify organizations with recent Slack activity. Without an -- index, this query scans all slack_user_mappings rows. -CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_slack_user_mappings_last_activity +-- Note: Not using CONCURRENTLY because migrations run in transactions +-- This table is small enough that the brief lock is acceptable +CREATE INDEX IF NOT EXISTS idx_slack_user_mappings_last_activity ON slack_user_mappings(last_slack_activity_at) WHERE last_slack_activity_at IS NOT NULL; From 020540809b871d437dcc054558ad5ff62637ccf1 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Thu, 15 Jan 2026 10:29:55 -0500 Subject: [PATCH 36/49] fix: Get meeting attendee emails from users table (#778) The working_group_memberships.user_email field is often NULL because it's not always populated when members are added. Updated the query to join with the users table to get the actual email. This fixes calendar invites not being sent because attendeeCount was 0. Co-authored-by: Claude Opus 4.5 --- .changeset/fix-meeting-attendee-emails.md | 10 ++++++++++ server/src/db/meetings-db.ts | 18 +++++++++++------- 2 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 .changeset/fix-meeting-attendee-emails.md diff --git a/.changeset/fix-meeting-attendee-emails.md b/.changeset/fix-meeting-attendee-emails.md new file mode 100644 index 0000000000..a06cbcfa4b --- /dev/null +++ b/.changeset/fix-meeting-attendee-emails.md @@ -0,0 +1,10 @@ +--- +--- + +fix: Get meeting attendee emails from users table + +The working_group_memberships.user_email field is often NULL because it's not +always populated when members are added. Updated the query to join with the +users table to get the actual email, falling back to the cached value if needed. + +This fixes calendar invites not being sent because attendeeCount was 0. diff --git a/server/src/db/meetings-db.ts b/server/src/db/meetings-db.ts index 42997d6dda..38f18eec47 100644 --- a/server/src/db/meetings-db.ts +++ b/server/src/db/meetings-db.ts @@ -560,12 +560,14 @@ export class MeetingsDatabase { if (topicSlugs && topicSlugs.length > 0) { // Invite only members subscribed to these topics + // Join with users table to get email (working_group_memberships.user_email is often NULL) memberQuery = ` SELECT wgm.workos_user_id, - wgm.user_email as email, - wgm.user_name as name + COALESCE(u.email, wgm.user_email) as email, + COALESCE(u.first_name || ' ' || u.last_name, wgm.user_name) as name FROM working_group_memberships wgm + LEFT JOIN users u ON u.workos_user_id = wgm.workos_user_id LEFT JOIN working_group_topic_subscriptions wgts ON wgts.working_group_id = wgm.working_group_id AND wgts.workos_user_id = wgm.workos_user_id @@ -576,13 +578,15 @@ export class MeetingsDatabase { memberParams = [workingGroupId, topicSlugs]; } else { // Invite all members + // Join with users table to get email (working_group_memberships.user_email is often NULL) memberQuery = ` SELECT - workos_user_id, - user_email as email, - user_name as name - FROM working_group_memberships - WHERE working_group_id = $1 AND status = 'active' + wgm.workos_user_id, + COALESCE(u.email, wgm.user_email) as email, + COALESCE(u.first_name || ' ' || u.last_name, wgm.user_name) as name + FROM working_group_memberships wgm + LEFT JOIN users u ON u.workos_user_id = wgm.workos_user_id + WHERE wgm.working_group_id = $1 AND wgm.status = 'active' `; memberParams = [workingGroupId]; } From bf82bbdedcbf2be47d15ca52c16df502f7554780 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Thu, 15 Jan 2026 11:59:16 -0500 Subject: [PATCH 37/49] fix: Strip Z suffix from Zoom start_time to fix timezone (#779) When creating Zoom meetings, toISOString() was adding a Z suffix which made Zoom interpret times as UTC instead of the specified timezone. Example: "11 AM ET" was scheduled for 11:00 UTC = 6:00 AM ET Fix: Strip the Z suffix so Zoom interprets the time using the timezone parameter (America/New_York). Co-authored-by: Claude Opus 4.5 --- .changeset/fix-meeting-attendee-emails.md | 10 ---------- .changeset/fix-zoom-meeting-timezone.md | 11 +++++++++++ server/src/services/meeting-service.ts | 19 ++++++++++++++++++- 3 files changed, 29 insertions(+), 11 deletions(-) delete mode 100644 .changeset/fix-meeting-attendee-emails.md create mode 100644 .changeset/fix-zoom-meeting-timezone.md diff --git a/.changeset/fix-meeting-attendee-emails.md b/.changeset/fix-meeting-attendee-emails.md deleted file mode 100644 index a06cbcfa4b..0000000000 --- a/.changeset/fix-meeting-attendee-emails.md +++ /dev/null @@ -1,10 +0,0 @@ ---- ---- - -fix: Get meeting attendee emails from users table - -The working_group_memberships.user_email field is often NULL because it's not -always populated when members are added. Updated the query to join with the -users table to get the actual email, falling back to the cached value if needed. - -This fixes calendar invites not being sent because attendeeCount was 0. diff --git a/.changeset/fix-zoom-meeting-timezone.md b/.changeset/fix-zoom-meeting-timezone.md new file mode 100644 index 0000000000..c3ffd19f75 --- /dev/null +++ b/.changeset/fix-zoom-meeting-timezone.md @@ -0,0 +1,11 @@ +--- +--- + +fix: Strip Z suffix from Zoom meeting start_time + +When creating Zoom meetings, toISOString() was adding a Z suffix which made +Zoom interpret times as UTC instead of the specified timezone. A meeting for +"11 AM ET" was being scheduled for 11:00 UTC = 6:00 AM ET. + +Fixed by stripping the Z suffix so Zoom interprets the time in the timezone +parameter (e.g., America/New_York). diff --git a/server/src/services/meeting-service.ts b/server/src/services/meeting-service.ts index e226fa44b2..a6a26c906f 100644 --- a/server/src/services/meeting-service.ts +++ b/server/src/services/meeting-service.ts @@ -31,6 +31,22 @@ const logger = createLogger('meeting-service'); // Host email for Zoom meetings const ZOOM_HOST_EMAIL = process.env.ZOOM_HOST_EMAIL || 'addie@agenticadvertising.org'; +/** + * Format a Date as ISO string WITHOUT the Z suffix. + * This is needed for Zoom API which interprets times with Z as UTC, + * but times without Z in the specified timezone parameter. + * + * When Claude sends "2026-01-15T11:00:00" for 11 AM ET, JavaScript parses it + * as 11:00 UTC (server local time). By stripping the Z suffix, we tell Zoom + * to interpret "2026-01-15T11:00:00" in the timezone we specify (America/New_York), + * resulting in the correct 11:00 AM ET meeting time. + */ +function formatDateForZoom(date: Date): string { + // toISOString() returns "2026-01-15T11:00:00.000Z" + // We strip the milliseconds and Z to get "2026-01-15T11:00:00" + return date.toISOString().replace(/\.\d{3}Z$/, ''); +} + const meetingsDb = new MeetingsDatabase(); const workingGroupDb = new WorkingGroupDatabase(); @@ -86,7 +102,8 @@ export async function scheduleMeeting(options: ScheduleMeetingOptions): Promise< try { zoomMeeting = await zoom.createMeeting(ZOOM_HOST_EMAIL, { topic: `${workingGroup.name}: ${options.title}`, - start_time: options.startTime.toISOString(), + // Format without Z suffix so Zoom interprets in the specified timezone + start_time: formatDateForZoom(options.startTime), duration: durationMinutes, timezone, agenda: options.agenda || options.description, From 85a9d856fd4e01759a7344037bf70c266b5f818a Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Thu, 15 Jan 2026 12:33:35 -0500 Subject: [PATCH 38/49] fix: Accept both UUID and Zoom meeting IDs in meeting tools (#780) * fix: Accept both UUID and Zoom meeting IDs in meeting tools - Show meeting ID in list_upcoming_meetings output so Claude can see it - Update get_meeting_details to try UUID first, then Zoom meeting ID - Update rsvp_to_meeting to try UUID first, then Zoom meeting ID - Update cancel_meeting to try UUID first, then Zoom meeting ID - Update add_meeting_attendee to try UUID first, then Zoom meeting ID This fixes the issue where Claude would try to use Zoom meeting IDs from meeting links but the tools only accepted our internal UUIDs. Co-Authored-By: Claude Opus 4.5 * fix: Strip Z suffix from Google Calendar dateTime to fix timezone Applied the same timezone fix to Google Calendar events that was applied to Zoom meetings in PR #779. The Z suffix was causing Google Calendar to interpret times as UTC, resulting in meetings showing 5 hours earlier than intended for ET. Renamed formatDateForZoom to formatDateWithoutZ since it's now used for both Zoom and Google Calendar. Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Claude Opus 4.5 --- .changeset/fix-calendar-timezone.md | 11 +++++ .changeset/fix-meeting-id-lookup.md | 12 ++++++ server/src/addie/mcp/meeting-tools.ts | 60 ++++++++++++++++++-------- server/src/services/meeting-service.ts | 25 +++++------ 4 files changed, 78 insertions(+), 30 deletions(-) create mode 100644 .changeset/fix-calendar-timezone.md create mode 100644 .changeset/fix-meeting-id-lookup.md diff --git a/.changeset/fix-calendar-timezone.md b/.changeset/fix-calendar-timezone.md new file mode 100644 index 0000000000..32b4b61c6b --- /dev/null +++ b/.changeset/fix-calendar-timezone.md @@ -0,0 +1,11 @@ +--- +--- + +fix: Strip Z suffix from Google Calendar dateTime to fix timezone + +Applied the same timezone fix to Google Calendar events that was applied to +Zoom meetings. The Z suffix was causing Google Calendar to interpret times +as UTC, resulting in meetings showing 5 hours earlier than intended for ET. + +For example, "1 PM ET" was showing as "8 AM ET" because the Z suffix told +Google Calendar the time was 13:00 UTC (which is 8 AM ET). diff --git a/.changeset/fix-meeting-id-lookup.md b/.changeset/fix-meeting-id-lookup.md new file mode 100644 index 0000000000..40bbb03b93 --- /dev/null +++ b/.changeset/fix-meeting-id-lookup.md @@ -0,0 +1,12 @@ +--- +--- + +fix: Accept both UUID and Zoom meeting IDs in meeting tools + +Updated meeting tools to accept both internal UUIDs and Zoom meeting IDs: +- Show meeting ID in list_upcoming_meetings output +- get_meeting_details, cancel_meeting, rsvp_to_meeting, add_meeting_attendee + now try UUID first, then fall back to Zoom meeting ID lookup + +This fixes the issue where Claude would see Zoom meeting IDs in meeting links +but the tools only accepted internal UUIDs. diff --git a/server/src/addie/mcp/meeting-tools.ts b/server/src/addie/mcp/meeting-tools.ts index caae338d51..7df8b9a81b 100644 --- a/server/src/addie/mcp/meeting-tools.ts +++ b/server/src/addie/mcp/meeting-tools.ts @@ -649,6 +649,7 @@ export function createMeetingToolHandlers( for (const meeting of meetings) { response += `📅 **${meeting.title}**\n`; + response += ` ID: ${meeting.id}\n`; response += ` ${formatDate(meeting.start_time)} at ${formatTime(meeting.start_time, meeting.timezone)}\n`; if (!groupName) { response += ` Group: ${meeting.working_group_name}\n`; @@ -707,14 +708,22 @@ export function createMeetingToolHandlers( // Get meeting details handlers.set('get_meeting_details', async (input) => { - const meetingId = input.meeting_id as string; + const inputId = input.meeting_id as string; - const meeting = await meetingsDb.getMeetingWithGroup(meetingId); + // Try to find by our UUID first, then by Zoom meeting ID + let meeting = await meetingsDb.getMeetingWithGroup(inputId); + if (!meeting) { + // Maybe it's a Zoom meeting ID - look that up first + const meetingByZoom = await meetingsDb.getMeetingByZoomId(inputId); + if (meetingByZoom) { + meeting = await meetingsDb.getMeetingWithGroup(meetingByZoom.id); + } + } if (!meeting) { - return `❌ Meeting not found: "${meetingId}"`; + return `❌ Meeting not found: "${inputId}". Use list_upcoming_meetings to find the correct meeting ID.`; } - const attendees = await meetingsDb.getAttendeesForMeeting(meetingId); + const attendees = await meetingsDb.getAttendeesForMeeting(meeting.id); const accepted = attendees.filter(a => a.rsvp_status === 'accepted'); const declined = attendees.filter(a => a.rsvp_status === 'declined'); const pending = attendees.filter(a => a.rsvp_status === 'pending'); @@ -759,21 +768,25 @@ export function createMeetingToolHandlers( return '❌ Unable to identify you. Please make sure you\'re logged in.'; } - const meetingId = input.meeting_id as string; + const inputId = input.meeting_id as string; const response = input.response as 'accepted' | 'declined' | 'tentative'; const note = input.note as string | undefined; - const meeting = await meetingsDb.getMeetingById(meetingId); + // Try to find by our UUID first, then by Zoom meeting ID + let meeting = await meetingsDb.getMeetingById(inputId); + if (!meeting) { + meeting = await meetingsDb.getMeetingByZoomId(inputId); + } if (!meeting) { - return `❌ Meeting not found: "${meetingId}"`; + return `❌ Meeting not found: "${inputId}". Use list_upcoming_meetings to find the correct meeting ID.`; } // Check if user is already an attendee - let attendee = await meetingsDb.getAttendee(meetingId, userId); + let attendee = await meetingsDb.getAttendee(meeting.id, userId); if (attendee) { // Update existing RSVP - attendee = await meetingsDb.updateAttendee(meetingId, userId, { + attendee = await meetingsDb.updateAttendee(meeting.id, userId, { rsvp_status: response, rsvp_note: note, }); @@ -785,7 +798,7 @@ export function createMeetingToolHandlers( : userEmail; attendee = await meetingsDb.addAttendee({ - meeting_id: meetingId, + meeting_id: meeting.id, workos_user_id: userId, email: userEmail, name: userName, @@ -810,9 +823,14 @@ export function createMeetingToolHandlers( const meetingId = input.meeting_id as string; - const meeting = await meetingsDb.getMeetingById(meetingId); + // Try to find by our UUID first, then by Zoom meeting ID + let meeting = await meetingsDb.getMeetingById(meetingId); + if (!meeting) { + // Maybe it's a Zoom meeting ID instead of our UUID + meeting = await meetingsDb.getMeetingByZoomId(meetingId); + } if (!meeting) { - return `❌ Meeting not found: "${meetingId}"`; + return `❌ Meeting not found: "${meetingId}". Use list_upcoming_meetings to find the correct meeting ID.`; } if (meeting.status === 'cancelled') { @@ -820,7 +838,8 @@ export function createMeetingToolHandlers( } try { - const result = await meetingService.cancelMeeting(meetingId); + // Use meeting.id (our UUID) not the input meetingId (might be Zoom ID) + const result = await meetingService.cancelMeeting(meeting.id); let response = `✅ Cancelled: **${meeting.title}**\n`; response += `Cancellation notices have been sent to attendees.`; @@ -830,7 +849,7 @@ export function createMeetingToolHandlers( response += result.errors.map(e => `• ${e}`).join('\n'); } - logger.info({ meetingId, cancelledBy: getUserId() }, 'Meeting cancelled via Addie'); + logger.info({ meetingId: meeting.id, cancelledBy: getUserId() }, 'Meeting cancelled via Addie'); return response; } catch (error) { @@ -844,17 +863,22 @@ export function createMeetingToolHandlers( const permCheck = await checkSchedulePermission(); if (permCheck) return permCheck; - const meetingId = input.meeting_id as string; + const inputId = input.meeting_id as string; const email = input.email as string; const name = input.name as string | undefined; - const meeting = await meetingsDb.getMeetingById(meetingId); + // Try to find by our UUID first, then by Zoom meeting ID + let meeting = await meetingsDb.getMeetingById(inputId); + if (!meeting) { + meeting = await meetingsDb.getMeetingByZoomId(inputId); + } if (!meeting) { - return `❌ Meeting not found: "${meetingId}"`; + return `❌ Meeting not found: "${inputId}". Use list_upcoming_meetings to find the correct meeting ID.`; } try { - const result = await meetingService.addAttendeesToMeeting(meetingId, [ + // Use meeting.id (our UUID) not the inputId (might be Zoom ID) + const result = await meetingService.addAttendeesToMeeting(meeting.id, [ { email, name }, ]); diff --git a/server/src/services/meeting-service.ts b/server/src/services/meeting-service.ts index a6a26c906f..05b2faf068 100644 --- a/server/src/services/meeting-service.ts +++ b/server/src/services/meeting-service.ts @@ -33,17 +33,17 @@ const ZOOM_HOST_EMAIL = process.env.ZOOM_HOST_EMAIL || 'addie@agenticadvertising /** * Format a Date as ISO string WITHOUT the Z suffix. - * This is needed for Zoom API which interprets times with Z as UTC, - * but times without Z in the specified timezone parameter. + * This is needed for both Zoom and Google Calendar APIs which interpret + * times with Z as UTC, but times without Z in the specified timezone parameter. * - * When Claude sends "2026-01-15T11:00:00" for 11 AM ET, JavaScript parses it - * as 11:00 UTC (server local time). By stripping the Z suffix, we tell Zoom - * to interpret "2026-01-15T11:00:00" in the timezone we specify (America/New_York), - * resulting in the correct 11:00 AM ET meeting time. + * When Claude sends "2026-01-15T13:00:00" for 1 PM, JavaScript parses it + * as 13:00 server local time (UTC on Fly.io). By stripping the Z suffix, + * we tell the APIs to interpret "2026-01-15T13:00:00" in the timezone we + * specify (America/New_York), resulting in the correct 1 PM ET meeting time. */ -function formatDateForZoom(date: Date): string { - // toISOString() returns "2026-01-15T11:00:00.000Z" - // We strip the milliseconds and Z to get "2026-01-15T11:00:00" +function formatDateWithoutZ(date: Date): string { + // toISOString() returns "2026-01-15T13:00:00.000Z" + // We strip the milliseconds and Z to get "2026-01-15T13:00:00" return date.toISOString().replace(/\.\d{3}Z$/, ''); } @@ -103,7 +103,7 @@ export async function scheduleMeeting(options: ScheduleMeetingOptions): Promise< zoomMeeting = await zoom.createMeeting(ZOOM_HOST_EMAIL, { topic: `${workingGroup.name}: ${options.title}`, // Format without Z suffix so Zoom interprets in the specified timezone - start_time: formatDateForZoom(options.startTime), + start_time: formatDateWithoutZ(options.startTime), duration: durationMinutes, timezone, agenda: options.agenda || options.description, @@ -172,15 +172,16 @@ export async function scheduleMeeting(options: ScheduleMeetingOptions): Promise< })); // Build calendar event + // Use formatDateWithoutZ so Google interprets in the specified timezone const eventInput: calendar.CreateCalendarEventInput = { summary: `${workingGroup.name}: ${options.title}`, description: buildCalendarDescription(options.description, options.agenda, zoomMeeting), start: { - dateTime: options.startTime.toISOString(), + dateTime: formatDateWithoutZ(options.startTime), timeZone: timezone, }, end: { - dateTime: endTime.toISOString(), + dateTime: formatDateWithoutZ(endTime), timeZone: timezone, }, attendees: attendeeEmails, From 7a16ac9b8b9d624334e80e9607c1dc367e4da5fb Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Thu, 15 Jan 2026 12:53:32 -0500 Subject: [PATCH 39/49] feat: Rename Research section to Perspectives and add homepage preview (#781) - Rename "Research & Ideas" to "Perspectives" across the site - Fix perspectives article detail page routing (was redirecting to section) - Add "The Latest" section to homepage showing recent perspectives - Add 301 redirect from /latest/research to /latest/perspectives - Update Addie MCP tools with new section URLs Co-authored-by: Claude Opus 4.5 --- .changeset/twelve-hounds-stand.md | 2 + server/public/index.html | 149 ++++++++++++++++++ server/public/nav.js | 4 +- server/src/addie/mcp/member-tools.ts | 10 +- .../172_rename_research_to_perspectives.sql | 8 + server/src/http.ts | 16 +- server/src/routes/latest.ts | 15 +- 7 files changed, 186 insertions(+), 18 deletions(-) create mode 100644 .changeset/twelve-hounds-stand.md create mode 100644 server/src/db/migrations/172_rename_research_to_perspectives.sql diff --git a/.changeset/twelve-hounds-stand.md b/.changeset/twelve-hounds-stand.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/twelve-hounds-stand.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/server/public/index.html b/server/public/index.html index 187c3a1a78..972063797a 100644 --- a/server/public/index.html +++ b/server/public/index.html @@ -409,6 +409,92 @@ gap: 1.5rem; } } + + /* Latest Posts Section */ + .latestPostsSection_AdCP { + padding: 4rem 0; + background: var(--ifm-color-emphasis-50, #f9fafb); + } + .latestPostsHeader_AdCP { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; + } + .latestPostsHeader_AdCP .sectionTitle_Ut5p { + margin-bottom: 0; + } + .viewAllLink_AdCP { + font-size: 0.95rem; + font-weight: 500; + color: var(--color-brand, #1a36b4); + text-decoration: none; + } + .viewAllLink_AdCP:hover { + text-decoration: underline; + } + .latestPostsGrid_AdCP { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1.5rem; + } + .latestPostCard_AdCP { + background: white; + border-radius: 12px; + padding: 1.5rem; + box-shadow: 0 2px 8px rgba(0,0,0,0.06); + transition: all 0.2s ease; + border: 1px solid transparent; + display: flex; + flex-direction: column; + } + .latestPostCard_AdCP:hover { + border-color: var(--color-brand, #1a36b4); + box-shadow: 0 4px 16px rgba(26, 54, 180, 0.1); + transform: translateY(-2px); + } + .latestPostMeta_AdCP { + font-size: 0.8rem; + color: var(--ifm-color-emphasis-600); + margin-bottom: 0.75rem; + } + .latestPostTitle_AdCP { + font-size: 1.1rem; + font-weight: 600; + line-height: 1.4; + margin-bottom: 0.75rem; + color: var(--ifm-heading-color); + } + .latestPostTitle_AdCP a { + color: inherit; + text-decoration: none; + } + .latestPostTitle_AdCP a:hover { + color: var(--color-brand, #1a36b4); + } + .latestPostExcerpt_AdCP { + font-size: 0.9rem; + color: var(--ifm-color-emphasis-700); + line-height: 1.6; + flex-grow: 1; + } + .latestPostsLoading_AdCP, + .latestPostsEmpty_AdCP { + grid-column: 1 / -1; + text-align: center; + padding: 2rem; + color: var(--ifm-color-emphasis-600); + } + @media (max-width: 900px) { + .latestPostsGrid_AdCP { + grid-template-columns: 1fr; + } + .latestPostsHeader_AdCP { + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + } + } @@ -696,6 +782,23 @@

Join the community

+ +
+
+
+
+
+

The Latest

+ View all perspectives +
+
+
Loading...
+
+
+
+
+
+
@@ -717,5 +820,51 @@

Built in the open

+ + \ No newline at end of file diff --git a/server/public/nav.js b/server/public/nav.js index 82f4a9107d..24531f17c4 100644 --- a/server/public/nav.js +++ b/server/public/nav.js @@ -204,7 +204,7 @@ @@ -633,56 +634,112 @@

${escapeHtml(s.title)}

document.getElementById('meetingModal').style.display = 'flex'; } + // Edit existing meeting + function editMeeting() { + if (!viewingMeeting) return; + + const m = viewingMeeting; + const date = new Date(m.start_time); + const tz = m.timezone || 'America/New_York'; + + // Format date/time in the meeting's timezone for editing + const dateFormatter = new Intl.DateTimeFormat('en-CA', { timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit' }); + const timeFormatter = new Intl.DateTimeFormat('en-US', { timeZone: tz, hour: '2-digit', minute: '2-digit', hour12: false }); + const formattedDate = dateFormatter.format(date); // YYYY-MM-DD format + const formattedTime = timeFormatter.format(date).replace(/^24:/, '00:'); // HH:MM format + + document.getElementById('modalTitle').textContent = 'Edit Meeting'; + document.getElementById('meetingId').value = m.id; + document.getElementById('workingGroupId').value = m.working_group_id; + document.getElementById('title').value = m.title; + document.getElementById('meetingDate').value = formattedDate; + document.getElementById('meetingTime').value = formattedTime; + document.getElementById('timezone').value = tz; + document.getElementById('description').value = m.description || m.agenda || ''; + + // Calculate duration from start_time and end_time + if (m.end_time) { + const endDate = new Date(m.end_time); + const durationMs = endDate.getTime() - date.getTime(); + document.getElementById('duration').value = Math.round(durationMs / 60000); + } else { + document.getElementById('duration').value = 60; + } + + // Hide invite mode for edits (can't change invites after creation) + document.getElementById('inviteMode').parentElement.style.display = 'none'; + + document.getElementById('saveBtn').textContent = 'Save Changes'; + closeDetailsModal(); + document.getElementById('meetingModal').style.display = 'flex'; + } + function closeModal() { document.getElementById('meetingModal').style.display = 'none'; + // Reset invite mode visibility + document.getElementById('inviteMode').parentElement.style.display = 'block'; + document.getElementById('saveBtn').textContent = 'Schedule'; } - // Save meeting + // Save meeting (create or update) async function saveMeeting(event) { event.preventDefault(); + const meetingId = document.getElementById('meetingId').value; + const isUpdate = !!meetingId; + const date = document.getElementById('meetingDate').value; const time = document.getElementById('meetingTime').value; const timezone = document.getElementById('timezone').value; + const durationMinutes = parseInt(document.getElementById('duration').value) || 60; // Construct ISO datetime const startTime = new Date(`${date}T${time}`); + const endTime = new Date(startTime.getTime() + durationMinutes * 60000); const data = { - working_group_id: document.getElementById('workingGroupId').value, title: document.getElementById('title').value.trim(), description: document.getElementById('description').value.trim() || null, start_time: startTime.toISOString(), - duration_minutes: parseInt(document.getElementById('duration').value) || 60, + end_time: endTime.toISOString(), timezone: timezone, - invite_mode: document.getElementById('inviteMode').value, }; + // Only include these fields for new meetings + if (!isUpdate) { + data.working_group_id = document.getElementById('workingGroupId').value; + data.duration_minutes = durationMinutes; + data.invite_mode = document.getElementById('inviteMode').value; + } + const saveBtn = document.getElementById('saveBtn'); saveBtn.disabled = true; - saveBtn.textContent = 'Scheduling...'; + saveBtn.textContent = isUpdate ? 'Saving...' : 'Scheduling...'; try { - const response = await fetch('/api/admin/meetings', { - method: 'POST', + const url = isUpdate ? `/api/admin/meetings/${meetingId}` : '/api/admin/meetings'; + const method = isUpdate ? 'PUT' : 'POST'; + + const response = await fetch(url, { + method: method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (!response.ok) { const error = await response.json(); - throw new Error(error.message || 'Failed to schedule meeting'); + throw new Error(error.message || `Failed to ${isUpdate ? 'update' : 'schedule'} meeting`); } closeModal(); loadMeetings(); } catch (error) { - console.error('Error scheduling meeting:', error); - alert('Failed to schedule meeting: ' + error.message); + console.error(`Error ${isUpdate ? 'updating' : 'scheduling'} meeting:`, error); + alert(`Failed to ${isUpdate ? 'update' : 'schedule'} meeting: ` + error.message); } saveBtn.disabled = false; - saveBtn.textContent = 'Schedule'; + saveBtn.textContent = isUpdate ? 'Save Changes' : 'Schedule'; } // View meeting details @@ -731,7 +788,10 @@

${escapeHtml(s.title)}

`; - document.getElementById('cancelMeetingBtn').style.display = m.status === 'scheduled' ? 'block' : 'none'; + // Only show Edit and Cancel buttons for scheduled meetings + const isScheduled = m.status === 'scheduled'; + document.getElementById('editMeetingBtn').style.display = isScheduled ? 'inline-block' : 'none'; + document.getElementById('cancelMeetingBtn').style.display = isScheduled ? 'inline-block' : 'none'; document.getElementById('detailsModal').style.display = 'flex'; } catch (error) { console.error('Error loading meeting:', error); diff --git a/server/public/working-groups/detail.html b/server/public/working-groups/detail.html index 2bc76e2327..a263930244 100644 --- a/server/public/working-groups/detail.html +++ b/server/public/working-groups/detail.html @@ -1674,10 +1674,10 @@

Recent Events

const response = await fetch(`/api/meetings?working_group_id=${currentGroup.id}`); if (!response.ok) return; - const meetings = await response.json(); - // Filter to only upcoming meetings + const data = await response.json(); + // API returns { meetings: [...] } - filter to scheduled upcoming meetings const now = new Date(); - const upcoming = meetings.filter(m => new Date(m.start_time) > now && m.status === 'scheduled'); + const upcoming = (data.meetings || []).filter(m => new Date(m.start_time) > now && m.status === 'scheduled'); const meetingsSection = document.getElementById('meetingsSection'); const meetingsList = document.getElementById('meetingsList'); diff --git a/server/src/addie/mcp/meeting-tools.ts b/server/src/addie/mcp/meeting-tools.ts index c4046fbc72..ae3915d1db 100644 --- a/server/src/addie/mcp/meeting-tools.ts +++ b/server/src/addie/mcp/meeting-tools.ts @@ -384,6 +384,48 @@ Example prompts this handles: required: ['meeting_id'], }, }, + { + name: 'update_meeting', + description: `Update an existing meeting's details. Use this when someone wants to change the time, title, description, or agenda of a scheduled meeting. + +This will update the meeting in the database, Zoom (if configured), and Google Calendar. + +IMPORTANT: For start_time, provide the time in the user's timezone WITHOUT a Z suffix (same as schedule_meeting).`, + input_schema: { + type: 'object' as const, + properties: { + meeting_id: { + type: 'string', + description: 'Meeting ID (UUID or Zoom meeting ID)', + }, + title: { + type: 'string', + description: 'New meeting title', + }, + description: { + type: 'string', + description: 'New meeting description', + }, + agenda: { + type: 'string', + description: 'New meeting agenda (markdown supported)', + }, + start_time: { + type: 'string', + description: 'New start time in ISO 8601 format WITHOUT timezone suffix (e.g., "2026-01-15T14:00:00" for 2pm). Use timezone parameter to specify the timezone.', + }, + duration_minutes: { + type: 'number', + description: 'New duration in minutes', + }, + timezone: { + type: 'string', + description: 'Timezone for start_time (default: America/New_York)', + }, + }, + required: ['meeting_id'], + }, + }, { name: 'add_meeting_attendee', description: `Add someone to a meeting. Use this when someone asks to add a specific person to a scheduled meeting.`, @@ -929,6 +971,165 @@ export function createMeetingToolHandlers( } }); + // Update meeting + handlers.set('update_meeting', async (input) => { + const permCheck = await checkSchedulePermission(); + if (permCheck) return permCheck; + + const meetingId = input.meeting_id as string; + + // Try to find by our UUID first, then by Zoom meeting ID + let meeting = await meetingsDb.getMeetingById(meetingId); + if (!meeting) { + meeting = await meetingsDb.getMeetingByZoomId(meetingId); + } + if (!meeting) { + return `❌ Meeting not found: "${meetingId}". Use list_upcoming_meetings to find the correct meeting ID.`; + } + + if (meeting.status === 'cancelled') { + return `❌ Cannot update a cancelled meeting.`; + } + + // Build updates object + const updates: Record = {}; + const changes: string[] = []; + + if (input.title) { + updates.title = input.title as string; + changes.push(`Title → "${input.title}"`); + } + + if (input.description !== undefined) { + updates.description = input.description as string; + changes.push('Description updated'); + } + + if (input.agenda !== undefined) { + updates.agenda = input.agenda as string; + changes.push('Agenda updated'); + } + + // Handle time updates + const startTimeStr = input.start_time as string | undefined; + const durationMinutes = input.duration_minutes as number | undefined; + const timezone = (input.timezone as string) || meeting.timezone || 'America/New_York'; + + // Calculate current duration from start_time and end_time + const currentDuration = meeting.start_time && meeting.end_time + ? Math.round((meeting.end_time.getTime() - meeting.start_time.getTime()) / 60000) + : 60; + + if (startTimeStr) { + const startTime = parseDateInTimezone(startTimeStr, timezone); + if (isNaN(startTime.getTime())) { + return `❌ Invalid start time format. Please use ISO 8601 format (e.g., "2026-01-15T14:00:00").`; + } + updates.start_time = startTime; + updates.timezone = timezone; + + const duration = durationMinutes || currentDuration; + updates.end_time = new Date(startTime.getTime() + duration * 60 * 1000); + + changes.push(`Time → ${formatDate(startTime)} at ${formatTime(startTime, timezone)}`); + } else if (durationMinutes && meeting.start_time) { + // Just updating duration, keep existing start time + updates.end_time = new Date(meeting.start_time.getTime() + durationMinutes * 60 * 1000); + changes.push(`Duration → ${durationMinutes} minutes`); + } + + if (changes.length === 0) { + return `No changes specified. You can update: title, description, agenda, start_time, duration_minutes, timezone.`; + } + + try { + // Update in database + const updatedMeeting = await meetingsDb.updateMeeting(meeting.id, updates); + if (!updatedMeeting) { + return `❌ Failed to update meeting in database.`; + } + + const errors: string[] = []; + + // Update Zoom meeting if time changed and Zoom meeting exists + if (updates.start_time && meeting.zoom_meeting_id && zoom.isZoomConfigured()) { + try { + const startTime = updates.start_time as Date; + await zoom.updateMeeting(meeting.zoom_meeting_id, { + start_time: startTime.toISOString(), + duration: durationMinutes || currentDuration, + timezone, + }); + } catch (error) { + const msg = error instanceof Error ? error.message : 'Unknown error'; + errors.push(`Zoom update failed: ${msg}`); + logger.error({ err: error, meetingId: meeting.id }, 'Failed to update Zoom meeting'); + } + } + + // Update Google Calendar event if it exists + if (meeting.google_calendar_event_id && calendar.isGoogleCalendarConfigured()) { + try { + const startTime = (updates.start_time as Date) || meeting.start_time; + const title = (updates.title as string) || meeting.title; + + // Calculate end time - use updated value, or compute from start + duration + let endTime = updates.end_time as Date | undefined; + if (!endTime) { + if (meeting.end_time) { + endTime = meeting.end_time; + } else { + // Fallback: compute from start time + current duration + endTime = new Date(startTime.getTime() + currentDuration * 60 * 1000); + } + } + + // Get working group for calendar event summary + const workingGroup = await workingGroupDb.getWorkingGroupById(meeting.working_group_id); + const summary = workingGroup ? `${workingGroup.name}: ${title}` : title; + + await calendar.updateEvent(meeting.google_calendar_event_id, { + summary, + description: (updates.description as string) || meeting.description || undefined, + start: { + dateTime: startTime.toISOString(), + timeZone: timezone, + }, + end: { + dateTime: endTime.toISOString(), + timeZone: timezone, + }, + }); + } catch (error) { + const msg = error instanceof Error ? error.message : 'Unknown error'; + errors.push(`Calendar update failed: ${msg}`); + logger.error({ err: error, meetingId: meeting.id }, 'Failed to update calendar event'); + } + } + + let response = `✅ Updated: **${updatedMeeting.title}**\n\n`; + response += `**Changes:**\n${changes.map(c => `• ${c}`).join('\n')}\n`; + + if (errors.length > 0) { + response += `\n⚠️ Some integrations had issues:\n${errors.map(e => `• ${e}`).join('\n')}`; + } else if (meeting.google_calendar_event_id) { + response += `\n📧 Calendar invites have been updated.`; + } + + logger.info({ + meetingId: meeting.id, + changes, + updatedBy: getUserId(), + }, 'Meeting updated via Addie'); + + return response; + } catch (error) { + const msg = error instanceof Error ? error.message : 'Unknown error'; + logger.error({ err: error, meetingId: meeting.id }, 'Failed to update meeting via Addie'); + return `❌ Failed to update meeting: ${msg}`; + } + }); + // Add attendee handlers.set('add_meeting_attendee', async (input) => { const permCheck = await checkSchedulePermission(); From 46b602c72e6388977e9274d12aa0f173bef13693 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Thu, 15 Jan 2026 19:44:05 -0500 Subject: [PATCH 45/49] fix: Add access control for private Slack channel search (#786) - Fix channel activity query to use slack_ts instead of created_at - Remove aao-admin from exclusion list (was blocking indexing) - Add access control for private channels based on working group membership - Filter search results to only show channels user has access to - Only index private channels that have linked working groups - Add getAccessiblePrivateChannelIds for efficient membership lookup - Add getWorkingGroupIdsByUser for local membership queries This ensures users can only see search results from: 1. Public channels (accessible to all workspace members) 2. Private channels where they are a working group member Co-authored-by: Claude Opus 4.5 --- .changeset/yellow-deer-brush.md | 4 + server/src/addie/bolt-app.ts | 13 ++ server/src/addie/handler.ts | 13 ++ .../src/addie/jobs/slack-history-backfill.ts | 33 +++- server/src/addie/mcp/knowledge-search.ts | 42 +++++- server/src/db/addie-db.ts | 53 ++++++- server/src/db/working-group-db.ts | 13 ++ server/src/slack/client.ts | 141 ++++++++++++++++++ server/src/slack/events.ts | 22 ++- 9 files changed, 315 insertions(+), 19 deletions(-) create mode 100644 .changeset/yellow-deer-brush.md diff --git a/.changeset/yellow-deer-brush.md b/.changeset/yellow-deer-brush.md new file mode 100644 index 0000000000..19e5573f43 --- /dev/null +++ b/.changeset/yellow-deer-brush.md @@ -0,0 +1,4 @@ +--- +--- + +Internal changes only - no protocol impact diff --git a/server/src/addie/bolt-app.ts b/server/src/addie/bolt-app.ts index 8f63a7a28f..5cec58c153 100644 --- a/server/src/addie/bolt-app.ts +++ b/server/src/addie/bolt-app.ts @@ -709,6 +709,19 @@ async function createUserScopedTools( allHandlers.set('bookmark_resource', createUserScopedBookmarkHandler(slackUserId)); } + // Override Slack search handlers with user-scoped versions (for private channel access control) + if (slackUserId) { + const userScopedKnowledgeHandlers = createKnowledgeToolHandlers(slackUserId); + const searchSlackHandler = userScopedKnowledgeHandlers.get('search_slack'); + const getChannelActivityHandler = userScopedKnowledgeHandlers.get('get_channel_activity'); + if (searchSlackHandler) { + allHandlers.set('search_slack', searchSlackHandler); + } + if (getChannelActivityHandler) { + allHandlers.set('get_channel_activity', getChannelActivityHandler); + } + } + return { tools: { tools: allTools, diff --git a/server/src/addie/handler.ts b/server/src/addie/handler.ts index 5990b644ed..955bf63590 100644 --- a/server/src/addie/handler.ts +++ b/server/src/addie/handler.ts @@ -300,6 +300,19 @@ async function createUserScopedTools( allHandlers.set('bookmark_resource', createUserScopedBookmarkHandler(slackUserId)); } + // Override Slack search handlers with user-scoped versions (for private channel access control) + if (slackUserId) { + const userScopedKnowledgeHandlers = createKnowledgeToolHandlers(slackUserId); + const searchSlackHandler = userScopedKnowledgeHandlers.get('search_slack'); + const getChannelActivityHandler = userScopedKnowledgeHandlers.get('get_channel_activity'); + if (searchSlackHandler) { + allHandlers.set('search_slack', searchSlackHandler); + } + if (getChannelActivityHandler) { + allHandlers.set('get_channel_activity', getChannelActivityHandler); + } + } + return { tools: { tools: allTools, diff --git a/server/src/addie/jobs/slack-history-backfill.ts b/server/src/addie/jobs/slack-history-backfill.ts index 15e9429548..76a1fba496 100644 --- a/server/src/addie/jobs/slack-history-backfill.ts +++ b/server/src/addie/jobs/slack-history-backfill.ts @@ -14,6 +14,7 @@ import { logger } from '../../logger.js'; import { AddieDatabase } from '../../db/addie-db.js'; +import { WorkingGroupDatabase } from '../../db/working-group-db.js'; import { getSlackChannels, getFullChannelHistory, @@ -24,13 +25,13 @@ import { } from '../../slack/client.js'; const addieDb = new AddieDatabase(); +const workingGroupDb = new WorkingGroupDatabase(); /** * Default channels to exclude from indexing (sensitive/admin channels) * These contain billing info, admin discussions, etc. that shouldn't be searchable */ const DEFAULT_EXCLUDED_CHANNELS = [ - 'aao-admin', 'aao-billing', 'admin', 'billing', @@ -65,7 +66,7 @@ export interface BackfillOptions { maxMessagesPerChannel?: number; /** Specific channel IDs to backfill (default: all accessible channels) */ channelIds?: string[]; - /** Include private channels (default: false) */ + /** Include private channels (default: true, access controlled at query time) */ includePrivateChannels?: boolean; /** Channel names to exclude (default: admin/billing channels) */ excludeChannelNames?: string[]; @@ -94,7 +95,7 @@ export async function runSlackHistoryBackfill(options: BackfillOptions = {}): Pr daysBack = 90, maxMessagesPerChannel = 1000, channelIds, - includePrivateChannels = false, + includePrivateChannels = true, // Access controlled at query time via membership checks excludeChannelNames = DEFAULT_EXCLUDED_CHANNELS, minMessageLength = 20, includeThreadReplies = true, @@ -172,11 +173,33 @@ export async function runSlackHistoryBackfill(options: BackfillOptions = {}): Pr })); } + // Filter private channels to only include those with working groups + // (enables fast local access checks without Slack API calls) + const privateChannelsBefore = channels.filter(c => c.isPrivate).length; + const filteredChannels: typeof channels = []; + for (const channel of channels) { + if (!channel.isPrivate) { + // Public channels - always include + filteredChannels.push(channel); + } else { + // Private channels - only include if they have a working group + const workingGroup = await workingGroupDb.getWorkingGroupBySlackChannelId(channel.id); + if (workingGroup) { + filteredChannels.push(channel); + } else { + logger.debug({ channelId: channel.id, channelName: channel.name }, 'Skipping private channel without working group'); + } + } + } + channels = filteredChannels; + const privateChannelsAfter = channels.filter(c => c.isPrivate).length; + logger.info({ channelCount: channels.length, publicCount: channels.filter(c => !c.isPrivate).length, - privateCount: channels.filter(c => c.isPrivate).length, - }, 'Backfilling channels'); + privateCount: privateChannelsAfter, + privateChannelsSkipped: privateChannelsBefore - privateChannelsAfter, + }, 'Backfilling channels (private channels without working groups skipped)'); // User cache to avoid repeated lookups const userCache = new Map(); diff --git a/server/src/addie/mcp/knowledge-search.ts b/server/src/addie/mcp/knowledge-search.ts index d0477c97f3..594b918066 100644 --- a/server/src/addie/mcp/knowledge-search.ts +++ b/server/src/addie/mcp/knowledge-search.ts @@ -38,6 +38,7 @@ import { // which is populated by channel history indexing. import { AddieDatabase } from '../../db/addie-db.js'; import { queueWebSearchResult } from '../services/content-curator.js'; +import { findChannelWithAccess, getAccessiblePrivateChannelIds } from '../../slack/client.js'; const addieDb = new AddieDatabase(); @@ -473,8 +474,9 @@ function extractSmartExcerpt(content: string, query: string, maxLength: number = /** * Tool handlers + * @param slackUserId - Optional Slack user ID for access control on private channels */ -export function createKnowledgeToolHandlers(): Map< +export function createKnowledgeToolHandlers(slackUserId?: string): Map< string, (input: Record) => Promise > { @@ -643,8 +645,27 @@ ${excerpt}`; const limit = Math.min((input.limit as number) || 10, 25); try { - // Search local database with optional channel filter - const localResults = await addieDb.searchSlackMessages(searchQuery, { limit, channel }); + // Check access if searching a specific channel and we have user context + if (channel && slackUserId) { + const accessResult = await findChannelWithAccess(channel, slackUserId); + if (accessResult && !accessResult.hasAccess) { + return `Cannot search #${accessResult.channel.name}: ${accessResult.reason || 'Access denied'}.\n\nPrivate channel messages are only accessible to channel members.`; + } + } + + // Get accessible private channel IDs for access filtering + // This ensures search results don't leak private channel content + let accessiblePrivateChannelIds: string[] | undefined; + if (slackUserId) { + accessiblePrivateChannelIds = await getAccessiblePrivateChannelIds(slackUserId); + } + + // Search local database with optional channel filter and access control + const localResults = await addieDb.searchSlackMessages(searchQuery, { + limit, + channel, + accessiblePrivateChannelIds, + }); if (localResults.length > 0) { const formatted = localResults @@ -682,6 +703,21 @@ ${excerpt}`; const limit = input.limit as number | undefined; try { + // Check access if we have user context + if (slackUserId) { + const accessResult = await findChannelWithAccess(channel, slackUserId); + if (accessResult && !accessResult.hasAccess) { + return `Cannot access #${accessResult.channel.name}: ${accessResult.reason || 'Access denied'}.\n\nPrivate channel activity is only accessible to channel members.`; + } + // If it's a private channel and not indexed, inform the user + if (accessResult && accessResult.channel.is_private) { + const messages = await addieDb.getChannelActivity(channel, { days, limit }); + if (messages.length === 0) { + return `No indexed activity found for private channel #${accessResult.channel.name}.\n\nPrivate channels are indexed but may not have historical data. Recent messages should appear after they are sent.`; + } + } + } + const messages = await addieDb.getChannelActivity(channel, { days, limit }); if (messages.length === 0) { diff --git a/server/src/db/addie-db.ts b/server/src/db/addie-db.ts index 699bdcb642..84961cfed6 100644 --- a/server/src/db/addie-db.ts +++ b/server/src/db/addie-db.ts @@ -717,21 +717,55 @@ export class AddieDatabase { /** * Search Slack messages stored locally using PostgreSQL full-text search + * + * @param accessiblePrivateChannelIds - List of private channel IDs the user has access to. + * If provided, results will only include public channels OR private channels in this list. + * If not provided (undefined), no access filtering is applied (use for internal/admin queries). */ async searchSlackMessages(searchQuery: string, options: { limit?: number; channel?: string; + accessiblePrivateChannelIds?: string[]; } = {}): Promise { const limit = options.limit ?? 10; const channel = options.channel; + const accessiblePrivateChannelIds = options.accessiblePrivateChannelIds; + + // Build dynamic query with optional filters + const params: (string | number | string[])[] = [searchQuery, limit]; + let paramIndex = 3; - // Build query with optional channel filter - const channelFilter = channel - ? `AND LOWER(slack_channel_name) LIKE LOWER($3)` - : ''; - const params: (string | number)[] = [searchQuery, limit]; + // Channel name filter + let channelFilter = ''; if (channel) { + channelFilter = `AND LOWER(slack_channel_name) LIKE LOWER($${paramIndex})`; params.push(`%${channel}%`); + paramIndex++; + } + + // Access control filter for private channels + // Only include messages from: + // 1. Public channels (those without a working group - tracked via slack_channel_id) + // 2. Private channels the user has access to (in accessiblePrivateChannelIds) + let accessFilter = ''; + if (accessiblePrivateChannelIds !== undefined) { + if (accessiblePrivateChannelIds.length > 0) { + // Include public channels (not in any working group) OR accessible private channels + accessFilter = `AND ( + NOT EXISTS ( + SELECT 1 FROM working_groups wg + WHERE wg.slack_channel_id = addie_knowledge.slack_channel_id + ) + OR slack_channel_id = ANY($${paramIndex}::text[]) + )`; + params.push(accessiblePrivateChannelIds); + } else { + // User has no private channel access - only show public channels + accessFilter = `AND NOT EXISTS ( + SELECT 1 FROM working_groups wg + WHERE wg.slack_channel_id = addie_knowledge.slack_channel_id + )`; + } } const result = await query( @@ -749,6 +783,7 @@ export class AddieDatabase { AND source_type = 'slack' AND search_vector @@ websearch_to_tsquery('english', $1) ${channelFilter} + ${accessFilter} ORDER BY rank DESC LIMIT $2`, params @@ -768,6 +803,7 @@ export class AddieDatabase { /** * Get recent messages from a channel (no keyword search, just by recency) + * Uses the actual Slack message timestamp (slack_ts) for filtering, not the DB record creation time */ async getChannelActivity(channel: string, options: { days?: number; @@ -794,13 +830,14 @@ export class AddieDatabase { slack_channel_name as channel_name, slack_username as username, slack_permalink as permalink, - created_at + TO_TIMESTAMP(slack_ts::numeric) as created_at FROM addie_knowledge WHERE is_active = TRUE AND source_type = 'slack' AND LOWER(slack_channel_name) LIKE LOWER($1) - AND created_at >= NOW() - INTERVAL '1 day' * $2 - ORDER BY created_at DESC + AND slack_ts IS NOT NULL + AND TO_TIMESTAMP(slack_ts::numeric) >= NOW() - INTERVAL '1 day' * $2 + ORDER BY slack_ts::numeric DESC LIMIT $3`, [`%${channel}%`, days, limit] ); diff --git a/server/src/db/working-group-db.ts b/server/src/db/working-group-db.ts index 86ebd6a910..ff86c68652 100644 --- a/server/src/db/working-group-db.ts +++ b/server/src/db/working-group-db.ts @@ -510,6 +510,19 @@ export class WorkingGroupDatabase { return result.rows.length > 0; } + /** + * Get all working group IDs a user is a member of + */ + async getWorkingGroupIdsByUser(userId: string): Promise { + const canonicalUserId = await this.resolveToCanonicalUserId(userId); + const result = await query<{ working_group_id: string }>( + `SELECT working_group_id FROM working_group_memberships + WHERE workos_user_id = $1 AND status = 'active'`, + [canonicalUserId] + ); + return result.rows.map(r => r.working_group_id); + } + /** * Get all memberships for a working group */ diff --git a/server/src/slack/client.ts b/server/src/slack/client.ts index 8494c497dd..29d048d598 100644 --- a/server/src/slack/client.ts +++ b/server/src/slack/client.ts @@ -7,6 +7,7 @@ import { logger } from '../logger.js'; import { SlackDatabase } from '../db/slack-db.js'; +import { WorkingGroupDatabase } from '../db/working-group-db.js'; import type { SlackUser, SlackChannel, @@ -23,6 +24,15 @@ function getSlackDb(): SlackDatabase { return slackDb; } +// Lazy-initialized working group database for access checks +let workingGroupDb: WorkingGroupDatabase | null = null; +function getWorkingGroupDb(): WorkingGroupDatabase { + if (!workingGroupDb) { + workingGroupDb = new WorkingGroupDatabase(); + } + return workingGroupDb; +} + // Use ADDIE_BOT_TOKEN as the primary token (fall back to SLACK_BOT_TOKEN for migration) const SLACK_BOT_TOKEN = process.env.ADDIE_BOT_TOKEN || process.env.SLACK_BOT_TOKEN; const SLACK_API_BASE = 'https://slack.com/api'; @@ -492,6 +502,137 @@ export async function getChannelMembers(channelId: string): Promise { return members; } +/** + * Check if a user has access to a channel + * Returns true for public channels, checks membership for private channels + * + * Private channels are only indexed if they have a linked working group, + * so we use local working group membership for access control (fast, no API calls). + */ +export async function checkChannelAccess( + channelId: string, + slackUserId: string +): Promise<{ hasAccess: boolean; isPrivate: boolean; reason?: string }> { + try { + const channelInfo = await getChannelInfo(channelId); + if (!channelInfo) { + return { hasAccess: false, isPrivate: false, reason: 'Channel not found' }; + } + + // Public channels are accessible to all workspace members + if (!channelInfo.is_private) { + return { hasAccess: true, isPrivate: false }; + } + + // Private channel - check local working group membership + const wgDb = getWorkingGroupDb(); + const workingGroup = await wgDb.getWorkingGroupBySlackChannelId(channelId); + + if (!workingGroup) { + // Private channel without a working group is not indexed + return { + hasAccess: false, + isPrivate: true, + reason: 'This private channel is not indexed (no linked working group)', + }; + } + + // Check local membership + const slackDb = getSlackDb(); + const mapping = await slackDb.getBySlackUserId(slackUserId); + + if (mapping?.workos_user_id) { + const isMember = await wgDb.isMember(workingGroup.id, mapping.workos_user_id); + if (isMember) { + return { hasAccess: true, isPrivate: true }; + } + } + + return { + hasAccess: false, + isPrivate: true, + reason: 'You are not a member of this private channel', + }; + } catch (error) { + logger.warn({ error, channelId, slackUserId }, 'Failed to check channel access'); + // Fail closed - deny access on error + return { hasAccess: false, isPrivate: false, reason: 'Failed to verify access' }; + } +} + +/** + * Find a channel by name (partial match) and check user access + * Returns channel info if found and accessible + */ +export async function findChannelWithAccess( + channelName: string, + slackUserId: string +): Promise<{ channel: SlackChannel; hasAccess: boolean; reason?: string } | null> { + try { + // Get all channels the bot can see + const allChannels = await getSlackChannels({ + types: 'public_channel,private_channel', + exclude_archived: true, + }); + + // Find channel by name (case-insensitive partial match) + const normalizedName = channelName.toLowerCase(); + const matchedChannel = allChannels.find( + (c) => c.name.toLowerCase().includes(normalizedName) + ); + + if (!matchedChannel) { + return null; + } + + // Check access + const access = await checkChannelAccess(matchedChannel.id, slackUserId); + + return { + channel: matchedChannel, + hasAccess: access.hasAccess, + reason: access.reason, + }; + } catch (error) { + logger.warn({ error, channelName, slackUserId }, 'Failed to find channel with access check'); + return null; + } +} + +/** + * Get the list of private channel IDs the user has access to + * Used to filter search results - only returns channels with working groups + */ +export async function getAccessiblePrivateChannelIds(slackUserId: string): Promise { + try { + const slackDb = getSlackDb(); + const wgDb = getWorkingGroupDb(); + + // Get user's WorkOS ID + const mapping = await slackDb.getBySlackUserId(slackUserId); + if (!mapping?.workos_user_id) { + return []; + } + + // Get all working groups the user is a member of + const workingGroupIds = await wgDb.getWorkingGroupIdsByUser(mapping.workos_user_id); + + // Get the channel IDs for these working groups + const channelIds: string[] = []; + for (const wgId of workingGroupIds) { + const workingGroup = await wgDb.getWorkingGroupById(wgId); + if (workingGroup?.slack_channel_id) { + channelIds.push(workingGroup.slack_channel_id); + } + } + + return channelIds; + } catch (error) { + logger.warn({ error, slackUserId }, 'Failed to get accessible private channel IDs'); + return []; + } +} + /** * Search message result */ diff --git a/server/src/slack/events.ts b/server/src/slack/events.ts index f4d97d3bf4..bb7374ec7c 100644 --- a/server/src/slack/events.ts +++ b/server/src/slack/events.ts @@ -407,9 +407,25 @@ export async function handleMessage(event: SlackMessageEvent): Promise { }, }); - // Index public channel messages for Addie's local search - // Skip DMs (im) and private channels - only index public messages - if (event.channel_type === 'channel' && event.text && event.text.length > 20) { + // Index channel messages for Addie's local search + // Public channels: always index + // Private channels (group): only index if linked to a working group (for fast local access checks) + const isPublicChannel = event.channel_type === 'channel'; + const isPrivateChannel = event.channel_type === 'group'; + + let shouldIndex = false; + if (isPublicChannel) { + shouldIndex = true; + } else if (isPrivateChannel) { + // Only index private channels that have a working group (enables fast local access checks) + const workingGroup = await workingGroupDb.getWorkingGroupBySlackChannelId(event.channel); + shouldIndex = !!workingGroup; + if (!shouldIndex) { + logger.debug({ channelId: event.channel }, 'Skipping private channel without working group'); + } + } + + if (shouldIndex && event.text && event.text.length > 20) { await indexMessageForSearch(event); // Queue for passive note extraction (async, rate-limited) From 1c4aadf94624822658d37184ab4144948a958449 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Thu, 15 Jan 2026 19:51:10 -0500 Subject: [PATCH 46/49] chore: Move scheduled job logs to debug level (#788) Reduce log noise from scheduled jobs by changing routine success logs from info to debug level while keeping error logs at info level. Affected jobs: - Industry monitor: RSS feed fetching - Content curator: RSS perspectives, community articles, pending resources - Outreach scheduler: Proactive outreach Errors are still logged at info level with a modified message suffix "with errors" to maintain visibility into failures. Co-authored-by: Claude Opus 4.5 --- .changeset/shiny-dots-act.md | 4 ++++ server/src/addie/services/feed-fetcher.ts | 7 ++++++- .../src/addie/services/proactive-outreach.ts | 6 ++++-- server/src/http.ts | 20 +++++++++---------- 4 files changed, 24 insertions(+), 13 deletions(-) create mode 100644 .changeset/shiny-dots-act.md diff --git a/.changeset/shiny-dots-act.md b/.changeset/shiny-dots-act.md new file mode 100644 index 0000000000..3aacdbb4cb --- /dev/null +++ b/.changeset/shiny-dots-act.md @@ -0,0 +1,4 @@ +--- +--- + +Reduce log noise from scheduled jobs by moving routine success logs to debug level while keeping error logs at info level. diff --git a/server/src/addie/services/feed-fetcher.ts b/server/src/addie/services/feed-fetcher.ts index f0a1811dc4..fe21bb37d8 100644 --- a/server/src/addie/services/feed-fetcher.ts +++ b/server/src/addie/services/feed-fetcher.ts @@ -228,8 +228,13 @@ export async function processFeedsToFetch(): Promise<{ } // Only log when there's something notable - if (totalNewPerspectives > 0 || errorCount > 0) { + if (errorCount > 0) { logger.info( + { feedsProcessed: feeds.length, newPerspectives: totalNewPerspectives, errors: errorCount }, + 'RSS feed processing complete with errors' + ); + } else if (totalNewPerspectives > 0) { + logger.debug( { feedsProcessed: feeds.length, newPerspectives: totalNewPerspectives, errors: errorCount }, 'RSS feed processing complete' ); diff --git a/server/src/addie/services/proactive-outreach.ts b/server/src/addie/services/proactive-outreach.ts index ff05335402..43964312cc 100644 --- a/server/src/addie/services/proactive-outreach.ts +++ b/server/src/addie/services/proactive-outreach.ts @@ -634,8 +634,10 @@ export async function runOutreachScheduler(options: { } } - if (sent > 0 || errors > 0) { - logger.info({ sent, skipped, errors }, 'Outreach scheduler completed'); + if (errors > 0) { + logger.info({ sent, skipped, errors }, 'Outreach scheduler completed with errors'); + } else if (sent > 0) { + logger.debug({ sent, skipped, errors }, 'Outreach scheduler completed'); } return { processed: candidates.length, sent, skipped, errors }; } diff --git a/server/src/http.ts b/server/src/http.ts index 80d2d0febd..cf818aba07 100644 --- a/server/src/http.ts +++ b/server/src/http.ts @@ -6958,17 +6958,17 @@ Disallow: /api/admin/ // Process manually queued resources const result = await processPendingResources({ limit: 5 }); if (result.processed > 0) { - logger.info(result, 'Content curator: processed pending resources'); + logger.debug(result, 'Content curator: processed pending resources'); } // Process RSS perspectives const rssResult = await processRssPerspectives({ limit: 5 }); if (rssResult.processed > 0) { - logger.info(rssResult, 'Content curator: processed RSS perspectives'); + logger.debug(rssResult, 'Content curator: processed RSS perspectives'); } // Process community articles const communityResult = await processCommunityArticles({ limit: 5 }); if (communityResult.processed > 0) { - logger.info(communityResult, 'Content curator: processed community articles'); + logger.debug(communityResult, 'Content curator: processed community articles'); } // Send replies to processed community articles const replyResult = await sendCommunityReplies(async (channelId, threadTs, text) => { @@ -6976,7 +6976,7 @@ Disallow: /api/admin/ return result.ok; }); if (replyResult.sent > 0) { - logger.info(replyResult, 'Content curator: sent community article replies'); + logger.debug(replyResult, 'Content curator: sent community article replies'); } } catch (err) { logger.error({ err }, 'Content curator: initial processing failed'); @@ -6989,17 +6989,17 @@ Disallow: /api/admin/ // Process manually queued resources const result = await processPendingResources({ limit: 5 }); if (result.processed > 0) { - logger.info(result, 'Content curator: processed pending resources'); + logger.debug(result, 'Content curator: processed pending resources'); } // Process RSS perspectives const rssResult = await processRssPerspectives({ limit: 5 }); if (rssResult.processed > 0) { - logger.info(rssResult, 'Content curator: processed RSS perspectives'); + logger.debug(rssResult, 'Content curator: processed RSS perspectives'); } // Process community articles const communityResult = await processCommunityArticles({ limit: 5 }); if (communityResult.processed > 0) { - logger.info(communityResult, 'Content curator: processed community articles'); + logger.debug(communityResult, 'Content curator: processed community articles'); } // Send replies to processed community articles const replyResult = await sendCommunityReplies(async (channelId, threadTs, text) => { @@ -7007,7 +7007,7 @@ Disallow: /api/admin/ return result.ok; }); if (replyResult.sent > 0) { - logger.info(replyResult, 'Content curator: sent community article replies'); + logger.debug(replyResult, 'Content curator: sent community article replies'); } } catch (err) { logger.error({ err }, 'Content curator: periodic processing failed'); @@ -7033,7 +7033,7 @@ Disallow: /api/admin/ try { const result = await processFeedsToFetch(); if (result.feedsProcessed > 0) { - logger.info(result, 'Industry monitor: fetched RSS feeds'); + logger.debug(result, 'Industry monitor: fetched RSS feeds'); } } catch (err) { logger.error({ err }, 'Industry monitor: initial feed fetch failed'); @@ -7044,7 +7044,7 @@ Disallow: /api/admin/ try { const result = await processFeedsToFetch(); if (result.feedsProcessed > 0) { - logger.info(result, 'Industry monitor: fetched RSS feeds'); + logger.debug(result, 'Industry monitor: fetched RSS feeds'); } } catch (err) { logger.error({ err }, 'Industry monitor: periodic feed fetch failed'); From 876b9510722126b4a4edecf48c513cdd9f893a31 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Fri, 16 Jan 2026 07:12:01 -0500 Subject: [PATCH 47/49] fix: Simplify members section to show only organizations (#791) - Remove "Founding Members" vs "Members" distinction - Show single "Members" section with organization logos only - Filter out individual members (non-organizations) from display - Remove unused CSS classes (.launch-members, .launch-member, .member-name-link) - Update dynamic loading to only show members with logos Co-authored-by: Claude Opus 4.5 --- .changeset/honest-stamps-switch.md | 2 + server/public/org-index.html | 82 ++++-------------------------- 2 files changed, 13 insertions(+), 71 deletions(-) create mode 100644 .changeset/honest-stamps-switch.md diff --git a/.changeset/honest-stamps-switch.md b/.changeset/honest-stamps-switch.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/honest-stamps-switch.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/server/public/org-index.html b/server/public/org-index.html index 24185f5c0f..bd411b3206 100644 --- a/server/public/org-index.html +++ b/server/public/org-index.html @@ -235,17 +235,6 @@ .member-logo:hover { opacity: 1; } - .launch-members { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 1rem 2rem; - margin-top: 1rem; - } - .launch-member { - font-size: 0.95rem; - color: var(--color-text-muted); - } .all-members-link { text-align: center; margin-top: 2.5rem; @@ -273,15 +262,6 @@ .member-logo-link:hover .member-logo { opacity: 1; } - .member-name-link { - color: var(--color-text-muted); - text-decoration: none; - transition: color 0.2s; - } - .member-name-link:hover { - color: var(--color-brand); - } - /* Go Agentic Section */ .go-agentic { padding: 2.5rem 2rem; @@ -518,11 +498,11 @@

Working Groups

Our Members

Industry leaders building the future of advertising together

- -
-
Founding Members
-
- + +
+
Members
+
+ @@ -533,34 +513,6 @@

Our Members

- - - @@ -608,30 +560,18 @@

Shape the Future of Advertising

const data = await response.json(); const members = data.members || []; - // Separate founding members (with logos) from others - const foundingMembers = members.filter(m => m.is_founding_member && m.show_in_carousel && m.logo_url); - const otherMembers = members.filter(m => !m.is_founding_member || !m.show_in_carousel || !m.logo_url); + // Only show members with logos (organizations, not individuals) + const membersWithLogos = members.filter(m => m.show_in_carousel && m.logo_url); - // Render founding members with logos - const foundingLogosEl = document.getElementById('founding-member-logos'); - if (foundingMembers.length > 0) { - foundingLogosEl.innerHTML = foundingMembers.map(member => ` + // Render members with logos + const memberLogosEl = document.getElementById('member-logos'); + if (membersWithLogos.length > 0) { + memberLogosEl.innerHTML = membersWithLogos.map(member => ` `).join(''); } - - // Render other members as text links - const otherNamesEl = document.getElementById('other-member-names'); - const otherMembersTier = document.getElementById('other-members-tier'); - if (otherMembers.length > 0) { - // Update tier label to "Members" (not "Launch Members") - otherMembersTier.querySelector('.tier-label').textContent = 'Members'; - otherNamesEl.innerHTML = otherMembers.map(member => ` - ${escapeHtml(member.display_name)} - `).join(''); - } } catch (error) { console.debug('Failed to load dynamic members, keeping static content:', error); // Keep static content on error From 37e39cf89c1c8bb217efd8f34e8130fd9d44ee92 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Fri, 16 Jan 2026 07:32:59 -0500 Subject: [PATCH 48/49] fix: Parse API response correctly in admin meetings page (#790) * fix: Parse API response correctly in admin meetings page The admin meetings page was treating API responses as raw arrays instead of extracting data from the response object structure. The APIs return { meetings: [...] }, { series: [...] }, and { meeting, attendees } but the frontend was assigning the entire response object directly. - Fix loadMeetings() to extract data.meetings from response - Fix loadSeries() to extract data.series and handle missing group filter - Fix viewMeeting() to properly merge meeting and attendees from response Co-Authored-By: Claude Opus 4.5 * feat: Add Schedule button to admin working groups page Allows admins to schedule a meeting directly from the committee list without having to navigate to the meetings page first. - Added Schedule button on each committee row - Auto-opens scheduling modal via URL parameter (?action=schedule) - Working group is pre-selected in the modal Co-Authored-By: Claude Opus 4.5 * chore: Clear action param from URL after opening schedule modal Prevents the modal from re-opening if the user refreshes the page or bookmarks it while the modal is open. Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Claude Opus 4.5 --- .changeset/shy-kings-crash.md | 4 ++++ .changeset/silly-paws-lick.md | 4 ++++ server/public/admin-meetings.html | 31 ++++++++++++++++++++----- server/public/admin-working-groups.html | 1 + 4 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 .changeset/shy-kings-crash.md create mode 100644 .changeset/silly-paws-lick.md diff --git a/.changeset/shy-kings-crash.md b/.changeset/shy-kings-crash.md new file mode 100644 index 0000000000..d21d7fc8c3 --- /dev/null +++ b/.changeset/shy-kings-crash.md @@ -0,0 +1,4 @@ +--- +--- + +Add Schedule button to admin working groups page diff --git a/.changeset/silly-paws-lick.md b/.changeset/silly-paws-lick.md new file mode 100644 index 0000000000..7572fa4a1a --- /dev/null +++ b/.changeset/silly-paws-lick.md @@ -0,0 +1,4 @@ +--- +--- + +Fix admin meetings page crash when loading meetings and series diff --git a/server/public/admin-meetings.html b/server/public/admin-meetings.html index 88c10ad2f3..3460bbd0f7 100644 --- a/server/public/admin-meetings.html +++ b/server/public/admin-meetings.html @@ -463,7 +463,8 @@

Meeting Details

const response = await fetch(url); if (!response.ok) throw new Error('Failed to load meetings'); - meetings = await response.json(); + const data = await response.json(); + meetings = data.meetings || []; renderMeetings(); document.getElementById('loading').style.display = 'none'; @@ -479,15 +480,20 @@

Meeting Details

async function loadSeries() { try { const groupId = document.getElementById('groupFilter').value; - let url = '/api/admin/meetings/series'; - if (groupId) { - url += `?working_group_id=${groupId}`; + + // Series endpoint requires a working group filter + if (!groupId) { + series = []; + renderSeries(); + return; } + const url = `/api/admin/meetings/series?working_group_id=${groupId}`; const response = await fetch(url); if (!response.ok) throw new Error('Failed to load series'); - series = await response.json(); + const data = await response.json(); + series = data.series || []; renderSeries(); } catch (error) { console.error('Error loading series:', error); @@ -748,7 +754,8 @@

${escapeHtml(s.title)}

const response = await fetch(`/api/admin/meetings/${id}`); if (!response.ok) throw new Error('Failed to load meeting'); - viewingMeeting = await response.json(); + const data = await response.json(); + viewingMeeting = { ...data.meeting, attendees: data.attendees }; const m = viewingMeeting; const group = workingGroups.find(g => g.id === m.working_group_id); const date = new Date(m.start_time); @@ -845,6 +852,18 @@

${escapeHtml(s.title)}

async function init() { await loadWorkingGroups(); await loadMeetings(); + + // Auto-open schedule modal if requested via URL + const params = new URLSearchParams(window.location.search); + if (params.get('action') === 'schedule') { + showCreateModal(); + // Clear the action param to prevent re-triggering on refresh + params.delete('action'); + const newUrl = params.toString() + ? `${window.location.pathname}?${params.toString()}` + : window.location.pathname; + window.history.replaceState({}, '', newUrl); + } } init(); diff --git a/server/public/admin-working-groups.html b/server/public/admin-working-groups.html index 31e81ad277..cbc53404da 100644 --- a/server/public/admin-working-groups.html +++ b/server/public/admin-working-groups.html @@ -788,6 +788,7 @@

${escapeHtml(group.name)}

+
From 0b32adf890657baf68863a84e910ae395df75373 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Fri, 16 Jan 2026 07:56:07 -0500 Subject: [PATCH 49/49] feat: Add member setup nudges for incomplete profiles (#792) Add proactive reminders for members about incomplete setup tasks: - Dashboard alerts for missing logo, tagline, and pending join requests - Addie scheduled job to DM members about missing setup items - Addie home alerts for same items via Slack home tab Only targets paying members with active subscriptions. Rate limited to once per 7 days per nudge type per user. Co-authored-by: Claude Opus 4.5 --- .changeset/icy-cases-dig.md | 2 + server/public/dashboard.html | 178 +++++++++ server/src/addie/home/builders/alerts.ts | 29 +- server/src/addie/jobs/scheduler.ts | 51 +++ server/src/addie/jobs/setup-nudges.ts | 345 ++++++++++++++++++ server/src/addie/member-context.ts | 32 ++ .../src/db/migrations/173_setup_nudge_log.sql | 14 + server/src/http.ts | 3 + 8 files changed, 652 insertions(+), 2 deletions(-) create mode 100644 .changeset/icy-cases-dig.md create mode 100644 server/src/addie/jobs/setup-nudges.ts create mode 100644 server/src/db/migrations/173_setup_nudge_log.sql diff --git a/.changeset/icy-cases-dig.md b/.changeset/icy-cases-dig.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/icy-cases-dig.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/server/public/dashboard.html b/server/public/dashboard.html index 26fbb00a38..93e1f81d5a 100644 --- a/server/public/dashboard.html +++ b/server/public/dashboard.html @@ -634,8 +634,64 @@

Tell Us About Your Organization

background: var(--color-gray-100); } + /* Setup alerts banner styles */ + .setup-alert { + background: var(--color-warning-50); + border: 1px solid var(--color-warning-200); + border-left: 4px solid var(--color-warning-500); + border-radius: var(--radius-md); + padding: var(--space-4); + margin-bottom: var(--space-4); + display: flex; + align-items: flex-start; + gap: var(--space-3); + } + .setup-alert-icon { + font-size: 18px; + line-height: 1; + flex-shrink: 0; + } + .setup-alert-content { + flex: 1; + min-width: 0; + } + .setup-alert-title { + font-weight: 600; + color: var(--color-warning-800); + margin: 0 0 2px 0; + font-size: 14px; + } + .setup-alert-message { + color: var(--color-warning-700); + margin: 0; + font-size: 13px; + } + .setup-alert-action { + flex-shrink: 0; + } + .setup-alert-action .btn { + font-size: 12px; + padding: 6px 12px; + } + .setup-alert-dismiss { + background: none; + border: none; + color: var(--color-warning-500); + cursor: pointer; + padding: 4px; + font-size: 16px; + line-height: 1; + opacity: 0.7; + } + .setup-alert-dismiss:hover { + opacity: 1; + } + + + + @@ -1289,6 +1345,9 @@

Unable to Load // Show member promo banner (e.g., for upcoming events) renderMemberPromoBanner(org); + + // Show setup alerts for incomplete profile items + renderSetupAlerts(org); } else { // Hide the small membership card and show CTA section membershipCard.style.display = 'none'; @@ -1578,6 +1637,125 @@

Join the Agentic Advertising Organization

// promoBanner.style.display = 'block'; } + // Render setup alerts for incomplete profile items (members only) + async function renderSetupAlerts(org) { + const setupAlerts = document.getElementById('setupAlerts'); + if (!setupAlerts) return; + + // Only show for active members + if (org.billing?.status !== 'active') { + setupAlerts.style.display = 'none'; + return; + } + + // Get dismissed alerts from session storage + const dismissedAlerts = JSON.parse(sessionStorage.getItem('dismissedSetupAlerts') || '[]'); + + // Fetch member profile to check for missing items + try { + const response = await fetch(`/api/member-profiles/org/${org.id}`); + if (!response.ok) { + setupAlerts.style.display = 'none'; + return; + } + + const profile = await response.json(); + const alerts = []; + + // Check for missing logo + if (!profile.logo_url && !dismissedAlerts.includes('missing-logo')) { + alerts.push({ + id: 'missing-logo', + icon: '🖼️', + title: 'Add Your Company Logo', + message: 'Upload a logo so your company appears in the member directory and on the homepage.', + actionLabel: 'Add Logo', + actionUrl: '/dashboard-settings#logo' + }); + } + + // Check for missing tagline + if (!profile.tagline && !dismissedAlerts.includes('missing-tagline')) { + alerts.push({ + id: 'missing-tagline', + icon: '✏️', + title: 'Add a Company Description', + message: 'Help others learn about your organization by adding a tagline.', + actionLabel: 'Add Description', + actionUrl: '/dashboard-settings#profile' + }); + } + + // Check for pending join requests (admins only) + try { + const pendingResponse = await fetch(`/api/organizations/${org.id}/join-requests`); + if (pendingResponse.ok) { + const pendingData = await pendingResponse.json(); + const pendingCount = pendingData.requests?.length || 0; + if (pendingCount > 0 && !dismissedAlerts.includes('pending-join-requests')) { + alerts.push({ + id: 'pending-join-requests', + icon: '👋', + title: `${pendingCount} Pending Join Request${pendingCount > 1 ? 's' : ''}`, + message: `${pendingCount} ${pendingCount > 1 ? 'people are' : 'person is'} waiting to join your team.`, + actionLabel: 'Review Requests', + actionUrl: `/team?org=${org.id}` + }); + } + } + } catch (e) { + // Ignore errors - user may not be admin + } + + // If no alerts, hide the container + if (alerts.length === 0) { + setupAlerts.style.display = 'none'; + return; + } + + // Render alerts + setupAlerts.innerHTML = alerts.map(alert => ` +
+
${alert.icon}
+
+

${escapeHtml(alert.title)}

+

${escapeHtml(alert.message)}

+
+ + +
+ `).join(''); + + setupAlerts.style.display = 'block'; + } catch (error) { + console.error('Error fetching member profile for setup alerts:', error); + setupAlerts.style.display = 'none'; + } + } + + // Dismiss a setup alert for the current session + function dismissSetupAlert(alertId) { + const dismissedAlerts = JSON.parse(sessionStorage.getItem('dismissedSetupAlerts') || '[]'); + if (!dismissedAlerts.includes(alertId)) { + dismissedAlerts.push(alertId); + sessionStorage.setItem('dismissedSetupAlerts', JSON.stringify(dismissedAlerts)); + } + + // Remove the alert from the DOM + const alertEl = document.querySelector(`.setup-alert[data-alert-id="${alertId}"]`); + if (alertEl) { + alertEl.remove(); + } + + // Hide container if no more alerts + const setupAlerts = document.getElementById('setupAlerts'); + if (setupAlerts && setupAlerts.children.length === 0) { + setupAlerts.style.display = 'none'; + } + } + // Load team count for status card async function loadTeamCount(orgId) { try { diff --git a/server/src/addie/home/builders/alerts.ts b/server/src/addie/home/builders/alerts.ts index ab96330c04..deec0e4658 100644 --- a/server/src/addie/home/builders/alerts.ts +++ b/server/src/addie/home/builders/alerts.ts @@ -52,7 +52,7 @@ export async function buildAlerts(memberContext: MemberContext): Promise 0) { + const count = memberContext.pending_join_requests_count; + alerts.push({ + id: 'pending-join-requests', + severity: 'warning', + title: `${count} Pending Join Request${count > 1 ? 's' : ''}`, + message: `${count} ${count > 1 ? 'people are' : 'person is'} waiting to join your team`, + actionLabel: 'Review Requests', + actionUrl: 'https://agenticadvertising.org/dashboard#team', + }); + } + + // 6. Upcoming renewal (within 30 days) if (memberContext.subscription?.current_period_end) { const daysUntilRenewal = Math.floor( (memberContext.subscription.current_period_end.getTime() - Date.now()) / (1000 * 60 * 60 * 24) diff --git a/server/src/addie/jobs/scheduler.ts b/server/src/addie/jobs/scheduler.ts index 8a50326a18..d813aa54f0 100644 --- a/server/src/addie/jobs/scheduler.ts +++ b/server/src/addie/jobs/scheduler.ts @@ -10,6 +10,7 @@ import { runDocumentIndexerJob } from './committee-document-indexer.js'; import { runSummaryGeneratorJob } from './committee-summary-generator.js'; import { runOutreachScheduler } from '../services/proactive-outreach.js'; import { enrichMissingOrganizations } from '../../services/enrichment.js'; +import { runSetupNudgesJob } from './setup-nudges.js'; const logger = baseLogger.child({ module: 'job-scheduler' }); @@ -198,6 +199,49 @@ class JobScheduler { logger.debug({ intervalHours: INTERVAL_HOURS }, 'Account enrichment job started'); } + /** + * Start the setup nudges job + * Sends DMs to members about incomplete setup tasks (missing logo, tagline, pending join requests) + */ + startSetupNudges(): void { + const JOB_NAME = 'setup-nudges'; + const INTERVAL_HOURS = 6; // Check every 6 hours + const INITIAL_DELAY_MS = 240000; // 4 minute delay on startup + + const job: ScheduledJob = { + name: JOB_NAME, + intervalId: null, + initialTimeoutId: null, + }; + + // Run after a delay on startup + job.initialTimeoutId = setTimeout(async () => { + try { + const result = await runSetupNudgesJob({ limit: 10 }); + if (result.nudgesSent > 0) { + logger.info(result, 'Setup nudges: initial run completed'); + } + } catch (err) { + logger.error({ err }, 'Setup nudges: initial run failed'); + } + }, INITIAL_DELAY_MS); + + // Then run periodically + job.intervalId = setInterval(async () => { + try { + const result = await runSetupNudgesJob({ limit: 10 }); + if (result.nudgesSent > 0) { + logger.info(result, 'Setup nudges: job completed'); + } + } catch (err) { + logger.error({ err }, 'Setup nudges: job failed'); + } + }, INTERVAL_HOURS * 60 * 60 * 1000); + + this.jobs.set(JOB_NAME, job); + logger.debug({ intervalHours: INTERVAL_HOURS }, 'Setup nudges job started'); + } + /** * Stop the document indexer job */ @@ -226,6 +270,13 @@ class JobScheduler { this.stopJob('account-enrichment'); } + /** + * Stop the setup nudges job + */ + stopSetupNudges(): void { + this.stopJob('setup-nudges'); + } + /** * Stop a specific job by name */ diff --git a/server/src/addie/jobs/setup-nudges.ts b/server/src/addie/jobs/setup-nudges.ts new file mode 100644 index 0000000000..e289bb9772 --- /dev/null +++ b/server/src/addie/jobs/setup-nudges.ts @@ -0,0 +1,345 @@ +/** + * Setup Nudges Job + * + * Sends proactive DMs to members about incomplete setup items: + * - Missing company logo (members only) + * - Missing company tagline (members only) + * - Pending join requests (org admins only) + * + * Runs periodically to remind users about setup tasks. + * Uses rate limiting to avoid spam (once per 7 days per user per nudge type). + */ + +import { logger } from '../../logger.js'; +import { query } from '../../db/client.js'; + +/** Don't re-nudge same user for same issue within this interval */ +const NUDGE_INTERVAL = '7 days'; + +/** Delay between Slack messages to avoid rate limits */ +const MESSAGE_DELAY_MS = 2000; + +type NudgeType = 'missing_logo' | 'missing_tagline' | 'pending_join_requests'; + +interface SetupNudge { + slack_user_id: string; + workos_user_id: string; + user_name: string | null; + org_id: string; + org_name: string; + nudge_type: NudgeType; + nudge_detail?: string; // e.g., count of pending requests +} + +interface NudgeResult { + nudgesChecked: number; + nudgesSent: number; + skipped: number; + errors: number; +} + +/** + * Get members missing logos who haven't been nudged recently + */ +async function getMembersWithMissingLogos(): Promise { + const result = await query( + `SELECT + sm.slack_user_id, + sm.workos_user_id, + COALESCE(sm.slack_display_name, sm.slack_real_name) as user_name, + o.workos_organization_id as org_id, + o.name as org_name, + 'missing_logo'::text as nudge_type + FROM member_profiles mp + JOIN organizations o ON o.workos_organization_id = mp.workos_organization_id + JOIN organization_memberships om ON om.workos_organization_id = o.workos_organization_id + JOIN slack_user_mappings sm ON sm.workos_user_id = om.workos_user_id + WHERE o.subscription_status = 'active' + AND (mp.logo_url IS NULL OR mp.logo_url = '') + AND sm.slack_user_id IS NOT NULL + AND NOT EXISTS ( + SELECT 1 FROM setup_nudge_log snl + WHERE snl.slack_user_id = sm.slack_user_id + AND snl.nudge_type = 'missing_logo' + AND snl.sent_at > NOW() - INTERVAL '7 days' + ) + ORDER BY o.created_at ASC + LIMIT 20` + ); + return result.rows; +} + +/** + * Get members missing taglines who haven't been nudged recently + */ +async function getMembersWithMissingTaglines(): Promise { + const result = await query( + `SELECT + sm.slack_user_id, + sm.workos_user_id, + COALESCE(sm.slack_display_name, sm.slack_real_name) as user_name, + o.workos_organization_id as org_id, + o.name as org_name, + 'missing_tagline'::text as nudge_type + FROM member_profiles mp + JOIN organizations o ON o.workos_organization_id = mp.workos_organization_id + JOIN organization_memberships om ON om.workos_organization_id = o.workos_organization_id + JOIN slack_user_mappings sm ON sm.workos_user_id = om.workos_user_id + WHERE o.subscription_status = 'active' + AND (mp.tagline IS NULL OR mp.tagline = '') + AND sm.slack_user_id IS NOT NULL + AND NOT EXISTS ( + SELECT 1 FROM setup_nudge_log snl + WHERE snl.slack_user_id = sm.slack_user_id + AND snl.nudge_type = 'missing_tagline' + AND snl.sent_at > NOW() - INTERVAL '7 days' + ) + ORDER BY o.created_at ASC + LIMIT 20` + ); + return result.rows; +} + +/** + * Get org admins with pending join requests who haven't been nudged recently + */ +async function getAdminsWithPendingRequests(): Promise { + const result = await query( + `SELECT + sm.slack_user_id, + sm.workos_user_id, + COALESCE(sm.slack_display_name, sm.slack_real_name) as user_name, + o.workos_organization_id as org_id, + o.name as org_name, + 'pending_join_requests'::text as nudge_type, + COUNT(jr.id)::text as pending_count + FROM organization_join_requests jr + JOIN organizations o ON o.workos_organization_id = jr.workos_organization_id + JOIN organization_memberships om ON om.workos_organization_id = o.workos_organization_id + JOIN slack_user_mappings sm ON sm.workos_user_id = om.workos_user_id + WHERE jr.status = 'pending' + AND om.role IN ('admin', 'owner') + AND sm.slack_user_id IS NOT NULL + AND NOT EXISTS ( + SELECT 1 FROM setup_nudge_log snl + WHERE snl.slack_user_id = sm.slack_user_id + AND snl.nudge_type = 'pending_join_requests' + AND snl.sent_at > NOW() - INTERVAL '7 days' + ) + GROUP BY sm.slack_user_id, sm.workos_user_id, sm.slack_display_name, sm.slack_real_name, + o.workos_organization_id, o.name + HAVING COUNT(jr.id) > 0 + ORDER BY COUNT(jr.id) DESC + LIMIT 20` + ); + + return result.rows.map(r => ({ + ...r, + nudge_detail: r.pending_count, + })); +} + +/** + * Build a nudge message based on type + */ +function buildNudgeMessage(nudge: SetupNudge): string { + const name = nudge.user_name || 'there'; + + switch (nudge.nudge_type) { + case 'missing_logo': + return [ + `Hi ${name}! Just a friendly reminder that your organization doesn't have a logo set up yet.`, + '', + `Adding a logo helps *${nudge.org_name}* stand out in the member directory and on our homepage.`, + '', + `You can add one at: https://agenticadvertising.org/dashboard-settings`, + '', + `_Let me know if you need help uploading your logo!_`, + ].join('\n'); + + case 'missing_tagline': + return [ + `Hi ${name}! I noticed *${nudge.org_name}* doesn't have a company description yet.`, + '', + `Adding a tagline helps other members learn what your company does and makes it easier to find collaboration opportunities.`, + '', + `You can add one at: https://agenticadvertising.org/dashboard-settings`, + '', + `_Feel free to ask if you need help crafting your description!_`, + ].join('\n'); + + case 'pending_join_requests': { + const count = parseInt(nudge.nudge_detail || '1', 10); + const people = count === 1 ? 'person is' : 'people are'; + return [ + `Hi ${name}! ${count} ${people} waiting to join *${nudge.org_name}*.`, + '', + `As an admin, you can review and approve their requests at: https://agenticadvertising.org/dashboard#team`, + '', + `_Let me know if you have any questions about managing your team!_`, + ].join('\n'); + } + + default: + return ''; + } +} + +/** + * Send a nudge DM via Slack + */ +async function sendNudgeDm(slackUserId: string, message: string): Promise { + const token = process.env.ADDIE_BOT_TOKEN; + if (!token) { + logger.warn('ADDIE_BOT_TOKEN not configured - cannot send setup nudges'); + return false; + } + + try { + // Open DM channel + const openResponse = await fetch('https://slack.com/api/conversations.open', { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ users: slackUserId }), + }); + + const openData = (await openResponse.json()) as { ok: boolean; channel?: { id: string }; error?: string }; + if (!openData.ok || !openData.channel?.id) { + logger.warn({ error: openData.error, slackUserId }, 'Failed to open DM channel for nudge'); + return false; + } + + // Send message + const sendResponse = await fetch('https://slack.com/api/chat.postMessage', { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + channel: openData.channel.id, + text: message, + mrkdwn: true, + }), + }); + + const sendData = (await sendResponse.json()) as { ok: boolean; error?: string }; + if (!sendData.ok) { + logger.warn({ error: sendData.error, slackUserId }, 'Failed to send nudge message'); + return false; + } + + return true; + } catch (error) { + logger.error({ error, slackUserId }, 'Error sending nudge DM'); + return false; + } +} + +/** + * Log that a nudge was sent + */ +async function logNudgeSent(slackUserId: string, nudgeType: NudgeType): Promise { + await query( + `INSERT INTO setup_nudge_log (slack_user_id, nudge_type, sent_at) + VALUES ($1, $2, NOW())`, + [slackUserId, nudgeType] + ); +} + +/** + * Run the setup nudges job + */ +export async function runSetupNudgesJob(options: { + dryRun?: boolean; + limit?: number; +} = {}): Promise { + const { dryRun = false, limit = 10 } = options; + + logger.debug({ dryRun, limit }, 'Running setup nudges job'); + + // Gather all nudges + const [missingLogos, missingTaglines, pendingRequests] = await Promise.all([ + getMembersWithMissingLogos(), + getMembersWithMissingTaglines(), + getAdminsWithPendingRequests(), + ]); + + // Combine and limit - prioritize pending requests, then logos, then taglines + const allNudges = [ + ...pendingRequests, + ...missingLogos, + ...missingTaglines, + ].slice(0, limit); + + let nudgesSent = 0; + let skipped = 0; + let errors = 0; + + for (const nudge of allNudges) { + const message = buildNudgeMessage(nudge); + if (!message) { + skipped++; + continue; + } + + if (dryRun) { + logger.info({ + slackUserId: nudge.slack_user_id, + nudgeType: nudge.nudge_type, + orgName: nudge.org_name, + message: message.substring(0, 100) + '...', + }, 'DRY RUN: Would send setup nudge'); + nudgesSent++; + continue; + } + + const success = await sendNudgeDm(nudge.slack_user_id, message); + if (success) { + await logNudgeSent(nudge.slack_user_id, nudge.nudge_type); + logger.debug({ + slackUserId: nudge.slack_user_id, + nudgeType: nudge.nudge_type, + orgName: nudge.org_name, + }, 'Sent setup nudge'); + nudgesSent++; + } else { + errors++; + } + + // Rate limit delay between Slack messages + await new Promise(resolve => setTimeout(resolve, MESSAGE_DELAY_MS)); + } + + if (nudgesSent > 0 || errors > 0) { + logger.info({ + nudgesChecked: allNudges.length, + nudgesSent, + skipped, + errors, + }, 'Setup nudges job completed'); + } + + return { + nudgesChecked: allNudges.length, + nudgesSent, + skipped, + errors, + }; +} + +/** + * Preview what nudges would be sent (dry run) + */ +export async function previewSetupNudges(): Promise { + const [missingLogos, missingTaglines, pendingRequests] = await Promise.all([ + getMembersWithMissingLogos(), + getMembersWithMissingTaglines(), + getAdminsWithPendingRequests(), + ]); + + return [...pendingRequests, ...missingLogos, ...missingTaglines]; +} diff --git a/server/src/addie/member-context.ts b/server/src/addie/member-context.ts index cb22cc6182..7c85972eb3 100644 --- a/server/src/addie/member-context.ts +++ b/server/src/addie/member-context.ts @@ -11,6 +11,7 @@ import { OrganizationDatabase } from '../db/organization-db.js'; import { WorkingGroupDatabase } from '../db/working-group-db.js'; import { EmailPreferencesDatabase } from '../db/email-preferences-db.js'; import { AddieDatabase } from '../db/addie-db.js'; +import { JoinRequestDatabase } from '../db/join-request-db.js'; import { getThreadService } from './thread-service.js'; import { workos } from '../auth/workos-client.js'; import { logger } from '../logger.js'; @@ -23,6 +24,7 @@ const orgDb = new OrganizationDatabase(); const workingGroupDb = new WorkingGroupDatabase(); const emailPrefsDb = new EmailPreferencesDatabase(); const addieDb = new AddieDatabase(); +const joinRequestDb = new JoinRequestDatabase(); /** * Get pending content count for a user @@ -157,6 +159,7 @@ export interface MemberContext { member_profile?: { display_name: string; tagline?: string; + logo_url?: string; offerings: string[]; headquarters?: string; }; @@ -229,6 +232,9 @@ export interface MemberContext { total: number; by_committee: Record; }; + + /** Pending join requests for the organization (admins only) */ + pending_join_requests_count?: number; } /** @@ -404,6 +410,7 @@ export async function getMemberContext(slackUserId: string): Promise 0) { + context.pending_join_requests_count = pendingJoinRequestsCount; + } + } catch (error) { + logger.warn({ error, organizationId }, 'Addie: Failed to get pending join requests count'); + } + } + logger.debug( { slackUserId, @@ -627,6 +646,7 @@ export async function getWebMemberContext(workosUserId: string): Promise 0) { + context.pending_join_requests_count = pendingJoinRequestsCount; + } + } catch (error) { + logger.warn({ error, organizationId }, 'Addie Web: Failed to get pending join requests count'); + } + } + logger.debug( { workosUserId, diff --git a/server/src/db/migrations/173_setup_nudge_log.sql b/server/src/db/migrations/173_setup_nudge_log.sql new file mode 100644 index 0000000000..00c6cdf724 --- /dev/null +++ b/server/src/db/migrations/173_setup_nudge_log.sql @@ -0,0 +1,14 @@ +-- Setup Nudge Log +-- Tracks when setup nudges are sent to prevent spam +-- Used by the Addie setup-nudges job + +CREATE TABLE IF NOT EXISTS setup_nudge_log ( + id SERIAL PRIMARY KEY, + slack_user_id VARCHAR(255) NOT NULL, + nudge_type VARCHAR(50) NOT NULL, -- missing_logo, missing_tagline, pending_join_requests + sent_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() +); + +-- Index for efficient lookup of recent nudges per user/type +CREATE INDEX IF NOT EXISTS idx_setup_nudge_log_lookup + ON setup_nudge_log (slack_user_id, nudge_type, sent_at DESC); diff --git a/server/src/http.ts b/server/src/http.ts index cf818aba07..93a74b2c65 100644 --- a/server/src/http.ts +++ b/server/src/http.ts @@ -6933,6 +6933,9 @@ Disallow: /api/admin/ // Start account enrichment job jobScheduler.startEnrichment(); + // Start setup nudges job (reminds members about missing logos, taglines, pending requests) + jobScheduler.startSetupNudges(); + this.server = this.app.listen(port, () => { logger.info({ port,