Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions schemas/cache/3.0/adagents.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,40 @@
},
"minItems": 1
},
"revoked_publisher_domains": {
"type": "array",
"description": "Publisher domains explicitly removed from this managed network. Validators MUST treat any publisher domain listed here as no-longer-authorized, taking precedence over any appearance of the same domain in `authorized_agents[].publisher_properties[].publisher_domain` / `.publisher_domains[]` or in top-level `properties[].publisher_domain`. Lets a network propagate per-publisher revocations on the next refresh instead of waiting for the file-level 7-day cache cap. Validators MUST hold previously-observed `(publisher_domain, revoked_at)` tuples for 7 days from the validator's first observation, even if the entry vanishes from a subsequent fetch — this closes the rollback gap where an attacker re-serves a stale file with the revocation removed. Networks SHOULD retain entries for at least 7 days after `revoked_at` so validators that didn't observe the original entry still pick it up on refresh.",
"items": {
"type": "object",
"properties": {
"publisher_domain": {
"type": "string",
"description": "Publisher domain being revoked. Matches against the same canonicalized form used in `publisher_properties[].publisher_domain`.",
"pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$"
},
"revoked_at": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 timestamp when this publisher was revoked. Validators MAY use this to order revocations against their own cached state."
},
"reason": {
"type": "string",
"enum": [
"relationship_ended",
"compliance_violation",
"publisher_request",
"other"
],
"description": "Reason for revocation. Operator-internal self-classification for review routing — not a public accusation. `relationship_ended` is the routine commercial case. `compliance_violation` SHOULD be used only when the network has itself determined the publisher is out of policy; for un-adjudicated third-party allegations (regulator inquiries, advertiser complaints, ongoing investigations), use `other` to avoid making a discoverable adverse statement. `publisher_request` is for publisher-initiated exits."
}
},
"required": [
"publisher_domain",
"revoked_at"
],
"additionalProperties": true
}
},
"collections": {
"type": "array",
"description": "Collections produced or distributed by this publisher. Declares the content programs whose inventory is sold through authorized agents. Products in get_products responses reference these collections by collection_id.",
Expand Down
90 changes: 78 additions & 12 deletions schemas/cache/3.0/core/publisher-property-selector.json
Original file line number Diff line number Diff line change
@@ -1,39 +1,72 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Publisher Property Selector",
"description": "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.",
"description": "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.",
"discriminator": {
"propertyName": "selection_type"
},
"oneOf": [
{
"type": "object",
"description": "Select all properties from the publisher domain",
"description": "Select all properties from one publisher domain, or from each publisher domain when `publisher_domains` is used.",
"properties": {
"publisher_domain": {
"type": "string",
"description": "Domain where publisher's adagents.json is hosted (e.g., 'cnn.com')",
"description": "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.",
"pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$"
},
"publisher_domains": {
"type": "array",
"description": "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.",
"items": {
"type": "string",
"pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$"
},
"minItems": 1,
"uniqueItems": true
},
"selection_type": {
"type": "string",
"const": "all",
"description": "Discriminator indicating all properties from this publisher are included"
"description": "Discriminator indicating all properties from each addressed publisher are included"
}
},
"required": [
"publisher_domain",
"selection_type"
],
"allOf": [
{
"not": {
"required": [
"publisher_domain",
"publisher_domains"
]
}
},
{
"anyOf": [
{
"required": [
"publisher_domain"
]
},
{
"required": [
"publisher_domains"
]
}
]
}
],
"additionalProperties": true
},
{
"type": "object",
"description": "Select specific properties by ID",
"description": "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.",
"properties": {
"publisher_domain": {
"type": "string",
"description": "Domain where publisher's adagents.json is hosted (e.g., 'cnn.com')",
"description": "Domain where publisher's adagents.json is hosted (e.g., 'cnn.com').",
"pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$"
},
"selection_type": {
Expand All @@ -59,33 +92,66 @@
},
{
"type": "object",
"description": "Select properties by tag membership",
"description": "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.",
"properties": {
"publisher_domain": {
"type": "string",
"description": "Domain where publisher's adagents.json is hosted (e.g., 'cnn.com')",
"description": "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.",
"pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$"
},
"publisher_domains": {
"type": "array",
"description": "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.",
"items": {
"type": "string",
"pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$"
},
"minItems": 1,
"uniqueItems": true
},
"selection_type": {
"type": "string",
"const": "by_tag",
"description": "Discriminator indicating selection by property tags"
},
"property_tags": {
"type": "array",
"description": "Property tags from the publisher's adagents.json. Selector covers all properties with these tags",
"description": "Property tags resolved against each addressed publisher's adagents.json. Selector covers all properties carrying any of these tags.",
"items": {
"$ref": "property-tag.json"
},
"minItems": 1
}
},
"required": [
"publisher_domain",
"selection_type",
"property_tags"
],
"allOf": [
{
"not": {
"required": [
"publisher_domain",
"publisher_domains"
]
}
},
{
"anyOf": [
{
"required": [
"publisher_domain"
]
},
{
"required": [
"publisher_domains"
]
}
]
}
],
"additionalProperties": true
}
]
}
}
8 changes: 8 additions & 0 deletions src/adcp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,19 @@
from importlib.metadata import version as _pkg_version

from adcp.adagents import (
AdagentsCacheEntry,
AdagentsEntryError,
AdagentsFetchResult,
AdagentsValidationReport,
AdAgentsValidationResult,
AuthorizationContext,
DiscoveryMethod,
EntryErrorKind,
domain_matches,
fetch_adagents,
fetch_adagents_with_cache,
fetch_agent_authorizations,
filter_revoked_selectors,
get_all_properties,
get_all_tags,
get_properties_by_agent,
Expand Down Expand Up @@ -813,13 +817,17 @@ def get_adcp_version() -> str:
"PushNotificationConfig",
# Adagents validation
"AdAgentsValidationResult",
"AdagentsCacheEntry",
"AdagentsEntryError",
"AdagentsFetchResult",
"AdagentsValidationReport",
"AuthorizationContext",
"DiscoveryMethod",
"EntryErrorKind",
"fetch_adagents",
"fetch_adagents_with_cache",
"fetch_agent_authorizations",
"filter_revoked_selectors",
"validate_adagents_domain",
"validate_adagents_structure",
"verify_agent_authorization",
Expand Down
Loading
Loading