Module adcp.decisioning.specialisms.governance
CampaignGovernancePlatform Protocol — covers governance-spend-authority
and governance-delivery-monitor.
A governance agent making runtime decisions for advertiser campaigns
implements the methods on this Protocol. Today's spec splits the
governance-AGENT role across two specialism slugs differing only by
capability (gating spend vs monitoring delivery); both share the
same Protocol surface. When adcontextprotocol/adcp#3329 lands and
the spec consolidates to a single campaign-governance slug, the
underlying type stays unchanged — only the slug map updates.
Mirrors the JS-side CampaignGovernancePlatform interface at
src/lib/server/decisioning/specialisms/campaign-governance.ts.
Distinct from governance-aware-seller. That third
governance-* slug names a SELLER claim — a sales-* archetype
that composes with a buyer's governance agent (calls
check_governance, accepts sync_governance, propagates
approvals/conditions/denials). It does NOT implement
CampaignGovernancePlatform itself; it integrates WITH a platform
that does. The framework's required-method coverage for
governance-aware-seller is therefore unenforced — the slug
remains a "spec-recognized but unenforced" claim until/unless
sync_governance handler shim wiring lands for sales adopters.
Security gate (foundation). Adopters claiming any of the three
governance-* slugs MUST set
DecisioningCapabilities.governance_aware=True AND wire a custom
:class:StateReader that returns real
:data:adcp.decisioning.GovernanceContextJWS values. The
foundation's :func:validate_platform()
fails-fast at server boot if any governance-* slug is claimed
without governance_aware=True. Required-method enforcement
(this PR) AND governance-aware enforcement (foundation) are
INDEPENDENT gates; both fire independently. A platform passing the
required-method gate but with governance_aware=False still
fails server boot — silent governance-gate skipping is a security
regression the framework refuses to ship.
Required methods (every governance-AGENT specialism):
- :meth:
check_governance— runtime decision (approved / denied / conditions). Sync. - :meth:
sync_plans— plan CRUD; buyers push their plans into the agent so it can maintain spend authority + delivery context. - :meth:
report_plan_outcome— buyer/orchestrator outcome reporting after a seller response. Sellers report delivery through execution checks instead. - :meth:
get_plan_audit_logs— chronological audit log read.
Async story: every method is sync at the wire level — none of the
governance response schemas declare a Submitted arm. Slow
approval pipelines (operator review) return current state (e.g.,
status: 'pending') and emit ctx.publish_status_change(
resource_type='plan', ...) when the human decision lands.
Classes
class CampaignGovernancePlatform (*args, **kwargs)-
Expand source code
@runtime_checkable class CampaignGovernancePlatform(Protocol, Generic[TMeta]): """Runtime governance decisioning for advertiser campaigns. A decision API: the agent inspects a proposed action (or running delivery) and returns ``approved``, ``denied``, or ``conditions`` (approved-if). Status changes (plan moving from ``pending_approval`` → ``active`` → ``closed``) flow via ``ctx.publish_status_change(resource_type='plan', ...)``. Methods may be sync (return ``T`` directly) or async (return ``Awaitable[T]``); the dispatch adapter detects via :func:`asyncio.iscoroutinefunction` and runs sync methods on a thread pool. Throw :class:`adcp.decisioning.AdcpError` for buyer-fixable rejection (``PLAN_NOT_FOUND``, ``INVALID_REQUEST``, etc.). Use the :meth:`check_governance` response ``status: 'denied'`` for governance decisions that ARE the answer (the plan exists and the agent is rejecting the action) — that's a legitimate business outcome, not an error. """ def check_governance( self, req: CheckGovernanceRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[CheckGovernanceResponse]: """Runtime governance decision. A buyer sends an intent-shaped request (``target_agent`` plus the exact downstream ``tool`` and ``payload``); an executing service sends an execution-shaped request (opaque ``governance_context`` plus seller-authoritative ``planned_delivery``). Modern responses identify the shape with ``check_type``. Conditions are valid only for ``check_type='intent'``. Execution is a binary approved/denied boundary. Its ``phase`` is ``purchase``, ``modification``, or ``delivery`` and never exposes the buyer's plan or original payload to the service. :raises adcp.decisioning.AdcpError: for buyer-fixable rejection (``PLAN_NOT_FOUND``, ``INVALID_REQUEST``). ``status: 'denied'`` on the response is the governance-decision-as-answer path — not an error. """ ... def sync_plans( self, req: SyncPlansRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[SyncPlansResponse]: """Plan CRUD with delta upsert semantics. Buyers sync their campaign plans into the governance agent so the agent can maintain spend authority + delivery context. The agent tracks plan state across the campaign lifecycle (pending_approval → active → closed); transitions are emitted via ``ctx.publish_status_change(resource_type='plan', ...)``. """ ... def report_plan_outcome( self, req: ReportPlanOutcomeRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ReportPlanOutcomeResponse]: """Buyer/orchestrator outcome reporting after a seller response. Completed and failed reports settle the exact approved action; buyer-attributed delivery observations reconcile a prior seller delivery check. Sellers themselves report canonical delivery through ``check_governance(phase='delivery')``. """ ... def get_plan_audit_logs( self, req: GetPlanAuditLogsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetPlanAuditLogsResponse]: """Audit log read. Returns the chronological history of governance decisions + outcome reports for a plan. Buyers and operators use this to reconstruct who approved what + when, what conditions were attached, and what the seller reported. """ ...Runtime governance decisioning for advertiser campaigns.
A decision API: the agent inspects a proposed action (or running delivery) and returns
approved,denied, orconditions(approved-if). Status changes (plan moving frompending_approval→active→closed) flow viactx.publish_status_change(resource_type='plan', ...).Methods may be sync (return
Tdirectly) or async (returnAwaitable[T]); the dispatch adapter detects via :func:asyncio.iscoroutinefunctionand runs sync methods on a thread pool.Throw :class:
AdcpErrorfor buyer-fixable rejection (PLAN_NOT_FOUND,INVALID_REQUEST, etc.). Use the :meth:check_governanceresponsestatus: 'denied'for governance decisions that ARE the answer (the plan exists and the agent is rejecting the action) — that's a legitimate business outcome, not an error.Ancestors
- typing.Protocol
- typing.Generic
Methods
def check_governance(self, req: CheckGovernanceRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[CheckGovernanceResponse]-
Expand source code
def check_governance( self, req: CheckGovernanceRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[CheckGovernanceResponse]: """Runtime governance decision. A buyer sends an intent-shaped request (``target_agent`` plus the exact downstream ``tool`` and ``payload``); an executing service sends an execution-shaped request (opaque ``governance_context`` plus seller-authoritative ``planned_delivery``). Modern responses identify the shape with ``check_type``. Conditions are valid only for ``check_type='intent'``. Execution is a binary approved/denied boundary. Its ``phase`` is ``purchase``, ``modification``, or ``delivery`` and never exposes the buyer's plan or original payload to the service. :raises adcp.decisioning.AdcpError: for buyer-fixable rejection (``PLAN_NOT_FOUND``, ``INVALID_REQUEST``). ``status: 'denied'`` on the response is the governance-decision-as-answer path — not an error. """ ...Runtime governance decision.
A buyer sends an intent-shaped request (
target_agentplus the exact downstreamtoolandpayload); an executing service sends an execution-shaped request (opaquegovernance_contextplus seller-authoritativeplanned_delivery). Modern responses identify the shape withcheck_type.Conditions are valid only for
check_type='intent'. Execution is a binary approved/denied boundary. Itsphaseispurchase,modification, ordeliveryand never exposes the buyer's plan or original payload to the service.:raises adcp.decisioning.AdcpError: for buyer-fixable rejection (
PLAN_NOT_FOUND,INVALID_REQUEST).status: 'denied'on the response is the governance-decision-as-answer path — not an error. def get_plan_audit_logs(self, req: GetPlanAuditLogsRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[GetPlanAuditLogsResponse]-
Expand source code
def get_plan_audit_logs( self, req: GetPlanAuditLogsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetPlanAuditLogsResponse]: """Audit log read. Returns the chronological history of governance decisions + outcome reports for a plan. Buyers and operators use this to reconstruct who approved what + when, what conditions were attached, and what the seller reported. """ ...Audit log read.
Returns the chronological history of governance decisions + outcome reports for a plan. Buyers and operators use this to reconstruct who approved what + when, what conditions were attached, and what the seller reported.
def report_plan_outcome(self, req: ReportPlanOutcomeRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[ReportPlanOutcomeResponse]-
Expand source code
def report_plan_outcome( self, req: ReportPlanOutcomeRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ReportPlanOutcomeResponse]: """Buyer/orchestrator outcome reporting after a seller response. Completed and failed reports settle the exact approved action; buyer-attributed delivery observations reconcile a prior seller delivery check. Sellers themselves report canonical delivery through ``check_governance(phase='delivery')``. """ ...Buyer/orchestrator outcome reporting after a seller response.
Completed and failed reports settle the exact approved action; buyer-attributed delivery observations reconcile a prior seller delivery check. Sellers themselves report canonical delivery through
check_governance(phase='delivery'). def sync_plans(self, req: SyncPlansRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[SyncPlansResponse]-
Expand source code
def sync_plans( self, req: SyncPlansRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[SyncPlansResponse]: """Plan CRUD with delta upsert semantics. Buyers sync their campaign plans into the governance agent so the agent can maintain spend authority + delivery context. The agent tracks plan state across the campaign lifecycle (pending_approval → active → closed); transitions are emitted via ``ctx.publish_status_change(resource_type='plan', ...)``. """ ...Plan CRUD with delta upsert semantics.
Buyers sync their campaign plans into the governance agent so the agent can maintain spend authority + delivery context. The agent tracks plan state across the campaign lifecycle (pending_approval → active → closed); transitions are emitted via
ctx.publish_status_change(resource_type='plan', ...).