Module adcp.decisioning.observed_modes
Process-scoped tracker of explicit Account.mode values returned
from :meth:AccountStore.resolve during framework-side comply-controller
dispatch.
Used by the sandbox gate's deprecated env-fallback path (ADCP_SANDBOX=1)
to fail closed when a process has resolved any explicit live-mode account.
Rationale: the env-fallback exists for back-compat with adopters that
have not yet adopted the per-account mode field. If those adopters
have ALSO begun returning explicit mode='live' from their resolver,
the env var is a misconfiguration — leaving it set would re-open the
gate for live principals after the resolver was meant to close it.
Implicit-default live (resolver returns no mode field, so the
dataclass default 'live' applies) is NOT observed here — those
adopters are exactly who the env-fallback bridge exists for. Only the
deliberate explicit mode (resolver populated mode='...' and stamped
_mode_explicit=True) trips the guard.
Multi-tenant blast radius. This tracker is process-scoped, not
tenant-scoped. In a multi-tenant SaaS process (salesagent-style — many
tenants share one Python process), if ANY tenant's resolver returns an
explicit mode='live' account, every subsequent comply-controller
call that would admit only via ADCP_SANDBOX=1 raises RuntimeError
across ALL tenants. SaaS adopters MUST NOT set ADCP_SANDBOX=1 in
shared dev/staging environments where some tenants may resolve live
accounts; either gate per tenant via mode='sandbox' on the resolved
account or run sandbox tenants in a separate process. JS takes the same
posture; the cross-tenant tripping is intentional (silent admission
under env-fallback would be the worse failure mode) but worth
surfacing.
Mirrors the JS-side
src/lib/server/decisioning/runtime/observed-modes.ts.
Functions
def has_observed_live_mode() ‑> bool-
Expand source code
def has_observed_live_mode() -> bool: """``True`` when this process has observed at least one explicit ``mode='live'`` account from :meth:`AccountStore.resolve`. The sandbox-gate env-fallback consults this to decide whether ``ADCP_SANDBOX=1`` is a safe legacy bridge or a misconfiguration. """ return "live" in _observedTruewhen this process has observed at least one explicitmode='live'account from :meth:AccountStore.resolve.The sandbox-gate env-fallback consults this to decide whether
ADCP_SANDBOX=1is a safe legacy bridge or a misconfiguration. def record_resolved_account_mode(account: Any) ‑> None-
Expand source code
def record_resolved_account_mode(account: Any) -> None: """Record an account returned from :meth:`AccountStore.resolve`. Only counts EXPLICIT mode values — those where the resolver deliberately populated ``mode`` (signaled via the ``_mode_explicit=True`` flag on the framework's :class:`Account` dataclass). Adopters whose resolvers don't yet stamp ``mode`` keep working with the env-fallback bridge: their accounts read as implicit live, which doesn't trip this guard. No-op when ``account`` is ``None`` / not an account-shaped value / lacks an explicit mode flag / mode is not a known string. The set only collects deliberate, resolver-stamped mode values. """ if account is None: return # Only count explicit modes — implicit-default live (legacy adopter # whose resolver didn't populate mode) is the back-compat target the # env-fallback exists for, not a misconfiguration. if not getattr(account, "_mode_explicit", False): return mode = getattr(account, "mode", None) if mode in ("live", "sandbox", "mock"): _observed.add(mode)Record an account returned from :meth:
AccountStore.resolve.Only counts EXPLICIT mode values — those where the resolver deliberately populated
mode(signaled via the_mode_explicit=Trueflag on the framework's :class:Accountdataclass). Adopters whose resolvers don't yet stampmodekeep working with the env-fallback bridge: their accounts read as implicit live, which doesn't trip this guard.No-op when
accountisNone/ not an account-shaped value / lacks an explicit mode flag / mode is not a known string. The set only collects deliberate, resolver-stamped mode values.