Add tenant-safe value, telemetry, connector, commercial, and production-readiness foundations.
167 lines
6.0 KiB
Python
167 lines
6.0 KiB
Python
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
|