Skip to content

Commit 1dfdcd5

Browse files
bokelleyclaude
andauthored
feat(decisioning): property_list resolver + intersection helper for get_products (#500)
* feat(decisioning): property_list resolver + intersection helper for get_products Closes #494 Adds capability-gated framework support for buyer-side property_list filtering in get_products, mirroring the webhook_emit post-adapter pattern. When Features(property_list_filtering=True) is declared and a PropertyListFetcher is wired, the framework fetches the buyer's authorized property IDs and filters the platform's product list post-adapter, setting response.property_list_applied=True. https://claude.ai/code/session_01GqPV3vkvFdC6DesCzwKFNQ * fixup(decisioning/property_list): address pre-PR review blockers - Remove {exc} interpolation from wire error message to prevent auth_token / credential leakage through upstream exception reprs - Extract property_list_capability_enabled() helper to eliminate duplicate getattr chains in handler.py and serve.py - Export validate_property_list_config and property_list_capability_enabled from adcp.decisioning public __all__ so self-managed adopters can call the boot guard themselves - Add two tests for by_id with empty property_ids (strict + permissive) confirming the vacuous-subset guard works https://claude.ai/code/session_01GqPV3vkvFdC6DesCzwKFNQ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 524c608 commit 1dfdcd5

5 files changed

Lines changed: 863 additions & 0 deletions

File tree

src/adcp/decisioning/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ def create_media_buy(
114114
DecisioningPlatform,
115115
)
116116
from adcp.decisioning.platform_router import PlatformRouter
117+
from adcp.decisioning.property_list import (
118+
PropertyListFetcher,
119+
filter_products_by_property_list,
120+
property_list_capability_enabled,
121+
resolve_property_list,
122+
validate_property_list_config,
123+
)
117124
from adcp.decisioning.registry import (
118125
ApiKeyCredential,
119126
BillingMode,
@@ -298,9 +305,14 @@ def __init__(self, *args: object, **kwargs: object) -> None:
298305
"PostgresTaskRegistry",
299306
"Proposal",
300307
"PropertyList",
308+
"PropertyListFetcher",
301309
"PropertyListReference",
302310
"ProductConfigStore",
311+
"property_list_capability_enabled",
303312
"PropertyListsPlatform",
313+
"filter_products_by_property_list",
314+
"resolve_property_list",
315+
"validate_property_list_config",
304316
"RateLimitedBuyerAgentRegistry",
305317
"RateLimitedError",
306318
"RequestContext",

src/adcp/decisioning/handler.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@
4545
)
4646
from adcp.decisioning.implementation_config import ProductConfigStore
4747
from adcp.decisioning.pagination import _query_hash, apply_framework_pagination
48+
from adcp.decisioning.property_list import (
49+
maybe_apply_property_list_filter,
50+
property_list_capability_enabled,
51+
)
4852
from adcp.decisioning.webhook_emit import maybe_emit_sync_completion
4953
from adcp.server.base import ADCPHandler, ToolContext
5054

@@ -151,6 +155,7 @@
151155
from concurrent.futures import ThreadPoolExecutor
152156

153157
from adcp.decisioning.platform import DecisioningPlatform
158+
from adcp.decisioning.property_list import PropertyListFetcher
154159
from adcp.decisioning.registry import BuyerAgent, BuyerAgentRegistry
155160
from adcp.decisioning.resolve import ResourceResolver
156161
from adcp.decisioning.state import StateReader
@@ -696,6 +701,7 @@ def __init__(
696701
auto_emit_completion_webhooks: bool = True,
697702
buyer_agent_registry: BuyerAgentRegistry | None = None,
698703
config_store: ProductConfigStore | None = None,
704+
property_list_fetcher: PropertyListFetcher | None = None,
699705
) -> None:
700706
super().__init__()
701707
self._platform = platform
@@ -708,6 +714,7 @@ def __init__(
708714
self._auto_emit_completion_webhooks = auto_emit_completion_webhooks
709715
self._buyer_agent_registry = buyer_agent_registry
710716
self._config_store = config_store
717+
self._property_list_fetcher = property_list_fetcher
711718

712719
# Cache whether the platform's create_media_buy accepts 'configs'
713720
# so we only pay the inspect.signature cost at construction time.
@@ -1076,6 +1083,16 @@ async def get_products( # type: ignore[override]
10761083
registry=self._registry,
10771084
),
10781085
)
1086+
# Post-adapter: capability-gated property-list filter.
1087+
response = cast(
1088+
"GetProductsResponse",
1089+
await maybe_apply_property_list_filter(
1090+
params=params,
1091+
response=response,
1092+
fetcher=self._property_list_fetcher,
1093+
capability_enabled=property_list_capability_enabled(self._platform),
1094+
),
1095+
)
10791096
if self._platform.capabilities.auto_paginate and params.pagination is not None:
10801097
response = cast(
10811098
"GetProductsResponse",

0 commit comments

Comments
 (0)