Module adcp.server.governance_enforcement
Governance authorization middleware for MCP and A2A service handlers.
Functions
def make_governance_enforcement_middleware(*,
tasks: Collection[str],
expected_audience: str,
resolve_issuer_jwks: IssuerJwksResolver,
idempotency_store: IdempotencyStore,
replay_store: GovernanceReplayStore,
resolve_enforcement: EnforcementResolver,
resolve_caller: CallerResolver | None = None,
on_authorized: AuthorizationCallback | None = None,
on_rejected: RejectionCallback | None = None,
**verifier_options: Any) ‑> Callable[[str, dict[str, typing.Any], ToolContext, Callable[[], Awaitable[typing.Any]]], Awaitable[typing.Any]]-
Expand source code
def make_governance_enforcement_middleware( *, tasks: Collection[str], expected_audience: str, resolve_issuer_jwks: IssuerJwksResolver, idempotency_store: IdempotencyStore, replay_store: GovernanceReplayStore, resolve_enforcement: EnforcementResolver, resolve_caller: CallerResolver | None = None, on_authorized: AuthorizationCallback | None = None, on_rejected: RejectionCallback | None = None, **verifier_options: Any, ) -> SkillMiddleware: """Build a fail-closed governed-service middleware. Verification and atomic replay consumption finish before ``call_next`` can invoke the handler. ``resolve_enforcement`` must hold the authoritative resource revision/write lock from applicability and commitment resolution through ``call_next``; it must not trust buyer-provided state. The default caller resolver reads ``ToolContext.caller_identity``. """ if isinstance(tasks, (str, bytes)): raise TypeError("tasks must be a collection of task names, not a string") governed_tasks = frozenset(tasks) if not governed_tasks or any(not isinstance(task, str) or not task for task in governed_tasks): raise ValueError("tasks must contain at least one non-empty task name") if not expected_audience: raise ValueError("expected_audience is required") if "expected_phase" in verifier_options or "expected_media_buy_id" in verifier_options: raise ValueError("the inbound enforcement gate always verifies intent-phase tokens") async def middleware( skill_name: str, params: dict[str, Any], context: ToolContext, call_next: Callable[[], Awaitable[Any]], ) -> Any: if skill_name not in governed_tasks: return await call_next() async def verify_then_call( _handler_self: object, _params: Mapping[str, Any], _context: ToolContext, ) -> Any: token = params.get("governance_context") async with resolve_enforcement(skill_name, params, context) as enforcement: if not isinstance(enforcement, GovernanceEnforcementDecision): raise TypeError("resolve_enforcement must yield GovernanceEnforcementDecision") if type(enforcement.required) is not bool: raise TypeError("GovernanceEnforcementDecision.required must be a boolean") # A supplied token is always verified, even when current account # registration or an authoritative risk-reducing delta says that # governance is not required for this invocation. if not enforcement.required and token is None: return await call_next() caller = await _maybe_await( resolve_caller(context) if resolve_caller is not None else context.caller_identity ) claimed_issuer = read_governance_authorization_issuer(token) jwks = ( await _maybe_await( resolve_issuer_jwks(claimed_issuer, skill_name, params, context) ) if claimed_issuer is not None else None ) if claimed_issuer is None or jwks is None: result: GovernanceAuthorizationFailure | GovernanceAuthorizationSuccess = ( GovernanceAuthorizationFailure( False, "governance_key_unknown", "issuer is not authorized by the authenticated buyer", ) ) elif enforcement.commitment is None: result = GovernanceAuthorizationFailure( False, "governance_token_invalid", "governed operation has no authoritative commitment", ) else: result = await verify_governance_authorization( token=token, expected_issuer=claimed_issuer, expected_audience=expected_audience, authenticated_caller=caller or "", expected_task=skill_name, payload=params, actual_commitment=enforcement.commitment, jwks=jwks, replay_store=replay_store, expected_phase="intent", **verifier_options, ) if not result.ok: if on_rejected is not None: await _maybe_await(on_rejected(result, context)) # Keep server package initialization independent of the # decisioning graph; that graph imports webhook helpers # which may already be mid-import in standalone consumers. from adcp.decisioning.errors import PermissionDeniedError raise PermissionDeniedError( message="Governance authorization rejected.", field="governance_context", suggestion="Obtain a fresh authorization for this exact request.", ) if on_authorized is not None: await _maybe_await(on_authorized(result, context)) return await call_next() # Idempotency is the outer gate: a completed identical retry returns # the cached result even after token expiry; a miss holds the request # key through verification, side effect, and cache commit. Bind the # resolved skill into the internal hash input so one key cannot replay # a different task whose parameter object happens to be identical. guarded = idempotency_store.wrap(verify_then_call) idempotency_params = {**params, "__adcp_governance_skill": skill_name} return await guarded(None, idempotency_params, context) return middlewareBuild a fail-closed governed-service middleware.
Verification and atomic replay consumption finish before
call_nextcan invoke the handler.resolve_enforcementmust hold the authoritative resource revision/write lock from applicability and commitment resolution throughcall_next; it must not trust buyer-provided state. The default caller resolver readsToolContext.caller_identity.
Classes
class GovernanceEnforcementDecision (required: bool,
commitment: GovernanceCommitment | Mapping[str, Any] | None = None)-
Expand source code
@dataclass(frozen=True) class GovernanceEnforcementDecision: """Authoritative applicability and commitment held under one lease.""" required: bool commitment: GovernanceCommitment | Mapping[str, Any] | None = NoneAuthoritative applicability and commitment held under one lease.
Instance variables
var commitment : GovernanceCommitment | collections.abc.Mapping[str, typing.Any] | Nonevar required : bool