from __future__ import annotations from decimal import Decimal from app.services.automation_eligibility import ( AutomationEligibilityCalculator, AutomationEligibilityInput, AutomationLevel, AutomationPolicy, ) def _enabled_policy(**updates) -> AutomationPolicy: values = { "enabled": True, "enterprise_max_level": AutomationLevel.L4_LOW_RISK_STRAIGHT_THROUGH, "enterprise_whitelist": frozenset({"expense.prefill_fields"}), "amount_cap": Decimal("1000"), "min_confidence": 0.95, "min_evidence_completeness": 0.95, "min_historical_precision": 0.98, "min_historical_samples": 100, "minimum_sampling_rate": 0.1, "release_stage": "active", } values.update(updates) return AutomationPolicy(**values) def _qualified_input(**updates) -> AutomationEligibilityInput: values = { "action_key": "expense.prefill_fields", "action_risk": "low", "amount": Decimal("0"), "confidence": 0.99, "evidence_completeness": 1.0, "reversible": True, "historical_precision": 0.995, "historical_samples": 1000, "sampling_rate": 0.2, } values.update(updates) return AutomationEligibilityInput(**values) def test_default_policy_is_shadow_only() -> None: decision = AutomationEligibilityCalculator().calculate(_qualified_input()) assert decision.eligible_level == AutomationLevel.L1_RECOMMEND assert decision.eligible_for_auto_execution is False assert decision.requires_human_confirmation is True def test_hard_whitelist_cannot_be_extended_by_enterprise() -> None: decision = AutomationEligibilityCalculator().calculate( _qualified_input(action_key="expense.unknown_action"), _enabled_policy(enterprise_whitelist=frozenset({"expense.unknown_action"})), ) assert decision.eligible_level == AutomationLevel.L1_RECOMMEND assert "action_not_in_hard_whitelist" in decision.reasons def test_payment_and_high_risk_policy_actions_never_auto_execute() -> None: calculator = AutomationEligibilityCalculator() payment = calculator.calculate( _qualified_input(action_key="expense.execute_payment", moves_money=True), _enabled_policy(enterprise_whitelist=frozenset({"expense.execute_payment"})), ) policy_change = calculator.calculate( _qualified_input( action_key="expense.publish_policy", changes_policy=True, action_risk="high", ), _enabled_policy(enterprise_whitelist=frozenset({"expense.publish_policy"})), ) assert payment.eligible_level == AutomationLevel.L5_HUMAN_CONTROLLED assert policy_change.eligible_level == AutomationLevel.L5_HUMAN_CONTROLLED assert not payment.eligible_for_auto_execution assert not policy_change.eligible_for_auto_execution def test_all_controls_can_reach_enterprise_cap() -> None: decision = AutomationEligibilityCalculator().calculate( _qualified_input(), _enabled_policy(enterprise_max_level=AutomationLevel.L3_REVERSIBLE_AUTO), ) assert decision.eligible_level == AutomationLevel.L3_REVERSIBLE_AUTO assert decision.eligible_for_auto_execution is True assert "clamped_by_enterprise_level" in decision.reasons def test_each_safety_gap_clamps_to_human_confirmation() -> None: calculator = AutomationEligibilityCalculator() candidates = [ _qualified_input(amount=Decimal("1001")), _qualified_input(action_risk="medium"), _qualified_input(reversible=False), _qualified_input(confidence=0.7), _qualified_input(evidence_completeness=0.7), _qualified_input(historical_precision=None), _qualified_input(historical_samples=10), ] for candidate in candidates: decision = calculator.calculate(candidate, _enabled_policy()) assert decision.eligible_level <= AutomationLevel.L2_PREFILL assert decision.requires_human_confirmation is True def test_low_sampling_rate_prevents_all_auto_execution() -> None: decision = AutomationEligibilityCalculator().calculate( _qualified_input(sampling_rate=0.01), _enabled_policy(), ) assert decision.eligible_level == AutomationLevel.L2_PREFILL assert decision.eligible_for_auto_execution is False assert "sampling_rate_below_threshold" in decision.reasons def test_invalid_amount_and_enterprise_l0_cap_fail_closed() -> None: calculator = AutomationEligibilityCalculator() invalid_amount = calculator.calculate( _qualified_input(amount=Decimal("NaN")), _enabled_policy(), ) human_only = calculator.calculate( _qualified_input(), _enabled_policy(enterprise_max_level=AutomationLevel.L0_EXPLAIN), ) assert invalid_amount.eligible_level == AutomationLevel.L2_PREFILL assert "amount_invalid" in invalid_amount.reasons assert human_only.eligible_level == AutomationLevel.L0_EXPLAIN def test_even_qualified_action_stays_non_executing_in_shadow() -> None: decision = AutomationEligibilityCalculator().calculate( _qualified_input(), _enabled_policy(release_stage="shadow"), ) assert decision.eligible_level == AutomationLevel.L4_LOW_RISK_STRAIGHT_THROUGH assert decision.mode == "shadow_low_risk_straight_through" assert decision.eligible_for_auto_execution is False def test_malformed_levels_and_sample_counts_fail_closed_without_runtime_error() -> None: decision = AutomationEligibilityCalculator().calculate( _qualified_input(historical_samples="not-a-number"), _enabled_policy( enterprise_max_level="not-a-level", min_historical_samples="not-a-count", ), ) assert decision.eligible_level == AutomationLevel.L1_RECOMMEND assert decision.eligible_for_auto_execution is False assert decision.checks["historical_samples"] == 0 assert decision.checks["historical_samples_valid"] is False assert decision.checks["minimum_historical_samples_valid"] is False assert decision.checks["enterprise_max_level"] == 1