"""Unit tests for the additive router budget gate. The gate reads a session's accumulated spend (assembled by the router step and passed in as plain data) and, when spend crosses a configured limit, either warns (annotate + log, tier UNCHANGED) or caps (lower the tier). The default state — no configured limit — is a complete no-op, and an unknown spend/price SUSPENDS rather than acting on missing data. Everything here runs on plain dataclasses: no classifier bundle, no TurnContext, no network, no session storage. Spend figures are synthetic. Old-vs-new parity of the full pipeline with the gate off is pinned separately by ``test_routing_policy_parity.py``; the paired-run below re-proves it at the engine level (default/None budget == a suspended budget == no gate). """ from __future__ import annotations from types import SimpleNamespace import pytest from opensquilla.engine.routing import ( BudgetGateInput, PolicyInputs, RoutingDecision, RoutingPolicyEngine, apply_budget_gate, budget_gate, route_class_for_tier, ) from opensquilla.engine.steps.squilla_router import _session_accumulated_spend VALID_TIERS = ["c0", "c1", "c2", "c3"] TIERS = { "c0": {"model": "dummy-nano-1"}, "c1": {"model": "dummy-mini-1"}, "c2": {"model": "dummy-pro-1"}, "c3": {"model": "dummy-max-1"}, } def budget_input(**overrides: object) -> BudgetGateInput: knobs: dict[str, object] = { "action": "warn", "limit_usd": 1.0, "spend_usd": 5.0, "spend_source": "billed", "session_key": "agent:budget:main", } knobs.update(overrides) return BudgetGateInput(**knobs) # type: ignore[arg-type] # --------------------------------------------------------------------------- # budget_gate — pure decision logic # --------------------------------------------------------------------------- def test_off_when_input_none() -> None: result = budget_gate("c2", valid_tiers=VALID_TIERS, budget=None) assert (result.tier, result.outcome) == ("c2", "off") def test_warn_over_limit_keeps_tier() -> None: result = budget_gate("c2", valid_tiers=VALID_TIERS, budget=budget_input(spend_usd=5.0)) assert (result.tier, result.outcome, result.action) == ("c2", "warn", "warn") assert result.spend_usd == 5.0 assert result.limit_usd == 1.0 def test_under_limit_is_noop() -> None: result = budget_gate("c2", valid_tiers=VALID_TIERS, budget=budget_input(spend_usd=0.5)) assert (result.tier, result.outcome) == ("c2", "under_limit") def test_at_limit_boundary_is_noop() -> None: # spend == limit is not "exceeds": stay under. result = budget_gate("c2", valid_tiers=VALID_TIERS, budget=budget_input(spend_usd=1.0)) assert result.outcome == "under_limit" def test_cap_lowers_to_cap_tier() -> None: result = budget_gate( "c3", valid_tiers=VALID_TIERS, budget=budget_input(action="cap", cap_tier="c0", spend_usd=9.0), ) assert (result.tier, result.outcome, result.from_tier) == ("c0", "cap", "c3") def test_cap_never_raises_when_target_at_or_above_current() -> None: # Working tier c0, cap target c2: capping would RAISE — degrade to warn. result = budget_gate( "c0", valid_tiers=VALID_TIERS, budget=budget_input(action="cap", cap_tier="c2", spend_usd=9.0), ) assert (result.tier, result.outcome) == ("c0", "warn") def test_cap_with_unknown_target_degrades_to_warn() -> None: result = budget_gate( "c3", valid_tiers=VALID_TIERS, budget=budget_input(action="cap", cap_tier="zz_unknown", spend_usd=9.0), ) assert (result.tier, result.outcome) == ("c3", "warn") def test_suspends_on_unknown_spend() -> None: # No billed/estimated total yet (or a required price could not be priced): # never act on missing data. result = budget_gate( "c2", valid_tiers=VALID_TIERS, budget=budget_input(spend_usd=None, spend_source="unknown"), ) assert (result.tier, result.outcome) == ("c2", "suspended") def test_cap_suspends_on_unknown_spend() -> None: # A cap action must also suspend on unknown spend — no downgrade on a guess. result = budget_gate( "c3", valid_tiers=VALID_TIERS, budget=budget_input(action="cap", cap_tier="c0", spend_usd=None), ) assert (result.tier, result.outcome) == ("c3", "suspended") def test_forward_estimate_pushes_over_limit() -> None: # spend alone (0.9) is under; spend + estimate (1.1) crosses. result = budget_gate( "c2", valid_tiers=VALID_TIERS, budget=budget_input(spend_usd=0.9, estimate_usd=0.2), ) assert result.outcome == "warn" assert result.projected_usd == pytest.approx(1.1) def test_absent_estimate_compares_spend_alone() -> None: result = budget_gate( "c2", valid_tiers=VALID_TIERS, budget=budget_input(spend_usd=0.9, estimate_usd=None), ) assert result.outcome == "under_limit" # --------------------------------------------------------------------------- # apply_budget_gate — decision + metadata effects # --------------------------------------------------------------------------- def _decision(tier: str = "c2") -> RoutingDecision: return RoutingDecision( tier=tier, model=TIERS[tier]["model"], confidence=0.9, source="v4_phase3" ) def test_apply_off_writes_nothing() -> None: extra: dict = {"route_class": "R2"} meta: dict = {} decision = _decision("c2") result = budget_gate("c2", valid_tiers=VALID_TIERS, budget=None) out = apply_budget_gate(decision, result, tiers=TIERS, extra=extra, metadata_updates=meta) assert out is decision assert meta == {} assert extra == {"route_class": "R2"} def test_apply_suspended_writes_nothing() -> None: extra: dict = {"route_class": "R2"} meta: dict = {} decision = _decision("c2") result = budget_gate( "c2", valid_tiers=VALID_TIERS, budget=budget_input(spend_usd=None) ) out = apply_budget_gate(decision, result, tiers=TIERS, extra=extra, metadata_updates=meta) assert out.tier == "c2" assert meta == {} assert extra == {"route_class": "R2"} def test_apply_under_limit_writes_nothing() -> None: extra: dict = {"route_class": "R2"} meta: dict = {} decision = _decision("c2") result = budget_gate( "c2", valid_tiers=VALID_TIERS, budget=budget_input(spend_usd=0.1) ) out = apply_budget_gate(decision, result, tiers=TIERS, extra=extra, metadata_updates=meta) assert out.tier == "c2" assert meta == {} assert extra == {"route_class": "R2"} def test_apply_warn_annotates_but_keeps_tier() -> None: extra: dict = {"route_class": "R2"} meta: dict = {} decision = _decision("c2") result = budget_gate("c2", valid_tiers=VALID_TIERS, budget=budget_input(spend_usd=5.0)) out = apply_budget_gate(decision, result, tiers=TIERS, extra=extra, metadata_updates=meta) # Tier + model UNCHANGED. assert (out.tier, out.model, out.source) == ("c2", "dummy-pro-1", "v4_phase3") assert meta["router_budget_applied"] is True assert meta["router_budget_outcome"] == "warn" assert meta["router_budget_action"] == "warn" assert meta["router_budget_spend_usd"] == 5.0 assert meta["router_budget_limit_usd"] == 1.0 trail = extra["routing_trail"] assert trail[-1]["stage"] == "budget_gate" assert trail[-1]["rule"] == "warn" assert extra["budget_gate_applied"] is True def test_apply_cap_rebinds_tier_and_model() -> None: extra: dict = {"route_class": "R3", "final_tier": "c3"} meta: dict = {} decision = _decision("c3") result = budget_gate( "c3", valid_tiers=VALID_TIERS, budget=budget_input(action="cap", cap_tier="c1", spend_usd=9.0), ) out = apply_budget_gate(decision, result, tiers=TIERS, extra=extra, metadata_updates=meta) assert (out.tier, out.model, out.source) == ("c1", "dummy-mini-1", "budget_cap") assert meta["router_budget_outcome"] == "cap" assert meta["router_budget_from_tier"] == "c3" assert meta["router_budget_to_tier"] == "c1" assert extra["final_tier"] == "c1" assert extra["final_route_class"] == route_class_for_tier("c1") trail = extra["routing_trail"] assert trail[-1] == { "stage": "budget_gate", "rule": "cap", "spend_usd": 9.0, "limit_usd": 1.0, "spend_source": "billed", "from_tier": "c3", "to_tier": "c1", } def test_apply_warn_records_spend_source_in_trail_and_metadata() -> None: # A warn outcome threads the spend basis into both the metadata trace and # the routing-trail entry so the decision is auditable. extra: dict = {"route_class": "R2"} meta: dict = {} decision = _decision("c2") result = budget_gate( "c2", valid_tiers=VALID_TIERS, budget=budget_input(spend_usd=5.0, spend_source="estimate_mixed"), ) apply_budget_gate(decision, result, tiers=TIERS, extra=extra, metadata_updates=meta) assert meta["router_budget_spend_source"] == "estimate_mixed" assert extra["routing_trail"][-1]["spend_source"] == "estimate_mixed" # --------------------------------------------------------------------------- # _session_accumulated_spend — seeded provenance -> spend source # --------------------------------------------------------------------------- def _spend_ctx(**metadata: object) -> SimpleNamespace: return SimpleNamespace(metadata=dict(metadata)) def test_accumulated_spend_billed_ignores_rollup_label() -> None: # A positive billed total is authoritative regardless of the rollup label. spend, source = _session_accumulated_spend( _spend_ctx( session_billed_cost_usd=3.0, session_total_cost_usd=3.0, session_cost_source="mixed", ) ) assert (spend, source) == (3.0, "billed") def test_accumulated_spend_estimate_label_maps_to_estimate() -> None: spend, source = _session_accumulated_spend( _spend_ctx( session_billed_cost_usd=0.0, session_total_cost_usd=2.5, session_estimated_cost_usd=2.5, session_cost_source="opensquilla_estimate", ) ) assert (spend, source) == (2.5, "estimate") def test_accumulated_spend_mixed_label_maps_to_estimate_mixed() -> None: spend, source = _session_accumulated_spend( _spend_ctx( session_billed_cost_usd=0.0, session_total_cost_usd=4.0, session_cost_source="mixed", ) ) assert (spend, source) == (4.0, "estimate_mixed") def test_accumulated_spend_absent_label_stays_estimate() -> None: spend, source = _session_accumulated_spend(_spend_ctx(session_total_cost_usd=1.5)) assert (spend, source) == (1.5, "estimate") def test_accumulated_spend_unknown_and_none_preserved() -> None: # No spend keys at all -> unknown (the gate suspends on missing data). assert _session_accumulated_spend(_spend_ctx()) == (None, "unknown") # Keys present but all zero -> a known-zero spend, not an unknown one, even # with a rollup label present. assert _session_accumulated_spend( _spend_ctx( session_billed_cost_usd=0.0, session_total_cost_usd=0.0, session_cost_source="mixed", ) ) == (0.0, "none") # --------------------------------------------------------------------------- # Full-engine integration + default-path parity # --------------------------------------------------------------------------- def router_cfg(**overrides: object) -> SimpleNamespace: knobs: dict[str, object] = { "default_tier": "c1", "confidence_threshold": 0.5, "confidence_high_tier_margin": 0.05, "complaint_upgrade_enabled": True, "complaint_upgrade_steps": 1, "complaint_upgrade_max_chars": 160, "kv_cache_anti_downgrade_enabled": True, "kv_cache_anti_downgrade_window_seconds": 600, } knobs.update(overrides) return SimpleNamespace(**knobs) def make_inputs( *, tier: str = "c2", confidence: float = 0.9, tokens: int = 0, budget: BudgetGateInput | None = None, extra: dict | None = None, ) -> PolicyInputs: return PolicyInputs( decision=RoutingDecision( tier=tier, model=TIERS[tier]["model"], confidence=confidence, source="v4_phase3" ), message="please summarize this short note", router_cfg=router_cfg(), tiers=TIERS, valid_tiers=VALID_TIERS, routing_history=None, extra=extra if extra is not None else {"route_class": route_class_for_tier(tier)}, thinking_mode="T2", prompt_policy="P1", history_strategy=True, material_estimated_tokens=tokens, context_window_tokens=200_000, now=1000.0, budget=budget, ) def test_engine_warn_over_limit_keeps_routed_tier() -> None: engine = RoutingPolicyEngine() result = engine.run( make_inputs(tier="c2", budget=budget_input(action="warn", limit_usd=1.0, spend_usd=9.0)) ) assert result.decision.tier == "c2" # warn never changes the tier assert result.metadata_updates["router_budget_outcome"] == "warn" assert result.metadata_updates["router_budget_applied"] is True def test_engine_cap_lowers_routed_tier() -> None: engine = RoutingPolicyEngine() result = engine.run( make_inputs( tier="c3", confidence=0.99, budget=budget_input(action="cap", limit_usd=1.0, spend_usd=9.0, cap_tier="c0"), ) ) assert result.decision.tier == "c0" assert result.decision.model == "dummy-nano-1" assert result.decision.source == "budget_cap" assert result.metadata_updates["router_budget_to_tier"] == "c0" def test_engine_default_budget_writes_no_budget_metadata() -> None: engine = RoutingPolicyEngine() result = engine.run(make_inputs(tier="c2", budget=None)) budget_keys = [k for k in result.metadata_updates if k.startswith("router_budget")] assert budget_keys == [] # The parity matrix: default (None) budget must equal an active-but-suspended # budget must equal an active-but-under-limit budget — all three are complete # no-ops. This re-proves, at the engine level, that the additive stage does not # perturb routing output when it does not act. PARITY_SCENARIOS = [ {"tier": "c2", "confidence": 0.9, "tokens": 0}, {"tier": "c0", "confidence": 0.40, "tokens": 0}, {"tier": "c3", "confidence": 0.20, "tokens": 0}, {"tier": "c1", "confidence": 0.9, "tokens": 30_000}, {"tier": "c0", "confidence": 0.9, "tokens": 90_000}, ] def _engine_snapshot(scenario: dict, budget: BudgetGateInput | None) -> tuple: engine = RoutingPolicyEngine() extra: dict = {"route_class": route_class_for_tier(scenario["tier"])} result = engine.run(make_inputs(budget=budget, extra=extra, **scenario)) return ( result.decision.tier, result.decision.model, result.decision.source, result.decision.confidence, result.thinking_mode, result.prompt_policy, extra, result.metadata_updates, ) @pytest.mark.parametrize("scenario", PARITY_SCENARIOS) def test_default_budget_matches_suspended_and_under_limit(scenario: dict) -> None: baseline = _engine_snapshot(scenario, None) suspended = _engine_snapshot( scenario, budget_input(limit_usd=1.0, spend_usd=None, spend_source="unknown") ) under_limit = _engine_snapshot(scenario, budget_input(limit_usd=1_000_000.0, spend_usd=0.01)) assert baseline == suspended assert baseline == under_limit