diff --git a/.changeset/adcp-3-1-rc4.md b/.changeset/adcp-3-1-rc4.md new file mode 100644 index 000000000..0d292f4f0 --- /dev/null +++ b/.changeset/adcp-3-1-rc4.md @@ -0,0 +1,12 @@ +--- +'@adcp/sdk': minor +--- + +Update the SDK schema pin and generated surfaces to AdCP 3.1.0-rc.4. + +Regenerates TypeScript/Zod schemas, docs, manifest-derived constants, entity +hydration metadata, and server wire field allowlists from the rc4 protocol +bundle. Adds GitHub-dist fallback for schema syncs when the website mirror has +not yet published a signed protocol bundle, and keeps the media-buy mode +mismatch recovery path tolerant of older 3.1 prerelease sellers that still emit +`requires_proposal`. diff --git a/ADCP_VERSION b/ADCP_VERSION index 633f1ccb5..289ff348c 100644 --- a/ADCP_VERSION +++ b/ADCP_VERSION @@ -1 +1 @@ -3.1.0-rc.3 +3.1.0-rc.4 diff --git a/docs/llms.txt b/docs/llms.txt index 60550919b..7ec961764 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -1076,10 +1076,13 @@ Flow: `sync_accounts → get_products → sync_event_sources → create_media_bu **Seller fulfills a performance (event-kind goal) media buy with a ROAS target** — Verifies that a seller advertising conversion_tracking with per_ad_spend in supported_targets can accept a media buy whose event-kind optimization_goal carries a ROAS (per_ad_spend) target bound to an event source with value_field, reject ROAS goals that omit value_field on every event-source entry, ingest valued purchase events, and report conversion_value + roas alongside conversions and cost_per_acquisition. Sibling to media_buy_seller/performance_buy_flow gated on the supported_targets sub-capability: sellers that don't advertise per_ad_spend (most broadcast TV, upper-funnel video, signal-only) grade not_applicable. Flow: `sync_accounts → get_products → sync_event_sources → create_media_buy → log_event → comply_test_controller → get_media_buy_delivery` +**Seller filters products by accepted pricing currencies** — Verifies that get_products filters.pricing_currencies returns only products buyable in the requested media pricing currency and prunes returned product pricing_options. +Flow: `get_products` + **Seller exposes wholesale signal options and honors package-level signal_targeting_groups** — Verifies that a seller with wholesale products and wholesale get_signals can expose signal targeting eligibility, accept package-level signal_targeting_groups with pricing, reject unknown signals, and echo the applied grouped expression on readback. Flow: `get_products → get_signals → create_media_buy → get_media_buys → create_media_buy` -**Seller handles proposal refinement and finalize** — Verifies the full proposal lifecycle: brief with proposals, refine a proposal, finalize to committed, and accept via create_media_buy. +**Seller handles proposal refinement and finalize** — Verifies the full proposal lifecycle: brief with proposals, refine a proposal, finalize to committed, and execute via create_media_buy. Flow: `sync_accounts → get_products → create_media_buy → get_products → create_media_buy` **Seller handles proposal finalize — asap start_time form** — Variant of proposal_finalize that exercises start_time: 'asap' on create_media_buy, catching wrapper-layer rejections of the spec-defined string literal form. @@ -1157,8 +1160,8 @@ Flow: `get_products → create_media_buy` ### Signals -**Signals baseline** — Baseline domain storyboard — every signals agent must discover signals and return an activation, regardless of whether they are owned or marketplace. -Flow: `get_adcp_capabilities → get_signals → activate_signal` +**Signals baseline** — Baseline domain storyboard — every signals agent must declare signals support and return discoverable signals. +Flow: `get_adcp_capabilities → get_signals` **Marketplace signal agent** — Signal agent that resells third-party data provider signals with verifiable provider-published provenance. Flow: `get_adcp_capabilities → get_signals → activate_signal` @@ -1166,8 +1169,8 @@ Flow: `get_adcp_capabilities → get_signals → activate_signal` **Signal agent rejects activation when governance denies** — Verifies that a signal agent propagates GOVERNANCE_DENIED when the buyer's governance plan denies activation. Flow: `sync_plans → sync_accounts → sync_governance → get_signals → activate_signal` -**Owned signal agent** — Signal agent serving first-party or proprietary audience data without external catalog verification. -Flow: `get_adcp_capabilities → get_signals → activate_signal` +**Owned signal agent** — Signal agent serving first-party or proprietary audience data for discovery without external catalog verification. +Flow: `get_adcp_capabilities → get_signals` **get_signals pagination cursor integrity** — Validates the cursor↔has_more invariant on a paginated get_signals response by walking from a continuation page to the next page under a broad query. Flow: `get_adcp_capabilities → get_signals` @@ -1389,7 +1392,7 @@ Agents use the `recovery` classification to decide what to do: `transient` → r | `RATE_LIMITED` | transient | Request rate exceeded. Retry after the retry_after interval. | | `READ_ONLY_SCOPE` | correctable | The caller's scope is read-only; the invoked task would mutate state and was rejected. Distinct from `SCOPE_INSUFFICIENT` (task not in scope at all) — the task is in some scopes this seller supports, just not this caller's. | | `REFERENCE_NOT_FOUND` | correctable | Generic fallback for a referenced identifier, grant, session, or other resource that does not exist or is not accessible by the caller. Use when no resource-specific not-found code applies (e.g., property lists, content standards, rights grants, SI offerings, proposals, catalogs, event sources, collection lists, brands, individual properties). Typed parameters that lack a dedicated standard code MUST also use REFERENCE_NOT_FOUND rather than minting a custom *_NOT_FOUND code. See 'Uniform response for inaccessible references' in error-handling.mdx for the full MUST list. Summary of the uniform-response MUST: sellers MUST return the same response for 'exists but the caller lacks access' as for 'does not exist' across every observable channel — error.code/message/field/details (message MUST be generic; error.field MUST be identical across both cases on typed parameters); HTTP status, A2A task.status.state, and MCP isError; response headers (ETag, Cache-Control, per-type rate-limit buckets, CDN tags); side effects (webhook/audit writes, background-job enqueues, per-type quota counters, DB-shard routing); and observability (logs, APM spans, third-party error telemetry like Sentry/Datadog). Sellers MUST perform the same resolution-and-authorization work on both paths (resolve-then-authorize; on true-miss still run an authorization decision of equivalent shape against an empty principal set so authorizer latency is not a side channel). Cache population MUST NOT be gated on authorization. Polymorphism is evaluated against the tool-schema's declared parameter shape before any lookup, and a tool's declared shape MUST be identical across all callers. | -| `REQUOTE_REQUIRED` | correctable | An update_media_buy request changes the parameter envelope (budget, flight dates, volume, targeting) the original quote was priced against. The pricing_option remains locked; the seller is declining the requested shape at that price. Distinct from TERMS_REJECTED (measurement) and POLICY_VIOLATION (content). Sellers SHOULD populate error.details.envelope_field with the field path(s) that breached the envelope (e.g., 'packages[0].budget', 'end_time') so the buyer's agent can autonomously re-discover. | +| `REQUOTE_REQUIRED` | correctable | An update_media_buy request changes the parameter envelope (budget, flight dates, volume, targeting) the original quote was priced against. The pricing_option remains locked; the seller is declining the requested shape at that price. Distinct from TERMS_REJECTED (measurement) and POLICY_VIOLATION (content). Sellers SHOULD populate error.details.envelope_field with the field path(s) that breached the envelope (e.g., 'packages[0].budget', 'end_time') so the buyer's agent can decide whether to adjust the update, rediscover products, add packages where supported, or create a separate media buy. AdCP 3.1 does not define an amendment-quote artifact that can be attached to update_media_buy. | | `SCOPE_INSUFFICIENT` | correctable | The authenticated caller is not authorized for the invoked task — the task is not in the caller's `allowed_tasks` for this account (discoverable via the `authorization` object on sync_accounts / list_accounts responses). Distinct from `PERMISSION_DENIED` (generic authz failure, often credential-shaped) by being narrowly about task-level scope. Sellers SHOULD populate `error.details.introspection_hint` pointing at where the caller can re-read its scope (strawman: `{ task: 'list_accounts', account: {...} }`). | | `SERVICE_UNAVAILABLE` | transient | Seller service is temporarily unavailable. Retry with exponential backoff. | | `SESSION_NOT_FOUND` | correctable | SI session ID is invalid, expired, or does not exist. | diff --git a/package.json b/package.json index 7b7f17ddd..2c92fbd0a 100644 --- a/package.json +++ b/package.json @@ -460,5 +460,5 @@ }, "minimatch": "^10.2.1" }, - "adcp_version": "3.1.0-rc.3" + "adcp_version": "3.1.0-rc.4" } diff --git a/scripts/sync-schemas.ts b/scripts/sync-schemas.ts index d09f93c1c..44aba769d 100644 --- a/scripts/sync-schemas.ts +++ b/scripts/sync-schemas.ts @@ -22,6 +22,7 @@ import * as tar from 'tar'; const DEFAULT_ADCP_BASE_URL = 'https://adcontextprotocol.org'; const ADCP_BASE_URL = process.env.ADCP_BASE_URL || DEFAULT_ADCP_BASE_URL; +const GITHUB_DIST_BASE_URL = 'https://raw.githubusercontent.com/adcontextprotocol/adcp/main/dist'; const REPO_ROOT = path.join(__dirname, '..'); const SCHEMA_CACHE_DIR = path.join(REPO_ROOT, 'schemas/cache'); const COMPLIANCE_CACHE_DIR = path.join(REPO_ROOT, 'compliance/cache'); @@ -416,8 +417,8 @@ async function syncFromTarball(version: string, baseUrl = ADCP_BASE_URL): Promis // Per-file schema fallback. Used only if the tarball endpoint is unavailable. // Compliance is NOT synced by this path — requires the tarball. -async function syncSchemasPerFile(version: string): Promise { - const indexUrl = `${ADCP_BASE_URL}/schemas/${version}/index.json`; +async function syncSchemasPerFile(version: string, baseUrl = ADCP_BASE_URL): Promise { + const indexUrl = `${baseUrl}/schemas/${version}/index.json`; console.log(`📥 Fetching schema index ${indexUrl}`); const schemaIndex: SchemaIndex = await fetchJson(indexUrl); @@ -443,14 +444,18 @@ async function syncSchemasPerFile(version: string): Promise { allRefs.add('/schemas/v1/adagents.json'); const semanticVersion = schemaIndex.adcp_version; - await Promise.allSettled(Array.from(allRefs).map(ref => downloadSchema(ref, versionCacheDir, semanticVersion))); + await Promise.allSettled( + Array.from(allRefs).map(ref => downloadSchema(ref, versionCacheDir, semanticVersion, baseUrl)) + ); // Resolve transitive $refs const attempted = new Set(); for (let depth = 0; depth < 10; depth++) { const missing = findMissingRefs(versionCacheDir, attempted); if (missing.size === 0) break; - await Promise.allSettled(Array.from(missing).map(ref => downloadSchema(ref, versionCacheDir, semanticVersion))); + await Promise.allSettled( + Array.from(missing).map(ref => downloadSchema(ref, versionCacheDir, semanticVersion, baseUrl)) + ); missing.forEach(r => attempted.add(r)); } @@ -461,8 +466,13 @@ async function syncSchemasPerFile(version: string): Promise { ); } -async function downloadSchema(schemaRef: string, cacheDir: string, semanticVersion: string): Promise { - const url = `${ADCP_BASE_URL}${schemaRef}`; +async function downloadSchema( + schemaRef: string, + cacheDir: string, + semanticVersion: string, + baseUrl = ADCP_BASE_URL +): Promise { + const url = `${baseUrl}${schemaRef}`; const localPath = refToLocalPath(schemaRef, cacheDir); mkdirSync(path.dirname(localPath), { recursive: true }); try { @@ -523,9 +533,21 @@ async function sync(version?: string): Promise { const adcpVersion = version || getTargetAdCPVersion(); console.log(`🔄 Syncing AdCP @ ${adcpVersion}`); - const viaTarball = await syncFromTarball(adcpVersion); - if (!viaTarball) { - await syncSchemasPerFile(adcpVersion); + async function syncWithBase(baseUrl: string): Promise { + const viaTarball = await syncFromTarball(adcpVersion, baseUrl); + if (!viaTarball) { + await syncSchemasPerFile(adcpVersion, baseUrl); + } + } + + try { + await syncWithBase(ADCP_BASE_URL); + } catch (err) { + if (ADCP_BASE_URL !== DEFAULT_ADCP_BASE_URL || process.env.ADCP_GITHUB_FALLBACK === '0') { + throw err; + } + console.warn(`⚠️ AdCP ${adcpVersion} was not reachable from adcontextprotocol.org; retrying against GitHub dist.`); + await syncWithBase(GITHUB_DIST_BASE_URL); } console.log(`✅ Sync complete for AdCP ${adcpVersion}`); diff --git a/scripts/sync-version.ts b/scripts/sync-version.ts index 4bffe68fd..a054920ea 100644 --- a/scripts/sync-version.ts +++ b/scripts/sync-version.ts @@ -96,6 +96,7 @@ const COMPATIBLE_PREFIX = [ '3.1.0-rc.1', '3.1.0-rc.2', '3.1.0-rc.3', + '3.1.0-rc.4', ] as const; /** diff --git a/src/lib/media-buy/preflight.ts b/src/lib/media-buy/preflight.ts index 30baf48d3..928016fc0 100644 --- a/src/lib/media-buy/preflight.ts +++ b/src/lib/media-buy/preflight.ts @@ -650,7 +650,9 @@ export function recoveryForModeMismatch( ): ModeMismatchRecovery | undefined { const entry = currentlyAvailable.find(a => a.action === attemptedAction); if (!entry) return undefined; - switch (entry.mode) { + // `requires_proposal` was removed from the rc4+ mode enum in favor of + // REQUOTE_REQUIRED, but older 3.1 prerelease sellers can still emit it. + switch (entry.mode as MediaBuyActionMode | 'requires_proposal') { case 'requires_proposal': return { kind: 'createProposal', diff --git a/src/lib/server/decisioning/runtime/entity-hydration.generated.ts b/src/lib/server/decisioning/runtime/entity-hydration.generated.ts index 08f90e56f..5926f83bf 100644 --- a/src/lib/server/decisioning/runtime/entity-hydration.generated.ts +++ b/src/lib/server/decisioning/runtime/entity-hydration.generated.ts @@ -1,6 +1,6 @@ // Generated entity-hydration field map — do NOT edit by hand // -// Source: `schemas/cache/3.1.0-rc.3/manifest.json` + per-tool request +// Source: `schemas/cache/3.1.0-rc.4/manifest.json` + per-tool request // schemas. Every top-level `x-entity`-tagged string field on a request // schema lands here. The runtime hydrator (`from-platform.ts` → // `hydrateForTool`) walks this map plus the hand-curated diff --git a/src/lib/server/wire-spec-fields.generated.ts b/src/lib/server/wire-spec-fields.generated.ts index 212f5f18e..0b6db2d5f 100644 --- a/src/lib/server/wire-spec-fields.generated.ts +++ b/src/lib/server/wire-spec-fields.generated.ts @@ -1,6 +1,6 @@ // AUTO-GENERATED by scripts/generate-wire-spec-fields.ts. DO NOT EDIT. -// Source: schemas/cache/3.1.0-rc.3/**/*-request.json -// Generated at: 2026-05-30T10:23:44.850Z +// Source: schemas/cache/3.1.0-rc.4/**/*-request.json +// Generated at: 2026-05-30T16:55:09.057Z import type { AcquireRightsRequest, @@ -53,147 +53,147 @@ import type { * pollution or shared-state mutation of the allowlist. */ export const WIRE_SPEC_FIELDS = Object.freeze({ - /** schemas/cache/3.1.0-rc.3/brand/acquire-rights-request.json */ + /** schemas/cache/3.1.0-rc.4/brand/acquire-rights-request.json */ AcquireRightsRequest: Object.freeze({ fields: Object.freeze(["account","buyer","campaign","context","ext","idempotency_key","pricing_option_id","push_notification_config","revocation_webhook","rights_id"]) as readonly string[], __type: null as unknown as AcquireRightsRequest, }), - /** schemas/cache/3.1.0-rc.3/signals/activate-signal-request.json */ + /** schemas/cache/3.1.0-rc.4/signals/activate-signal-request.json */ ActivateSignalRequest: Object.freeze({ fields: Object.freeze(["account","action","context","destinations","ext","idempotency_key","pricing_option_id","signal_agent_segment_id"]) as readonly string[], __type: null as unknown as ActivateSignalRequest, }), - /** schemas/cache/3.1.0-rc.3/media-buy/build-creative-request.json */ + /** schemas/cache/3.1.0-rc.4/media-buy/build-creative-request.json */ BuildCreativeRequest: Object.freeze({ fields: Object.freeze(["account","brand","concept_id","context","creative_id","creative_manifest","ext","idempotency_key","include_preview","item_limit","macro_values","media_buy_id","message","package_id","preview_inputs","preview_output_format","preview_quality","quality","target_format_id","target_format_ids"]) as readonly string[], __type: null as unknown as BuildCreativeRequest, }), - /** schemas/cache/3.1.0-rc.3/content-standards/calibrate-content-request.json */ + /** schemas/cache/3.1.0-rc.4/content-standards/calibrate-content-request.json */ CalibrateContentRequest: Object.freeze({ fields: Object.freeze(["artifact","context","ext","idempotency_key","standards_id"]) as readonly string[], __type: null as unknown as CalibrateContentRequest, }), - /** schemas/cache/3.1.0-rc.3/collection/create-collection-list-request.json */ + /** schemas/cache/3.1.0-rc.4/collection/create-collection-list-request.json */ CreateCollectionListRequest: Object.freeze({ fields: Object.freeze(["account","base_collections","brand","context","description","ext","filters","idempotency_key","name"]) as readonly string[], __type: null as unknown as CreateCollectionListRequest, }), - /** schemas/cache/3.1.0-rc.3/content-standards/create-content-standards-request.json */ + /** schemas/cache/3.1.0-rc.4/content-standards/create-content-standards-request.json */ CreateContentStandardsRequest: Object.freeze({ fields: Object.freeze(["calibration_exemplars","context","ext","idempotency_key","policies","registry_policy_ids","scope"]) as readonly string[], __type: null as unknown as CreateContentStandardsRequest, }), - /** schemas/cache/3.1.0-rc.3/media-buy/create-media-buy-request.json */ + /** schemas/cache/3.1.0-rc.4/media-buy/create-media-buy-request.json */ CreateMediaBuyRequest: Object.freeze({ fields: Object.freeze(["account","advertiser_industry","agency_estimate_number","artifact_webhook","brand","context","end_time","ext","idempotency_key","invoice_recipient","io_acceptance","packages","plan_id","po_number","proposal_id","push_notification_config","reporting_webhook","start_time","total_budget"]) as readonly string[], __type: null as unknown as CreateMediaBuyRequest, }), - /** schemas/cache/3.1.0-rc.3/property/create-property-list-request.json */ + /** schemas/cache/3.1.0-rc.4/property/create-property-list-request.json */ CreatePropertyListRequest: Object.freeze({ fields: Object.freeze(["account","base_properties","brand","context","description","ext","filters","idempotency_key","name"]) as readonly string[], __type: null as unknown as CreatePropertyListRequest, }), - /** schemas/cache/3.1.0-rc.3/collection/delete-collection-list-request.json */ + /** schemas/cache/3.1.0-rc.4/collection/delete-collection-list-request.json */ DeleteCollectionListRequest: Object.freeze({ fields: Object.freeze(["account","context","ext","idempotency_key","list_id"]) as readonly string[], __type: null as unknown as DeleteCollectionListRequest, }), - /** schemas/cache/3.1.0-rc.3/property/delete-property-list-request.json */ + /** schemas/cache/3.1.0-rc.4/property/delete-property-list-request.json */ DeletePropertyListRequest: Object.freeze({ fields: Object.freeze(["account","context","ext","idempotency_key","list_id"]) as readonly string[], __type: null as unknown as DeletePropertyListRequest, }), - /** schemas/cache/3.1.0-rc.3/media-buy/get-media-buy-delivery-request.json */ + /** schemas/cache/3.1.0-rc.4/media-buy/get-media-buy-delivery-request.json */ GetMediaBuyDeliveryRequest: Object.freeze({ fields: Object.freeze(["account","attribution_window","context","end_date","ext","include_package_daily_breakdown","include_window_breakdown","media_buy_ids","reporting_dimensions","start_date","status_filter","time_granularity"]) as readonly string[], __type: null as unknown as GetMediaBuyDeliveryRequest, }), - /** schemas/cache/3.1.0-rc.3/media-buy/log-event-request.json */ + /** schemas/cache/3.1.0-rc.4/media-buy/log-event-request.json */ LogEventRequest: Object.freeze({ fields: Object.freeze(["context","event_source_id","events","ext","idempotency_key","test_event_code"]) as readonly string[], __type: null as unknown as LogEventRequest, }), - /** schemas/cache/3.1.0-rc.3/media-buy/provide-performance-feedback-request.json */ + /** schemas/cache/3.1.0-rc.4/media-buy/provide-performance-feedback-request.json */ ProvidePerformanceFeedbackRequest: Object.freeze({ fields: Object.freeze(["context","creative_id","ext","feedback_source","idempotency_key","measurement_period","media_buy_id","metric_type","package_id","performance_index"]) as readonly string[], __type: null as unknown as ProvidePerformanceFeedbackRequest, }), - /** schemas/cache/3.1.0-rc.3/governance/report-plan-outcome-request.json */ + /** schemas/cache/3.1.0-rc.4/governance/report-plan-outcome-request.json */ ReportPlanOutcomeRequest: Object.freeze({ fields: Object.freeze(["check_id","context","delivery","error","ext","governance_context","idempotency_key","outcome","plan_id","purchase_type","seller_response"]) as readonly string[], __type: null as unknown as ReportPlanOutcomeRequest, }), - /** schemas/cache/3.1.0-rc.3/account/report-usage-request.json */ + /** schemas/cache/3.1.0-rc.4/account/report-usage-request.json */ ReportUsageRequest: Object.freeze({ fields: Object.freeze(["context","ext","idempotency_key","reporting_period","usage"]) as readonly string[], __type: null as unknown as ReportUsageRequest, }), - /** schemas/cache/3.1.0-rc.3/sponsored-intelligence/si-initiate-session-request.json */ + /** schemas/cache/3.1.0-rc.4/sponsored-intelligence/si-initiate-session-request.json */ SIInitiateSessionRequest: Object.freeze({ fields: Object.freeze(["context","ext","idempotency_key","identity","intent","media_buy_id","offering_id","offering_token","placement","supported_capabilities"]) as readonly string[], __type: null as unknown as SIInitiateSessionRequest, }), - /** schemas/cache/3.1.0-rc.3/sponsored-intelligence/si-send-message-request.json */ + /** schemas/cache/3.1.0-rc.4/sponsored-intelligence/si-send-message-request.json */ SISendMessageRequest: Object.freeze({ fields: Object.freeze(["action_response","context","ext","idempotency_key","message","session_id"]) as readonly string[], __type: null as unknown as SISendMessageRequest, }), - /** schemas/cache/3.1.0-rc.3/account/sync-accounts-request.json */ + /** schemas/cache/3.1.0-rc.4/account/sync-accounts-request.json */ SyncAccountsRequest: Object.freeze({ fields: Object.freeze(["accounts","context","delete_missing","dry_run","ext","idempotency_key","push_notification_config"]) as readonly string[], __type: null as unknown as SyncAccountsRequest, }), - /** schemas/cache/3.1.0-rc.3/media-buy/sync-audiences-request.json */ + /** schemas/cache/3.1.0-rc.4/media-buy/sync-audiences-request.json */ SyncAudiencesRequest: Object.freeze({ fields: Object.freeze(["account","audiences","context","delete_missing","ext","idempotency_key"]) as readonly string[], __type: null as unknown as SyncAudiencesRequest, }), - /** schemas/cache/3.1.0-rc.3/media-buy/sync-catalogs-request.json */ + /** schemas/cache/3.1.0-rc.4/media-buy/sync-catalogs-request.json */ SyncCatalogsRequest: Object.freeze({ fields: Object.freeze(["account","catalog_ids","catalogs","context","delete_missing","dry_run","ext","idempotency_key","push_notification_config","validation_mode"]) as readonly string[], __type: null as unknown as SyncCatalogsRequest, }), - /** schemas/cache/3.1.0-rc.3/creative/sync-creatives-request.json */ + /** schemas/cache/3.1.0-rc.4/creative/sync-creatives-request.json */ SyncCreativesRequest: Object.freeze({ fields: Object.freeze(["account","assignments","context","creative_ids","creatives","delete_missing","dry_run","ext","idempotency_key","push_notification_config","validation_mode"]) as readonly string[], __type: null as unknown as SyncCreativesRequest, }), - /** schemas/cache/3.1.0-rc.3/media-buy/sync-event-sources-request.json */ + /** schemas/cache/3.1.0-rc.4/media-buy/sync-event-sources-request.json */ SyncEventSourcesRequest: Object.freeze({ fields: Object.freeze(["account","context","delete_missing","event_sources","ext","idempotency_key"]) as readonly string[], __type: null as unknown as SyncEventSourcesRequest, }), - /** schemas/cache/3.1.0-rc.3/account/sync-governance-request.json */ + /** schemas/cache/3.1.0-rc.4/account/sync-governance-request.json */ SyncGovernanceRequest: Object.freeze({ fields: Object.freeze(["accounts","context","ext","idempotency_key"]) as readonly string[], __type: null as unknown as SyncGovernanceRequest, }), - /** schemas/cache/3.1.0-rc.3/governance/sync-plans-request.json */ + /** schemas/cache/3.1.0-rc.4/governance/sync-plans-request.json */ SyncPlansRequest: Object.freeze({ fields: Object.freeze(["context","ext","idempotency_key","plans"]) as readonly string[], __type: null as unknown as SyncPlansRequest, }), - /** schemas/cache/3.1.0-rc.3/collection/update-collection-list-request.json */ + /** schemas/cache/3.1.0-rc.4/collection/update-collection-list-request.json */ UpdateCollectionListRequest: Object.freeze({ fields: Object.freeze(["account","base_collections","brand","context","description","ext","filters","idempotency_key","list_id","name","webhook_url"]) as readonly string[], __type: null as unknown as UpdateCollectionListRequest, }), - /** schemas/cache/3.1.0-rc.3/content-standards/update-content-standards-request.json */ + /** schemas/cache/3.1.0-rc.4/content-standards/update-content-standards-request.json */ UpdateContentStandardsRequest: Object.freeze({ fields: Object.freeze(["calibration_exemplars","context","ext","idempotency_key","policies","registry_policy_ids","scope","standards_id"]) as readonly string[], __type: null as unknown as UpdateContentStandardsRequest, }), - /** schemas/cache/3.1.0-rc.3/media-buy/update-media-buy-request.json */ + /** schemas/cache/3.1.0-rc.4/media-buy/update-media-buy-request.json */ UpdateMediaBuyRequest: Object.freeze({ fields: Object.freeze(["account","canceled","cancellation_reason","context","end_time","ext","idempotency_key","invoice_recipient","media_buy_id","new_packages","packages","paused","push_notification_config","reporting_webhook","revision","start_time"]) as readonly string[], __type: null as unknown as UpdateMediaBuyRequest, }), - /** schemas/cache/3.1.0-rc.3/property/update-property-list-request.json */ + /** schemas/cache/3.1.0-rc.4/property/update-property-list-request.json */ UpdatePropertyListRequest: Object.freeze({ fields: Object.freeze(["account","base_properties","brand","context","description","ext","filters","idempotency_key","list_id","name","webhook_url"]) as readonly string[], __type: null as unknown as UpdatePropertyListRequest, }), - /** schemas/cache/3.1.0-rc.3/brand/update-rights-request.json */ + /** schemas/cache/3.1.0-rc.4/brand/update-rights-request.json */ UpdateRightsRequest: Object.freeze({ fields: Object.freeze(["account","context","end_date","ext","idempotency_key","impression_cap","paused","pricing_option_id","push_notification_config","rights_id"]) as readonly string[], __type: null as unknown as UpdateRightsRequest, diff --git a/src/lib/types/core.generated.ts b/src/lib/types/core.generated.ts index a7e9d9bde..b472c1d00 100644 --- a/src/lib/types/core.generated.ts +++ b/src/lib/types/core.generated.ts @@ -1,5 +1,5 @@ -// Generated AdCP core types from official schemas v3.1.0-rc.3 -// Generated at: 2026-05-30T10:30:20.747Z +// Generated AdCP core types from official schemas v3.1.0-rc.4 +// Generated at: 2026-05-30T16:54:37.082Z // MEDIA-BUY SCHEMA /** @@ -4659,9 +4659,9 @@ export type MediaBuyValidAction = | 'update_packages' | 'sync_creatives'; /** - * How a seller honors a given action on a media buy. Buyers branch on this to decide whether to expect a synchronous response, an automatic-with-fallback flow, a proposal lifecycle round-trip, or an asynchronous human approval. The mode is declared on each entry of `allowed_actions[]` (product, as `modes[]` array) or `available_actions[]` (buy, as singular `mode`). + * How a seller honors a given action on a media buy. Buyers branch on this to decide whether to expect a synchronous response, an automatic-with-fallback flow, or an asynchronous human approval. The mode is declared on each entry of `allowed_actions[]` (product, as `modes[]` array) or `available_actions[]` (buy, as singular `mode`). Requotes that fall outside the current buy envelope are not an action mode in 3.1; sellers return REQUOTE_REQUIRED from update_media_buy instead. */ -export type MediaBuyActionMode = 'self_serve' | 'conditional_self_serve' | 'requires_proposal' | 'requires_approval'; +export type MediaBuyActionMode = 'self_serve' | 'conditional_self_serve' | 'requires_approval'; /** * Available frequencies for delivery reports and metrics updates */ @@ -6895,7 +6895,7 @@ export interface ProductAllowedAction { */ export interface SLAWindow { /** - * Maximum time from when the buyer issues the action to when the seller acknowledges receipt (mode-appropriate: synchronous response for self_serve, queue ack for requires_approval, proposal task creation for requires_proposal). ISO 8601 duration. + * Maximum time from when the buyer issues the action to when the seller acknowledges receipt (mode-appropriate: synchronous response for self_serve, tolerance decision for conditional_self_serve, or queue ack for requires_approval). ISO 8601 duration. * @pattern ^P(?!$)(\d+Y)?(\d+M)?(\d+D)?(T(\d+H)?(\d+M)?(\d+S)?)?$ */ response_max?: string; @@ -7661,7 +7661,7 @@ export type AdCPAsyncResponseData = | SyncCatalogsAsyncInputRequired | SyncCatalogsAsyncSubmitted; /** - * Lifecycle status of this proposal. When absent, the proposal is ready to buy (backward compatible). 'draft' means indicative pricing — finalize via refine before purchasing. 'committed' means firm pricing with inventory reserved until expires_at. + * Lifecycle status of this proposal and the per-proposal source of truth for whether finalization is required before create_media_buy. When absent, the proposal is ready to buy (backward compatible). 'draft' means indicative pricing — finalize via refine before purchasing. 'committed' means firm pricing with inventory reserved until expires_at and executable via create_media_buy. */ export type ProposalStatus = 'draft' | 'committed'; /** @@ -8441,7 +8441,7 @@ export interface GetProductsResponse { */ total_candidates?: number; /** - * Per-filter exclusion counts, keyed by the filter property name as it appears in the request's `filters` object (e.g., `required_metrics`, `required_vendor_metrics`, `required_geo_targeting`, `budget_range`). Values are objects carrying `count` and optional filter-specific detail. Only filters that actually narrowed the set need appear here; absence of a key means that filter did not exclude anything (or was not in the request). + * Per-filter exclusion counts, keyed by the filter property name as it appears in the request's `filters` object (e.g., `pricing_currencies`, `required_metrics`, `required_vendor_metrics`, `required_geo_targeting`, `budget_range`). Values are objects carrying `count` and optional filter-specific detail. Only filters that actually narrowed the set need appear here; absence of a key means that filter did not exclude anything (or was not in the request). */ excluded_by?: { [k: string]: @@ -8629,11 +8629,11 @@ export interface PushNotificationConfig { }; } /** - * A proposed media plan with budget allocations across products. Represents the publisher's strategic recommendation for how to structure a campaign based on the brief. Proposals are actionable - buyers can execute them directly via create_media_buy by providing the proposal_id. + * A proposed media plan with budget allocations across products. Represents the publisher's strategic recommendation for how to structure a campaign based on the brief. Proposals are actionable: committed proposals can be executed directly via create_media_buy by providing the proposal_id; draft proposals must first be finalized via get_products refine action 'finalize'. */ export interface Proposal { /** - * Unique identifier for this proposal. Used to execute it via create_media_buy. + * Unique identifier for this proposal. Used to finalize a draft proposal and to execute a committed proposal via create_media_buy. * @maxLength 255 */ proposal_id: string; @@ -12565,6 +12565,86 @@ export interface VerifyBrandClaimSuccess { */ context_note?: string; context?: ContextObject; + /** + * Payload-envelope JWS attesting the canonical success response for verify_brand_claim. The signed payload response MUST match the unsigned task-body fields on this response, excluding signed_response and protocol/version envelope fields. + */ + signed_response: ResponsePayloadJWSEnvelope & { + payload?: { + task: 'verify_brand_claim'; + response: SignedSuccessPayload; + }; + }; + ext?: ExtensionObject; +} +/** + * Decoded-payload JWS envelope for the closed designated-task response-signing profile. The protected member is the base64url-encoded JWS protected header; payload is the decoded signed payload that verifiers canonicalize with RFC 8785/JCS and base64url-encode before checking the ordinary JWS signature. + */ +export interface ResponsePayloadJWSEnvelope { + /** + * Base64url-encoded JWS protected header. The decoded header MUST include alg, kid, and typ: adcp-response-payload+jws, and MUST NOT include the RFC 7797 b64 header. Verifiers enforce the key purpose by resolving kid to a JWK with adcp_use: response-signing. + * @pattern ^[A-Za-z0-9_-]+$ + */ + protected: string; + payload: ResponsePayload; + /** + * Base64url-encoded JWS signature over the protected header and canonicalized payload. + * @pattern ^[A-Za-z0-9_-]+$ + */ + signature: string; +} +/** + * Decoded signed payload. Signers compute the JWS payload bytes from the RFC 8785/JCS canonicalization of this object. + */ +export interface ResponsePayload { + /** + * Type discriminator preventing cross-profile replay. + */ + typ: 'adcp-response-payload+jws'; + /** + * Designated task whose response payload is signed. + */ + task: 'verify_brand_claim' | 'verify_brand_claims'; + /** + * Brand tenant whose policy store produced the answer. The signer MUST derive this from server-side tenant resolution, not caller-supplied request fields. + * @pattern ^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$ + */ + brand_domain: string; + /** + * Canonical URL of the responding brand agent entry whose response-signing key verifies this envelope. + */ + agent_url: string; + /** + * sha256: prefix plus unpadded base64url SHA-256 of the canonical request-binding object for this call. + * @pattern ^sha256:[A-Za-z0-9_-]{43}$ + */ + request_hash: string; + /** + * Issued-at time as Unix epoch seconds. + * @minimum 0 + */ + iat: number; + /** + * Expiration time as Unix epoch seconds. Online verifiers reject envelopes after this time, allowing only implementation-defined clock skew. + * @minimum 0 + */ + exp: number; + /** + * Canonical task-body success response payload being attested. Any unsigned task-body fields on the outer response, excluding signed_response and protocol/version envelope fields, MUST match this object. + */ + response: {}; +} +/** + * Canonical task-body success payload signed inside signed_response.payload.response. Excludes protocol/version envelope fields and signed_response itself. + */ +export interface SignedSuccessPayload { + claim_type: 'subsidiary' | 'parent' | 'property' | 'trademark'; + verification_status: VerificationStatus; + details?: {}; + /** + * @maxLength 500 + */ + context_note?: string; + context?: ContextObject; ext?: ExtensionObject; } export interface VerifyBrandClaimError { @@ -12657,6 +12737,15 @@ export interface VerifyBrandClaimsSuccess { */ results: ResultEntry[]; context?: ContextObject; + /** + * Payload-envelope JWS attesting the canonical bulk success response for verify_brand_claims. The signed payload response MUST match the unsigned task-body fields on this response, excluding signed_response and protocol/version envelope fields. + */ + signed_response: ResponsePayloadJWSEnvelope & { + payload?: { + task: 'verify_brand_claims'; + response: SignedSuccessPayload; + }; + }; ext?: ExtensionObject; } export interface VerifyBrandClaimsResultSuccess { @@ -16508,7 +16597,7 @@ export interface CreateMediaBuyRequest { plan_id?: string; account: AccountReference; /** - * ID of a proposal from get_products to execute. When provided with total_budget, the publisher converts the proposal's allocation percentages into packages automatically. Alternative to providing packages array. + * ID of a committed proposal from get_products to execute. When provided with total_budget, the publisher converts the proposal's allocation percentages into packages automatically. Alternative to providing packages array. If the referenced proposal has proposal_status: 'draft', the seller MUST reject with PROPOSAL_NOT_COMMITTED; the buyer finalizes first via get_products refine action 'finalize'. */ proposal_id?: string; /** @@ -17740,7 +17829,7 @@ export interface GetMediaBuysResponseMediaBuy { updated_at?: string; context?: ContextObject; /** - * Flat-vocabulary actions the buyer can perform on this media buy in its current state. Eliminates the need for agents to internalize the state machine — the seller declares what is permitted right now. Deprecated in favor of `available_actions[]`, which carries `mode` (self_serve / conditional_self_serve / requires_proposal / requires_approval), optional SLA, and optional `terms_ref`. Sellers SHOULD populate both during the 3.x deprecation window; consumers MUST prefer `available_actions[]` when both are present. Removed in 4.0. + * Flat-vocabulary actions the buyer can perform on this media buy in its current state. Eliminates the need for agents to internalize the state machine — the seller declares what is permitted right now. Deprecated in favor of `available_actions[]`, which carries `mode` (self_serve / conditional_self_serve / requires_approval), optional SLA, and optional `terms_ref`. Sellers SHOULD populate both during the 3.x deprecation window; consumers MUST prefer `available_actions[]` when both are present. Removed in 4.0. */ valid_actions?: MediaBuyValidAction[]; /** @@ -18111,6 +18200,10 @@ export interface ProductFilters { * Filter by pricing availability and returned pricing options: true = products offering fixed pricing (at least one option with fixed_price), false = products offering auction pricing (at least one option without fixed_price). Products with both fixed and auction options match both true and false, but sellers MUST return only the pricing_options entries matching the requested pricing type so buyers can deterministically select from the returned options. */ is_fixed_price?: boolean; + /** + * Filter by currencies the buyer can use for the media product transaction, using ISO 4217 currency codes. Products match when they offer at least one product-level pricing_options entry in one of the requested currencies and any seller-applied or otherwise mandatory product-scoped signal charges are satisfiable in one of those currencies or have no incremental price. Mandatory custom signal pricing without currency is not satisfiable for this filter unless the seller can truthfully treat it as having no incremental price. Sellers MUST return only product pricing_options entries whose currency is in this list so buyers can select deterministically from discovery. This filter does not require pruning optional signal or vendor add-on pricing; buyers should avoid optional add-ons priced only in unsupported currencies. + */ + pricing_currencies?: string[]; /** * Filter by specific format IDs */ @@ -20420,7 +20513,7 @@ export type GetAdCPCapabilitiesResponse = ProtocolEnvelope & { idempotency: IdempotencySupported | IdempotencyUnsupported; }; /** - * AdCP protocols this agent supports. Each value both (a) declares which tools the agent implements and (b) commits the agent to pass the baseline compliance storyboard at /compliance/{version}/protocols/{protocol}/ (with snake_case → kebab-case path mapping, e.g. media_buy → /compliance/.../protocols/media-buy/). The `measurement` protocol is in development — currently scoped to `get_adcp_capabilities` for catalog discovery; additional measurement tasks (reporting, attribution, etc.) and a baseline storyboard land in subsequent minors. Compliance testing support is declared separately via the `compliance_testing` capability block (below), not as a protocol claim. + * AdCP protocols this agent supports. Stable values both (a) declare which tools the agent implements and (b) commit the agent to pass the baseline compliance storyboard at /compliance/{version}/protocols/{protocol}/ (with snake_case → kebab-case path mapping, e.g. media_buy → /compliance/.../protocols/media-buy/). The `measurement` protocol is experimental in 3.1 and currently scoped to `get_adcp_capabilities` catalog discovery; agents implementing it MUST also list `measurement.core` in `experimental_features`. Additional measurement tasks (reporting, attribution, etc.) and a baseline storyboard land in subsequent minors. Compliance testing support is declared separately via the `compliance_testing` capability block (below), not as a protocol claim. */ supported_protocols: ( | 'media_buy' @@ -20469,7 +20562,7 @@ export type GetAdCPCapabilitiesResponse = ProtocolEnvelope & { */ supported_pricing_models?: PricingModel[]; /** - * Buying modes this seller supports on get_products. 'brief' (semantic discovery driven by the brief) is universally supported and implicit. 'wholesale' (raw wholesale product feed enumeration — caller omits brief and the seller returns the full priced product feed, paginated) is opt-in and SHOULD be declared explicitly so buyers can probe before issuing wholesale calls. 'refine' (iterate on prior products/proposals) is implicit when the seller declares supports_proposals or otherwise honors the refine array. Sellers MAY declare ['brief', 'wholesale'] to signal wholesale support; absent declaration is treated as ['brief'] for wholesale-feed probing purposes and sellers MAY return INVALID_REQUEST for wholesale calls they do not support. Symmetric with signals.discovery_modes. + * Buying modes this seller supports on get_products. 'brief' (semantic discovery driven by the brief) is universally supported and implicit. 'wholesale' (raw wholesale product feed enumeration — caller omits brief and the seller returns the full priced product feed, paginated) is opt-in and SHOULD be declared explicitly so buyers can probe before issuing wholesale calls. 'refine' lets buyers iterate on prior products/proposals and is also the vehicle for finalizing draft proposals when the seller returns them. Sellers MAY declare ['brief', 'wholesale'] to signal wholesale support; absent declaration is treated as ['brief'] for wholesale-feed probing purposes and sellers MAY return INVALID_REQUEST for wholesale calls they do not support. Symmetric with signals.discovery_modes. */ buying_modes?: ('brief' | 'wholesale' | 'refine')[]; /** @@ -20481,7 +20574,7 @@ export type GetAdCPCapabilitiesResponse = ProtocolEnvelope & { */ offline_delivery_protocols?: CloudStorageProtocol[]; /** - * Whether this seller commits to the proposal lifecycle on get_products: when called with buying_mode: 'brief' the seller will return at least one entry in proposals[]; when called with buying_mode: 'refine' + action: 'finalize' the seller will transition a proposal from draft to committed. A declaration of true is a commitment the seller will be graded against, not just a feature flag — sellers that decline a brief on policy grounds still owe a structured proposal-shaped rejection rather than an empty proposals[]. Most guaranteed-deal sellers (premium pubs, broadcast, CTV) declare true; auction-based PG, retail SKU, and quoted-rate direct-buy flows declare false. When false or absent, the seller serves products directly without proposal abstraction; conformance runners skip proposal-lifecycle storyboards. + * Conformance declaration that this seller supports the full proposal lifecycle on get_products: returned proposals are actionable, draft proposals can be finalized with buying_mode: 'refine' + action: 'finalize', and committed proposals can be executed via create_media_buy with proposal_id before expires_at. Buyers SHOULD NOT use this field to decide whether a specific returned proposal is executable; proposal_status is the per-proposal source of truth. A declaration of true opts the seller into proposal-lifecycle grading. When false or absent, conformance runners skip proposal-lifecycle storyboards, but buyers should still honor any proposals the seller actually returns. */ supports_proposals?: boolean; /** @@ -21123,7 +21216,7 @@ export type GetAdCPCapabilitiesResponse = ProtocolEnvelope & { }; }; /** - * Measurement capability block. Presence indicates this agent computes one or more quantitative metrics about ad delivery, exposure, or effect, and is willing to be discovered as a measurement vendor. Returns metric definitions (this surface), not pricing/coverage (negotiated via `measurement_terms` on `create_media_buy`) or live values (returned per buy via `vendor_metric_values`). Modeled as a capability block (like `compliance_testing` and `webhook_signing`) rather than a `supported_protocols` value because measurement agents have one surface — this catalog — not a tool-set with mandatory tasks. AAO crawls each measurement agent's `metrics[]` on a TTL to populate the federated cross-vendor index. Same self-describing pattern as `governance.property_features[]`: agents own the catalog; the registry aggregates. + * Experimental measurement capability block. Presence indicates this agent computes one or more quantitative metrics about ad delivery, exposure, or effect, and is willing to be discovered as a measurement vendor. Agents implementing this block MUST list `measurement.core` in experimental_features. Returns metric definitions (this surface), not pricing/coverage (negotiated via `measurement_terms` on `create_media_buy`) or live values (returned per buy via `vendor_metric_values`). AAO crawls each measurement agent's `metrics[]` on a TTL to populate the federated cross-vendor index. Same self-describing pattern as `governance.property_features[]`: agents own the catalog; the registry aggregates. */ measurement?: { /** @@ -21634,7 +21727,7 @@ export type GetSignalsRequest = { countries?: string[]; filters?: SignalFilters; /** - * Specific signal fields to include in the response, aligned with get_products.fields. Required identity and activation fields such as signal_ref or signal_id, signal_agent_segment_id, name, description, signal_type, coverage_percentage, and deployments are always included when required by the response schema. Use for progressive disclosure of rich signal-definition metadata: request fields such as taxonomy, data_sources, methodology, segmentation_criteria, criteria_url, refresh_cadence, lookback_window, onboarder, modeling, audience_expansion, device_expansion, countries, consent_basis, restricted_attributes, policy_categories, art9_basis, and data_subject_rights when the buyer needs them inline. Omit for the agent's default discovery projection. Agents SHOULD honor requested fields for exact lookup, refinement, and small custom-signal result sets when available. For broad discovery and wholesale pages, agents MAY return compact pointers instead of inlining large resources, especially when provider-published definitions can be resolved from signal_ref, taxonomy.ref, criteria_url, disclosure_url, and validators such as taxonomy.etag. + * Specific signal fields to include in the response, aligned with get_products.fields. Required identity and activation fields such as signal_ref or signal_id, signal_agent_segment_id, name, description, signal_type, coverage_percentage, and deployments are always included when required by the response schema. Use for progressive disclosure of rich signal-definition metadata: request fields such as taxonomy, data_sources, methodology, segmentation_criteria, criteria_url, refresh_cadence, lookback_window, onboarder, modeling, audience_expansion, device_expansion, countries, consent_basis, restricted_attributes, policy_categories, art9_basis, and data_subject_rights when the buyer needs them inline. Omit for the agent's default discovery projection. Agents SHOULD honor requested fields for exact lookup, refinement, small custom-signal result sets, and private/source-native signals when available. fields is a projection request, not an entitlement grant; agents MAY redact requested definition fields unless the caller is authorized for the underlying lineage, methodology, and rights-routing metadata. For broad discovery and wholesale pages, agents MAY return compact pointers instead of inlining large resources, especially when provider-published definitions can be resolved from signal_ref, taxonomy.ref, criteria_url, disclosure_url, and validators such as resolved URL plus catalog_etag, HTTP ETag/Last-Modified, or taxonomy.etag. */ fields?: ( | 'signal_ref' @@ -25900,7 +25993,7 @@ export interface SignalCoverageForecast { ext?: ExtensionObject; } -// core/signal-definition.json +// core/signal-definition-enrichment.json /** * Personal data categories that may be restricted from use in audience targeting. Combines GDPR Article 9 special categories with US civil-rights protected classes (FHA familial_status, ADEA age). Used in two places: (1) on campaign plans via restricted_attributes to declare which categories are prohibited, and (2) on signal-definition.json via restricted_attributes to declare which categories a signal touches. Governance agents match plan restrictions against signal declarations for structural validation. */ @@ -25916,7 +26009,204 @@ export type RestrictedAttribute = | 'age' | 'familial_status'; /** - * Definition of a signal in published adagents.json signals[]. The publishing domain supplies the namespace, so this definition carries a local id rather than a signal_ref. Media-buy products reference this definition with signal_ref scope 'data_provider', data_provider_domain set to the publishing domain, and signal_id set to this id. + * Optional signal-definition enrichment fields that may be projected inline on signal listings when requested through get_signals.fields. This schema intentionally excludes signal identity and required definition fields so source-native, private, or compact listings can include typed partial disclosure without becoming a full adagents.json signal definition. + */ +export interface SignalDefinitionEnrichment { + /** + * Restricted attribute categories this signal touches. + */ + restricted_attributes?: RestrictedAttribute[]; + /** + * Policy categories this signal is sensitive for. + */ + policy_categories?: string[]; + /** + * Optional taxonomy metadata describing what this signal means in an external audience, content, retail-media, or provider-owned taxonomy. + */ + taxonomy?: { + ref: string; + version?: string; + /** + * @minimum 1 + */ + segtax?: number; + etag?: string; + values: { + /** + * @minLength 1 + */ + id: string; + path?: string; + modifiers?: string[]; + }[]; + value_mappings?: { + value: string; + taxonomy_value_id: string; + path?: string; + modifiers?: string[]; + }[]; + parent_match_behavior?: 'exact_only' | 'descendants_supported' | 'unknown'; + }; + /** + * @maxLength 500 + */ + segmentation_criteria?: string; + criteria_url?: string; + data_sources?: ( + | 'app_behavior' + | 'app_usage' + | 'web_usage' + | 'geo_location' + | 'email' + | 'tv_ott_or_stb_device' + | 'panel' + | 'online_ecommerce' + | 'credit_data' + | 'loyalty_card' + | 'transaction' + | 'online_survey' + | 'offline_survey' + | 'public_record_census' + | 'public_record_voter_file' + | 'public_record_other' + | 'offline_transaction' + )[]; + methodology?: 'observed' | 'declared' | 'derived' | 'inferred' | 'modeled'; + audience_expansion?: boolean; + device_expansion?: boolean; + refresh_cadence?: + | 'intra_day' + | 'daily' + | 'weekly' + | 'monthly' + | 'bi_monthly' + | 'quarterly' + | 'bi_annually' + | 'annually'; + lookback_window?: + | 'intra_day' + | 'daily' + | 'weekly' + | 'monthly' + | 'bi_monthly' + | 'quarterly' + | 'bi_annually' + | 'annually'; + onboarder?: { + match_keys: ( + | 'name' + | 'address' + | 'email' + | 'postal' + | 'lat_long' + | 'mobile_id' + | 'cookie_id' + | 'ip' + | 'customer_id' + | 'phone' + )[]; + pre_onboarding_audience_expansion?: boolean; + pre_onboarding_device_expansion?: boolean; + pre_onboarding_precision_level?: 'individual' | 'household' | 'business' | 'geography'; + }; + countries?: string[]; + consent_basis?: ConsentBasis[]; + art9_basis?: 'explicit_consent' | 'manifestly_made_public' | 'substantial_public_interest' | 'vital_interests'; + modeling?: { + method: 'lookalike' | 'supervised' | 'embedding' | 'rules'; + seed_source: { + type: 'first_party_crm' | 'panel' | 'declared_survey' | 'transactional' | 'behavioral'; + /** + * Provider assertion that the seed source carries a signed attestation. Consumers MUST NOT treat this boolean alone as cryptographic proof. + */ + provider_signed: boolean; + }; + training_data_jurisdictions: string[]; + ai_act_risk_class: 'minimal' | 'limited' | 'high_risk'; + disclosure?: SignalModelingDisclosure; + }; + /** + * Per-signal data-subject-rights routing. This is a contact/routing reference, not a machine-callable AdCP API. + */ + data_subject_rights?: { + /** + * @maxLength 253 + */ + upstream_source_domain?: string; + channels: { + rights: ('access' | 'rectification' | 'erasure' | 'portability' | 'objection')[]; + /** + * @pattern ^https:\/\/ + */ + url?: string; + /** + * @format email + */ + email?: string; + languages?: string[]; + countries?: string[]; + }[]; + /** + * @minimum 1 + * @maximum 90 + */ + response_sla_days?: number; + /** + * @pattern ^https:\/\/ + */ + ccpa_opt_out_url?: string; + }; + dts_compliant_version?: string; +} +/** + * Disclosure requirements and jurisdictional notes for modeled data signals. This schema is intentionally separate from core/provenance.json because creative provenance is about generated content, render guidance, and asset-level chain of custody, while signal modeling disclosure is about data-segment methodology and data-use transparency. + */ +export interface SignalModelingDisclosure { + /** + * The provider's claim that a modeling or AI-use disclosure is required for this signal in at least one applicable jurisdiction. This is a declared compliance signal, not a protocol-level legal determination. + */ + required: boolean; + /** + * Jurisdictions where a modeling or AI-use disclosure applies. + */ + jurisdictions?: { + /** + * ISO 3166-1 alpha-2 country code. + * @pattern ^[A-Z]{2}$ + */ + country: string; + /** + * Provider-defined sub-national region code or name when the obligation is regional. No global canonical format is implied. + */ + region?: string; + /** + * Provider-supplied regulation identifier for the disclosure obligation. + */ + regulation: string; + /** + * Human-readable disclosure text or summary the provider expects buyers or reviewers to see. + */ + disclosure_text?: string; + /** + * Optional URL to the provider's canonical disclosure or methodology page for this jurisdiction. + */ + disclosure_url?: string; + /** + * Primary audience for this disclosure entry. + */ + audience?: 'buyer' | 'data_subject' | 'regulator' | 'public'; + }[]; + /** + * Optional provider notes on how the disclosure should be interpreted. Informational only; buyers should not branch programmatically on this text. + * @maxLength 2000 + */ + notes?: string; +} + + +// core/signal-definition.json +/** + * Definition of a signal in published adagents.json signals[]. The publishing domain supplies the namespace, so this definition carries a local id rather than a signal_ref. Media-buy products reference this definition with signal_ref scope 'data_provider', data_provider_domain set to the publishing domain, and signal_id set to this id. Some constraints use JSON Schema draft-07 conditional keywords such as if/then and contains; SDK generators that drop those keywords need equivalent runtime guards. */ export interface SignalDefinition { /** @@ -26148,7 +26438,7 @@ export interface SignalDefinition { */ originating_domain?: string; /** - * ISO 3166-1 alpha-2 country codes where the signal is applicable. Sellers must not expose a signal for media buys in countries outside this list unless their own policy allows a narrower operational override. + * ISO 3166-1 alpha-2 country codes where the signal is applicable. Sellers must not expose a signal for media buys in countries outside this list. Federating agents that surface a peer's signal MUST treat the peer-published list as an upper bound, re-check it against the buyer's intended deployment countries, and may apply only narrower local policy. */ countries?: string[]; /** @@ -26167,7 +26457,7 @@ export interface SignalDefinition { seed_source: { type: 'first_party_crm' | 'panel' | 'declared_survey' | 'transactional' | 'behavioral'; /** - * Whether the seed source carries a signed attestation under one of the provider's published signing keys. + * Whether the seed source carries a signed attestation under one of the provider's published signing keys. This is a forward-looking claim until the consumer can resolve and verify the provider's applicable signing keys through the AdCP signing profile; consumers MUST NOT treat this boolean alone as cryptographic proof. */ provider_signed: boolean; }; @@ -26182,7 +26472,7 @@ export interface SignalDefinition { disclosure?: SignalModelingDisclosure; }; /** - * Per-signal data-subject-rights routing. Inline on the signal because upstream source and rights routing can differ by segment even when the publishing domain is the same. This is a contact/routing reference, not a machine-callable AdCP API. + * Per-signal data-subject-rights routing. Inline on the signal because upstream source, rights routing, and response commitments can differ by segment or may be unavailable from a public provider document for custom/private signals. This is a contact/routing reference, not a machine-callable AdCP API. */ data_subject_rights?: { /** @@ -26191,7 +26481,7 @@ export interface SignalDefinition { */ upstream_source_domain?: string; /** - * Rights request channels and the rights each channel supports. + * Rights request channels and the rights each channel supports. At least one declared channel MUST support one or more of access, erasure, or objection; schema validators enforce this with draft-07 contains, and consumers whose SDK drops contains need an equivalent runtime check. */ channels: { /** @@ -26218,15 +26508,11 @@ export interface SignalDefinition { countries?: string[]; }[]; /** - * Maximum response time in days for the declared rights channels. + * Maximum response time in days for rights requests handled through the declared rights channels. For provider-published signals, providers SHOULD avoid duplicating this field across every signal unless the value varies by signal or upstream source; consumers MAY also consult the provider's public privacy policy or registry disclosures when present. * @minimum 1 * @maximum 90 */ response_sla_days?: number; - /** - * Whether Global Privacy Control signals are honored as objection or opt-out requests for this signal. - */ - gpc_honored?: boolean; /** * US-specific 'Do Not Sell or Share' opt-out URL where required. * @pattern ^https:\/\/ @@ -26238,51 +26524,6 @@ export interface SignalDefinition { */ dts_compliant_version?: string; } -/** - * Signal/modeling-specific disclosure requirements and jurisdictional notes. This is not creative provenance render guidance. - */ -export interface SignalModelingDisclosure { - /** - * The provider's claim that a modeling or AI-use disclosure is required for this signal in at least one applicable jurisdiction. This is a declared compliance signal, not a protocol-level legal determination. - */ - required: boolean; - /** - * Jurisdictions where a modeling or AI-use disclosure applies. - */ - jurisdictions?: { - /** - * ISO 3166-1 alpha-2 country code. - * @pattern ^[A-Z]{2}$ - */ - country: string; - /** - * Provider-defined sub-national region code or name when the obligation is regional. No global canonical format is implied. - */ - region?: string; - /** - * Provider-supplied regulation identifier for the disclosure obligation. - */ - regulation: string; - /** - * Human-readable disclosure text or summary the provider expects buyers or reviewers to see. - */ - disclosure_text?: string; - /** - * Optional URL to the provider's canonical disclosure or methodology page for this jurisdiction. - */ - disclosure_url?: string; - /** - * Primary audience for this disclosure entry. - */ - audience?: 'buyer' | 'data_subject' | 'regulator' | 'public'; - }[]; - /** - * Optional provider notes on how the disclosure should be interpreted. Informational only; buyers should not branch programmatically on this text. - * @maxLength 2000 - */ - notes?: string; -} - // core/signal-pricing-option.json /** diff --git a/src/lib/types/enums.generated.ts b/src/lib/types/enums.generated.ts index b2f136f3b..45abcd2c4 100644 --- a/src/lib/types/enums.generated.ts +++ b/src/lib/types/enums.generated.ts @@ -111,7 +111,7 @@ export const MakegoodRemedyValues = ["additional_delivery", "credit", "invoice_a export const MarkdownFlavorValues = ["commonmark", "gfm"] as const; export const MatchIDTypeValues = ["hashed_email", "hashed_phone", "rampid", "id5", "uid2", "euid", "pairid", "maid", "other"] as const; export const MatchTypeValues = ["broad", "phrase", "exact"] as const; -export const MediaBuyActionModeValues = ["self_serve", "conditional_self_serve", "requires_proposal", "requires_approval"] as const; +export const MediaBuyActionModeValues = ["self_serve", "conditional_self_serve", "requires_approval"] as const; export const MediaBuyHealthValues = ["ok", "impaired"] as const; export const MediaBuyStatusValues = ["pending_creatives", "pending_start", "active", "paused", "completed", "rejected", "canceled"] as const; export const MediaBuyValidActionValues = ["pause", "resume", "cancel", "extend_flight", "shorten_flight", "update_flight_dates", "increase_budget", "decrease_budget", "reallocate_budget", "update_targeting", "update_pacing", "update_frequency_caps", "replace_creative", "update_creative_assignments", "remove_creative", "add_packages", "remove_packages", "update_budget", "update_dates", "update_packages", "sync_creatives"] as const; diff --git a/src/lib/types/manifest.generated.ts b/src/lib/types/manifest.generated.ts index d100ebada..4e68a77e6 100644 --- a/src/lib/types/manifest.generated.ts +++ b/src/lib/types/manifest.generated.ts @@ -1,8 +1,8 @@ -// AUTO-GENERATED FROM schemas/cache/3.1.0-rc.3/manifest.json — DO NOT EDIT. +// AUTO-GENERATED FROM schemas/cache/3.1.0-rc.4/manifest.json — DO NOT EDIT. // Run `npm run generate-manifest-derived` to regenerate. /** - * Manifest-derived constants for AdCP 3.1.0-rc.3. + * Manifest-derived constants for AdCP 3.1.0-rc.4. * * Single source of truth for tool↔protocol grouping, error-code metadata * (description + recovery + suggestion), and specialism→required-tools @@ -12,8 +12,8 @@ * previously lived in `src/lib/utils/capabilities.ts` and * `src/lib/types/error-codes.ts`. * - * Source: `schemas/cache/3.1.0-rc.3/manifest.json` (adcp_version: 3.1.0-rc.3, generated_at: - * 2026-05-30T09:57:43.437Z). Re-run `npm run sync-schemas` then + * Source: `schemas/cache/3.1.0-rc.4/manifest.json` (adcp_version: 3.1.0-rc.4, generated_at: + * 2026-05-30T16:39:33.313Z). Re-run `npm run sync-schemas` then * `npm run generate-manifest-derived` to refresh after a spec bump. */ @@ -71,7 +71,7 @@ export const STANDARD_ERROR_CODES_FROM_MANIFEST = { ACTION_NOT_ALLOWED: { description: "The requested mutation maps to an action that is not currently available on this media buy. Sellers MUST populate `error.details` with `attempted_action` (the `media_buy_valid_action` value the request maps to), `reason` (an `action-not-allowed-reason` value: `wrong_status`, `not_supported_on_product`, `not_supported_on_buy`, or `mode_mismatch`), and `currently_available_actions` (echo of the buy's resolved `available_actions[]` so the buyer SDK can offer recovery without a separate get_media_buys round-trip).", recovery: "correctable", - suggestion: "branch on error.details.reason: for wrong_status, wait for or transition to a status listed under the action's allowed_statuses; for mode_mismatch, this is a flow switch (not a retry against update_media_buy) — re-issue through the flow named in available_actions[].mode (call create_proposal/finalize_proposal for requires_proposal; await the seller's webhook for requires_approval); for not_supported_on_product or not_supported_on_buy, do not retry — the action is unavailable on this buy and buyer must select a different product or renegotiate" + suggestion: "branch on error.details.reason: for wrong_status, wait for or transition to a status listed under the action's allowed_statuses; for mode_mismatch, this is a flow switch (not a retry against update_media_buy) — follow the mode named in available_actions[].mode (await the seller's webhook for requires_approval); for not_supported_on_product or not_supported_on_buy, do not retry — the action is unavailable on this buy and buyer must select a different product or renegotiate" }, AGENT_BLOCKED: { description: "The calling buyer agent's commercial relationship with the seller is permanently denied — the agent is blocked. Sibling to `AGENT_SUSPENDED` on the agent-relationship axis but with no recovery path (a suspension may lift via re-onboarding; a block does not). The code itself is the discriminator — same posture as `AGENT_SUSPENDED`: no `error.details` payload, no per-agent commercial state, cross-tenant onboarding oracle clamp + channel-coverage requirements normative in error-handling.mdx Per-Agent Authorization Gate.", @@ -389,9 +389,9 @@ export const STANDARD_ERROR_CODES_FROM_MANIFEST = { suggestion: "verify the referenced identifier exists and is accessible to the caller" }, REQUOTE_REQUIRED: { - description: "An update_media_buy request changes the parameter envelope (budget, flight dates, volume, targeting) the original quote was priced against. The pricing_option remains locked; the seller is declining the requested shape at that price. Distinct from TERMS_REJECTED (measurement) and POLICY_VIOLATION (content). Sellers SHOULD populate error.details.envelope_field with the field path(s) that breached the envelope (e.g., 'packages[0].budget', 'end_time') so the buyer's agent can autonomously re-discover.", + description: "An update_media_buy request changes the parameter envelope (budget, flight dates, volume, targeting) the original quote was priced against. The pricing_option remains locked; the seller is declining the requested shape at that price. Distinct from TERMS_REJECTED (measurement) and POLICY_VIOLATION (content). Sellers SHOULD populate error.details.envelope_field with the field path(s) that breached the envelope (e.g., 'packages[0].budget', 'end_time') so the buyer's agent can decide whether to adjust the update, rediscover products, add packages where supported, or create a separate media buy. AdCP 3.1 does not define an amendment-quote artifact that can be attached to update_media_buy.", recovery: "correctable", - suggestion: "re-negotiate via get_products in 'refine' mode against the existing proposal_id to obtain a fresh quote, then resubmit against the new proposal_id" + suggestion: "adjust the update to stay within the current quote envelope, rediscover products/terms, add packages when available, or create a separate media buy; 3.1 does not define an amendment-quote artifact for update_media_buy" }, SCOPE_INSUFFICIENT: { description: "The authenticated caller is not authorized for the invoked task — the task is not in the caller's `allowed_tasks` for this account (discoverable via the `authorization` object on sync_accounts / list_accounts responses). Distinct from `PERMISSION_DENIED` (generic authz failure, often credential-shaped) by being narrowly about task-level scope. Sellers SHOULD populate `error.details.introspection_hint` pointing at where the caller can re-read its scope (strawman: `{ task: 'list_accounts', account: {...} }`).", diff --git a/src/lib/types/schemas.generated.ts b/src/lib/types/schemas.generated.ts index 7b6208037..6e6b53339 100644 --- a/src/lib/types/schemas.generated.ts +++ b/src/lib/types/schemas.generated.ts @@ -1,5 +1,5 @@ // Generated Zod v4 schemas from TypeScript types -// Generated at: 2026-05-30T11:05:42.029Z +// Generated at: 2026-05-30T16:54:57.781Z // Sources: // - core.generated.ts (core types) // - tools.generated.ts (tool types) @@ -809,7 +809,7 @@ export const ForecastMethodSchema = z.union([z.literal("estimate"), z.literal("m export const MediaBuyValidActionSchema = z.union([z.literal("pause"), z.literal("resume"), z.literal("cancel"), z.literal("extend_flight"), z.literal("shorten_flight"), z.literal("update_flight_dates"), z.literal("increase_budget"), z.literal("decrease_budget"), z.literal("reallocate_budget"), z.literal("update_targeting"), z.literal("update_pacing"), z.literal("update_frequency_caps"), z.literal("replace_creative"), z.literal("update_creative_assignments"), z.literal("remove_creative"), z.literal("add_packages"), z.literal("remove_packages"), z.literal("update_budget"), z.literal("update_dates"), z.literal("update_packages"), z.literal("sync_creatives")]); -export const MediaBuyActionModeSchema = z.union([z.literal("self_serve"), z.literal("conditional_self_serve"), z.literal("requires_proposal"), z.literal("requires_approval")]); +export const MediaBuyActionModeSchema = z.union([z.literal("self_serve"), z.literal("conditional_self_serve"), z.literal("requires_approval")]); export const ReportingFrequencySchema = z.union([z.literal("hourly"), z.literal("daily"), z.literal("monthly")]); @@ -2446,7 +2446,13 @@ export const VerifyTrademarkClaimSchema = z.object({ }).passthrough() }).passthrough(); -export const VerifyBrandClaimSuccessSchema = z.object({ +export const VerifyBrandClaimErrorSchema = z.object({ + errors: z.array(ErrorSchema), + context: ContextObjectSchema.optional(), + ext: ExtensionObjectSchema.optional() +}).passthrough(); + +export const SignedSuccessPayloadSchema = z.object({ claim_type: z.union([z.literal("subsidiary"), z.literal("parent"), z.literal("property"), z.literal("trademark")]), verification_status: VerificationStatusSchema, details: z.object({}).passthrough().optional(), @@ -2455,10 +2461,15 @@ export const VerifyBrandClaimSuccessSchema = z.object({ ext: ExtensionObjectSchema.optional() }).passthrough(); -export const VerifyBrandClaimErrorSchema = z.object({ - errors: z.array(ErrorSchema), - context: ContextObjectSchema.optional(), - ext: ExtensionObjectSchema.optional() +export const ResponsePayloadSchema = z.object({ + typ: z.literal("adcp-response-payload+jws"), + task: z.union([z.literal("verify_brand_claim"), z.literal("verify_brand_claims")]), + brand_domain: z.string().regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/), + agent_url: z.string(), + request_hash: z.string().regex(/^sha256:[A-Za-z0-9_-]{43}$/), + iat: z.number().min(0), + exp: z.number().min(0), + response: z.object({}).passthrough() }).passthrough(); export const ClaimEntrySchema = z.union([VerifySubsidiaryClaimSchema, VerifyParentClaimSchema, VerifyPropertyClaimSchema, VerifyTrademarkClaimSchema]); @@ -2488,6 +2499,12 @@ export const VerifyBrandClaimsResultErrorSchema = z.object({ export const ResultEntrySchema = z.union([VerifyBrandClaimsResultSuccessSchema, VerifyBrandClaimsResultErrorSchema]); +export const ResponsePayloadJWSEnvelopeSchema = z.object({ + protected: z.string().regex(/^[A-Za-z0-9_-]+$/), + payload: ResponsePayloadSchema, + signature: z.string().regex(/^[A-Za-z0-9_-]+$/) +}).passthrough(); + export const AssetAccessSchema = z.union([z.object({ method: z.literal("bearer_token"), token: z.string() @@ -5096,6 +5113,85 @@ export const SignalModelingDisclosureSchema = z.object({ notes: z.string().max(2000).optional() }).passthrough(); +export const SignalDefinitionSchema = z.object({ + id: z.string().regex(/^[a-zA-Z0-9_-]+$/), + name: z.string().min(1).max(255), + description: z.string().max(2000).optional(), + value_type: SignalValueTypeSchema, + tags: z.array(z.string()).optional(), + allowed_values: z.array(z.string()).optional(), + restricted_attributes: z.array(RestrictedAttributeSchema).optional(), + policy_categories: z.array(z.string()).optional(), + range: z.object({ + min: z.number(), + max: z.number(), + unit: z.string().optional() + }).passthrough().optional(), + taxonomy: z.object({ + ref: z.string(), + version: z.string().optional(), + segtax: z.number().min(1).optional(), + etag: z.string().optional(), + values: z.array(z.object({ + id: z.string().min(1), + path: z.string().optional(), + modifiers: z.array(z.string()).optional() + }).passthrough()), + value_mappings: z.array(z.object({ + value: z.string(), + taxonomy_value_id: z.string(), + path: z.string().optional(), + modifiers: z.array(z.string()).optional() + }).passthrough()).optional(), + parent_match_behavior: z.union([z.literal("exact_only"), z.literal("descendants_supported"), z.literal("unknown")]).optional() + }).passthrough().optional(), + segmentation_criteria: z.string().max(500).optional(), + criteria_url: z.string().optional(), + data_sources: z.array(z.union([z.literal("app_behavior"), z.literal("app_usage"), z.literal("web_usage"), z.literal("geo_location"), z.literal("email"), z.literal("tv_ott_or_stb_device"), z.literal("panel"), z.literal("online_ecommerce"), z.literal("credit_data"), z.literal("loyalty_card"), z.literal("transaction"), z.literal("online_survey"), z.literal("offline_survey"), z.literal("public_record_census"), z.literal("public_record_voter_file"), z.literal("public_record_other"), z.literal("offline_transaction")])).optional(), + methodology: z.union([z.literal("observed"), z.literal("declared"), z.literal("derived"), z.literal("inferred"), z.literal("modeled")]).optional(), + audience_expansion: z.boolean().optional(), + device_expansion: z.boolean().optional(), + refresh_cadence: z.union([z.literal("intra_day"), z.literal("daily"), z.literal("weekly"), z.literal("monthly"), z.literal("bi_monthly"), z.literal("quarterly"), z.literal("bi_annually"), z.literal("annually")]).optional(), + lookback_window: z.union([z.literal("intra_day"), z.literal("daily"), z.literal("weekly"), z.literal("monthly"), z.literal("bi_monthly"), z.literal("quarterly"), z.literal("bi_annually"), z.literal("annually")]).optional(), + onboarder: z.object({ + match_keys: z.array(z.union([z.literal("name"), z.literal("address"), z.literal("email"), z.literal("postal"), z.literal("lat_long"), z.literal("mobile_id"), z.literal("cookie_id"), z.literal("ip"), z.literal("customer_id"), z.literal("phone")])), + pre_onboarding_audience_expansion: z.boolean().optional(), + pre_onboarding_device_expansion: z.boolean().optional(), + pre_onboarding_precision_level: z.union([z.literal("individual"), z.literal("household"), z.literal("business"), z.literal("geography")]).optional() + }).passthrough().optional(), + subject_type: z.union([z.literal("individual"), z.literal("household"), z.literal("business"), z.literal("contextual"), z.literal("none")]).optional(), + resolution_method: z.union([z.literal("deterministic_id"), z.literal("probabilistic_device"), z.literal("browser"), z.literal("geographic"), z.literal("content_signal"), z.literal("mixed")]).optional(), + id_types: z.array(z.union([z.literal("cookie"), z.literal("mobile_id"), z.literal("platform_id"), z.literal("user_enabled_id")])).optional(), + audience_scope: z.union([z.literal("single_domain"), z.literal("cross_domain_owned"), z.literal("cross_domain_unowned"), z.literal("offline")]).optional(), + originating_domain: z.string().optional(), + countries: z.array(z.string()).optional(), + consent_basis: z.array(ConsentBasisSchema).optional(), + art9_basis: z.union([z.literal("explicit_consent"), z.literal("manifestly_made_public"), z.literal("substantial_public_interest"), z.literal("vital_interests")]).optional(), + modeling: z.object({ + method: z.union([z.literal("lookalike"), z.literal("supervised"), z.literal("embedding"), z.literal("rules")]), + seed_source: z.object({ + type: z.union([z.literal("first_party_crm"), z.literal("panel"), z.literal("declared_survey"), z.literal("transactional"), z.literal("behavioral")]), + provider_signed: z.boolean() + }).passthrough(), + training_data_jurisdictions: z.array(z.string()), + ai_act_risk_class: z.union([z.literal("minimal"), z.literal("limited"), z.literal("high_risk")]), + disclosure: SignalModelingDisclosureSchema.optional() + }).passthrough().optional(), + data_subject_rights: z.object({ + upstream_source_domain: z.string().max(253).optional(), + channels: z.array(z.object({ + rights: z.array(z.union([z.literal("access"), z.literal("rectification"), z.literal("erasure"), z.literal("portability"), z.literal("objection")])), + url: z.string().regex(/^https:\/\//).optional(), + email: z.email().optional(), + languages: z.array(z.string()).optional(), + countries: z.array(z.string()).optional() + }).passthrough()), + response_sla_days: z.number().min(1).max(90).optional(), + ccpa_opt_out_url: z.string().regex(/^https:\/\//).optional() + }).passthrough().optional(), + dts_compliant_version: z.string().optional() +}).passthrough(); + export const VendorPricingSchema = z.union([CpmPricingSchema, PercentOfMediaPricingSchema, FlatFeePricingSchema, PerUnitPricingSchema, CustomPricingSchema]); export const StoreItemSchema = z.object({ @@ -5643,6 +5739,7 @@ export const ProductFiltersSchema = z.object({ delivery_type: DeliveryTypeSchema.optional(), exclusivity: ExclusivitySchema.optional(), is_fixed_price: z.boolean().optional(), + pricing_currencies: z.array(z.string()).optional(), format_ids: z.array(FormatReferenceStructuredObjectSchema).optional(), standard_formats_only: z.boolean().optional(), min_exposures: z.number().min(1).optional(), @@ -6841,6 +6938,66 @@ export const GetSignalsResponseSchema = z.object({ adcp_version: z.string().optional(), adcp_major_version: z.number().optional(), signals: z.array(z.object({ + restricted_attributes: z.array(RestrictedAttributeSchema).optional(), + policy_categories: z.array(z.string()).optional(), + taxonomy: z.object({ + ref: z.string(), + version: z.string().optional(), + segtax: z.number().optional(), + etag: z.string().optional(), + values: z.array(z.object({ + id: z.string(), + path: z.string().optional(), + modifiers: z.array(z.string()).optional() + }).passthrough()), + value_mappings: z.array(z.object({ + value: z.string(), + taxonomy_value_id: z.string(), + path: z.string().optional(), + modifiers: z.array(z.string()).optional() + }).passthrough()).optional(), + parent_match_behavior: z.union([z.literal("exact_only"), z.literal("descendants_supported"), z.literal("unknown")]).optional() + }).passthrough().optional(), + segmentation_criteria: z.string().optional(), + criteria_url: z.string().optional(), + data_sources: z.array(z.union([z.literal("app_behavior"), z.literal("app_usage"), z.literal("web_usage"), z.literal("geo_location"), z.literal("email"), z.literal("tv_ott_or_stb_device"), z.literal("panel"), z.literal("online_ecommerce"), z.literal("credit_data"), z.literal("loyalty_card"), z.literal("transaction"), z.literal("online_survey"), z.literal("offline_survey"), z.literal("public_record_census"), z.literal("public_record_voter_file"), z.literal("public_record_other"), z.literal("offline_transaction")])).optional(), + methodology: z.union([z.literal("observed"), z.literal("declared"), z.literal("derived"), z.literal("inferred"), z.literal("modeled")]).optional(), + audience_expansion: z.boolean().optional(), + device_expansion: z.boolean().optional(), + refresh_cadence: z.union([z.literal("intra_day"), z.literal("daily"), z.literal("weekly"), z.literal("monthly"), z.literal("bi_monthly"), z.literal("quarterly"), z.literal("bi_annually"), z.literal("annually")]).optional(), + lookback_window: z.union([z.literal("intra_day"), z.literal("daily"), z.literal("weekly"), z.literal("monthly"), z.literal("bi_monthly"), z.literal("quarterly"), z.literal("bi_annually"), z.literal("annually")]).optional(), + onboarder: z.object({ + match_keys: z.array(z.union([z.literal("name"), z.literal("address"), z.literal("email"), z.literal("postal"), z.literal("lat_long"), z.literal("mobile_id"), z.literal("cookie_id"), z.literal("ip"), z.literal("customer_id"), z.literal("phone")])), + pre_onboarding_audience_expansion: z.boolean().optional(), + pre_onboarding_device_expansion: z.boolean().optional(), + pre_onboarding_precision_level: z.union([z.literal("individual"), z.literal("household"), z.literal("business"), z.literal("geography")]).optional() + }).passthrough().optional(), + countries: z.array(z.string()).optional(), + consent_basis: z.array(ConsentBasisSchema).optional(), + art9_basis: z.union([z.literal("explicit_consent"), z.literal("manifestly_made_public"), z.literal("substantial_public_interest"), z.literal("vital_interests")]).optional(), + modeling: z.object({ + method: z.union([z.literal("lookalike"), z.literal("supervised"), z.literal("embedding"), z.literal("rules")]), + seed_source: z.object({ + type: z.union([z.literal("first_party_crm"), z.literal("panel"), z.literal("declared_survey"), z.literal("transactional"), z.literal("behavioral")]), + provider_signed: z.boolean() + }).passthrough(), + training_data_jurisdictions: z.array(z.string()), + ai_act_risk_class: z.union([z.literal("minimal"), z.literal("limited"), z.literal("high_risk")]), + disclosure: SignalModelingDisclosureSchema.optional() + }).passthrough().optional(), + data_subject_rights: z.object({ + upstream_source_domain: z.string().optional(), + channels: z.array(z.object({ + rights: z.array(z.union([z.literal("access"), z.literal("rectification"), z.literal("erasure"), z.literal("portability"), z.literal("objection")])), + url: z.string().optional(), + email: z.string().optional(), + languages: z.array(z.string()).optional(), + countries: z.array(z.string()).optional() + }).passthrough()), + response_sla_days: z.number().optional(), + ccpa_opt_out_url: z.string().optional() + }).passthrough().optional(), + dts_compliant_version: z.string().optional(), signal_ref: SignalRefSchema.optional(), signal_id: SignalIDSchema.optional(), name: z.string(), @@ -8735,25 +8892,30 @@ export const UpdateRightsResponseSchema = z.object({ export const VerifyBrandClaimRequestSchema = z.union([AdCPVersionEnvelopeSchema.merge(VerifySubsidiaryClaimSchema), AdCPVersionEnvelopeSchema.merge(VerifyParentClaimSchema), AdCPVersionEnvelopeSchema.merge(VerifyPropertyClaimSchema), AdCPVersionEnvelopeSchema.merge(VerifyTrademarkClaimSchema)]); -export const VerifyBrandClaimResponseSchema = z.object({ - context_id: z.string().optional(), +export const VerifyBrandClaimSuccessSchema = z.object({ + claim_type: z.union([z.literal("subsidiary"), z.literal("parent"), z.literal("property"), z.literal("trademark")]), + verification_status: VerificationStatusSchema, + details: z.object({}).passthrough().optional(), + context_note: z.string().max(500).optional(), context: ContextObjectSchema.optional(), - task_id: z.string().optional(), - status: TaskStatusSchema, - message: z.string().optional(), - timestamp: z.string().optional(), - replayed: z.boolean().optional(), - adcp_error: ErrorSchema.optional(), - push_notification_config: PushNotificationConfigSchema.optional(), - governance_context: z.string().optional(), - payload: z.object({}).passthrough().optional(), - adcp_version: z.string().optional(), - adcp_major_version: z.number().optional() -}).passthrough().and(z.union([VerifyBrandClaimSuccessSchema, VerifyBrandClaimErrorSchema])); + signed_response: ResponsePayloadJWSEnvelopeSchema.and(z.object({ + payload: z.object({ + task: z.literal("verify_brand_claim"), + response: SignedSuccessPayloadSchema + }).passthrough().optional() + }).passthrough()), + ext: ExtensionObjectSchema.optional() +}).passthrough(); export const VerifyBrandClaimsSuccessSchema = z.object({ results: z.array(ResultEntrySchema), context: ContextObjectSchema.optional(), + signed_response: ResponsePayloadJWSEnvelopeSchema.and(z.object({ + payload: z.object({ + task: z.literal("verify_brand_claims"), + response: SignedSuccessPayloadSchema + }).passthrough().optional() + }).passthrough()), ext: ExtensionObjectSchema.optional() }).passthrough(); @@ -9331,20 +9493,9 @@ export const CatalogRequirementsSchema = z.object({ field_bindings: z.array(CatalogFieldBindingSchema).optional() }).passthrough(); -export const SignalDefinitionSchema = z.object({ - id: z.string().regex(/^[a-zA-Z0-9_-]+$/), - name: z.string().min(1).max(255), - description: z.string().max(2000).optional(), - value_type: SignalValueTypeSchema, - tags: z.array(z.string()).optional(), - allowed_values: z.array(z.string()).optional(), +export const SignalDefinitionEnrichmentSchema = z.object({ restricted_attributes: z.array(RestrictedAttributeSchema).optional(), policy_categories: z.array(z.string()).optional(), - range: z.object({ - min: z.number(), - max: z.number(), - unit: z.string().optional() - }).passthrough().optional(), taxonomy: z.object({ ref: z.string(), version: z.string().optional(), @@ -9377,11 +9528,6 @@ export const SignalDefinitionSchema = z.object({ pre_onboarding_device_expansion: z.boolean().optional(), pre_onboarding_precision_level: z.union([z.literal("individual"), z.literal("household"), z.literal("business"), z.literal("geography")]).optional() }).passthrough().optional(), - subject_type: z.union([z.literal("individual"), z.literal("household"), z.literal("business"), z.literal("contextual"), z.literal("none")]).optional(), - resolution_method: z.union([z.literal("deterministic_id"), z.literal("probabilistic_device"), z.literal("browser"), z.literal("geographic"), z.literal("content_signal"), z.literal("mixed")]).optional(), - id_types: z.array(z.union([z.literal("cookie"), z.literal("mobile_id"), z.literal("platform_id"), z.literal("user_enabled_id")])).optional(), - audience_scope: z.union([z.literal("single_domain"), z.literal("cross_domain_owned"), z.literal("cross_domain_unowned"), z.literal("offline")]).optional(), - originating_domain: z.string().optional(), countries: z.array(z.string()).optional(), consent_basis: z.array(ConsentBasisSchema).optional(), art9_basis: z.union([z.literal("explicit_consent"), z.literal("manifestly_made_public"), z.literal("substantial_public_interest"), z.literal("vital_interests")]).optional(), @@ -9405,7 +9551,6 @@ export const SignalDefinitionSchema = z.object({ countries: z.array(z.string()).optional() }).passthrough()), response_sla_days: z.number().min(1).max(90).optional(), - gpc_honored: z.boolean().optional(), ccpa_opt_out_url: z.string().regex(/^https:\/\//).optional() }).passthrough().optional(), dts_compliant_version: z.string().optional() @@ -9998,6 +10143,22 @@ export const GetRightsResponseSchema = z.object({ export const SearchBrandsResponseSchema = AdCPVersionEnvelopeSchema.merge(ProtocolEnvelopeSchema).and(z.union([SearchBrandsSuccessSchema, SearchBrandsErrorSchema])); +export const VerifyBrandClaimResponseSchema = z.object({ + context_id: z.string().optional(), + context: ContextObjectSchema.optional(), + task_id: z.string().optional(), + status: TaskStatusSchema, + message: z.string().optional(), + timestamp: z.string().optional(), + replayed: z.boolean().optional(), + adcp_error: ErrorSchema.optional(), + push_notification_config: PushNotificationConfigSchema.optional(), + governance_context: z.string().optional(), + payload: z.object({}).passthrough().optional(), + adcp_version: z.string().optional(), + adcp_major_version: z.number().optional() +}).passthrough().and(z.union([VerifyBrandClaimSuccessSchema, VerifyBrandClaimErrorSchema])); + export const VerifyBrandClaimsResponseBulkSchema = z.object({ context_id: z.string().optional(), context: ContextObjectSchema.optional(), diff --git a/src/lib/types/tools.generated.ts b/src/lib/types/tools.generated.ts index 4bb4ebed7..4911b4d19 100644 --- a/src/lib/types/tools.generated.ts +++ b/src/lib/types/tools.generated.ts @@ -851,6 +851,10 @@ export interface ProductFilters { * Filter by pricing availability and returned pricing options: true = products offering fixed pricing (at least one option with fixed_price), false = products offering auction pricing (at least one option without fixed_price). Products with both fixed and auction options match both true and false, but sellers MUST return only the pricing_options entries matching the requested pricing type so buyers can deterministically select from the returned options. */ is_fixed_price?: boolean; + /** + * Filter by currencies the buyer can use for the media product transaction, using ISO 4217 currency codes. Products match when they offer at least one product-level pricing_options entry in one of the requested currencies and any seller-applied or otherwise mandatory product-scoped signal charges are satisfiable in one of those currencies or have no incremental price. Mandatory custom signal pricing without currency is not satisfiable for this filter unless the seller can truthfully treat it as having no incremental price. Sellers MUST return only product pricing_options entries whose currency is in this list so buyers can select deterministically from discovery. This filter does not require pruning optional signal or vendor add-on pricing; buyers should avoid optional add-ons priced only in unsupported currencies. + */ + pricing_currencies?: string[]; /** * Filter by specific format IDs */ @@ -2392,9 +2396,9 @@ export type MediaBuyValidAction = | 'update_packages' | 'sync_creatives'; /** - * How a seller honors a given action on a media buy. Buyers branch on this to decide whether to expect a synchronous response, an automatic-with-fallback flow, a proposal lifecycle round-trip, or an asynchronous human approval. The mode is declared on each entry of `allowed_actions[]` (product, as `modes[]` array) or `available_actions[]` (buy, as singular `mode`). + * How a seller honors a given action on a media buy. Buyers branch on this to decide whether to expect a synchronous response, an automatic-with-fallback flow, or an asynchronous human approval. The mode is declared on each entry of `allowed_actions[]` (product, as `modes[]` array) or `available_actions[]` (buy, as singular `mode`). Requotes that fall outside the current buy envelope are not an action mode in 3.1; sellers return REQUOTE_REQUIRED from update_media_buy instead. */ -export type MediaBuyActionMode = 'self_serve' | 'conditional_self_serve' | 'requires_proposal' | 'requires_approval'; +export type MediaBuyActionMode = 'self_serve' | 'conditional_self_serve' | 'requires_approval'; /** * Status of a media buy. */ @@ -2714,7 +2718,7 @@ export type UIDType = */ export type DayOfWeek = 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday' | 'sunday'; /** - * Lifecycle status of this proposal. When absent, the proposal is ready to buy (backward compatible). 'draft' means indicative pricing — finalize via refine before purchasing. 'committed' means firm pricing with inventory reserved until expires_at. + * Lifecycle status of this proposal and the per-proposal source of truth for whether finalization is required before create_media_buy. When absent, the proposal is ready to buy (backward compatible). 'draft' means indicative pricing — finalize via refine before purchasing. 'committed' means firm pricing with inventory reserved until expires_at and executable via create_media_buy. */ export type ProposalStatus = 'draft' | 'committed'; @@ -2896,7 +2900,7 @@ export interface GetProductsResponse { */ total_candidates?: number; /** - * Per-filter exclusion counts, keyed by the filter property name as it appears in the request's `filters` object (e.g., `required_metrics`, `required_vendor_metrics`, `required_geo_targeting`, `budget_range`). Values are objects carrying `count` and optional filter-specific detail. Only filters that actually narrowed the set need appear here; absence of a key means that filter did not exclude anything (or was not in the request). + * Per-filter exclusion counts, keyed by the filter property name as it appears in the request's `filters` object (e.g., `pricing_currencies`, `required_metrics`, `required_vendor_metrics`, `required_geo_targeting`, `budget_range`). Values are objects carrying `count` and optional filter-specific detail. Only filters that actually narrowed the set need appear here; absence of a key means that filter did not exclude anything (or was not in the request). */ excluded_by?: { [k: string]: @@ -5135,7 +5139,7 @@ export interface ProductAllowedAction { */ export interface SLAWindow { /** - * Maximum time from when the buyer issues the action to when the seller acknowledges receipt (mode-appropriate: synchronous response for self_serve, queue ack for requires_approval, proposal task creation for requires_proposal). ISO 8601 duration. + * Maximum time from when the buyer issues the action to when the seller acknowledges receipt (mode-appropriate: synchronous response for self_serve, tolerance decision for conditional_self_serve, or queue ack for requires_approval). ISO 8601 duration. * @pattern ^P(?!$)(\d+Y)?(\d+M)?(\d+D)?(T(\d+H)?(\d+M)?(\d+S)?)?$ */ response_max?: string; @@ -5744,11 +5748,11 @@ export interface MaterialDeadline { label?: string; } /** - * A proposed media plan with budget allocations across products. Represents the publisher's strategic recommendation for how to structure a campaign based on the brief. Proposals are actionable - buyers can execute them directly via create_media_buy by providing the proposal_id. + * A proposed media plan with budget allocations across products. Represents the publisher's strategic recommendation for how to structure a campaign based on the brief. Proposals are actionable: committed proposals can be executed directly via create_media_buy by providing the proposal_id; draft proposals must first be finalized via get_products refine action 'finalize'. */ export interface Proposal { /** - * Unique identifier for this proposal. Used to execute it via create_media_buy. + * Unique identifier for this proposal. Used to finalize a draft proposal and to execute a committed proposal via create_media_buy. * @maxLength 255 */ proposal_id: string; @@ -7482,7 +7486,7 @@ export interface CreateMediaBuyRequest { plan_id?: string; account: AccountReference; /** - * ID of a proposal from get_products to execute. When provided with total_budget, the publisher converts the proposal's allocation percentages into packages automatically. Alternative to providing packages array. + * ID of a committed proposal from get_products to execute. When provided with total_budget, the publisher converts the proposal's allocation percentages into packages automatically. Alternative to providing packages array. If the referenced proposal has proposal_status: 'draft', the seller MUST reject with PROPOSAL_NOT_COMMITTED; the buyer finalizes first via get_products refine action 'finalize'. */ proposal_id?: string; /** @@ -10412,7 +10416,7 @@ export interface GetMediaBuysResponseMediaBuy { updated_at?: string; context?: ContextObject; /** - * Flat-vocabulary actions the buyer can perform on this media buy in its current state. Eliminates the need for agents to internalize the state machine — the seller declares what is permitted right now. Deprecated in favor of `available_actions[]`, which carries `mode` (self_serve / conditional_self_serve / requires_proposal / requires_approval), optional SLA, and optional `terms_ref`. Sellers SHOULD populate both during the 3.x deprecation window; consumers MUST prefer `available_actions[]` when both are present. Removed in 4.0. + * Flat-vocabulary actions the buyer can perform on this media buy in its current state. Eliminates the need for agents to internalize the state machine — the seller declares what is permitted right now. Deprecated in favor of `available_actions[]`, which carries `mode` (self_serve / conditional_self_serve / requires_approval), optional SLA, and optional `terms_ref`. Sellers SHOULD populate both during the 3.x deprecation window; consumers MUST prefer `available_actions[]` when both are present. Removed in 4.0. */ valid_actions?: MediaBuyValidAction[]; /** @@ -15247,7 +15251,7 @@ export type GetSignalsRequest = { countries?: string[]; filters?: SignalFilters; /** - * Specific signal fields to include in the response, aligned with get_products.fields. Required identity and activation fields such as signal_ref or signal_id, signal_agent_segment_id, name, description, signal_type, coverage_percentage, and deployments are always included when required by the response schema. Use for progressive disclosure of rich signal-definition metadata: request fields such as taxonomy, data_sources, methodology, segmentation_criteria, criteria_url, refresh_cadence, lookback_window, onboarder, modeling, audience_expansion, device_expansion, countries, consent_basis, restricted_attributes, policy_categories, art9_basis, and data_subject_rights when the buyer needs them inline. Omit for the agent's default discovery projection. Agents SHOULD honor requested fields for exact lookup, refinement, and small custom-signal result sets when available. For broad discovery and wholesale pages, agents MAY return compact pointers instead of inlining large resources, especially when provider-published definitions can be resolved from signal_ref, taxonomy.ref, criteria_url, disclosure_url, and validators such as taxonomy.etag. + * Specific signal fields to include in the response, aligned with get_products.fields. Required identity and activation fields such as signal_ref or signal_id, signal_agent_segment_id, name, description, signal_type, coverage_percentage, and deployments are always included when required by the response schema. Use for progressive disclosure of rich signal-definition metadata: request fields such as taxonomy, data_sources, methodology, segmentation_criteria, criteria_url, refresh_cadence, lookback_window, onboarder, modeling, audience_expansion, device_expansion, countries, consent_basis, restricted_attributes, policy_categories, art9_basis, and data_subject_rights when the buyer needs them inline. Omit for the agent's default discovery projection. Agents SHOULD honor requested fields for exact lookup, refinement, small custom-signal result sets, and private/source-native signals when available. fields is a projection request, not an entitlement grant; agents MAY redact requested definition fields unless the caller is authorized for the underlying lineage, methodology, and rights-routing metadata. For broad discovery and wholesale pages, agents MAY return compact pointers instead of inlining large resources, especially when provider-published definitions can be resolved from signal_ref, taxonomy.ref, criteria_url, disclosure_url, and validators such as resolved URL plus catalog_etag, HTTP ETag/Last-Modified, or taxonomy.etag. */ fields?: ( | 'signal_ref' @@ -15371,6 +15375,20 @@ export interface SignalFilters { } // get_signals response +/** + * Personal data categories that may be restricted from use in audience targeting. Combines GDPR Article 9 special categories with US civil-rights protected classes (FHA familial_status, ADEA age). Used in two places: (1) on campaign plans via restricted_attributes to declare which categories are prohibited, and (2) on signal-definition.json via restricted_attributes to declare which categories a signal touches. Governance agents match plan restrictions against signal declarations for structural validation. + */ +export type RestrictedAttribute = + | 'racial_ethnic_origin' + | 'political_opinions' + | 'religious_beliefs' + | 'trade_union_membership' + | 'health_data' + | 'sex_life_sexual_orientation' + | 'genetic_data' + | 'biometric_data' + | 'age' + | 'familial_status'; /** * A signal deployment to a specific deployment target with activation status and key */ @@ -15485,6 +15503,126 @@ export interface GetSignalsResponse { * Array of matching signals */ signals?: { + /** + * Restricted attribute categories this signal touches. + */ + restricted_attributes?: RestrictedAttribute[]; + /** + * Policy categories this signal is sensitive for. + */ + policy_categories?: string[]; + /** + * Optional taxonomy metadata describing what this signal means in an external audience, content, retail-media, or provider-owned taxonomy. + */ + taxonomy?: { + ref: string; + version?: string; + segtax?: number; + etag?: string; + values: { + id: string; + path?: string; + modifiers?: string[]; + }[]; + value_mappings?: { + value: string; + taxonomy_value_id: string; + path?: string; + modifiers?: string[]; + }[]; + parent_match_behavior?: 'exact_only' | 'descendants_supported' | 'unknown'; + }; + segmentation_criteria?: string; + criteria_url?: string; + data_sources?: ( + | 'app_behavior' + | 'app_usage' + | 'web_usage' + | 'geo_location' + | 'email' + | 'tv_ott_or_stb_device' + | 'panel' + | 'online_ecommerce' + | 'credit_data' + | 'loyalty_card' + | 'transaction' + | 'online_survey' + | 'offline_survey' + | 'public_record_census' + | 'public_record_voter_file' + | 'public_record_other' + | 'offline_transaction' + )[]; + methodology?: 'observed' | 'declared' | 'derived' | 'inferred' | 'modeled'; + audience_expansion?: boolean; + device_expansion?: boolean; + refresh_cadence?: + | 'intra_day' + | 'daily' + | 'weekly' + | 'monthly' + | 'bi_monthly' + | 'quarterly' + | 'bi_annually' + | 'annually'; + lookback_window?: + | 'intra_day' + | 'daily' + | 'weekly' + | 'monthly' + | 'bi_monthly' + | 'quarterly' + | 'bi_annually' + | 'annually'; + onboarder?: { + match_keys: ( + | 'name' + | 'address' + | 'email' + | 'postal' + | 'lat_long' + | 'mobile_id' + | 'cookie_id' + | 'ip' + | 'customer_id' + | 'phone' + )[]; + pre_onboarding_audience_expansion?: boolean; + pre_onboarding_device_expansion?: boolean; + pre_onboarding_precision_level?: 'individual' | 'household' | 'business' | 'geography'; + }; + countries?: string[]; + consent_basis?: ConsentBasis[]; + art9_basis?: 'explicit_consent' | 'manifestly_made_public' | 'substantial_public_interest' | 'vital_interests'; + modeling?: { + method: 'lookalike' | 'supervised' | 'embedding' | 'rules'; + seed_source: { + type: 'first_party_crm' | 'panel' | 'declared_survey' | 'transactional' | 'behavioral'; + /** + * Provider assertion that the seed source carries a signed attestation. Consumers MUST NOT treat this boolean alone as cryptographic proof. + */ + provider_signed: boolean; + }; + training_data_jurisdictions: string[]; + ai_act_risk_class: 'minimal' | 'limited' | 'high_risk'; + disclosure?: SignalModelingDisclosure; + }; + /** + * Per-signal data-subject-rights routing. This is a contact/routing reference, not a machine-callable AdCP API. + */ + data_subject_rights?: { + upstream_source_domain?: string; + channels: { + rights: ('access' | 'rectification' | 'erasure' | 'portability' | 'objection')[]; + url?: string; + email?: string; + languages?: string[]; + countries?: string[]; + }[]; + response_sla_days?: number; + ccpa_opt_out_url?: string; + }; + dts_compliant_version?: string; signal_ref?: SignalRef; signal_id?: SignalID; /** @@ -15591,6 +15729,50 @@ export interface GetSignalsResponse { sandbox?: boolean; ext?: ExtensionObject; } +/** + * Disclosure requirements and jurisdictional notes for modeled data signals. This schema is intentionally separate from core/provenance.json because creative provenance is about generated content, render guidance, and asset-level chain of custody, while signal modeling disclosure is about data-segment methodology and data-use transparency. + */ +export interface SignalModelingDisclosure { + /** + * The provider's claim that a modeling or AI-use disclosure is required for this signal in at least one applicable jurisdiction. This is a declared compliance signal, not a protocol-level legal determination. + */ + required: boolean; + /** + * Jurisdictions where a modeling or AI-use disclosure applies. + */ + jurisdictions?: { + /** + * ISO 3166-1 alpha-2 country code. + * @pattern ^[A-Z]{2}$ + */ + country: string; + /** + * Provider-defined sub-national region code or name when the obligation is regional. No global canonical format is implied. + */ + region?: string; + /** + * Provider-supplied regulation identifier for the disclosure obligation. + */ + regulation: string; + /** + * Human-readable disclosure text or summary the provider expects buyers or reviewers to see. + */ + disclosure_text?: string; + /** + * Optional URL to the provider's canonical disclosure or methodology page for this jurisdiction. + */ + disclosure_url?: string; + /** + * Primary audience for this disclosure entry. + */ + audience?: 'buyer' | 'data_subject' | 'regulator' | 'public'; + }[]; + /** + * Optional provider notes on how the disclosure should be interpreted. Informational only; buyers should not branch programmatically on this text. + * @maxLength 2000 + */ + notes?: string; +} /** * Optional forecast-shaped signal availability guidance. When present, this is authoritative for signal-level discovery coverage. Use this to disclose the denominator, bucket semantics, not-present bucket, aggregate present bucket, and per-value coverage distribution for the signal. */ @@ -18825,20 +19007,6 @@ export interface CreativeAuditObservation { } // sync_plans parameters -/** - * Personal data categories that may be restricted from use in audience targeting. Combines GDPR Article 9 special categories with US civil-rights protected classes (FHA familial_status, ADEA age). Used in two places: (1) on campaign plans via restricted_attributes to declare which categories are prohibited, and (2) on signal-definition.json via restricted_attributes to declare which categories a signal touches. Governance agents match plan restrictions against signal declarations for structural validation. - */ -export type RestrictedAttribute = - | 'racial_ethnic_origin' - | 'political_opinions' - | 'religious_beliefs' - | 'trade_union_membership' - | 'health_data' - | 'sex_life_sexual_orientation' - | 'genetic_data' - | 'biometric_data' - | 'age' - | 'familial_status'; /** * Authority level granted to this agent. */ @@ -21001,7 +21169,7 @@ export interface GetAdCPCapabilitiesResponse { idempotency: IdempotencySupported | IdempotencyUnsupported; }; /** - * AdCP protocols this agent supports. Each value both (a) declares which tools the agent implements and (b) commits the agent to pass the baseline compliance storyboard at /compliance/{version}/protocols/{protocol}/ (with snake_case → kebab-case path mapping, e.g. media_buy → /compliance/.../protocols/media-buy/). The `measurement` protocol is in development — currently scoped to `get_adcp_capabilities` for catalog discovery; additional measurement tasks (reporting, attribution, etc.) and a baseline storyboard land in subsequent minors. Compliance testing support is declared separately via the `compliance_testing` capability block (below), not as a protocol claim. + * AdCP protocols this agent supports. Stable values both (a) declare which tools the agent implements and (b) commit the agent to pass the baseline compliance storyboard at /compliance/{version}/protocols/{protocol}/ (with snake_case → kebab-case path mapping, e.g. media_buy → /compliance/.../protocols/media-buy/). The `measurement` protocol is experimental in 3.1 and currently scoped to `get_adcp_capabilities` catalog discovery; agents implementing it MUST also list `measurement.core` in `experimental_features`. Additional measurement tasks (reporting, attribution, etc.) and a baseline storyboard land in subsequent minors. Compliance testing support is declared separately via the `compliance_testing` capability block (below), not as a protocol claim. */ supported_protocols: ( | 'media_buy' @@ -21050,7 +21218,7 @@ export interface GetAdCPCapabilitiesResponse { */ supported_pricing_models?: PricingModel[]; /** - * Buying modes this seller supports on get_products. 'brief' (semantic discovery driven by the brief) is universally supported and implicit. 'wholesale' (raw wholesale product feed enumeration — caller omits brief and the seller returns the full priced product feed, paginated) is opt-in and SHOULD be declared explicitly so buyers can probe before issuing wholesale calls. 'refine' (iterate on prior products/proposals) is implicit when the seller declares supports_proposals or otherwise honors the refine array. Sellers MAY declare ['brief', 'wholesale'] to signal wholesale support; absent declaration is treated as ['brief'] for wholesale-feed probing purposes and sellers MAY return INVALID_REQUEST for wholesale calls they do not support. Symmetric with signals.discovery_modes. + * Buying modes this seller supports on get_products. 'brief' (semantic discovery driven by the brief) is universally supported and implicit. 'wholesale' (raw wholesale product feed enumeration — caller omits brief and the seller returns the full priced product feed, paginated) is opt-in and SHOULD be declared explicitly so buyers can probe before issuing wholesale calls. 'refine' lets buyers iterate on prior products/proposals and is also the vehicle for finalizing draft proposals when the seller returns them. Sellers MAY declare ['brief', 'wholesale'] to signal wholesale support; absent declaration is treated as ['brief'] for wholesale-feed probing purposes and sellers MAY return INVALID_REQUEST for wholesale calls they do not support. Symmetric with signals.discovery_modes. */ buying_modes?: ('brief' | 'wholesale' | 'refine')[]; /** @@ -21062,7 +21230,7 @@ export interface GetAdCPCapabilitiesResponse { */ offline_delivery_protocols?: CloudStorageProtocol[]; /** - * Whether this seller commits to the proposal lifecycle on get_products: when called with buying_mode: 'brief' the seller will return at least one entry in proposals[]; when called with buying_mode: 'refine' + action: 'finalize' the seller will transition a proposal from draft to committed. A declaration of true is a commitment the seller will be graded against, not just a feature flag — sellers that decline a brief on policy grounds still owe a structured proposal-shaped rejection rather than an empty proposals[]. Most guaranteed-deal sellers (premium pubs, broadcast, CTV) declare true; auction-based PG, retail SKU, and quoted-rate direct-buy flows declare false. When false or absent, the seller serves products directly without proposal abstraction; conformance runners skip proposal-lifecycle storyboards. + * Conformance declaration that this seller supports the full proposal lifecycle on get_products: returned proposals are actionable, draft proposals can be finalized with buying_mode: 'refine' + action: 'finalize', and committed proposals can be executed via create_media_buy with proposal_id before expires_at. Buyers SHOULD NOT use this field to decide whether a specific returned proposal is executable; proposal_status is the per-proposal source of truth. A declaration of true opts the seller into proposal-lifecycle grading. When false or absent, conformance runners skip proposal-lifecycle storyboards, but buyers should still honor any proposals the seller actually returns. */ supports_proposals?: boolean; /** @@ -21704,7 +21872,7 @@ export interface GetAdCPCapabilitiesResponse { }; }; /** - * Measurement capability block. Presence indicates this agent computes one or more quantitative metrics about ad delivery, exposure, or effect, and is willing to be discovered as a measurement vendor. Returns metric definitions (this surface), not pricing/coverage (negotiated via `measurement_terms` on `create_media_buy`) or live values (returned per buy via `vendor_metric_values`). Modeled as a capability block (like `compliance_testing` and `webhook_signing`) rather than a `supported_protocols` value because measurement agents have one surface — this catalog — not a tool-set with mandatory tasks. AAO crawls each measurement agent's `metrics[]` on a TTL to populate the federated cross-vendor index. Same self-describing pattern as `governance.property_features[]`: agents own the catalog; the registry aggregates. + * Experimental measurement capability block. Presence indicates this agent computes one or more quantitative metrics about ad delivery, exposure, or effect, and is willing to be discovered as a measurement vendor. Agents implementing this block MUST list `measurement.core` in experimental_features. Returns metric definitions (this surface), not pricing/coverage (negotiated via `measurement_terms` on `create_media_buy`) or live values (returned per buy via `vendor_metric_values`). AAO crawls each measurement agent's `metrics[]` on a TTL to populate the federated cross-vendor index. Same self-describing pattern as `governance.property_features[]`: agents own the catalog; the registry aggregates. */ measurement?: { /** diff --git a/src/lib/types/wellknown-schemas.generated.ts b/src/lib/types/wellknown-schemas.generated.ts index 1e60a640d..848ede895 100644 --- a/src/lib/types/wellknown-schemas.generated.ts +++ b/src/lib/types/wellknown-schemas.generated.ts @@ -1,5 +1,5 @@ // Generated Zod schemas for AdCP well-known files (brand.json, adagents.json) -// Generated at: 2026-05-29T21:50:57.206Z +// Generated at: 2026-05-30T17:06:34.934Z // Source: schemas/cache/latest/*.json → json-schema-to-zod // // DO NOT EDIT — regenerate with: npm run generate-wellknown-schemas @@ -26,7 +26,7 @@ export const AdagentsJsonSchema = z.union([z.object({ "$schema": z.string().desc if (!result.success) { result.error.issues.forEach((error: any) => ctx.addIssue(error)) } -}), z.intersection(z.any().refine((value) => !z.intersection(z.any(), z.any()).safeParse(value).success, "Invalid input: Should NOT be valid against schema"), z.any().refine((value) => !z.any().safeParse(value).success, "Invalid input: Should NOT be valid against schema")))).describe("Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical's parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product's `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product's declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1's named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say 'this format is the only one that works' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: \"image\"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or a duration range, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration's accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside it.\n\n**Custom format_kind** (`format_kind: \"custom\"`): for adopter-defined shapes that don't fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue."), z.object({ "applies_to_property_ids": z.array(z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.")).describe("Optional property IDs from this file's `properties[]` that this format declaration applies to. When omitted, the declaration applies to all properties in the file. Mutually compatible with `applies_to_property_tags` (union is the effective scope). Example: Meta declares Reels with `applies_to_property_ids: [\"instagram\", \"facebook\"]` because WhatsApp doesn't carry Reels inventory.").optional(), "applies_to_property_tags": z.array(z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.")).describe("Optional property tags from this file's `tags` map that this format declaration applies to. When omitted, the declaration applies to all properties in the file. Useful for network-wide formats (e.g., a managed network declaring `applies_to_property_tags: [\"premium_video\"]`). Compatible with `applies_to_property_ids` — a property is in scope if it matches either ID list or tag list.").optional() }))).describe("Publisher-authoritative format catalog. Declares the 3.1+ canonical format-option shapes the publisher supports across its properties — the single place a publisher (or its community-registry stand-in) asserts \"these are the formats my inventory accepts.\" Products selling this publisher's inventory SHOULD reference these declarations by `format_option_id` on the placement or via inline `format_options` whose `format_option_id` matches an entry here, eliminating the N-copies-of-Meta-Reels-on-N-products drift surface.\n\nEach item is a `ProductFormatDeclaration` (same 3.1+ canonical format-option shape used on Products) plus optional `applies_to_property_ids` / `applies_to_property_tags` for property scoping within the file. A `formats[]` entry without scope applies to all properties in the file; with scope, only to the named subset (e.g., Reels applies to Instagram + Facebook but not WhatsApp).\n\n**Community registry pattern (normative for unadopted platforms).** When a platform hasn't adopted AdCP (Meta, TikTok, Snap, Pinterest, etc.), AAO publishes a community-maintained adagents.json at `https://creative.adcontextprotocol.org/translated//adagents.json` carrying that platform's `formats[]`. Buyer SDKs fetch the platform's own `/.well-known/adagents.json` first; on 404 or absence-of-formats[], they fall back to the AAO mirror. When the platform adopts AdCP and publishes their own adagents.json with `formats[]`, the platform-hosted file takes precedence and the mirror entry becomes redundant (AAO maintainers deprecate it).\n\nThe `v1_format_ref.agent_url` on each declaration SHOULD match the agent_url of the file's hosting location — platform-hosted formats point at the platform's agent, community-mirror formats point at `https://creative.adcontextprotocol.org/translated/`. This keeps the legacy named-format namespace converged regardless of which side hosts the catalog. See `docs/creative/canonical-formats.mdx` Meta Reels worked example.").optional(), "superseded_by": z.string().url().regex(new RegExp("^https://")).describe("Optional pointer indicating this adagents.json file has been superseded by another adagents.json at a different URL. Used by the AAO community-mirror lifecycle: when a platform (e.g., Meta) adopts AdCP and publishes its own adagents.json at `/.well-known/adagents.json`, the AAO mirror file at `creative.adcontextprotocol.org/translated//adagents.json` sets `superseded_by` to the platform-hosted URL. Buyer SDKs encountering a file with `superseded_by` SHOULD short-circuit and re-fetch from the named URL rather than serving stale content from the mirror. The mirror SHOULD continue serving with `superseded_by` set for ≥1 minor release after platform adoption so buyer caches keyed on the mirror URL get an explicit migration signal rather than a silent break.").optional(), "tags": z.record(z.string(), z.object({ "name": z.string().describe("Human-readable name for this tag"), "description": z.string().describe("Description of what this tag represents") }).catchall(z.any())).describe("Metadata for each tag referenced by properties. Provides human-readable context for property tag values.").optional(), "placement_tags": z.record(z.string(), z.object({ "name": z.string().describe("Human-readable name for this placement tag"), "description": z.string().describe("Description of what this placement tag represents") }).catchall(z.any())).describe("Metadata for each tag referenced by placements. Provides human-readable context for publisher-defined placement tag values used in grouping and authorization.").optional(), "authorized_agents": z.array(z.union([z.object({ "authorization_type": z.literal("property_ids").describe("Discriminator indicating authorization by specific property IDs"), "property_ids": z.array(z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.")).describe("Property IDs this agent is authorized for. Resolved against the top-level properties array in this file"), "collections": z.array(z.object({ "publisher_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain where the adagents.json declaring these collections is hosted (e.g., 'mrbeast.com'). The collections array in that file contains the authoritative collection definitions."), "collection_ids": z.array(z.string()).describe("Collection IDs from the adagents.json collections array. Each ID must match a collection_id declared in that file.") }).catchall(z.any()).describe("References collections declared in an adagents.json. Buyers resolve full collection objects by fetching the adagents.json at the given domain and matching collection_ids against its collections array.")).describe("Optional collection constraints. When present, authorization only applies to inventory associated with these collections.").optional(), "placement_ids": z.array(z.string()).describe("Optional placement constraints. When present, authorization only applies to these placement IDs from the top-level placements array in this file.").optional(), "placement_tags": z.array(z.string()).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Optional placement tag constraints. When present, authorization only applies to placements whose tags include any of these publisher-defined values.").optional(), "delegation_type": z.enum(["direct","delegated","ad_network"]).describe("Commercial relationship for this inventory path. 'direct' means the publisher treats this as a direct way to buy from them, even if a third party operates the software. 'delegated' means the agent is authorized to sell on the publisher's behalf. 'ad_network' means the inventory is sold as part of a network/package context rather than as the publisher's direct endpoint.").optional(), "exclusive": z.boolean().describe("Whether this agent is the publisher's sole authorized path for the scoped inventory slice. When false or absent, other authorized agents may also sell the same inventory.").optional(), "countries": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Optional ISO 3166-1 alpha-2 country codes limiting where this authorization applies. Omit for worldwide authorization.").optional(), "effective_from": z.string().datetime().describe("Optional start time for this authorization window.").optional(), "effective_until": z.string().datetime().describe("Optional end time for this authorization window.").optional() }).catchall(z.any()).and(z.object({ "url": z.string().url().describe("The authorized agent's API endpoint URL. Callers comparing this URL against a registry (sales-agent list, signal-provider registry, TMP provider lookup, etc.) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality — two URLs that differ only in case, default port, or percent-encoding of unreserved characters are the same agent. See docs/reference/url-canonicalization."), "authorized_for": z.string().min(1).max(500).describe("Human-readable description of what this agent is authorized to do — what it sells (for sales/property agents) or what data it provides (for signal agents). The variant the entry uses (`property_ids`, `signal_tags`, etc.) tells consumers which inflection applies; this field carries the operator-supplied label."), "signing_keys": z.array(z.object({ "kid": z.string().describe("Key identifier for selecting the correct signing key."), "kty": z.string().describe("JWK key type, such as 'OKP', 'EC', or 'RSA'."), "alg": z.string().describe("Expected signing algorithm for this key, such as 'EdDSA' or 'RS256'.").optional(), "use": z.string().describe("Optional JWK use value. Typically 'sig' for signing keys.").optional(), "crv": z.string().describe("Curve name for OKP or EC keys, such as 'Ed25519' or 'P-256'.").optional(), "x": z.string().describe("Base64url-encoded public key x coordinate or public key value for OKP keys.").optional(), "y": z.string().describe("Base64url-encoded public key y coordinate for EC keys.").optional(), "n": z.string().describe("Base64url-encoded RSA modulus.").optional(), "e": z.string().describe("Base64url-encoded RSA public exponent.").optional(), "revoked_at": z.string().datetime().describe("Optional revocation timestamp. When present, verifiers MUST reject any signature produced with this key whose signing epoch (or equivalent time reference) is at or after this timestamp. The key may continue to appear in the trust anchor during a grace period so caches that have not yet refreshed still find the key and can evaluate the revocation marker. Keys past their revocation can be removed once the cache TTL (recommended: 5 minutes) has elapsed across all verifiers.").optional() }).catchall(z.any()).describe("Publisher-attested public key material for an authorized agent. Buyers use these keys to verify signed agent responses against the trust anchor published in adagents.json rather than trusting key discovery from the agent domain alone.")).describe("Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.").optional(), "encryption_keys": z.array(z.object({ "kid": z.string().max(8).describe("Key identifier. Opaque — MUST NOT encode geographic or deployment information."), "kty": z.literal("OKP").describe("JWK key type. Must be OKP for X25519."), "crv": z.literal("X25519").describe("Curve name. Must be X25519 for TMPX encryption."), "use": z.literal("enc").describe("JWK use value. Must be enc for encryption keys."), "x": z.string().describe("Base64url-encoded X25519 public key (32 bytes).") }).strict().describe("X25519 public key for HPKE encryption. Used for TMPX exposure token encryption with HPKE mode_base.")).describe("X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.").optional(), "last_updated": z.string().datetime().describe("Optional ISO 8601 timestamp indicating when this `authorized_agents[]` entry last changed. Independent of the file-level `last_updated`. Lets validators perform a partial walk by skipping entries whose `last_updated` is older than their indexed value. Advisory — consumers MAY ignore and re-index the full file.").optional() }).describe("Fields shared by every variant of `authorized_agents[*]` in adagents.json, regardless of authorization_type. Variants `allOf` this base and add their discriminator-specific fields. Centralized to prevent drift across all authorization-type variants.")), z.object({ "authorization_type": z.literal("property_tags").describe("Discriminator indicating authorization by property tags"), "property_tags": z.array(z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.")).describe("Tags identifying which properties this agent is authorized for. Resolved against the top-level properties array in this file using tag matching"), "collections": z.array(z.object({ "publisher_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain where the adagents.json declaring these collections is hosted (e.g., 'mrbeast.com'). The collections array in that file contains the authoritative collection definitions."), "collection_ids": z.array(z.string()).describe("Collection IDs from the adagents.json collections array. Each ID must match a collection_id declared in that file.") }).catchall(z.any()).describe("References collections declared in an adagents.json. Buyers resolve full collection objects by fetching the adagents.json at the given domain and matching collection_ids against its collections array.")).describe("Optional collection constraints. When present, authorization only applies to inventory associated with these collections.").optional(), "placement_ids": z.array(z.string()).describe("Optional placement constraints. When present, authorization only applies to these placement IDs from the top-level placements array in this file.").optional(), "placement_tags": z.array(z.string()).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Optional placement tag constraints. When present, authorization only applies to placements whose tags include any of these publisher-defined values.").optional(), "delegation_type": z.enum(["direct","delegated","ad_network"]).describe("Commercial relationship for this inventory path. 'direct' means the publisher treats this as a direct way to buy from them, even if a third party operates the software. 'delegated' means the agent is authorized to sell on the publisher's behalf. 'ad_network' means the inventory is sold as part of a network/package context rather than as the publisher's direct endpoint.").optional(), "exclusive": z.boolean().describe("Whether this agent is the publisher's sole authorized path for the scoped inventory slice. When false or absent, other authorized agents may also sell the same inventory.").optional(), "countries": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Optional ISO 3166-1 alpha-2 country codes limiting where this authorization applies. Omit for worldwide authorization.").optional(), "effective_from": z.string().datetime().describe("Optional start time for this authorization window.").optional(), "effective_until": z.string().datetime().describe("Optional end time for this authorization window.").optional() }).catchall(z.any()).and(z.object({ "url": z.string().url().describe("The authorized agent's API endpoint URL. Callers comparing this URL against a registry (sales-agent list, signal-provider registry, TMP provider lookup, etc.) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality — two URLs that differ only in case, default port, or percent-encoding of unreserved characters are the same agent. See docs/reference/url-canonicalization."), "authorized_for": z.string().min(1).max(500).describe("Human-readable description of what this agent is authorized to do — what it sells (for sales/property agents) or what data it provides (for signal agents). The variant the entry uses (`property_ids`, `signal_tags`, etc.) tells consumers which inflection applies; this field carries the operator-supplied label."), "signing_keys": z.array(z.object({ "kid": z.string().describe("Key identifier for selecting the correct signing key."), "kty": z.string().describe("JWK key type, such as 'OKP', 'EC', or 'RSA'."), "alg": z.string().describe("Expected signing algorithm for this key, such as 'EdDSA' or 'RS256'.").optional(), "use": z.string().describe("Optional JWK use value. Typically 'sig' for signing keys.").optional(), "crv": z.string().describe("Curve name for OKP or EC keys, such as 'Ed25519' or 'P-256'.").optional(), "x": z.string().describe("Base64url-encoded public key x coordinate or public key value for OKP keys.").optional(), "y": z.string().describe("Base64url-encoded public key y coordinate for EC keys.").optional(), "n": z.string().describe("Base64url-encoded RSA modulus.").optional(), "e": z.string().describe("Base64url-encoded RSA public exponent.").optional(), "revoked_at": z.string().datetime().describe("Optional revocation timestamp. When present, verifiers MUST reject any signature produced with this key whose signing epoch (or equivalent time reference) is at or after this timestamp. The key may continue to appear in the trust anchor during a grace period so caches that have not yet refreshed still find the key and can evaluate the revocation marker. Keys past their revocation can be removed once the cache TTL (recommended: 5 minutes) has elapsed across all verifiers.").optional() }).catchall(z.any()).describe("Publisher-attested public key material for an authorized agent. Buyers use these keys to verify signed agent responses against the trust anchor published in adagents.json rather than trusting key discovery from the agent domain alone.")).describe("Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.").optional(), "encryption_keys": z.array(z.object({ "kid": z.string().max(8).describe("Key identifier. Opaque — MUST NOT encode geographic or deployment information."), "kty": z.literal("OKP").describe("JWK key type. Must be OKP for X25519."), "crv": z.literal("X25519").describe("Curve name. Must be X25519 for TMPX encryption."), "use": z.literal("enc").describe("JWK use value. Must be enc for encryption keys."), "x": z.string().describe("Base64url-encoded X25519 public key (32 bytes).") }).strict().describe("X25519 public key for HPKE encryption. Used for TMPX exposure token encryption with HPKE mode_base.")).describe("X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.").optional(), "last_updated": z.string().datetime().describe("Optional ISO 8601 timestamp indicating when this `authorized_agents[]` entry last changed. Independent of the file-level `last_updated`. Lets validators perform a partial walk by skipping entries whose `last_updated` is older than their indexed value. Advisory — consumers MAY ignore and re-index the full file.").optional() }).describe("Fields shared by every variant of `authorized_agents[*]` in adagents.json, regardless of authorization_type. Variants `allOf` this base and add their discriminator-specific fields. Centralized to prevent drift across all authorization-type variants.")), z.object({ "authorization_type": z.literal("inline_properties").describe("Discriminator indicating authorization by inline property definitions. Companion field is `properties` (not `inline_properties`) — the only authorization_type whose companion field name does not mirror the discriminator value."), "properties": z.array(z.object({ "property_id": z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Unique identifier for this property (optional). Enables referencing properties by ID instead of repeating full objects.").optional(), "property_type": z.enum(["website","mobile_app","ctv_app","desktop_app","dooh","podcast","radio","linear_tv","streaming_audio","ai_assistant"]).describe("Type of advertising property"), "name": z.string().describe("Human-readable property name"), "identifiers": z.array(z.object({ "type": z.enum(["domain","subdomain","network_id","ios_bundle","android_package","apple_app_store_id","google_play_id","roku_store_id","fire_tv_asin","samsung_app_id","apple_tv_bundle","bundle_id","venue_id","screen_id","openooh_venue_type","rss_url","apple_podcast_id","spotify_collection_id","podcast_guid","station_id","facility_id"]).describe("Type of identifier for this property"), "value": z.string().describe("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") }).catchall(z.any())).describe("Array of identifiers for this property"), "tags": z.array(z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Tags for categorization and grouping (e.g., network membership, content categories)").optional(), "supported_channels": z.array(z.enum(["display","olv","social","search","ctv","linear_tv","radio","streaming_audio","podcast","dooh","ooh","print","cinema","email","gaming","retail_media","influencer","affiliate","product_placement","sponsored_intelligence"]).describe("Standardized advertising media channels describing how buyers allocate budget. Channels are planning abstractions, not technical substrates. See the Media Channel Taxonomy specification for detailed definitions.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Advertising channels this property supports (e.g., ['display', 'olv', 'social']). Publishers declare which channels their inventory aligns with. Properties may support multiple channels. See the Media Channel Taxonomy for definitions.").optional(), "publisher_domain": z.string().describe("Domain where adagents.json should be checked for authorization validation. Optional in adagents.json (file location implies domain).").optional() }).catchall(z.any()).describe("An advertising property that can be validated via adagents.json")).describe("Specific properties this agent is authorized for, defined inline on the agent entry (alternative to property_ids/property_tags). Note: this is the companion field for `authorization_type: \"inline_properties\"` — the field is named `properties`, not `inline_properties`."), "collections": z.array(z.object({ "publisher_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain where the adagents.json declaring these collections is hosted (e.g., 'mrbeast.com'). The collections array in that file contains the authoritative collection definitions."), "collection_ids": z.array(z.string()).describe("Collection IDs from the adagents.json collections array. Each ID must match a collection_id declared in that file.") }).catchall(z.any()).describe("References collections declared in an adagents.json. Buyers resolve full collection objects by fetching the adagents.json at the given domain and matching collection_ids against its collections array.")).describe("Optional collection constraints. When present, authorization only applies to inventory associated with these collections.").optional(), "placement_ids": z.array(z.string()).describe("Optional placement constraints. When present, authorization only applies to these placement IDs from the top-level placements array in this file.").optional(), "placement_tags": z.array(z.string()).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Optional placement tag constraints. When present, authorization only applies to placements whose tags include any of these publisher-defined values.").optional(), "delegation_type": z.enum(["direct","delegated","ad_network"]).describe("Commercial relationship for this inventory path. 'direct' means the publisher treats this as a direct way to buy from them, even if a third party operates the software. 'delegated' means the agent is authorized to sell on the publisher's behalf. 'ad_network' means the inventory is sold as part of a network/package context rather than as the publisher's direct endpoint.").optional(), "exclusive": z.boolean().describe("Whether this agent is the publisher's sole authorized path for the scoped inventory slice. When false or absent, other authorized agents may also sell the same inventory.").optional(), "countries": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Optional ISO 3166-1 alpha-2 country codes limiting where this authorization applies. Omit for worldwide authorization.").optional(), "effective_from": z.string().datetime().describe("Optional start time for this authorization window.").optional(), "effective_until": z.string().datetime().describe("Optional end time for this authorization window.").optional() }).catchall(z.any()).and(z.object({ "url": z.string().url().describe("The authorized agent's API endpoint URL. Callers comparing this URL against a registry (sales-agent list, signal-provider registry, TMP provider lookup, etc.) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality — two URLs that differ only in case, default port, or percent-encoding of unreserved characters are the same agent. See docs/reference/url-canonicalization."), "authorized_for": z.string().min(1).max(500).describe("Human-readable description of what this agent is authorized to do — what it sells (for sales/property agents) or what data it provides (for signal agents). The variant the entry uses (`property_ids`, `signal_tags`, etc.) tells consumers which inflection applies; this field carries the operator-supplied label."), "signing_keys": z.array(z.object({ "kid": z.string().describe("Key identifier for selecting the correct signing key."), "kty": z.string().describe("JWK key type, such as 'OKP', 'EC', or 'RSA'."), "alg": z.string().describe("Expected signing algorithm for this key, such as 'EdDSA' or 'RS256'.").optional(), "use": z.string().describe("Optional JWK use value. Typically 'sig' for signing keys.").optional(), "crv": z.string().describe("Curve name for OKP or EC keys, such as 'Ed25519' or 'P-256'.").optional(), "x": z.string().describe("Base64url-encoded public key x coordinate or public key value for OKP keys.").optional(), "y": z.string().describe("Base64url-encoded public key y coordinate for EC keys.").optional(), "n": z.string().describe("Base64url-encoded RSA modulus.").optional(), "e": z.string().describe("Base64url-encoded RSA public exponent.").optional(), "revoked_at": z.string().datetime().describe("Optional revocation timestamp. When present, verifiers MUST reject any signature produced with this key whose signing epoch (or equivalent time reference) is at or after this timestamp. The key may continue to appear in the trust anchor during a grace period so caches that have not yet refreshed still find the key and can evaluate the revocation marker. Keys past their revocation can be removed once the cache TTL (recommended: 5 minutes) has elapsed across all verifiers.").optional() }).catchall(z.any()).describe("Publisher-attested public key material for an authorized agent. Buyers use these keys to verify signed agent responses against the trust anchor published in adagents.json rather than trusting key discovery from the agent domain alone.")).describe("Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.").optional(), "encryption_keys": z.array(z.object({ "kid": z.string().max(8).describe("Key identifier. Opaque — MUST NOT encode geographic or deployment information."), "kty": z.literal("OKP").describe("JWK key type. Must be OKP for X25519."), "crv": z.literal("X25519").describe("Curve name. Must be X25519 for TMPX encryption."), "use": z.literal("enc").describe("JWK use value. Must be enc for encryption keys."), "x": z.string().describe("Base64url-encoded X25519 public key (32 bytes).") }).strict().describe("X25519 public key for HPKE encryption. Used for TMPX exposure token encryption with HPKE mode_base.")).describe("X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.").optional(), "last_updated": z.string().datetime().describe("Optional ISO 8601 timestamp indicating when this `authorized_agents[]` entry last changed. Independent of the file-level `last_updated`. Lets validators perform a partial walk by skipping entries whose `last_updated` is older than their indexed value. Advisory — consumers MAY ignore and re-index the full file.").optional() }).describe("Fields shared by every variant of `authorized_agents[*]` in adagents.json, regardless of authorization_type. Variants `allOf` this base and add their discriminator-specific fields. Centralized to prevent drift across all authorization-type variants.")), z.object({ "authorization_type": z.literal("publisher_properties").describe("Discriminator indicating authorization for properties from other publisher domains"), "publisher_properties": z.array(z.union([z.object({ "publisher_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.").optional(), "publisher_domains": z.array(z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$"))).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Compact form for fanning the same selector across many publishers (e.g., a managed network listing every publisher it represents). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.").optional(), "selection_type": z.literal("all").describe("Discriminator indicating all properties from each addressed publisher are included") }).catchall(z.any()).and(z.intersection(z.any().refine((value) => !z.any().safeParse(value).success, "Invalid input: Should NOT be valid against schema"), z.union([z.any(), z.any()]))).describe("Select all properties from one publisher domain, or from each publisher domain when `publisher_domains` is used. Consumers MAY satisfy the selector from the parent file's top-level `properties[]` when those properties carry a `publisher_domain` matching one of the listed domains (see Resolution paths in the spec)."), z.object({ "publisher_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain where publisher's adagents.json is hosted (e.g., 'cnn.com')."), "selection_type": z.literal("by_id").describe("Discriminator indicating selection by specific property IDs"), "property_ids": z.array(z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.")).describe("Specific property IDs from the publisher's adagents.json") }).catchall(z.any()).describe("Select specific properties by ID. Single-publisher only — property IDs are publisher-scoped, so the compact `publisher_domains[]` form is intentionally NOT available for this selector. Use multiple `publisher_properties[]` entries (one per publisher) when each publisher's ID set differs."), z.object({ "publisher_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.").optional(), "publisher_domains": z.array(z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$"))).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Compact form for fanning the same tag predicate across many publishers (canonical managed-network shape). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.").optional(), "selection_type": z.literal("by_tag").describe("Discriminator indicating selection by property tags"), "property_tags": z.array(z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.")).describe("Property tags resolved against each addressed publisher's adagents.json, OR against the parent file's top-level `properties[]` when those properties carry a `publisher_domain` matching the selector. Selector covers all properties carrying any of these tags.") }).catchall(z.any()).and(z.intersection(z.any().refine((value) => !z.any().safeParse(value).success, "Invalid input: Should NOT be valid against schema"), z.union([z.any(), z.any()]))).describe("Select properties by tag membership. With `publisher_domains`, the same `property_tags` predicate is resolved against each listed publisher's adagents.json — the common managed-network case where every represented site tags inventory with a shared label. Consumers MAY also satisfy the predicate from the parent file's top-level `properties[]` when those properties carry a `publisher_domain` matching one of the selector's `publisher_domains[]` (see Resolution paths in the spec).")]).describe("Selects properties from a publisher's adagents.json. Used for both product definitions and agent authorization. Supports three selection patterns: all properties, specific IDs, or by tags. Each selector targets one publisher via `publisher_domain` (string) or a fan-out across many publishers that share the same selector via `publisher_domains` (array). Exactly one of `publisher_domain` or `publisher_domains` MUST be present. When `publisher_domains` is used, the selector is logically equivalent to repeating the same entry once per listed domain.")).describe("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"), "collections": z.array(z.object({ "publisher_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain where the adagents.json declaring these collections is hosted (e.g., 'mrbeast.com'). The collections array in that file contains the authoritative collection definitions."), "collection_ids": z.array(z.string()).describe("Collection IDs from the adagents.json collections array. Each ID must match a collection_id declared in that file.") }).catchall(z.any()).describe("References collections declared in an adagents.json. Buyers resolve full collection objects by fetching the adagents.json at the given domain and matching collection_ids against its collections array.")).describe("Optional collection constraints. When present, authorization only applies to inventory associated with these collections.").optional(), "placement_ids": z.array(z.string()).describe("Optional placement constraints. When present, authorization only applies to these placement IDs from the top-level placements array in this file.").optional(), "placement_tags": z.array(z.string()).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Optional placement tag constraints. When present, authorization only applies to placements whose tags include any of these publisher-defined values.").optional(), "delegation_type": z.enum(["direct","delegated","ad_network"]).describe("Commercial relationship for this inventory path. 'direct' means the publisher treats this as a direct way to buy from them, even if a third party operates the software. 'delegated' means the agent is authorized to sell on the publisher's behalf. 'ad_network' means the inventory is sold as part of a network/package context rather than as the publisher's direct endpoint.").optional(), "exclusive": z.boolean().describe("Whether this agent is the publisher's sole authorized path for the scoped inventory slice. When false or absent, other authorized agents may also sell the same inventory.").optional(), "countries": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Optional ISO 3166-1 alpha-2 country codes limiting where this authorization applies. Omit for worldwide authorization.").optional(), "effective_from": z.string().datetime().describe("Optional start time for this authorization window.").optional(), "effective_until": z.string().datetime().describe("Optional end time for this authorization window.").optional() }).catchall(z.any()).and(z.object({ "url": z.string().url().describe("The authorized agent's API endpoint URL. Callers comparing this URL against a registry (sales-agent list, signal-provider registry, TMP provider lookup, etc.) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality — two URLs that differ only in case, default port, or percent-encoding of unreserved characters are the same agent. See docs/reference/url-canonicalization."), "authorized_for": z.string().min(1).max(500).describe("Human-readable description of what this agent is authorized to do — what it sells (for sales/property agents) or what data it provides (for signal agents). The variant the entry uses (`property_ids`, `signal_tags`, etc.) tells consumers which inflection applies; this field carries the operator-supplied label."), "signing_keys": z.array(z.object({ "kid": z.string().describe("Key identifier for selecting the correct signing key."), "kty": z.string().describe("JWK key type, such as 'OKP', 'EC', or 'RSA'."), "alg": z.string().describe("Expected signing algorithm for this key, such as 'EdDSA' or 'RS256'.").optional(), "use": z.string().describe("Optional JWK use value. Typically 'sig' for signing keys.").optional(), "crv": z.string().describe("Curve name for OKP or EC keys, such as 'Ed25519' or 'P-256'.").optional(), "x": z.string().describe("Base64url-encoded public key x coordinate or public key value for OKP keys.").optional(), "y": z.string().describe("Base64url-encoded public key y coordinate for EC keys.").optional(), "n": z.string().describe("Base64url-encoded RSA modulus.").optional(), "e": z.string().describe("Base64url-encoded RSA public exponent.").optional(), "revoked_at": z.string().datetime().describe("Optional revocation timestamp. When present, verifiers MUST reject any signature produced with this key whose signing epoch (or equivalent time reference) is at or after this timestamp. The key may continue to appear in the trust anchor during a grace period so caches that have not yet refreshed still find the key and can evaluate the revocation marker. Keys past their revocation can be removed once the cache TTL (recommended: 5 minutes) has elapsed across all verifiers.").optional() }).catchall(z.any()).describe("Publisher-attested public key material for an authorized agent. Buyers use these keys to verify signed agent responses against the trust anchor published in adagents.json rather than trusting key discovery from the agent domain alone.")).describe("Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.").optional(), "encryption_keys": z.array(z.object({ "kid": z.string().max(8).describe("Key identifier. Opaque — MUST NOT encode geographic or deployment information."), "kty": z.literal("OKP").describe("JWK key type. Must be OKP for X25519."), "crv": z.literal("X25519").describe("Curve name. Must be X25519 for TMPX encryption."), "use": z.literal("enc").describe("JWK use value. Must be enc for encryption keys."), "x": z.string().describe("Base64url-encoded X25519 public key (32 bytes).") }).strict().describe("X25519 public key for HPKE encryption. Used for TMPX exposure token encryption with HPKE mode_base.")).describe("X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.").optional(), "last_updated": z.string().datetime().describe("Optional ISO 8601 timestamp indicating when this `authorized_agents[]` entry last changed. Independent of the file-level `last_updated`. Lets validators perform a partial walk by skipping entries whose `last_updated` is older than their indexed value. Advisory — consumers MAY ignore and re-index the full file.").optional() }).describe("Fields shared by every variant of `authorized_agents[*]` in adagents.json, regardless of authorization_type. Variants `allOf` this base and add their discriminator-specific fields. Centralized to prevent drift across all authorization-type variants.")), z.object({ "authorization_type": z.literal("signal_ids").describe("Discriminator indicating authorization by specific signal IDs"), "signal_ids": z.array(z.string().regex(new RegExp("^[a-zA-Z0-9_-]+$"))).describe("Signal IDs this agent is authorized to resell. Resolved against the top-level signals array in this file") }).catchall(z.any()).and(z.object({ "url": z.string().url().describe("The authorized agent's API endpoint URL. Callers comparing this URL against a registry (sales-agent list, signal-provider registry, TMP provider lookup, etc.) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality — two URLs that differ only in case, default port, or percent-encoding of unreserved characters are the same agent. See docs/reference/url-canonicalization."), "authorized_for": z.string().min(1).max(500).describe("Human-readable description of what this agent is authorized to do — what it sells (for sales/property agents) or what data it provides (for signal agents). The variant the entry uses (`property_ids`, `signal_tags`, etc.) tells consumers which inflection applies; this field carries the operator-supplied label."), "signing_keys": z.array(z.object({ "kid": z.string().describe("Key identifier for selecting the correct signing key."), "kty": z.string().describe("JWK key type, such as 'OKP', 'EC', or 'RSA'."), "alg": z.string().describe("Expected signing algorithm for this key, such as 'EdDSA' or 'RS256'.").optional(), "use": z.string().describe("Optional JWK use value. Typically 'sig' for signing keys.").optional(), "crv": z.string().describe("Curve name for OKP or EC keys, such as 'Ed25519' or 'P-256'.").optional(), "x": z.string().describe("Base64url-encoded public key x coordinate or public key value for OKP keys.").optional(), "y": z.string().describe("Base64url-encoded public key y coordinate for EC keys.").optional(), "n": z.string().describe("Base64url-encoded RSA modulus.").optional(), "e": z.string().describe("Base64url-encoded RSA public exponent.").optional(), "revoked_at": z.string().datetime().describe("Optional revocation timestamp. When present, verifiers MUST reject any signature produced with this key whose signing epoch (or equivalent time reference) is at or after this timestamp. The key may continue to appear in the trust anchor during a grace period so caches that have not yet refreshed still find the key and can evaluate the revocation marker. Keys past their revocation can be removed once the cache TTL (recommended: 5 minutes) has elapsed across all verifiers.").optional() }).catchall(z.any()).describe("Publisher-attested public key material for an authorized agent. Buyers use these keys to verify signed agent responses against the trust anchor published in adagents.json rather than trusting key discovery from the agent domain alone.")).describe("Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.").optional(), "encryption_keys": z.array(z.object({ "kid": z.string().max(8).describe("Key identifier. Opaque — MUST NOT encode geographic or deployment information."), "kty": z.literal("OKP").describe("JWK key type. Must be OKP for X25519."), "crv": z.literal("X25519").describe("Curve name. Must be X25519 for TMPX encryption."), "use": z.literal("enc").describe("JWK use value. Must be enc for encryption keys."), "x": z.string().describe("Base64url-encoded X25519 public key (32 bytes).") }).strict().describe("X25519 public key for HPKE encryption. Used for TMPX exposure token encryption with HPKE mode_base.")).describe("X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.").optional(), "last_updated": z.string().datetime().describe("Optional ISO 8601 timestamp indicating when this `authorized_agents[]` entry last changed. Independent of the file-level `last_updated`. Lets validators perform a partial walk by skipping entries whose `last_updated` is older than their indexed value. Advisory — consumers MAY ignore and re-index the full file.").optional() }).describe("Fields shared by every variant of `authorized_agents[*]` in adagents.json, regardless of authorization_type. Variants `allOf` this base and add their discriminator-specific fields. Centralized to prevent drift across all authorization-type variants.")).describe("Authorization for signals by specific signal IDs"), z.object({ "authorization_type": z.literal("signal_tags").describe("Discriminator indicating authorization by signal tags"), "signal_tags": z.array(z.string().regex(new RegExp("^[a-z0-9_-]+$"))).describe("Signal tags this agent is authorized for. Agent can resell all signals with these tags") }).catchall(z.any()).and(z.object({ "url": z.string().url().describe("The authorized agent's API endpoint URL. Callers comparing this URL against a registry (sales-agent list, signal-provider registry, TMP provider lookup, etc.) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality — two URLs that differ only in case, default port, or percent-encoding of unreserved characters are the same agent. See docs/reference/url-canonicalization."), "authorized_for": z.string().min(1).max(500).describe("Human-readable description of what this agent is authorized to do — what it sells (for sales/property agents) or what data it provides (for signal agents). The variant the entry uses (`property_ids`, `signal_tags`, etc.) tells consumers which inflection applies; this field carries the operator-supplied label."), "signing_keys": z.array(z.object({ "kid": z.string().describe("Key identifier for selecting the correct signing key."), "kty": z.string().describe("JWK key type, such as 'OKP', 'EC', or 'RSA'."), "alg": z.string().describe("Expected signing algorithm for this key, such as 'EdDSA' or 'RS256'.").optional(), "use": z.string().describe("Optional JWK use value. Typically 'sig' for signing keys.").optional(), "crv": z.string().describe("Curve name for OKP or EC keys, such as 'Ed25519' or 'P-256'.").optional(), "x": z.string().describe("Base64url-encoded public key x coordinate or public key value for OKP keys.").optional(), "y": z.string().describe("Base64url-encoded public key y coordinate for EC keys.").optional(), "n": z.string().describe("Base64url-encoded RSA modulus.").optional(), "e": z.string().describe("Base64url-encoded RSA public exponent.").optional(), "revoked_at": z.string().datetime().describe("Optional revocation timestamp. When present, verifiers MUST reject any signature produced with this key whose signing epoch (or equivalent time reference) is at or after this timestamp. The key may continue to appear in the trust anchor during a grace period so caches that have not yet refreshed still find the key and can evaluate the revocation marker. Keys past their revocation can be removed once the cache TTL (recommended: 5 minutes) has elapsed across all verifiers.").optional() }).catchall(z.any()).describe("Publisher-attested public key material for an authorized agent. Buyers use these keys to verify signed agent responses against the trust anchor published in adagents.json rather than trusting key discovery from the agent domain alone.")).describe("Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.").optional(), "encryption_keys": z.array(z.object({ "kid": z.string().max(8).describe("Key identifier. Opaque — MUST NOT encode geographic or deployment information."), "kty": z.literal("OKP").describe("JWK key type. Must be OKP for X25519."), "crv": z.literal("X25519").describe("Curve name. Must be X25519 for TMPX encryption."), "use": z.literal("enc").describe("JWK use value. Must be enc for encryption keys."), "x": z.string().describe("Base64url-encoded X25519 public key (32 bytes).") }).strict().describe("X25519 public key for HPKE encryption. Used for TMPX exposure token encryption with HPKE mode_base.")).describe("X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.").optional(), "last_updated": z.string().datetime().describe("Optional ISO 8601 timestamp indicating when this `authorized_agents[]` entry last changed. Independent of the file-level `last_updated`. Lets validators perform a partial walk by skipping entries whose `last_updated` is older than their indexed value. Advisory — consumers MAY ignore and re-index the full file.").optional() }).describe("Fields shared by every variant of `authorized_agents[*]` in adagents.json, regardless of authorization_type. Variants `allOf` this base and add their discriminator-specific fields. Centralized to prevent drift across all authorization-type variants.")).describe("Authorization for signals by tag membership")])).describe("Array of sales agents authorized to make inventory from this file available to buyers. Authorization can be scoped to specific properties, collections, countries, and time windows, with optional delegation metadata indicating whether the path is direct, delegated, or network-mediated."), "last_updated": z.string().datetime().describe("ISO 8601 timestamp indicating when this file was last updated").optional(), "property_features": z.array(z.object({ "url": z.string().url().describe("The agent's API endpoint URL. Callers comparing this URL against a feature-provider registry MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality. See docs/reference/url-canonicalization."), "name": z.string().describe("Human-readable name of the vendor/agent (e.g., 'Scope3', 'TAG', 'OneTrust')"), "features": z.array(z.string()).describe("Feature IDs this agent provides (e.g., 'carbon_score', 'tag_certified_against_fraud'). Use get_adcp_capabilities on the agent for full definitions."), "publisher_id": z.string().describe("Optional publisher identifier at this agent (for lookup)").optional() }).catchall(z.any())).describe("[AdCP 3.0] Optional list of agents that provide property feature data (certifications, scores, compliance status). Used for discovery - actual data is accessed through property list filters.").optional(), "signals": z.array(z.object({ "id": z.string().regex(new RegExp("^[a-zA-Z0-9_-]+$")).describe("Signal identifier within the publishing domain's adagents.json signals[]"), "name": z.string().min(1).max(255).describe("Human-readable signal name"), "description": z.string().max(2000).describe("Detailed description of what this signal represents and how it's derived").optional(), "value_type": z.enum(["binary","categorical","numeric"]).describe("The data type of this signal's values"), "tags": z.array(z.string().regex(new RegExp("^[a-z0-9_-]+$"))).describe("Tags for grouping and filtering this domain's published signal definitions").optional(), "allowed_values": z.array(z.string()).describe("For categorical signals, the valid values users can be assigned").optional(), "restricted_attributes": z.array(z.enum(["racial_ethnic_origin","political_opinions","religious_beliefs","trade_union_membership","health_data","sex_life_sexual_orientation","genetic_data","biometric_data","age","familial_status"]).describe("Personal data categories that may be restricted from use in audience targeting. Combines GDPR Article 9 special categories with US civil-rights protected classes (FHA familial_status, ADEA age). Used in two places: (1) on campaign plans via restricted_attributes to declare which categories are prohibited, and (2) on signal-definition.json via restricted_attributes to declare which categories a signal touches. Governance agents match plan restrictions against signal declarations for structural validation.")).describe("Restricted attribute categories this signal touches. Data providers SHOULD declare these so governance agents can structurally match signals against a plan's restricted_attributes without relying on semantic inference from the signal name or description.").optional(), "policy_categories": z.array(z.string()).describe("Policy categories this signal is sensitive for (e.g., a children's interest signal declares ['children_directed']). Governance agents match these against a plan's policy_categories to flag sensitive data usage.").optional(), "range": z.object({ "min": z.number().describe("Minimum value"), "max": z.number().describe("Maximum value"), "unit": z.string().describe("Unit of measurement (e.g., 'score', 'dollars', 'years')").optional() }).strict().describe("For numeric signals, the valid value range").optional(), "taxonomy": z.object({ "ref": z.string().url().describe("URI identifying the taxonomy or taxonomy documentation."), "version": z.string().describe("Version identifier for the taxonomy when the taxonomy has versioned definitions.").optional(), "segtax": z.number().int().gte(1).describe("OpenRTB segtax code when the taxonomy maps to an OpenRTB segment taxonomy.").optional(), "etag": z.string().describe("Optional validator for custom taxonomy definitions so consumers can detect drift between signal publication and taxonomy resolution.").optional(), "values": z.array(z.object({ "id": z.string().min(1).describe("Taxonomy node identifier."), "path": z.string().describe("Optional human-readable or taxonomy-native path for display and review.").optional(), "modifiers": z.array(z.string()).describe("Optional taxonomy-specific modifiers that qualify the node.").optional() }).strict()).describe("Taxonomy node values that describe this signal. These are meaning/discovery metadata for the signal definition, not the values a buyer submits in package targeting expressions."), "value_mappings": z.array(z.object({ "value": z.string().describe("Categorical value from allowed_values[]."), "taxonomy_value_id": z.string().describe("Taxonomy node identifier corresponding to this categorical value."), "path": z.string().describe("Optional human-readable or taxonomy-native path for display and review.").optional(), "modifiers": z.array(z.string()).describe("Optional taxonomy-specific modifiers that qualify the mapped value.").optional() }).strict()).describe("For categorical signals, maps package-targeting allowed_values[] strings to stable taxonomy node identifiers. The allowed_values[] strings remain the wire values buyers submit; this mapping explains how those strings resolve into the published taxonomy. Each value_mappings[].value SHOULD match one of the signal's allowed_values[] entries; JSON Schema draft-07 cannot enforce this cross-array constraint.").optional(), "parent_match_behavior": z.enum(["exact_only","descendants_supported","unknown"]).describe("Whether this signal definition supports treating a parent taxonomy node as matching descendant nodes for discovery/filtering or seller-side expansion. 'exact_only' means only explicitly listed node ids match. 'descendants_supported' means the seller can expand known, version-pinned parent nodes to descendants internally, typically by ORing the children in its execution system. 'unknown' means the provider has not declared parent-node behavior. This field is metadata about discovery/translation behavior, not a package targeting operator.").optional() }).strict().describe("Optional taxonomy metadata describing what this signal means in an external audience, content, retail-media, or provider-owned taxonomy. Taxonomy metadata does not create a new value_type and does not change package targeting grammar: buyers still target the named signal according to value_type. When a taxonomy value is a parent node, parent/descendant expansion is seller behavior and must be declared through parent_match_behavior rather than assumed.").optional(), "segmentation_criteria": z.string().max(500).describe("Rules governing inclusion of identifiers in the segment. Aligns with IAB Data Transparency Standard audience criteria disclosure.").optional(), "criteria_url": z.string().url().describe("Optional URL to a longer-form methodology or criteria document. This is a disclosure pointer; buyers should not branch programmatically on the linked content.").optional(), "data_sources": z.array(z.enum(["app_behavior","app_usage","web_usage","geo_location","email","tv_ott_or_stb_device","panel","online_ecommerce","credit_data","loyalty_card","transaction","online_survey","offline_survey","public_record_census","public_record_voter_file","public_record_other","offline_transaction"])).describe("Origin categories of the raw data used to compile the signal, aligned with IAB Data Transparency Standard source disclosure. Use 'panel' for respondent-panel or JIC-style audience sources. Offline and public-record sources require onboarder disclosure. Co-viewing projection and reconciliation of seller claims against a JIC or measurement vendor belong in measurement reporting/vendor metrics rather than package signal targeting.").optional(), "methodology": z.enum(["observed","declared","derived","inferred","modeled"]).describe("How the signal's audience membership or attribute was determined. 'modeled' requires the modeling block.").optional(), "audience_expansion": z.boolean().describe("Whether look-alike or similar-audience expansion was used to include additional identifiers. When true, modeling is required.").optional(), "device_expansion": z.boolean().describe("Whether the signal was expanded deterministically across devices of the same user, household, or business. Probabilistic cross-device expansion is modeling and should use methodology 'modeled' or the modeling block.").optional(), "refresh_cadence": z.enum(["intra_day","daily","weekly","monthly","bi_monthly","quarterly","bi_annually","annually"]).describe("Cadence at which the signal definition's underlying segment membership is refreshed.").optional(), "lookback_window": z.enum(["intra_day","daily","weekly","monthly","bi_monthly","quarterly","bi_annually","annually"]).describe("Time window in which a qualifying event can occur for inclusion.").optional(), "onboarder": z.object({ "match_keys": z.array(z.enum(["name","address","email","postal","lat_long","mobile_id","cookie_id","ip","customer_id","phone"])), "pre_onboarding_audience_expansion": z.boolean().optional(), "pre_onboarding_device_expansion": z.boolean().optional(), "pre_onboarding_precision_level": z.enum(["individual","household","business","geography"]).optional() }).strict().describe("Onboarder disclosure. Required when data_sources includes an offline_* or public_record_* source.").optional(), "subject_type": z.enum(["individual","household","business","contextual","none"]).describe("What kind of subject this signal characterizes.").optional(), "resolution_method": z.enum(["deterministic_id","probabilistic_device","browser","geographic","content_signal","mixed"]).describe("How the subject is resolved at decision time.").optional(), "id_types": z.array(z.enum(["cookie","mobile_id","platform_id","user_enabled_id"])).describe("Identifier currencies analyzed to determine audience membership or attributes.").optional(), "audience_scope": z.enum(["single_domain","cross_domain_owned","cross_domain_unowned","offline"]).describe("Context within which the audience attribute was determined. 'single_domain' requires originating_domain.").optional(), "originating_domain": z.string().describe("Domain of the digital property where the audience originates. Required when audience_scope is 'single_domain'.").optional(), "countries": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).describe("ISO 3166-1 alpha-2 country codes where the signal is applicable. Sellers must not expose a signal for media buys in countries outside this list unless their own policy allows a narrower operational override.").optional(), "consent_basis": z.array(z.enum(["consent","legitimate_interest","contract","legal_obligation"]).describe("Common GDPR lawful bases relevant to advertising. Covers the Article 6(1) bases used in programmatic advertising contexts.")).describe("Declared GDPR Article 6 lawful basis or consent basis under which this signal's data is processed. For non-GDPR regimes, use countries, policy_categories, and disclosure fields to describe jurisdiction-specific obligations unless a future enum value applies.").optional(), "art9_basis": z.enum(["explicit_consent","manifestly_made_public","substantial_public_interest","vital_interests"]).describe("GDPR Article 9 basis when restricted_attributes is non-empty and the signal is used in jurisdictions where Article 9 applies. Required by policy for applicable use cases rather than universally required at schema level because sensitivity and lawful basis are jurisdiction-relative.").optional(), "modeling": z.object({ "method": z.enum(["lookalike","supervised","embedding","rules"]), "seed_source": z.object({ "type": z.enum(["first_party_crm","panel","declared_survey","transactional","behavioral"]), "provider_signed": z.boolean().describe("Whether the seed source carries a signed attestation under one of the provider's published signing keys.") }).strict(), "training_data_jurisdictions": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).describe("ISO 3166-1 alpha-2 country codes where the model's training data was collected."), "ai_act_risk_class": z.enum(["minimal","limited","high_risk"]).describe("EU AI Act risk classification self-declared by the provider. Prohibited-risk modeled signals must not be published as AdCP signal definitions."), "disclosure": z.object({ "required": z.boolean().describe("The provider's claim that a modeling or AI-use disclosure is required for this signal in at least one applicable jurisdiction. This is a declared compliance signal, not a protocol-level legal determination."), "jurisdictions": z.array(z.object({ "country": z.string().regex(new RegExp("^[A-Z]{2}$")).describe("ISO 3166-1 alpha-2 country code."), "region": z.string().describe("Provider-defined sub-national region code or name when the obligation is regional. No global canonical format is implied.").optional(), "regulation": z.string().describe("Provider-supplied regulation identifier for the disclosure obligation."), "disclosure_text": z.string().describe("Human-readable disclosure text or summary the provider expects buyers or reviewers to see.").optional(), "disclosure_url": z.string().url().describe("Optional URL to the provider's canonical disclosure or methodology page for this jurisdiction.").optional(), "audience": z.enum(["buyer","data_subject","regulator","public"]).describe("Primary audience for this disclosure entry.").optional() }).strict()).describe("Jurisdictions where a modeling or AI-use disclosure applies.").optional(), "notes": z.string().max(2000).describe("Optional provider notes on how the disclosure should be interpreted. Informational only; buyers should not branch programmatically on this text.").optional() }).strict().and(z.any()).describe("Signal/modeling-specific disclosure requirements and jurisdictional notes. This is not creative provenance render guidance.").optional() }).strict().describe("Modeling disclosure for modeled data signals. Required when methodology is 'modeled' or audience_expansion is true. This describes data modeling and intentionally does not reuse creative provenance, which is content/render oriented.").optional(), "data_subject_rights": z.object({ "upstream_source_domain": z.string().max(253).describe("Domain of the upstream data source whose rights process these channels reach, when different from the publishing domain.").optional(), "channels": z.array(z.object({ "rights": z.array(z.enum(["access","rectification","erasure","portability","objection"])).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Rights supported by this channel."), "url": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL for submitting the rights request.").optional(), "email": z.string().email().describe("Email address for submitting the rights request.").optional(), "languages": z.array(z.string()).describe("BCP 47 language tags supported by this channel.").optional(), "countries": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).describe("ISO 3166-1 alpha-2 countries this channel serves, when the provider routes rights by country.").optional() }).strict().and(z.union([z.any(), z.any()]))).describe("Rights request channels and the rights each channel supports."), "response_sla_days": z.number().int().gte(1).lte(90).describe("Maximum response time in days for the declared rights channels.").optional(), "gpc_honored": z.boolean().describe("Whether Global Privacy Control signals are honored as objection or opt-out requests for this signal.").optional(), "ccpa_opt_out_url": z.string().url().regex(new RegExp("^https://")).describe("US-specific 'Do Not Sell or Share' opt-out URL where required.").optional() }).strict().describe("Per-signal data-subject-rights routing. Inline on the signal because upstream source and rights routing can differ by segment even when the publishing domain is the same. This is a contact/routing reference, not a machine-callable AdCP API.").optional(), "dts_compliant_version": z.string().describe("IAB Data Transparency Standard version this signal definition self-attests as satisfying, when applicable.").optional() }).catchall(z.any()).and(z.intersection(z.intersection(z.any(), z.any()), z.intersection(z.any(), z.intersection(z.any(), z.any())))).describe("Definition of a signal in published adagents.json signals[]. The publishing domain supplies the namespace, so this definition carries a local id rather than a signal_ref. Media-buy products reference this definition with signal_ref scope 'data_provider', data_provider_domain set to the publishing domain, and signal_id set to this id.")).describe("Signal definitions published by this domain. Each entry defines a signal id within this file's publishing-domain namespace; entries do not include signal_ref objects. Signals Protocol discovery and media-buy product targeting reference these through signal_ref scope 'data_provider', with data_provider_domain set to this file's publishing domain and signal_id set to signals[].id.").optional(), "signal_tags": z.record(z.string(), z.object({ "name": z.string().describe("Human-readable name for this tag"), "description": z.string().describe("Description of what this tag represents") }).catchall(z.any())).describe("Metadata for each tag referenced by signals. Provides human-readable context for signal tag values.").optional() }).catchall(z.any()).describe("Inline structure variant - contains full agent authorization data")]).describe("Declaration of authorized agents for advertising inventory and data signals. Hosted at /.well-known/adagents.json on publisher domains (for properties) or data provider domains (for signals). Can either contain the full structure inline or reference an authoritative URL.") +}), z.intersection(z.any().refine((value) => !z.intersection(z.any(), z.any()).safeParse(value).success, "Invalid input: Should NOT be valid against schema"), z.any().refine((value) => !z.any().safeParse(value).success, "Invalid input: Should NOT be valid against schema")))).describe("Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical's parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product's `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product's declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1's named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say 'this format is the only one that works' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: \"image\"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or a duration range, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration's accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside it.\n\n**Custom format_kind** (`format_kind: \"custom\"`): for adopter-defined shapes that don't fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue."), z.object({ "applies_to_property_ids": z.array(z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.")).describe("Optional property IDs from this file's `properties[]` that this format declaration applies to. When omitted, the declaration applies to all properties in the file. Mutually compatible with `applies_to_property_tags` (union is the effective scope). Example: Meta declares Reels with `applies_to_property_ids: [\"instagram\", \"facebook\"]` because WhatsApp doesn't carry Reels inventory.").optional(), "applies_to_property_tags": z.array(z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.")).describe("Optional property tags from this file's `tags` map that this format declaration applies to. When omitted, the declaration applies to all properties in the file. Useful for network-wide formats (e.g., a managed network declaring `applies_to_property_tags: [\"premium_video\"]`). Compatible with `applies_to_property_ids` — a property is in scope if it matches either ID list or tag list.").optional() }))).describe("Publisher-authoritative format catalog. Declares the 3.1+ canonical format-option shapes the publisher supports across its properties — the single place a publisher (or its community-registry stand-in) asserts \"these are the formats my inventory accepts.\" Products selling this publisher's inventory SHOULD reference these declarations by `format_option_id` on the placement or via inline `format_options` whose `format_option_id` matches an entry here, eliminating the N-copies-of-Meta-Reels-on-N-products drift surface.\n\nEach item is a `ProductFormatDeclaration` (same 3.1+ canonical format-option shape used on Products) plus optional `applies_to_property_ids` / `applies_to_property_tags` for property scoping within the file. A `formats[]` entry without scope applies to all properties in the file; with scope, only to the named subset (e.g., Reels applies to Instagram + Facebook but not WhatsApp).\n\n**Community registry pattern (normative for unadopted platforms).** When a platform hasn't adopted AdCP (Meta, TikTok, Snap, Pinterest, etc.), AAO publishes a community-maintained adagents.json at `https://creative.adcontextprotocol.org/translated//adagents.json` carrying that platform's `formats[]`. Buyer SDKs fetch the platform's own `/.well-known/adagents.json` first; on 404 or absence-of-formats[], they fall back to the AAO mirror. When the platform adopts AdCP and publishes their own adagents.json with `formats[]`, the platform-hosted file takes precedence and the mirror entry becomes redundant (AAO maintainers deprecate it).\n\nThe `v1_format_ref.agent_url` on each declaration SHOULD match the agent_url of the file's hosting location — platform-hosted formats point at the platform's agent, community-mirror formats point at `https://creative.adcontextprotocol.org/translated/`. This keeps the legacy named-format namespace converged regardless of which side hosts the catalog. See `docs/creative/canonical-formats.mdx` Meta Reels worked example.").optional(), "superseded_by": z.string().url().regex(new RegExp("^https://")).describe("Optional pointer indicating this adagents.json file has been superseded by another adagents.json at a different URL. Used by the AAO community-mirror lifecycle: when a platform (e.g., Meta) adopts AdCP and publishes its own adagents.json at `/.well-known/adagents.json`, the AAO mirror file at `creative.adcontextprotocol.org/translated//adagents.json` sets `superseded_by` to the platform-hosted URL. Buyer SDKs encountering a file with `superseded_by` SHOULD short-circuit and re-fetch from the named URL rather than serving stale content from the mirror. The mirror SHOULD continue serving with `superseded_by` set for ≥1 minor release after platform adoption so buyer caches keyed on the mirror URL get an explicit migration signal rather than a silent break.").optional(), "tags": z.record(z.string(), z.object({ "name": z.string().describe("Human-readable name for this tag"), "description": z.string().describe("Description of what this tag represents") }).catchall(z.any())).describe("Metadata for each tag referenced by properties. Provides human-readable context for property tag values.").optional(), "placement_tags": z.record(z.string(), z.object({ "name": z.string().describe("Human-readable name for this placement tag"), "description": z.string().describe("Description of what this placement tag represents") }).catchall(z.any())).describe("Metadata for each tag referenced by placements. Provides human-readable context for publisher-defined placement tag values used in grouping and authorization.").optional(), "authorized_agents": z.array(z.union([z.object({ "authorization_type": z.literal("property_ids").describe("Discriminator indicating authorization by specific property IDs"), "property_ids": z.array(z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.")).describe("Property IDs this agent is authorized for. Resolved against the top-level properties array in this file"), "collections": z.array(z.object({ "publisher_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain where the adagents.json declaring these collections is hosted (e.g., 'mrbeast.com'). The collections array in that file contains the authoritative collection definitions."), "collection_ids": z.array(z.string()).describe("Collection IDs from the adagents.json collections array. Each ID must match a collection_id declared in that file.") }).catchall(z.any()).describe("References collections declared in an adagents.json. Buyers resolve full collection objects by fetching the adagents.json at the given domain and matching collection_ids against its collections array.")).describe("Optional collection constraints. When present, authorization only applies to inventory associated with these collections.").optional(), "placement_ids": z.array(z.string()).describe("Optional placement constraints. When present, authorization only applies to these placement IDs from the top-level placements array in this file.").optional(), "placement_tags": z.array(z.string()).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Optional placement tag constraints. When present, authorization only applies to placements whose tags include any of these publisher-defined values.").optional(), "delegation_type": z.enum(["direct","delegated","ad_network"]).describe("Commercial relationship for this inventory path. 'direct' means the publisher treats this as a direct way to buy from them, even if a third party operates the software. 'delegated' means the agent is authorized to sell on the publisher's behalf. 'ad_network' means the inventory is sold as part of a network/package context rather than as the publisher's direct endpoint.").optional(), "exclusive": z.boolean().describe("Whether this agent is the publisher's sole authorized path for the scoped inventory slice. When false or absent, other authorized agents may also sell the same inventory.").optional(), "countries": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Optional ISO 3166-1 alpha-2 country codes limiting where this authorization applies. Omit for worldwide authorization.").optional(), "effective_from": z.string().datetime().describe("Optional start time for this authorization window.").optional(), "effective_until": z.string().datetime().describe("Optional end time for this authorization window.").optional() }).catchall(z.any()).and(z.object({ "url": z.string().url().describe("The authorized agent's API endpoint URL. Callers comparing this URL against a registry (sales-agent list, signal-provider registry, TMP provider lookup, etc.) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality — two URLs that differ only in case, default port, or percent-encoding of unreserved characters are the same agent. See docs/reference/url-canonicalization."), "authorized_for": z.string().min(1).max(500).describe("Human-readable description of what this agent is authorized to do — what it sells (for sales/property agents) or what data it provides (for signal agents). The variant the entry uses (`property_ids`, `signal_tags`, etc.) tells consumers which inflection applies; this field carries the operator-supplied label."), "signing_keys": z.array(z.object({ "kid": z.string().describe("Key identifier for selecting the correct signing key."), "kty": z.string().describe("JWK key type, such as 'OKP', 'EC', or 'RSA'."), "alg": z.string().describe("Expected signing algorithm for this key, such as 'EdDSA' or 'RS256'.").optional(), "use": z.string().describe("Optional JWK use value. Typically 'sig' for signing keys.").optional(), "crv": z.string().describe("Curve name for OKP or EC keys, such as 'Ed25519' or 'P-256'.").optional(), "x": z.string().describe("Base64url-encoded public key x coordinate or public key value for OKP keys.").optional(), "y": z.string().describe("Base64url-encoded public key y coordinate for EC keys.").optional(), "n": z.string().describe("Base64url-encoded RSA modulus.").optional(), "e": z.string().describe("Base64url-encoded RSA public exponent.").optional(), "revoked_at": z.string().datetime().describe("Optional revocation timestamp. When present, verifiers MUST reject any signature produced with this key whose signing epoch (or equivalent time reference) is at or after this timestamp. The key may continue to appear in the trust anchor during a grace period so caches that have not yet refreshed still find the key and can evaluate the revocation marker. Keys past their revocation can be removed once the cache TTL (recommended: 5 minutes) has elapsed across all verifiers.").optional() }).catchall(z.any()).describe("Publisher-attested public key material for an authorized agent. Buyers use these keys to verify signed agent responses against the trust anchor published in adagents.json rather than trusting key discovery from the agent domain alone.")).describe("Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.").optional(), "encryption_keys": z.array(z.object({ "kid": z.string().max(8).describe("Key identifier. Opaque — MUST NOT encode geographic or deployment information."), "kty": z.literal("OKP").describe("JWK key type. Must be OKP for X25519."), "crv": z.literal("X25519").describe("Curve name. Must be X25519 for TMPX encryption."), "use": z.literal("enc").describe("JWK use value. Must be enc for encryption keys."), "x": z.string().describe("Base64url-encoded X25519 public key (32 bytes).") }).strict().describe("X25519 public key for HPKE encryption. Used for TMPX exposure token encryption with HPKE mode_base.")).describe("X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.").optional(), "last_updated": z.string().datetime().describe("Optional ISO 8601 timestamp indicating when this `authorized_agents[]` entry last changed. Independent of the file-level `last_updated`. Lets validators perform a partial walk by skipping entries whose `last_updated` is older than their indexed value. Advisory — consumers MAY ignore and re-index the full file.").optional() }).describe("Fields shared by every variant of `authorized_agents[*]` in adagents.json, regardless of authorization_type. Variants `allOf` this base and add their discriminator-specific fields. Centralized to prevent drift across all authorization-type variants.")), z.object({ "authorization_type": z.literal("property_tags").describe("Discriminator indicating authorization by property tags"), "property_tags": z.array(z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.")).describe("Tags identifying which properties this agent is authorized for. Resolved against the top-level properties array in this file using tag matching"), "collections": z.array(z.object({ "publisher_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain where the adagents.json declaring these collections is hosted (e.g., 'mrbeast.com'). The collections array in that file contains the authoritative collection definitions."), "collection_ids": z.array(z.string()).describe("Collection IDs from the adagents.json collections array. Each ID must match a collection_id declared in that file.") }).catchall(z.any()).describe("References collections declared in an adagents.json. Buyers resolve full collection objects by fetching the adagents.json at the given domain and matching collection_ids against its collections array.")).describe("Optional collection constraints. When present, authorization only applies to inventory associated with these collections.").optional(), "placement_ids": z.array(z.string()).describe("Optional placement constraints. When present, authorization only applies to these placement IDs from the top-level placements array in this file.").optional(), "placement_tags": z.array(z.string()).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Optional placement tag constraints. When present, authorization only applies to placements whose tags include any of these publisher-defined values.").optional(), "delegation_type": z.enum(["direct","delegated","ad_network"]).describe("Commercial relationship for this inventory path. 'direct' means the publisher treats this as a direct way to buy from them, even if a third party operates the software. 'delegated' means the agent is authorized to sell on the publisher's behalf. 'ad_network' means the inventory is sold as part of a network/package context rather than as the publisher's direct endpoint.").optional(), "exclusive": z.boolean().describe("Whether this agent is the publisher's sole authorized path for the scoped inventory slice. When false or absent, other authorized agents may also sell the same inventory.").optional(), "countries": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Optional ISO 3166-1 alpha-2 country codes limiting where this authorization applies. Omit for worldwide authorization.").optional(), "effective_from": z.string().datetime().describe("Optional start time for this authorization window.").optional(), "effective_until": z.string().datetime().describe("Optional end time for this authorization window.").optional() }).catchall(z.any()).and(z.object({ "url": z.string().url().describe("The authorized agent's API endpoint URL. Callers comparing this URL against a registry (sales-agent list, signal-provider registry, TMP provider lookup, etc.) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality — two URLs that differ only in case, default port, or percent-encoding of unreserved characters are the same agent. See docs/reference/url-canonicalization."), "authorized_for": z.string().min(1).max(500).describe("Human-readable description of what this agent is authorized to do — what it sells (for sales/property agents) or what data it provides (for signal agents). The variant the entry uses (`property_ids`, `signal_tags`, etc.) tells consumers which inflection applies; this field carries the operator-supplied label."), "signing_keys": z.array(z.object({ "kid": z.string().describe("Key identifier for selecting the correct signing key."), "kty": z.string().describe("JWK key type, such as 'OKP', 'EC', or 'RSA'."), "alg": z.string().describe("Expected signing algorithm for this key, such as 'EdDSA' or 'RS256'.").optional(), "use": z.string().describe("Optional JWK use value. Typically 'sig' for signing keys.").optional(), "crv": z.string().describe("Curve name for OKP or EC keys, such as 'Ed25519' or 'P-256'.").optional(), "x": z.string().describe("Base64url-encoded public key x coordinate or public key value for OKP keys.").optional(), "y": z.string().describe("Base64url-encoded public key y coordinate for EC keys.").optional(), "n": z.string().describe("Base64url-encoded RSA modulus.").optional(), "e": z.string().describe("Base64url-encoded RSA public exponent.").optional(), "revoked_at": z.string().datetime().describe("Optional revocation timestamp. When present, verifiers MUST reject any signature produced with this key whose signing epoch (or equivalent time reference) is at or after this timestamp. The key may continue to appear in the trust anchor during a grace period so caches that have not yet refreshed still find the key and can evaluate the revocation marker. Keys past their revocation can be removed once the cache TTL (recommended: 5 minutes) has elapsed across all verifiers.").optional() }).catchall(z.any()).describe("Publisher-attested public key material for an authorized agent. Buyers use these keys to verify signed agent responses against the trust anchor published in adagents.json rather than trusting key discovery from the agent domain alone.")).describe("Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.").optional(), "encryption_keys": z.array(z.object({ "kid": z.string().max(8).describe("Key identifier. Opaque — MUST NOT encode geographic or deployment information."), "kty": z.literal("OKP").describe("JWK key type. Must be OKP for X25519."), "crv": z.literal("X25519").describe("Curve name. Must be X25519 for TMPX encryption."), "use": z.literal("enc").describe("JWK use value. Must be enc for encryption keys."), "x": z.string().describe("Base64url-encoded X25519 public key (32 bytes).") }).strict().describe("X25519 public key for HPKE encryption. Used for TMPX exposure token encryption with HPKE mode_base.")).describe("X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.").optional(), "last_updated": z.string().datetime().describe("Optional ISO 8601 timestamp indicating when this `authorized_agents[]` entry last changed. Independent of the file-level `last_updated`. Lets validators perform a partial walk by skipping entries whose `last_updated` is older than their indexed value. Advisory — consumers MAY ignore and re-index the full file.").optional() }).describe("Fields shared by every variant of `authorized_agents[*]` in adagents.json, regardless of authorization_type. Variants `allOf` this base and add their discriminator-specific fields. Centralized to prevent drift across all authorization-type variants.")), z.object({ "authorization_type": z.literal("inline_properties").describe("Discriminator indicating authorization by inline property definitions. Companion field is `properties` (not `inline_properties`) — the only authorization_type whose companion field name does not mirror the discriminator value."), "properties": z.array(z.object({ "property_id": z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Unique identifier for this property (optional). Enables referencing properties by ID instead of repeating full objects.").optional(), "property_type": z.enum(["website","mobile_app","ctv_app","desktop_app","dooh","podcast","radio","linear_tv","streaming_audio","ai_assistant"]).describe("Type of advertising property"), "name": z.string().describe("Human-readable property name"), "identifiers": z.array(z.object({ "type": z.enum(["domain","subdomain","network_id","ios_bundle","android_package","apple_app_store_id","google_play_id","roku_store_id","fire_tv_asin","samsung_app_id","apple_tv_bundle","bundle_id","venue_id","screen_id","openooh_venue_type","rss_url","apple_podcast_id","spotify_collection_id","podcast_guid","station_id","facility_id"]).describe("Type of identifier for this property"), "value": z.string().describe("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") }).catchall(z.any())).describe("Array of identifiers for this property"), "tags": z.array(z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Tags for categorization and grouping (e.g., network membership, content categories)").optional(), "supported_channels": z.array(z.enum(["display","olv","social","search","ctv","linear_tv","radio","streaming_audio","podcast","dooh","ooh","print","cinema","email","gaming","retail_media","influencer","affiliate","product_placement","sponsored_intelligence"]).describe("Standardized advertising media channels describing how buyers allocate budget. Channels are planning abstractions, not technical substrates. See the Media Channel Taxonomy specification for detailed definitions.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Advertising channels this property supports (e.g., ['display', 'olv', 'social']). Publishers declare which channels their inventory aligns with. Properties may support multiple channels. See the Media Channel Taxonomy for definitions.").optional(), "publisher_domain": z.string().describe("Domain where adagents.json should be checked for authorization validation. Optional in adagents.json (file location implies domain).").optional() }).catchall(z.any()).describe("An advertising property that can be validated via adagents.json")).describe("Specific properties this agent is authorized for, defined inline on the agent entry (alternative to property_ids/property_tags). Note: this is the companion field for `authorization_type: \"inline_properties\"` — the field is named `properties`, not `inline_properties`."), "collections": z.array(z.object({ "publisher_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain where the adagents.json declaring these collections is hosted (e.g., 'mrbeast.com'). The collections array in that file contains the authoritative collection definitions."), "collection_ids": z.array(z.string()).describe("Collection IDs from the adagents.json collections array. Each ID must match a collection_id declared in that file.") }).catchall(z.any()).describe("References collections declared in an adagents.json. Buyers resolve full collection objects by fetching the adagents.json at the given domain and matching collection_ids against its collections array.")).describe("Optional collection constraints. When present, authorization only applies to inventory associated with these collections.").optional(), "placement_ids": z.array(z.string()).describe("Optional placement constraints. When present, authorization only applies to these placement IDs from the top-level placements array in this file.").optional(), "placement_tags": z.array(z.string()).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Optional placement tag constraints. When present, authorization only applies to placements whose tags include any of these publisher-defined values.").optional(), "delegation_type": z.enum(["direct","delegated","ad_network"]).describe("Commercial relationship for this inventory path. 'direct' means the publisher treats this as a direct way to buy from them, even if a third party operates the software. 'delegated' means the agent is authorized to sell on the publisher's behalf. 'ad_network' means the inventory is sold as part of a network/package context rather than as the publisher's direct endpoint.").optional(), "exclusive": z.boolean().describe("Whether this agent is the publisher's sole authorized path for the scoped inventory slice. When false or absent, other authorized agents may also sell the same inventory.").optional(), "countries": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Optional ISO 3166-1 alpha-2 country codes limiting where this authorization applies. Omit for worldwide authorization.").optional(), "effective_from": z.string().datetime().describe("Optional start time for this authorization window.").optional(), "effective_until": z.string().datetime().describe("Optional end time for this authorization window.").optional() }).catchall(z.any()).and(z.object({ "url": z.string().url().describe("The authorized agent's API endpoint URL. Callers comparing this URL against a registry (sales-agent list, signal-provider registry, TMP provider lookup, etc.) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality — two URLs that differ only in case, default port, or percent-encoding of unreserved characters are the same agent. See docs/reference/url-canonicalization."), "authorized_for": z.string().min(1).max(500).describe("Human-readable description of what this agent is authorized to do — what it sells (for sales/property agents) or what data it provides (for signal agents). The variant the entry uses (`property_ids`, `signal_tags`, etc.) tells consumers which inflection applies; this field carries the operator-supplied label."), "signing_keys": z.array(z.object({ "kid": z.string().describe("Key identifier for selecting the correct signing key."), "kty": z.string().describe("JWK key type, such as 'OKP', 'EC', or 'RSA'."), "alg": z.string().describe("Expected signing algorithm for this key, such as 'EdDSA' or 'RS256'.").optional(), "use": z.string().describe("Optional JWK use value. Typically 'sig' for signing keys.").optional(), "crv": z.string().describe("Curve name for OKP or EC keys, such as 'Ed25519' or 'P-256'.").optional(), "x": z.string().describe("Base64url-encoded public key x coordinate or public key value for OKP keys.").optional(), "y": z.string().describe("Base64url-encoded public key y coordinate for EC keys.").optional(), "n": z.string().describe("Base64url-encoded RSA modulus.").optional(), "e": z.string().describe("Base64url-encoded RSA public exponent.").optional(), "revoked_at": z.string().datetime().describe("Optional revocation timestamp. When present, verifiers MUST reject any signature produced with this key whose signing epoch (or equivalent time reference) is at or after this timestamp. The key may continue to appear in the trust anchor during a grace period so caches that have not yet refreshed still find the key and can evaluate the revocation marker. Keys past their revocation can be removed once the cache TTL (recommended: 5 minutes) has elapsed across all verifiers.").optional() }).catchall(z.any()).describe("Publisher-attested public key material for an authorized agent. Buyers use these keys to verify signed agent responses against the trust anchor published in adagents.json rather than trusting key discovery from the agent domain alone.")).describe("Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.").optional(), "encryption_keys": z.array(z.object({ "kid": z.string().max(8).describe("Key identifier. Opaque — MUST NOT encode geographic or deployment information."), "kty": z.literal("OKP").describe("JWK key type. Must be OKP for X25519."), "crv": z.literal("X25519").describe("Curve name. Must be X25519 for TMPX encryption."), "use": z.literal("enc").describe("JWK use value. Must be enc for encryption keys."), "x": z.string().describe("Base64url-encoded X25519 public key (32 bytes).") }).strict().describe("X25519 public key for HPKE encryption. Used for TMPX exposure token encryption with HPKE mode_base.")).describe("X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.").optional(), "last_updated": z.string().datetime().describe("Optional ISO 8601 timestamp indicating when this `authorized_agents[]` entry last changed. Independent of the file-level `last_updated`. Lets validators perform a partial walk by skipping entries whose `last_updated` is older than their indexed value. Advisory — consumers MAY ignore and re-index the full file.").optional() }).describe("Fields shared by every variant of `authorized_agents[*]` in adagents.json, regardless of authorization_type. Variants `allOf` this base and add their discriminator-specific fields. Centralized to prevent drift across all authorization-type variants.")), z.object({ "authorization_type": z.literal("publisher_properties").describe("Discriminator indicating authorization for properties from other publisher domains"), "publisher_properties": z.array(z.union([z.object({ "publisher_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.").optional(), "publisher_domains": z.array(z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$"))).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Compact form for fanning the same selector across many publishers (e.g., a managed network listing every publisher it represents). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.").optional(), "selection_type": z.literal("all").describe("Discriminator indicating all properties from each addressed publisher are included") }).catchall(z.any()).and(z.intersection(z.any().refine((value) => !z.any().safeParse(value).success, "Invalid input: Should NOT be valid against schema"), z.union([z.any(), z.any()]))).describe("Select all properties from one publisher domain, or from each publisher domain when `publisher_domains` is used. Consumers MAY satisfy the selector from the parent file's top-level `properties[]` when those properties carry a `publisher_domain` matching one of the listed domains (see Resolution paths in the spec)."), z.object({ "publisher_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain where publisher's adagents.json is hosted (e.g., 'cnn.com')."), "selection_type": z.literal("by_id").describe("Discriminator indicating selection by specific property IDs"), "property_ids": z.array(z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.")).describe("Specific property IDs from the publisher's adagents.json") }).catchall(z.any()).describe("Select specific properties by ID. Single-publisher only — property IDs are publisher-scoped, so the compact `publisher_domains[]` form is intentionally NOT available for this selector. Use multiple `publisher_properties[]` entries (one per publisher) when each publisher's ID set differs."), z.object({ "publisher_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.").optional(), "publisher_domains": z.array(z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$"))).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Compact form for fanning the same tag predicate across many publishers (canonical managed-network shape). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.").optional(), "selection_type": z.literal("by_tag").describe("Discriminator indicating selection by property tags"), "property_tags": z.array(z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.")).describe("Property tags resolved against each addressed publisher's adagents.json, OR against the parent file's top-level `properties[]` when those properties carry a `publisher_domain` matching the selector. Selector covers all properties carrying any of these tags.") }).catchall(z.any()).and(z.intersection(z.any().refine((value) => !z.any().safeParse(value).success, "Invalid input: Should NOT be valid against schema"), z.union([z.any(), z.any()]))).describe("Select properties by tag membership. With `publisher_domains`, the same `property_tags` predicate is resolved against each listed publisher's adagents.json — the common managed-network case where every represented site tags inventory with a shared label. Consumers MAY also satisfy the predicate from the parent file's top-level `properties[]` when those properties carry a `publisher_domain` matching one of the selector's `publisher_domains[]` (see Resolution paths in the spec).")]).describe("Selects properties from a publisher's adagents.json. Used for both product definitions and agent authorization. Supports three selection patterns: all properties, specific IDs, or by tags. Each selector targets one publisher via `publisher_domain` (string) or a fan-out across many publishers that share the same selector via `publisher_domains` (array). Exactly one of `publisher_domain` or `publisher_domains` MUST be present. When `publisher_domains` is used, the selector is logically equivalent to repeating the same entry once per listed domain.")).describe("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"), "collections": z.array(z.object({ "publisher_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain where the adagents.json declaring these collections is hosted (e.g., 'mrbeast.com'). The collections array in that file contains the authoritative collection definitions."), "collection_ids": z.array(z.string()).describe("Collection IDs from the adagents.json collections array. Each ID must match a collection_id declared in that file.") }).catchall(z.any()).describe("References collections declared in an adagents.json. Buyers resolve full collection objects by fetching the adagents.json at the given domain and matching collection_ids against its collections array.")).describe("Optional collection constraints. When present, authorization only applies to inventory associated with these collections.").optional(), "placement_ids": z.array(z.string()).describe("Optional placement constraints. When present, authorization only applies to these placement IDs from the top-level placements array in this file.").optional(), "placement_tags": z.array(z.string()).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Optional placement tag constraints. When present, authorization only applies to placements whose tags include any of these publisher-defined values.").optional(), "delegation_type": z.enum(["direct","delegated","ad_network"]).describe("Commercial relationship for this inventory path. 'direct' means the publisher treats this as a direct way to buy from them, even if a third party operates the software. 'delegated' means the agent is authorized to sell on the publisher's behalf. 'ad_network' means the inventory is sold as part of a network/package context rather than as the publisher's direct endpoint.").optional(), "exclusive": z.boolean().describe("Whether this agent is the publisher's sole authorized path for the scoped inventory slice. When false or absent, other authorized agents may also sell the same inventory.").optional(), "countries": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Optional ISO 3166-1 alpha-2 country codes limiting where this authorization applies. Omit for worldwide authorization.").optional(), "effective_from": z.string().datetime().describe("Optional start time for this authorization window.").optional(), "effective_until": z.string().datetime().describe("Optional end time for this authorization window.").optional() }).catchall(z.any()).and(z.object({ "url": z.string().url().describe("The authorized agent's API endpoint URL. Callers comparing this URL against a registry (sales-agent list, signal-provider registry, TMP provider lookup, etc.) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality — two URLs that differ only in case, default port, or percent-encoding of unreserved characters are the same agent. See docs/reference/url-canonicalization."), "authorized_for": z.string().min(1).max(500).describe("Human-readable description of what this agent is authorized to do — what it sells (for sales/property agents) or what data it provides (for signal agents). The variant the entry uses (`property_ids`, `signal_tags`, etc.) tells consumers which inflection applies; this field carries the operator-supplied label."), "signing_keys": z.array(z.object({ "kid": z.string().describe("Key identifier for selecting the correct signing key."), "kty": z.string().describe("JWK key type, such as 'OKP', 'EC', or 'RSA'."), "alg": z.string().describe("Expected signing algorithm for this key, such as 'EdDSA' or 'RS256'.").optional(), "use": z.string().describe("Optional JWK use value. Typically 'sig' for signing keys.").optional(), "crv": z.string().describe("Curve name for OKP or EC keys, such as 'Ed25519' or 'P-256'.").optional(), "x": z.string().describe("Base64url-encoded public key x coordinate or public key value for OKP keys.").optional(), "y": z.string().describe("Base64url-encoded public key y coordinate for EC keys.").optional(), "n": z.string().describe("Base64url-encoded RSA modulus.").optional(), "e": z.string().describe("Base64url-encoded RSA public exponent.").optional(), "revoked_at": z.string().datetime().describe("Optional revocation timestamp. When present, verifiers MUST reject any signature produced with this key whose signing epoch (or equivalent time reference) is at or after this timestamp. The key may continue to appear in the trust anchor during a grace period so caches that have not yet refreshed still find the key and can evaluate the revocation marker. Keys past their revocation can be removed once the cache TTL (recommended: 5 minutes) has elapsed across all verifiers.").optional() }).catchall(z.any()).describe("Publisher-attested public key material for an authorized agent. Buyers use these keys to verify signed agent responses against the trust anchor published in adagents.json rather than trusting key discovery from the agent domain alone.")).describe("Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.").optional(), "encryption_keys": z.array(z.object({ "kid": z.string().max(8).describe("Key identifier. Opaque — MUST NOT encode geographic or deployment information."), "kty": z.literal("OKP").describe("JWK key type. Must be OKP for X25519."), "crv": z.literal("X25519").describe("Curve name. Must be X25519 for TMPX encryption."), "use": z.literal("enc").describe("JWK use value. Must be enc for encryption keys."), "x": z.string().describe("Base64url-encoded X25519 public key (32 bytes).") }).strict().describe("X25519 public key for HPKE encryption. Used for TMPX exposure token encryption with HPKE mode_base.")).describe("X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.").optional(), "last_updated": z.string().datetime().describe("Optional ISO 8601 timestamp indicating when this `authorized_agents[]` entry last changed. Independent of the file-level `last_updated`. Lets validators perform a partial walk by skipping entries whose `last_updated` is older than their indexed value. Advisory — consumers MAY ignore and re-index the full file.").optional() }).describe("Fields shared by every variant of `authorized_agents[*]` in adagents.json, regardless of authorization_type. Variants `allOf` this base and add their discriminator-specific fields. Centralized to prevent drift across all authorization-type variants.")), z.object({ "authorization_type": z.literal("signal_ids").describe("Discriminator indicating authorization by specific signal IDs"), "signal_ids": z.array(z.string().regex(new RegExp("^[a-zA-Z0-9_-]+$"))).describe("Signal IDs this agent is authorized to resell. Resolved against the top-level signals array in this file") }).catchall(z.any()).and(z.object({ "url": z.string().url().describe("The authorized agent's API endpoint URL. Callers comparing this URL against a registry (sales-agent list, signal-provider registry, TMP provider lookup, etc.) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality — two URLs that differ only in case, default port, or percent-encoding of unreserved characters are the same agent. See docs/reference/url-canonicalization."), "authorized_for": z.string().min(1).max(500).describe("Human-readable description of what this agent is authorized to do — what it sells (for sales/property agents) or what data it provides (for signal agents). The variant the entry uses (`property_ids`, `signal_tags`, etc.) tells consumers which inflection applies; this field carries the operator-supplied label."), "signing_keys": z.array(z.object({ "kid": z.string().describe("Key identifier for selecting the correct signing key."), "kty": z.string().describe("JWK key type, such as 'OKP', 'EC', or 'RSA'."), "alg": z.string().describe("Expected signing algorithm for this key, such as 'EdDSA' or 'RS256'.").optional(), "use": z.string().describe("Optional JWK use value. Typically 'sig' for signing keys.").optional(), "crv": z.string().describe("Curve name for OKP or EC keys, such as 'Ed25519' or 'P-256'.").optional(), "x": z.string().describe("Base64url-encoded public key x coordinate or public key value for OKP keys.").optional(), "y": z.string().describe("Base64url-encoded public key y coordinate for EC keys.").optional(), "n": z.string().describe("Base64url-encoded RSA modulus.").optional(), "e": z.string().describe("Base64url-encoded RSA public exponent.").optional(), "revoked_at": z.string().datetime().describe("Optional revocation timestamp. When present, verifiers MUST reject any signature produced with this key whose signing epoch (or equivalent time reference) is at or after this timestamp. The key may continue to appear in the trust anchor during a grace period so caches that have not yet refreshed still find the key and can evaluate the revocation marker. Keys past their revocation can be removed once the cache TTL (recommended: 5 minutes) has elapsed across all verifiers.").optional() }).catchall(z.any()).describe("Publisher-attested public key material for an authorized agent. Buyers use these keys to verify signed agent responses against the trust anchor published in adagents.json rather than trusting key discovery from the agent domain alone.")).describe("Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.").optional(), "encryption_keys": z.array(z.object({ "kid": z.string().max(8).describe("Key identifier. Opaque — MUST NOT encode geographic or deployment information."), "kty": z.literal("OKP").describe("JWK key type. Must be OKP for X25519."), "crv": z.literal("X25519").describe("Curve name. Must be X25519 for TMPX encryption."), "use": z.literal("enc").describe("JWK use value. Must be enc for encryption keys."), "x": z.string().describe("Base64url-encoded X25519 public key (32 bytes).") }).strict().describe("X25519 public key for HPKE encryption. Used for TMPX exposure token encryption with HPKE mode_base.")).describe("X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.").optional(), "last_updated": z.string().datetime().describe("Optional ISO 8601 timestamp indicating when this `authorized_agents[]` entry last changed. Independent of the file-level `last_updated`. Lets validators perform a partial walk by skipping entries whose `last_updated` is older than their indexed value. Advisory — consumers MAY ignore and re-index the full file.").optional() }).describe("Fields shared by every variant of `authorized_agents[*]` in adagents.json, regardless of authorization_type. Variants `allOf` this base and add their discriminator-specific fields. Centralized to prevent drift across all authorization-type variants.")).describe("Authorization for signals by specific signal IDs"), z.object({ "authorization_type": z.literal("signal_tags").describe("Discriminator indicating authorization by signal tags"), "signal_tags": z.array(z.string().regex(new RegExp("^[a-z0-9_-]+$"))).describe("Signal tags this agent is authorized for. Agent can resell all signals with these tags") }).catchall(z.any()).and(z.object({ "url": z.string().url().describe("The authorized agent's API endpoint URL. Callers comparing this URL against a registry (sales-agent list, signal-provider registry, TMP provider lookup, etc.) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality — two URLs that differ only in case, default port, or percent-encoding of unreserved characters are the same agent. See docs/reference/url-canonicalization."), "authorized_for": z.string().min(1).max(500).describe("Human-readable description of what this agent is authorized to do — what it sells (for sales/property agents) or what data it provides (for signal agents). The variant the entry uses (`property_ids`, `signal_tags`, etc.) tells consumers which inflection applies; this field carries the operator-supplied label."), "signing_keys": z.array(z.object({ "kid": z.string().describe("Key identifier for selecting the correct signing key."), "kty": z.string().describe("JWK key type, such as 'OKP', 'EC', or 'RSA'."), "alg": z.string().describe("Expected signing algorithm for this key, such as 'EdDSA' or 'RS256'.").optional(), "use": z.string().describe("Optional JWK use value. Typically 'sig' for signing keys.").optional(), "crv": z.string().describe("Curve name for OKP or EC keys, such as 'Ed25519' or 'P-256'.").optional(), "x": z.string().describe("Base64url-encoded public key x coordinate or public key value for OKP keys.").optional(), "y": z.string().describe("Base64url-encoded public key y coordinate for EC keys.").optional(), "n": z.string().describe("Base64url-encoded RSA modulus.").optional(), "e": z.string().describe("Base64url-encoded RSA public exponent.").optional(), "revoked_at": z.string().datetime().describe("Optional revocation timestamp. When present, verifiers MUST reject any signature produced with this key whose signing epoch (or equivalent time reference) is at or after this timestamp. The key may continue to appear in the trust anchor during a grace period so caches that have not yet refreshed still find the key and can evaluate the revocation marker. Keys past their revocation can be removed once the cache TTL (recommended: 5 minutes) has elapsed across all verifiers.").optional() }).catchall(z.any()).describe("Publisher-attested public key material for an authorized agent. Buyers use these keys to verify signed agent responses against the trust anchor published in adagents.json rather than trusting key discovery from the agent domain alone.")).describe("Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.").optional(), "encryption_keys": z.array(z.object({ "kid": z.string().max(8).describe("Key identifier. Opaque — MUST NOT encode geographic or deployment information."), "kty": z.literal("OKP").describe("JWK key type. Must be OKP for X25519."), "crv": z.literal("X25519").describe("Curve name. Must be X25519 for TMPX encryption."), "use": z.literal("enc").describe("JWK use value. Must be enc for encryption keys."), "x": z.string().describe("Base64url-encoded X25519 public key (32 bytes).") }).strict().describe("X25519 public key for HPKE encryption. Used for TMPX exposure token encryption with HPKE mode_base.")).describe("X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.").optional(), "last_updated": z.string().datetime().describe("Optional ISO 8601 timestamp indicating when this `authorized_agents[]` entry last changed. Independent of the file-level `last_updated`. Lets validators perform a partial walk by skipping entries whose `last_updated` is older than their indexed value. Advisory — consumers MAY ignore and re-index the full file.").optional() }).describe("Fields shared by every variant of `authorized_agents[*]` in adagents.json, regardless of authorization_type. Variants `allOf` this base and add their discriminator-specific fields. Centralized to prevent drift across all authorization-type variants.")).describe("Authorization for signals by tag membership")])).describe("Array of sales agents authorized to make inventory from this file available to buyers. Authorization can be scoped to specific properties, collections, countries, and time windows, with optional delegation metadata indicating whether the path is direct, delegated, or network-mediated."), "last_updated": z.string().datetime().describe("ISO 8601 timestamp indicating when this file was last updated").optional(), "property_features": z.array(z.object({ "url": z.string().url().describe("The agent's API endpoint URL. Callers comparing this URL against a feature-provider registry MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality. See docs/reference/url-canonicalization."), "name": z.string().describe("Human-readable name of the vendor/agent (e.g., 'Scope3', 'TAG', 'OneTrust')"), "features": z.array(z.string()).describe("Feature IDs this agent provides (e.g., 'carbon_score', 'tag_certified_against_fraud'). Use get_adcp_capabilities on the agent for full definitions."), "publisher_id": z.string().describe("Optional publisher identifier at this agent (for lookup)").optional() }).catchall(z.any())).describe("[AdCP 3.0] Optional list of agents that provide property feature data (certifications, scores, compliance status). Used for discovery - actual data is accessed through property list filters.").optional(), "signals": z.array(z.object({ "id": z.string().regex(new RegExp("^[a-zA-Z0-9_-]+$")).describe("Signal identifier within the publishing domain's adagents.json signals[]"), "name": z.string().min(1).max(255).describe("Human-readable signal name"), "description": z.string().max(2000).describe("Detailed description of what this signal represents and how it's derived").optional(), "value_type": z.enum(["binary","categorical","numeric"]).describe("The data type of this signal's values"), "tags": z.array(z.string().regex(new RegExp("^[a-z0-9_-]+$"))).describe("Tags for grouping and filtering this domain's published signal definitions").optional(), "allowed_values": z.array(z.string()).describe("For categorical signals, the valid values users can be assigned").optional(), "restricted_attributes": z.array(z.enum(["racial_ethnic_origin","political_opinions","religious_beliefs","trade_union_membership","health_data","sex_life_sexual_orientation","genetic_data","biometric_data","age","familial_status"]).describe("Personal data categories that may be restricted from use in audience targeting. Combines GDPR Article 9 special categories with US civil-rights protected classes (FHA familial_status, ADEA age). Used in two places: (1) on campaign plans via restricted_attributes to declare which categories are prohibited, and (2) on signal-definition.json via restricted_attributes to declare which categories a signal touches. Governance agents match plan restrictions against signal declarations for structural validation.")).describe("Restricted attribute categories this signal touches. Data providers SHOULD declare these so governance agents can structurally match signals against a plan's restricted_attributes without relying on semantic inference from the signal name or description.").optional(), "policy_categories": z.array(z.string()).describe("Policy categories this signal is sensitive for (e.g., a children's interest signal declares ['children_directed']). Governance agents match these against a plan's policy_categories to flag sensitive data usage.").optional(), "range": z.object({ "min": z.number().describe("Minimum value"), "max": z.number().describe("Maximum value"), "unit": z.string().describe("Unit of measurement (e.g., 'score', 'dollars', 'years')").optional() }).strict().describe("For numeric signals, the valid value range").optional(), "taxonomy": z.object({ "ref": z.string().url().describe("URI identifying the taxonomy or taxonomy documentation."), "version": z.string().describe("Version identifier for the taxonomy when the taxonomy has versioned definitions.").optional(), "segtax": z.number().int().gte(1).describe("OpenRTB segtax code when the taxonomy maps to an OpenRTB segment taxonomy.").optional(), "etag": z.string().describe("Optional validator for custom taxonomy definitions so consumers can detect drift between signal publication and taxonomy resolution.").optional(), "values": z.array(z.object({ "id": z.string().min(1).describe("Taxonomy node identifier."), "path": z.string().describe("Optional human-readable or taxonomy-native path for display and review.").optional(), "modifiers": z.array(z.string()).describe("Optional taxonomy-specific modifiers that qualify the node.").optional() }).strict()).describe("Taxonomy node values that describe this signal. These are meaning/discovery metadata for the signal definition, not the values a buyer submits in package targeting expressions."), "value_mappings": z.array(z.object({ "value": z.string().describe("Categorical value from allowed_values[]."), "taxonomy_value_id": z.string().describe("Taxonomy node identifier corresponding to this categorical value."), "path": z.string().describe("Optional human-readable or taxonomy-native path for display and review.").optional(), "modifiers": z.array(z.string()).describe("Optional taxonomy-specific modifiers that qualify the mapped value.").optional() }).strict()).describe("For categorical signals, maps package-targeting allowed_values[] strings to stable taxonomy node identifiers. The allowed_values[] strings remain the wire values buyers submit; this mapping explains how those strings resolve into the published taxonomy. Each value_mappings[].value SHOULD match one of the signal's allowed_values[] entries; JSON Schema draft-07 cannot enforce this cross-array constraint.").optional(), "parent_match_behavior": z.enum(["exact_only","descendants_supported","unknown"]).describe("Whether this signal definition supports treating a parent taxonomy node as matching descendant nodes for discovery/filtering or seller-side expansion. 'exact_only' means only explicitly listed node ids match. 'descendants_supported' means the seller can expand known, version-pinned parent nodes to descendants internally, typically by ORing the children in its execution system. 'unknown' means the provider has not declared parent-node behavior. This field is metadata about discovery/translation behavior, not a package targeting operator.").optional() }).strict().describe("Optional taxonomy metadata describing what this signal means in an external audience, content, retail-media, or provider-owned taxonomy. Taxonomy metadata does not create a new value_type and does not change package targeting grammar: buyers still target the named signal according to value_type. When a taxonomy value is a parent node, parent/descendant expansion is seller behavior and must be declared through parent_match_behavior rather than assumed.").optional(), "segmentation_criteria": z.string().max(500).describe("Rules governing inclusion of identifiers in the segment. Aligns with IAB Data Transparency Standard audience criteria disclosure.").optional(), "criteria_url": z.string().url().describe("Optional URL to a longer-form methodology or criteria document. This is a disclosure pointer; buyers should not branch programmatically on the linked content.").optional(), "data_sources": z.array(z.enum(["app_behavior","app_usage","web_usage","geo_location","email","tv_ott_or_stb_device","panel","online_ecommerce","credit_data","loyalty_card","transaction","online_survey","offline_survey","public_record_census","public_record_voter_file","public_record_other","offline_transaction"])).describe("Origin categories of the raw data used to compile the signal, aligned with IAB Data Transparency Standard source disclosure. Use 'panel' for respondent-panel or JIC-style audience sources. Offline and public-record sources require onboarder disclosure. Co-viewing projection and reconciliation of seller claims against a JIC or measurement vendor belong in measurement reporting/vendor metrics rather than package signal targeting.").optional(), "methodology": z.enum(["observed","declared","derived","inferred","modeled"]).describe("How the signal's audience membership or attribute was determined. 'modeled' requires the modeling block.").optional(), "audience_expansion": z.boolean().describe("Whether look-alike or similar-audience expansion was used to include additional identifiers. When true, modeling is required.").optional(), "device_expansion": z.boolean().describe("Whether the signal was expanded deterministically across devices of the same user, household, or business. Probabilistic cross-device expansion is modeling and should use methodology 'modeled' or the modeling block.").optional(), "refresh_cadence": z.enum(["intra_day","daily","weekly","monthly","bi_monthly","quarterly","bi_annually","annually"]).describe("Cadence at which the signal definition's underlying segment membership is refreshed.").optional(), "lookback_window": z.enum(["intra_day","daily","weekly","monthly","bi_monthly","quarterly","bi_annually","annually"]).describe("Time window in which a qualifying event can occur for inclusion.").optional(), "onboarder": z.object({ "match_keys": z.array(z.enum(["name","address","email","postal","lat_long","mobile_id","cookie_id","ip","customer_id","phone"])), "pre_onboarding_audience_expansion": z.boolean().optional(), "pre_onboarding_device_expansion": z.boolean().optional(), "pre_onboarding_precision_level": z.enum(["individual","household","business","geography"]).optional() }).strict().describe("Onboarder disclosure. Required when data_sources includes an offline_* or public_record_* source.").optional(), "subject_type": z.enum(["individual","household","business","contextual","none"]).describe("What kind of subject this signal characterizes.").optional(), "resolution_method": z.enum(["deterministic_id","probabilistic_device","browser","geographic","content_signal","mixed"]).describe("How the subject is resolved at decision time.").optional(), "id_types": z.array(z.enum(["cookie","mobile_id","platform_id","user_enabled_id"])).describe("Identifier currencies analyzed to determine audience membership or attributes.").optional(), "audience_scope": z.enum(["single_domain","cross_domain_owned","cross_domain_unowned","offline"]).describe("Context within which the audience attribute was determined. 'single_domain' requires originating_domain.").optional(), "originating_domain": z.string().describe("Domain of the digital property where the audience originates. Required when audience_scope is 'single_domain'.").optional(), "countries": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).describe("ISO 3166-1 alpha-2 country codes where the signal is applicable. Sellers must not expose a signal for media buys in countries outside this list. Federating agents that surface a peer's signal MUST treat the peer-published list as an upper bound, re-check it against the buyer's intended deployment countries, and may apply only narrower local policy.").optional(), "consent_basis": z.array(z.enum(["consent","legitimate_interest","contract","legal_obligation"]).describe("Common GDPR lawful bases relevant to advertising. Covers the Article 6(1) bases used in programmatic advertising contexts.")).describe("Declared GDPR Article 6 lawful basis or consent basis under which this signal's data is processed. For non-GDPR regimes, use countries, policy_categories, and disclosure fields to describe jurisdiction-specific obligations unless a future enum value applies.").optional(), "art9_basis": z.enum(["explicit_consent","manifestly_made_public","substantial_public_interest","vital_interests"]).describe("GDPR Article 9 basis when restricted_attributes is non-empty and the signal is used in jurisdictions where Article 9 applies. Required by policy for applicable use cases rather than universally required at schema level because sensitivity and lawful basis are jurisdiction-relative.").optional(), "modeling": z.object({ "method": z.enum(["lookalike","supervised","embedding","rules"]), "seed_source": z.object({ "type": z.enum(["first_party_crm","panel","declared_survey","transactional","behavioral"]), "provider_signed": z.boolean().describe("Whether the seed source carries a signed attestation under one of the provider's published signing keys. This is a forward-looking claim until the consumer can resolve and verify the provider's applicable signing keys through the AdCP signing profile; consumers MUST NOT treat this boolean alone as cryptographic proof.") }).strict(), "training_data_jurisdictions": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).describe("ISO 3166-1 alpha-2 country codes where the model's training data was collected."), "ai_act_risk_class": z.enum(["minimal","limited","high_risk"]).describe("EU AI Act risk classification self-declared by the provider. Prohibited-risk modeled signals must not be published as AdCP signal definitions."), "disclosure": z.object({ "required": z.boolean().describe("The provider's claim that a modeling or AI-use disclosure is required for this signal in at least one applicable jurisdiction. This is a declared compliance signal, not a protocol-level legal determination."), "jurisdictions": z.array(z.object({ "country": z.string().regex(new RegExp("^[A-Z]{2}$")).describe("ISO 3166-1 alpha-2 country code."), "region": z.string().describe("Provider-defined sub-national region code or name when the obligation is regional. No global canonical format is implied.").optional(), "regulation": z.string().describe("Provider-supplied regulation identifier for the disclosure obligation."), "disclosure_text": z.string().describe("Human-readable disclosure text or summary the provider expects buyers or reviewers to see.").optional(), "disclosure_url": z.string().url().describe("Optional URL to the provider's canonical disclosure or methodology page for this jurisdiction.").optional(), "audience": z.enum(["buyer","data_subject","regulator","public"]).describe("Primary audience for this disclosure entry.").optional() }).strict()).describe("Jurisdictions where a modeling or AI-use disclosure applies.").optional(), "notes": z.string().max(2000).describe("Optional provider notes on how the disclosure should be interpreted. Informational only; buyers should not branch programmatically on this text.").optional() }).strict().and(z.any()).describe("Signal/modeling-specific disclosure requirements and jurisdictional notes. This is not creative provenance render guidance.").optional() }).strict().describe("Modeling disclosure for modeled data signals. Required when methodology is 'modeled' or audience_expansion is true. This describes data modeling and intentionally does not reuse creative provenance, which is content/render oriented.").optional(), "data_subject_rights": z.object({ "upstream_source_domain": z.string().max(253).describe("Domain of the upstream data source whose rights process these channels reach, when different from the publishing domain.").optional(), "channels": z.array(z.object({ "rights": z.array(z.enum(["access","rectification","erasure","portability","objection"])).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Rights supported by this channel."), "url": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL for submitting the rights request.").optional(), "email": z.string().email().describe("Email address for submitting the rights request.").optional(), "languages": z.array(z.string()).describe("BCP 47 language tags supported by this channel.").optional(), "countries": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).describe("ISO 3166-1 alpha-2 countries this channel serves, when the provider routes rights by country.").optional() }).strict().and(z.union([z.any(), z.any()]))).describe("Rights request channels and the rights each channel supports. At least one declared channel MUST support one or more of access, erasure, or objection; schema validators enforce this with draft-07 contains, and consumers whose SDK drops contains need an equivalent runtime check."), "response_sla_days": z.number().int().gte(1).lte(90).describe("Maximum response time in days for rights requests handled through the declared rights channels. For provider-published signals, providers SHOULD avoid duplicating this field across every signal unless the value varies by signal or upstream source; consumers MAY also consult the provider's public privacy policy or registry disclosures when present.").optional(), "ccpa_opt_out_url": z.string().url().regex(new RegExp("^https://")).describe("US-specific 'Do Not Sell or Share' opt-out URL where required.").optional() }).strict().describe("Per-signal data-subject-rights routing. Inline on the signal because upstream source, rights routing, and response commitments can differ by segment or may be unavailable from a public provider document for custom/private signals. This is a contact/routing reference, not a machine-callable AdCP API.").optional(), "dts_compliant_version": z.string().describe("IAB Data Transparency Standard version this signal definition self-attests as satisfying, when applicable.").optional() }).catchall(z.any()).and(z.intersection(z.intersection(z.any(), z.any()), z.intersection(z.any(), z.intersection(z.any(), z.any())))).describe("Definition of a signal in published adagents.json signals[]. The publishing domain supplies the namespace, so this definition carries a local id rather than a signal_ref. Media-buy products reference this definition with signal_ref scope 'data_provider', data_provider_domain set to the publishing domain, and signal_id set to this id. Some constraints use JSON Schema draft-07 conditional keywords such as if/then and contains; SDK generators that drop those keywords need equivalent runtime guards.")).describe("Signal definitions published by this domain. Each entry defines a signal id within this file's publishing-domain namespace; entries do not include signal_ref objects. Signals Protocol discovery and media-buy product targeting reference these through signal_ref scope 'data_provider', with data_provider_domain set to this file's publishing domain and signal_id set to signals[].id.").optional(), "signal_tags": z.record(z.string(), z.object({ "name": z.string().describe("Human-readable name for this tag"), "description": z.string().describe("Description of what this tag represents") }).catchall(z.any())).describe("Metadata for each tag referenced by signals. Provides human-readable context for signal tag values.").optional() }).catchall(z.any()).describe("Inline structure variant - contains full agent authorization data")]).describe("Declaration of authorized agents for advertising inventory and data signals. Hosted at /.well-known/adagents.json on publisher domains (for properties) or data provider domains (for signals). Can either contain the full structure inline or reference an authoritative URL.") export type AdagentsJson = z.infer; diff --git a/src/lib/version.ts b/src/lib/version.ts index 731185c8f..dbd437838 100644 --- a/src/lib/version.ts +++ b/src/lib/version.ts @@ -9,7 +9,7 @@ export const LIBRARY_VERSION = '8.1.0-beta.16'; /** * AdCP specification version this library is built for */ -export const ADCP_VERSION = '3.1.0-rc.3'; +export const ADCP_VERSION = '3.1.0-rc.4'; /** * AdCP major version sent with every request (adcp_major_version field). @@ -52,6 +52,8 @@ export const COMPATIBLE_ADCP_VERSIONS = [ '3.1-rc.2', '3.1.0-rc.3', '3.1-rc.3', + '3.1.0-rc.4', + '3.1-rc.4', '3.0.0', '3.0.1', '3.0.2', @@ -82,9 +84,9 @@ export type AdcpVersion = (typeof COMPATIBLE_ADCP_VERSIONS)[number]; */ export const VERSION_INFO = { library: '8.1.0-beta.16', - adcp: '3.1.0-rc.3', + adcp: '3.1.0-rc.4', compatibleVersions: COMPATIBLE_ADCP_VERSIONS, - generatedAt: '2026-05-30T10:30:20.275Z', + generatedAt: '2026-05-30T16:53:19.351Z', } as const; /**