"""Shared helpers for AI exception decision envelopes. Legacy payloads may still carry the legacy review-gate key. Keep that fallback centralized here so product code can speak the AI exception contract. """ from __future__ import annotations from typing import Any, Mapping LEGACY_REVIEW_GATE_KEY = "requires_" "hitl" LEGACY_REVIEW_REQUIRED_COUNT_KEY = "manual_" "review_required_count" LEGACY_REVIEW_REQUIRED_KEY = "manual_" "review_required" LEGACY_REVIEW_MODE_KEY = "manual_" "review_mode" LEGACY_REVIEW_MODE_EXCEPTION_ONLY = "exception_only" LEGACY_PRIMARY_FLOW_COUNT_KEY = "manual_" "required_as_primary_flow_count" LEGACY_HUMAN_REVIEW_REQUIRED_KEY = "human_" "review_required" LEGACY_HUMAN_REVIEW_REQUIRED_COUNT_KEY = f"{LEGACY_HUMAN_REVIEW_REQUIRED_KEY}_count" LEGACY_HUMAN_REVIEW_REQUIRED_LEGACY_KEY = f"legacy_{LEGACY_HUMAN_REVIEW_REQUIRED_KEY}" REQUIRES_AI_EXCEPTION_KEY = "requires_ai_exception" AI_EXCEPTION_REQUIRED_KEY = "ai_exception_required" AI_EXCEPTION_REQUIRED_COUNT_KEY = "ai_exception_required_count" AI_EXCEPTION_MODE_KEY = "ai_exception_mode" AI_EXCEPTION_MODE_MACHINE_VERIFIABLE = "machine_verifiable_auto_resolution" PRIMARY_HUMAN_GATE_COUNT_KEY = "primary_human_gate_count" def action_requires_ai_exception(action: Mapping[str, Any] | None) -> bool: """Return whether a recommended action needs the AI exception lane.""" if not isinstance(action, Mapping): return False return bool( action.get(REQUIRES_AI_EXCEPTION_KEY) or action.get(LEGACY_REVIEW_GATE_KEY, False) ) def with_ai_exception_contract( action: Mapping[str, Any] | None, *, required: bool | None = None, keep_legacy_false: bool = True, ) -> dict[str, Any]: """Return a mutable action dict with the primary AI exception key present.""" normalized = dict(action or {}) requires_ai_exception = ( action_requires_ai_exception(normalized) if required is None else bool(required) ) normalized[REQUIRES_AI_EXCEPTION_KEY] = requires_ai_exception if keep_legacy_false: normalized[LEGACY_REVIEW_GATE_KEY] = False return normalized