import pytest from agent.errors import MoAPresetNotFoundError from hermes_cli.moa_config import ( DEFAULT_MOA_AGGREGATOR, DEFAULT_MOA_PRESET_NAME, DEFAULT_MOA_REFERENCE_MODELS, build_moa_turn_prompt, decode_moa_turn, exact_moa_preset_name, normalize_moa_config, resolve_moa_preset, set_active_moa_preset, ) def test_moa_slot_picker_excludes_unconfigured_providers(monkeypatch): from hermes_cli import moa_cmd captured = {} monkeypatch.setattr(moa_cmd, "load_picker_context", lambda: object()) def fake_build(_context, **kwargs): captured.update(kwargs) return { "providers": [ {"slug": "moa", "models": ["default"]}, {"slug": "opencode-go", "models": ["deepseek-v4-pro"]}, ] } monkeypatch.setattr(moa_cmd, "build_models_payload", fake_build) assert [row["slug"] for row in moa_cmd._model_options()] == ["opencode-go"] assert captured["include_unconfigured"] is False def _enabled_refs(refs): return [{**slot, "enabled": True} for slot in refs] def test_normalize_moa_config_uses_default_named_preset(): cfg = normalize_moa_config({}) assert cfg["default_preset"] == DEFAULT_MOA_PRESET_NAME assert list(cfg["presets"]) == [DEFAULT_MOA_PRESET_NAME] assert cfg["reference_models"] == _enabled_refs(DEFAULT_MOA_REFERENCE_MODELS) assert cfg["aggregator"] == DEFAULT_MOA_AGGREGATOR def test_normalize_moa_config_preserves_named_presets(): cfg = normalize_moa_config( { "default_preset": "coding", "presets": { "coding": { "reference_models": [{"provider": "openai-codex", "model": "gpt-5.5"}], "aggregator": {"provider": "openrouter", "model": "anthropic/claude-opus-4.8"}, }, "review": { "reference_models": [{"provider": "openrouter", "model": "deepseek/deepseek-v4-pro"}], "aggregator": {"provider": "openrouter", "model": "anthropic/claude-opus-4.8"}, }, }, } ) assert cfg["default_preset"] == "coding" assert set(cfg["presets"]) == {"coding", "review"} assert cfg["reference_models"] == [{"provider": "openai-codex", "model": "gpt-5.5", "enabled": True}] def test_normalize_moa_config_defaults_reference_enabled_true(): cfg = normalize_moa_config( { "presets": { "review": { "reference_models": [{"provider": "openrouter", "model": "deepseek/deepseek-v4-pro"}], "aggregator": {"provider": "openrouter", "model": "anthropic/claude-opus-4.8"}, } } } ) assert cfg["presets"]["review"]["reference_models"] == [ {"provider": "openrouter", "model": "deepseek/deepseek-v4-pro", "enabled": True} ] def test_normalize_moa_config_preserves_disabled_reference(): cfg = normalize_moa_config( { "presets": { "review": { "reference_models": [ {"provider": "openai-codex", "model": "gpt-5.5", "enabled": False}, {"provider": "openrouter", "model": "deepseek/deepseek-v4-pro", "enabled": "false"}, ], "aggregator": {"provider": "openrouter", "model": "anthropic/claude-opus-4.8"}, } } } ) assert cfg["presets"]["review"]["reference_models"] == [ {"provider": "openai-codex", "model": "gpt-5.5", "enabled": False}, {"provider": "openrouter", "model": "deepseek/deepseek-v4-pro", "enabled": False}, ] def test_legacy_flat_config_becomes_default_preset(): cfg = normalize_moa_config( { "reference_models": [{"provider": "openai-codex", "model": "gpt-5.5"}], "aggregator": {"provider": "openrouter", "model": "anthropic/claude-opus-4.8"}, } ) assert cfg["presets"][DEFAULT_MOA_PRESET_NAME]["reference_models"] == [ {"provider": "openai-codex", "model": "gpt-5.5", "enabled": True} ] def test_normalize_moa_config_tolerates_non_numeric_values(): """Non-numeric strings in hand-edited config.yaml must degrade to defaults instead of crashing normalize_moa_config with ValueError.""" cfg = normalize_moa_config( { "presets": { "broken": { "max_tokens": "notanumber", "reference_temperature": "hot", "aggregator_temperature": "", } } } ) preset = cfg["presets"]["broken"] assert preset["max_tokens"] == 4096 # Unparseable/blank temperatures degrade to None = "don't send the # parameter; provider default applies" (matching single-model behavior), # not to a hardcoded sampling value. assert preset["reference_temperature"] is None assert preset["aggregator_temperature"] is None def test_normalize_moa_config_tolerates_non_list_reference_models(): """A hand-edited scalar reference_models must degrade to defaults instead of crashing normalize_moa_config with TypeError (symmetric with the non-numeric scalar-field tolerance).""" cfg = normalize_moa_config( {"presets": {"broken": {"reference_models": 2}}} ) assert cfg["presets"]["broken"]["reference_models"] == _enabled_refs(DEFAULT_MOA_REFERENCE_MODELS) def test_normalize_moa_config_wraps_bare_dict_reference_models(): """A single reference slot written without the list wrapper is rescued.""" cfg = normalize_moa_config( {"presets": {"p": {"reference_models": {"provider": "openai", "model": "gpt-4o"}}}} ) assert cfg["presets"]["p"]["reference_models"] == [{"provider": "openai", "model": "gpt-4o", "enabled": True}] def test_normalize_moa_config_parses_json_string_reference_models(): """reference_models stored as a JSON string (hand-edited config.yaml or a stringified GUI save) must round-trip to the parsed model list instead of being discarded for defaults.""" import json models = [ {"provider": "openai", "model": "gpt-4o"}, {"provider": "anthropic", "model": "claude-sonnet-4"}, ] cfg = normalize_moa_config( {"presets": {"p": {"reference_models": json.dumps(models)}}} ) assert cfg["presets"]["p"]["reference_models"] == [ {**m, "enabled": True} for m in models ] def test_normalize_moa_config_malformed_json_string_falls_back_to_defaults(): """A malformed JSON string reference_models must degrade to the default reference models without raising.""" cfg = normalize_moa_config( {"presets": {"p": {"reference_models": "[{'provider': broken"}}} ) assert cfg["presets"]["p"]["reference_models"] == [ {**m, "enabled": True} for m in DEFAULT_MOA_REFERENCE_MODELS ] def test_normalize_moa_config_preserves_slot_reasoning_effort(): cfg = normalize_moa_config( { "presets": { "p": { "reference_models": [ {"provider": "openai-codex", "model": "gpt-5.6-sol", "reasoning_effort": "LOW"}, {"provider": "openai-codex", "model": "gpt-5.6-sol", "reasoning_effort": False}, {"provider": "openai-codex", "model": "gpt-5.6-sol", "reasoning_effort": "nonsense"}, {"provider": "openai-codex", "model": "gpt-5.6-sol", "reasoning_effort": "ultra"}, ], "aggregator": {"provider": "openai-codex", "model": "gpt-5.6-sol", "reasoning_effort": "xhigh"}, } } } ) preset = cfg["presets"]["p"] assert preset["reference_models"][0]["reasoning_effort"] == "low" assert preset["reference_models"][1]["reasoning_effort"] == "none" assert "reasoning_effort" not in preset["reference_models"][2] assert preset["reference_models"][3]["reasoning_effort"] == "ultra" assert preset["aggregator"]["reasoning_effort"] == "xhigh" def test_normalize_moa_config_round_trips_reasoning_effort_and_enabled(): """Regression: a client that GETs the config and PUTs it straight back must not strip per-slot keys. reasoning_effort AND enabled have to survive a normalize → normalize round trip together (a save path that re-normalizes the previously normalized payload is the exact client round-trip shape).""" cfg = normalize_moa_config( { "presets": { "p": { "reference_models": [ {"provider": "openai-codex", "model": "gpt-5.5", "reasoning_effort": "high", "enabled": False}, {"provider": "openrouter", "model": "deepseek/deepseek-v4-pro", "enabled": True}, ], "aggregator": { "provider": "openrouter", "model": "anthropic/claude-opus-4.8", "reasoning_effort": "xhigh", }, } } } ) round_tripped = normalize_moa_config(cfg) refs = round_tripped["presets"]["p"]["reference_models"] assert refs[0] == { "provider": "openai-codex", "model": "gpt-5.5", "reasoning_effort": "high", "enabled": False, } assert refs[1] == {"provider": "openrouter", "model": "deepseek/deepseek-v4-pro", "enabled": True} assert round_tripped["presets"]["p"]["aggregator"]["reasoning_effort"] == "xhigh" def test_normalize_moa_config_coerces_numeric_strings(): """Valid numeric strings (e.g. from YAML round-trip) must coerce correctly.""" cfg = normalize_moa_config({"max_tokens": "8192", "reference_temperature": "0.9"}) preset = cfg["presets"][DEFAULT_MOA_PRESET_NAME] assert preset["max_tokens"] == 8192 assert preset["reference_temperature"] == 0.9 def test_normalize_moa_config_coerces_float_max_tokens(): """max_tokens: 4096.0 (float from YAML) must coerce to int.""" cfg = normalize_moa_config({"max_tokens": 4096.0}) assert cfg["presets"][DEFAULT_MOA_PRESET_NAME]["max_tokens"] == 4096 cfg2 = normalize_moa_config({"max_tokens": "4096.5"}) assert cfg2["presets"][DEFAULT_MOA_PRESET_NAME]["max_tokens"] == 4096 def test_exact_preset_matching_is_not_fuzzy(): config = {"presets": {"coding": {}, "review": {}}} assert exact_moa_preset_name(config, "coding") == "coding" assert exact_moa_preset_name(config, "cod") is None assert exact_moa_preset_name(config, "coding please fix this") is None def test_exact_preset_matching_skips_disabled_presets(): """A disabled preset must not match the implicit bare-name switch path. Regression for #55187: with ``enabled: false`` presets, a plain model switch whose name collides with a preset key (e.g. ``default``) silently pivoted the session onto the MoA virtual provider. The per-preset ``enabled`` opt-out must gate this implicit match. """ config = { "presets": { "default": {"enabled": False}, "klo": {"enabled": False}, }, } assert exact_moa_preset_name(config, "default") is None assert exact_moa_preset_name(config, "klo") is None def test_exact_preset_matching_allows_enabled_presets(): """An explicitly enabled preset still matches the bare-name switch path.""" config = { "presets": { "fast": {"enabled": True}, "slow": {"enabled": False}, }, } assert exact_moa_preset_name(config, "fast") == "fast" assert exact_moa_preset_name(config, "slow") is None # Default (no explicit enabled key) is enabled and still matches. assert exact_moa_preset_name({"presets": {"x": {}}}, "x") == "x" def test_active_preset_toggle_validation(): config = {"default_preset": "coding", "presets": {"coding": {}, "review": {}}} active = set_active_moa_preset(config, "review") assert active["active_preset"] == "review" inactive = set_active_moa_preset(active, "") assert inactive["active_preset"] == "" def test_resolve_moa_preset_returns_requested_model_set(): cfg = normalize_moa_config( { "presets": { "coding": {"reference_models": [{"provider": "openai-codex", "model": "gpt-5.5"}]}, "review": {"reference_models": [{"provider": "openrouter", "model": "deepseek/deepseek-v4-pro"}]}, } } ) assert resolve_moa_preset(cfg, "review")["reference_models"] == [ {"provider": "openrouter", "model": "deepseek/deepseek-v4-pro", "enabled": True} ] def test_resolve_missing_moa_preset_has_actionable_error(): cfg = { "default_preset": "日常对话-高峰", "presets": {"日常对话-高峰": {}, "日常对话-非高峰": {}}, } with pytest.raises(MoAPresetNotFoundError) as exc_info: resolve_moa_preset(cfg, "日常对话-高峰期") message = str(exc_info.value) assert "日常对话-高峰期" in message assert "日常对话-高峰" in message assert "日常对话-非高峰" in message assert "hermes moa list" in message def test_resolve_missing_moa_preset_does_not_silently_fallback(): cfg = { "default_preset": "日常对话-高峰", "presets": {"日常对话-高峰": {}}, } with pytest.raises(MoAPresetNotFoundError): resolve_moa_preset(cfg, "renamed-preset") def test_missing_moa_preset_is_non_retryable(): from agent.error_classifier import FailoverReason, classify_api_error result = classify_api_error( MoAPresetNotFoundError("MoA preset 'old' was not found"), provider="moa", model="old", ) assert result.reason == FailoverReason.model_not_found assert result.retryable is False assert result.should_fallback is False def test_build_moa_turn_prompt_encodes_one_shot_default_preset(): prompt = build_moa_turn_prompt("write a file then inspect it") decoded_prompt, cfg = decode_moa_turn(prompt) assert decoded_prompt == "write a file then inspect it" assert cfg is not None assert cfg["reference_models"] == _enabled_refs(DEFAULT_MOA_REFERENCE_MODELS) def test_moa_provider_rejected_as_reference_slot(): """A reference slot pointing at the moa virtual provider is dropped, so a preset cannot recursively reference another MoA run.""" cfg = normalize_moa_config( { "presets": { "p": { "reference_models": [ {"provider": "moa", "model": "default"}, {"provider": "openrouter", "model": "deepseek/deepseek-v4-pro"}, ], "aggregator": {"provider": "openrouter", "model": "anthropic/claude-opus-4.8"}, } } } ) refs = cfg["presets"]["p"]["reference_models"] assert {"provider": "moa", "model": "default"} not in refs assert refs == [{"provider": "openrouter", "model": "deepseek/deepseek-v4-pro", "enabled": True}] def test_moa_provider_rejected_as_aggregator_slot(): """An aggregator slot pointing at the moa virtual provider is dropped and falls back to the default aggregator, never a recursive MoA aggregator.""" cfg = normalize_moa_config( { "presets": { "p": { "reference_models": [{"provider": "openrouter", "model": "deepseek/deepseek-v4-pro"}], "aggregator": {"provider": "moa", "model": "default"}, } } } ) agg = cfg["presets"]["p"]["aggregator"] assert agg["provider"] != "moa" assert agg == DEFAULT_MOA_AGGREGATOR def test_moa_provider_rejected_case_insensitive(): """Case variants like ``MoA`` are also blocked.""" cfg = normalize_moa_config( {"presets": {"p": {"aggregator": {"provider": "MoA", "model": "default"}}}} ) assert cfg["presets"]["p"]["aggregator"]["provider"] != "moa" assert cfg["presets"]["p"]["aggregator"] == DEFAULT_MOA_AGGREGATOR def _preset(**extra): base = { "reference_models": [{"provider": "openrouter", "model": "anthropic/claude-opus-4.8"}], "aggregator": {"provider": "openrouter", "model": "anthropic/claude-opus-4.8"}, } base.update(extra) return {"default_preset": "p", "presets": {"p": base}} def test_reference_max_tokens_defaults_to_none_uncapped(): """Unset reference_max_tokens resolves to None (no cap) so existing presets keep their prior uncapped advisor behavior — no silent regression.""" p = resolve_moa_preset(_preset(), "p") assert p["reference_max_tokens"] is None def test_reference_max_tokens_positive_value_preserved(): """A positive cap flows through resolve_moa_preset to the runtime path.""" p = resolve_moa_preset(_preset(reference_max_tokens=600), "p") assert p["reference_max_tokens"] == 600 def test_reference_max_tokens_invalid_falls_back_to_none(): """Non-positive / non-numeric caps degrade to None (uncapped) rather than clamping advisors to a nonsense value or crashing.""" for bad in (0, -5, "abc", "", None): p = resolve_moa_preset(_preset(reference_max_tokens=bad), "p") assert p["reference_max_tokens"] is None, bad def test_reference_max_tokens_string_number_coerced(): """A hand-edited config.yaml string like '600' coerces to int.""" p = resolve_moa_preset(_preset(reference_max_tokens="600"), "p") assert p["reference_max_tokens"] == 600 def test_reference_max_tokens_in_flattened_view(): """The flattened compatibility view (dashboard/desktop callers) exposes the active preset's reference_max_tokens.""" cfg = normalize_moa_config(_preset(reference_max_tokens=750)) assert cfg["reference_max_tokens"] == 750 # ── validate_moa_payload (write-boundary validation, #64156) ───────────────── # # normalize_moa_config is deliberately tolerant at READ time (hand-edited # configs degrade to defaults). validate_moa_payload is the strict WRITE-time # counterpart: it must flag exactly the payloads normalize would silently # repair, so API save paths reject them instead of corrupting user config. def _valid_preset_payload(): return { "reference_models": [{"provider": "openrouter", "model": "deepseek/deepseek-v4-pro"}], "aggregator": {"provider": "openrouter", "model": "anthropic/claude-opus-4.8"}, } def test_validate_moa_payload_accepts_complete_presets(): from hermes_cli.moa_config import validate_moa_payload assert validate_moa_payload({"presets": {"default": _valid_preset_payload()}}) == [] def test_validate_moa_payload_accepts_legacy_flat_payload(): from hermes_cli.moa_config import validate_moa_payload assert validate_moa_payload(_valid_preset_payload()) == [] def test_validate_moa_payload_flags_half_filled_reference_slot(): """The #64156 shape: provider picked, model still empty (mid-edit autosave).""" from hermes_cli.moa_config import validate_moa_payload preset = _valid_preset_payload() preset["reference_models"].append({"provider": "kilo", "model": ""}) problems = validate_moa_payload({"presets": {"default": preset}}) assert problems assert any("reference 2" in p and "model is required" in p for p in problems) def test_validate_moa_payload_flags_half_filled_aggregator(): from hermes_cli.moa_config import validate_moa_payload preset = _valid_preset_payload() preset["aggregator"] = {"provider": "openrouter", "model": ""} problems = validate_moa_payload({"presets": {"default": preset}}) assert any("aggregator" in p and "model is required" in p for p in problems) def test_validate_moa_payload_flags_empty_references(): from hermes_cli.moa_config import validate_moa_payload preset = _valid_preset_payload() preset["reference_models"] = [] problems = validate_moa_payload({"presets": {"default": preset}}) assert any("at least one complete reference model" in p for p in problems) def test_validate_moa_payload_flags_recursive_moa_slot(): from hermes_cli.moa_config import validate_moa_payload preset = _valid_preset_payload() preset["aggregator"] = {"provider": "MoA", "model": "default"} problems = validate_moa_payload({"presets": {"default": preset}}) assert any("recursive MoA" in p for p in problems) def test_validate_moa_payload_names_the_broken_preset(): """Multi-preset payloads must say WHICH preset is broken.""" from hermes_cli.moa_config import validate_moa_payload problems = validate_moa_payload( { "presets": { "good": _valid_preset_payload(), "broken": { "reference_models": [{"provider": "", "model": ""}], "aggregator": {"provider": "a", "model": "b"}, }, } } ) assert problems assert all("'broken'" in p for p in problems) assert not any("'good'" in p for p in problems) def test_validate_moa_payload_agrees_with_clean_slot(): """Contract: a payload validate accepts must survive normalize UNCHANGED in its slots — validate and _clean_slot can never disagree (else a payload could pass validation and still be swapped for defaults).""" from hermes_cli.moa_config import validate_moa_payload payload = {"presets": {"p": _valid_preset_payload()}} assert validate_moa_payload(payload) == [] cfg = normalize_moa_config(payload) # Slots survive with only the canonical enabled=True default added — no # provider/model swap, no defaults substitution. assert cfg["presets"]["p"]["reference_models"] == _enabled_refs(payload["presets"]["p"]["reference_models"]) assert cfg["presets"]["p"]["aggregator"] == payload["presets"]["p"]["aggregator"] def test_validate_moa_payload_rejects_non_dict(): from hermes_cli.moa_config import validate_moa_payload assert validate_moa_payload(None) assert validate_moa_payload([1, 2]) assert validate_moa_payload({"presets": {"p": "not-a-dict"}}) # ── Per-slot max_tokens ──────────────────────────────────────────────────── def test_slot_max_tokens_preserved(): """A max_tokens field on a reference slot survives normalization.""" cfg = normalize_moa_config( { "presets": { "p": { "reference_models": [ {"provider": "openrouter", "model": "deepseek/deepseek-v4-pro", "max_tokens": 600}, {"provider": "openai-codex", "model": "gpt-5.5"}, ], "aggregator": {"provider": "openrouter", "model": "anthropic/claude-opus-4.8"}, } } } ) refs = cfg["presets"]["p"]["reference_models"] assert refs[0] == {"provider": "openrouter", "model": "deepseek/deepseek-v4-pro", "max_tokens": 600, "enabled": True} assert refs[1] == {"provider": "openai-codex", "model": "gpt-5.5", "enabled": True} def test_slot_max_tokens_coerced_from_string(): """Hand-edited YAML string '600' coerces to int on a slot.""" cfg = normalize_moa_config( { "presets": { "p": { "reference_models": [ {"provider": "openrouter", "model": "deepseek/deepseek-v4-pro", "max_tokens": "600"}, ], } } } ) refs = cfg["presets"]["p"]["reference_models"] assert refs[0]["max_tokens"] == 600 def test_slot_max_tokens_invalid_dropped(): """Non-positive / non-numeric slot max_tokens is dropped (slot kept).""" for bad in (0, -5, "abc", "", None): cfg = normalize_moa_config( { "presets": { "p": { "reference_models": [ {"provider": "openrouter", "model": "deepseek/deepseek-v4-pro", "max_tokens": bad}, ], } } } ) ref = cfg["presets"]["p"]["reference_models"][0] assert "max_tokens" not in ref, bad assert ref["provider"] == "openrouter" def test_slot_max_tokens_absent_by_default(): """Slots without max_tokens don't get the field — backward compat.""" cfg = normalize_moa_config( { "presets": { "p": { "reference_models": [ {"provider": "openrouter", "model": "deepseek/deepseek-v4-pro"}, ], } } } ) ref = cfg["presets"]["p"]["reference_models"][0] assert "max_tokens" not in ref # --- fanout cadence normalization (every_n) --- def test_fanout_defaults_to_user_turn(): # Default is the cheapest cadence (#67199): advisors once per user turn. cfg = normalize_moa_config({}) assert cfg["fanout"] == "user_turn" def test_fanout_per_iteration_still_selectable(): cfg = normalize_moa_config({"fanout": "per_iteration"}) assert cfg["fanout"] == "per_iteration" def test_fanout_every_n_string_form_normalized(): cfg = normalize_moa_config({"fanout": "every_n:3"}) assert cfg["fanout"] == "every_n:3" assert cfg["presets"][DEFAULT_MOA_PRESET_NAME]["fanout"] == "every_n:3" def test_fanout_every_n_mapping_form_normalized_to_string(): cfg = normalize_moa_config({"fanout": {"mode": "every_n", "n": 4}}) assert cfg["fanout"] == "every_n:4" def test_fanout_every_n_degenerate_n_falls_back(): # n=1 means "every iteration" — that semantically IS per_iteration; # n=0 / negative / garbage is unparseable and falls to the default # cadence (user_turn, the cheapest — #67199). assert normalize_moa_config({"fanout": "every_n:1"})["fanout"] == "per_iteration" assert normalize_moa_config({"fanout": "every_n:0"})["fanout"] == "user_turn" assert normalize_moa_config({"fanout": "every_n:-2"})["fanout"] == "user_turn" assert normalize_moa_config({"fanout": "every_n:x"})["fanout"] == "user_turn" assert normalize_moa_config({"fanout": "every_n"})["fanout"] == "user_turn" assert normalize_moa_config({"fanout": {"mode": "every_n"}})["fanout"] == "user_turn" def test_fanout_every_n_round_trips_through_normalize(): once = normalize_moa_config({"fanout": "every_n:3"}) twice = normalize_moa_config(once) assert twice["fanout"] == "every_n:3" assert twice["presets"][DEFAULT_MOA_PRESET_NAME]["fanout"] == "every_n:3" def test_fanout_mapping_user_turn_mode_accepted(): cfg = normalize_moa_config({"fanout": {"mode": "user_turn"}}) assert cfg["fanout"] == "user_turn" # --- privacy_filter normalization --- def test_privacy_filter_defaults_off(): cfg = normalize_moa_config({}) assert cfg["privacy_filter"] == "" def test_privacy_filter_modes_normalized(): from hermes_cli.moa_config import coerce_privacy_filter assert coerce_privacy_filter("display") == "display" assert coerce_privacy_filter("FULL") == "full" assert coerce_privacy_filter(True) == "full" # legacy boolean → issue #59959 ask assert coerce_privacy_filter("true") == "full" assert coerce_privacy_filter(False) == "" assert coerce_privacy_filter(None) == "" assert coerce_privacy_filter("bogus") == "" assert coerce_privacy_filter("off") == "" def test_privacy_filter_round_trips_through_normalize(): once = normalize_moa_config({"privacy_filter": "display"}) assert once["privacy_filter"] == "display" assert normalize_moa_config(once)["privacy_filter"] == "display" full = normalize_moa_config({"privacy_filter": "full"}) assert normalize_moa_config(full)["privacy_filter"] == "full" def test_reference_failure_controls_are_normalized_per_preset_and_flattened(): cfg = normalize_moa_config( _preset(reference_timeout="120.5", degraded_reference_policy="silent") ) preset = cfg["presets"]["p"] assert preset["reference_timeout"] == 120.5 assert preset["degraded_reference_policy"] == "silent" assert cfg["reference_timeout"] == 120.5 assert cfg["degraded_reference_policy"] == "silent" @pytest.mark.parametrize("value", [None, "", 0, -1, "bad"]) def test_reference_timeout_invalid_values_fall_back_to_default(value): # None = inherit auxiliary.moa_reference.timeout (no per-preset override). assert resolve_moa_preset(_preset(reference_timeout=value), "p")["reference_timeout"] is None def test_reference_timeout_is_uncapped_and_unknown_policy_is_loud(): preset = resolve_moa_preset( _preset(reference_timeout=9999, degraded_reference_policy="wat"), "p" ) # Explicit per-preset values are honored as-is — long-thinking advisor # models legitimately run beyond any fixed cap. assert preset["reference_timeout"] == 9999.0 assert preset["degraded_reference_policy"] == "loud"