Skip to content

Commit dcda500

Browse files
authored
feat(media-buy): resolve acceptance policy catalogs (#1097)
1 parent 5580279 commit dcda500

12 files changed

Lines changed: 2271 additions & 1 deletion

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Seller acceptance-policy discovery
2+
3+
AdCP 3.2 sellers can advertise an `acceptance_policy_discovery` catalog in
4+
their media-buy capabilities. Products can add
5+
`acceptance_policy_profile_ids`. The Python SDK can resolve those profiles and
6+
produce a conservative advisory assessment before a buyer sends a task.
7+
8+
Discovery never replaces the seller's task response. An `allowed` assessment
9+
means only that the verified, published profiles allow the contemplated action.
10+
An absent catalog, a partial profile, an unavailable registry policy, a missing
11+
buyer fact, or an invalid digest produces `unknown` rather than permission.
12+
13+
```python
14+
from adcp import AcceptancePolicyOutcome, AcceptancePolicyResolver
15+
16+
discovery = capabilities.media_buy.acceptance_policy_discovery
17+
product_profiles = product.acceptance_policy_profile_ids or []
18+
19+
async with AcceptancePolicyResolver() as resolver:
20+
assessment = await resolver.assess(
21+
discovery,
22+
{
23+
"subjects": [
24+
{
25+
"subject_category": "political",
26+
"subject_facets": ["candidate_election"],
27+
}
28+
],
29+
"advertiser_roles": ["political_actor"],
30+
"delivery_jurisdictions": ["US"],
31+
},
32+
applies_to="media_buy",
33+
product_profile_ids=product_profiles,
34+
cache_ttl_seconds=capabilities.capability_changes.cache_ttl_seconds,
35+
capabilities_version=capabilities.capability_changes.capabilities_version,
36+
)
37+
38+
if assessment.outcome is AcceptancePolicyOutcome.prohibited:
39+
# Do not submit this configuration.
40+
...
41+
elif assessment.outcome is AcceptancePolicyOutcome.unknown:
42+
# Ask the seller or submit the exact task and handle its authoritative result.
43+
...
44+
```
45+
46+
The other outcomes identify the coarse next step:
47+
48+
- `requires_disclosure`: add a declaration, disclosure, funding statement, or
49+
transparency information described by the typed requirements.
50+
- `requires_setup`: complete advertiser verification, eligibility, account
51+
setup, licensing, or certification.
52+
- `requires_review`: obtain authorization or seller review, or satisfy a typed
53+
targeting, creative, destination, format, or time restriction.
54+
55+
Do not execute the free-text `description` fields in catalogs, profiles, rules,
56+
or policies. They are display-only. Use typed rule dimensions and requirements;
57+
the exact obligations remain in the digest-pinned registry policies.
58+
59+
## Cache and invalidation
60+
61+
Pass only the TTL advertised by `capability_changes.cache_ttl_seconds`. The
62+
resolver keys cached catalogs by canonical URL and exact digest, bounds the
63+
cache, and never caches failures. Call `invalidate_capabilities()` after a
64+
`capabilities.changed` notification, then re-read seller capabilities before
65+
assessing again.
66+
67+
Catalog requests use HTTPS with no caller credentials, no redirects, an
68+
IP-pinned public-address transport, a five-second default timeout, and a 1 MiB
69+
decoded-body limit. The catalog digest is checked against the exact decoded
70+
representation bytes before JSON parsing or schema validation. Local profile
71+
digests and registry policy content use RFC 8785 JCS as specified by AdCP.

src/adcp/__init__.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ def _resolve_version() -> str:
9292
# names and caches the result. Grouped by module (and mirrored verbatim in the
9393
# ``TYPE_CHECKING`` block at the bottom) so it stays readable and auditable.
9494
_LAZY_MODULES: dict[str, tuple[str, ...]] = {
95+
"adcp.acceptance": (
96+
"AcceptancePolicyAssessment",
97+
"AcceptancePolicyDiagnostic",
98+
"AcceptancePolicyDiagnosticCode",
99+
"AcceptancePolicyOutcome",
100+
"AcceptancePolicyRegistry",
101+
"AcceptancePolicyResolution",
102+
"AcceptancePolicyResolver",
103+
"AcceptancePolicySurface",
104+
"DEFAULT_ACCEPTANCE_CATALOG_CACHE_ENTRIES",
105+
"DEFAULT_ACCEPTANCE_CATALOG_MAX_BYTES",
106+
"DEFAULT_ACCEPTANCE_CATALOG_TIMEOUT_SECONDS",
107+
),
95108
"adcp.adagents": (
96109
"AdagentsCacheEntry",
97110
"AdagentsEntryError",
@@ -238,6 +251,15 @@ def _resolve_version() -> str:
238251
"AcceptProposalRequest",
239252
"AcceptProposalResponse",
240253
"AcceptedLoss",
254+
"AcceptanceContext",
255+
"AcceptancePolicyCatalog",
256+
"AcceptancePolicyDiscovery",
257+
"AcceptancePolicyProfile",
258+
"AcceptancePolicyProfileId",
259+
"AcceptancePolicyProfileIds",
260+
"AcceptancePolicyRequirement",
261+
"AcceptancePolicyRule",
262+
"RegistryAcceptancePolicyProfileReference",
241263
"BuyProductsRequest",
242264
"BuyProductsResponse",
243265
"ControlMediaBuyRequest",
@@ -880,6 +902,27 @@ def get_adcp_version() -> str:
880902
"get_tracer",
881903
"inject_trace_headers",
882904
"is_tracing_available",
905+
# Seller acceptance-policy discovery and advisory assessment
906+
"AcceptancePolicyAssessment",
907+
"AcceptancePolicyDiagnostic",
908+
"AcceptancePolicyDiagnosticCode",
909+
"AcceptancePolicyOutcome",
910+
"AcceptancePolicyRegistry",
911+
"AcceptancePolicyResolution",
912+
"AcceptancePolicyResolver",
913+
"AcceptancePolicySurface",
914+
"DEFAULT_ACCEPTANCE_CATALOG_CACHE_ENTRIES",
915+
"DEFAULT_ACCEPTANCE_CATALOG_MAX_BYTES",
916+
"DEFAULT_ACCEPTANCE_CATALOG_TIMEOUT_SECONDS",
917+
"AcceptanceContext",
918+
"AcceptancePolicyCatalog",
919+
"AcceptancePolicyDiscovery",
920+
"AcceptancePolicyProfile",
921+
"AcceptancePolicyProfileId",
922+
"AcceptancePolicyProfileIds",
923+
"AcceptancePolicyRequirement",
924+
"AcceptancePolicyRule",
925+
"RegistryAcceptancePolicyProfileReference",
883926
# Buyer OAuth authorization-code helpers
884927
"InMemoryPendingOAuthFlowStore",
885928
"OAuthAuthorizationError",
@@ -1526,6 +1569,19 @@ def get_adcp_version() -> str:
15261569
if TYPE_CHECKING:
15271570
# Eager re-exports for type checkers / IDEs. Resolved lazily at runtime
15281571
# via ``__getattr__`` so ``import adcp`` does not build the schema graph.
1572+
from adcp.acceptance import ( # noqa: F401
1573+
DEFAULT_ACCEPTANCE_CATALOG_CACHE_ENTRIES,
1574+
DEFAULT_ACCEPTANCE_CATALOG_MAX_BYTES,
1575+
DEFAULT_ACCEPTANCE_CATALOG_TIMEOUT_SECONDS,
1576+
AcceptancePolicyAssessment,
1577+
AcceptancePolicyDiagnostic,
1578+
AcceptancePolicyDiagnosticCode,
1579+
AcceptancePolicyOutcome,
1580+
AcceptancePolicyRegistry,
1581+
AcceptancePolicyResolution,
1582+
AcceptancePolicyResolver,
1583+
AcceptancePolicySurface,
1584+
)
15291585
from adcp.adagents import (
15301586
AdagentsCacheEntry,
15311587
AdagentsEntryError,
@@ -1657,6 +1713,30 @@ def get_adcp_version() -> str:
16571713
test_agent_client,
16581714
test_agent_no_auth,
16591715
)
1716+
from adcp.types import (
1717+
AcceptanceContext as AcceptanceContext,
1718+
)
1719+
from adcp.types import (
1720+
AcceptancePolicyCatalog as AcceptancePolicyCatalog,
1721+
)
1722+
from adcp.types import (
1723+
AcceptancePolicyDiscovery as AcceptancePolicyDiscovery,
1724+
)
1725+
from adcp.types import (
1726+
AcceptancePolicyProfile as AcceptancePolicyProfile,
1727+
)
1728+
from adcp.types import (
1729+
AcceptancePolicyProfileId as AcceptancePolicyProfileId,
1730+
)
1731+
from adcp.types import (
1732+
AcceptancePolicyProfileIds as AcceptancePolicyProfileIds,
1733+
)
1734+
from adcp.types import (
1735+
AcceptancePolicyRequirement as AcceptancePolicyRequirement,
1736+
)
1737+
from adcp.types import (
1738+
AcceptancePolicyRule as AcceptancePolicyRule,
1739+
)
16601740
from adcp.types import (
16611741
AcceptedLoss,
16621742
AcceptProposalRequest,
@@ -1973,6 +2053,9 @@ def get_adcp_version() -> str:
19732053
ZipAsset,
19742054
aliases,
19752055
)
2056+
from adcp.types import (
2057+
RegistryAcceptancePolicyProfileReference as RegistryAcceptancePolicyProfileReference,
2058+
)
19762059
from adcp.types import _generated as generated
19772060
from adcp.types.aliases import (
19782061
AccountReferenceById,

0 commit comments

Comments
 (0)