"""Sovereignty enforcement tests. These tests implement the acceptance criteria from issue #94: [p0] Cut cloud inheritance from active harness config and cron Every test in this file catches a specific way that cloud dependency can creep back into the active config. If any test fails, Timmy is phoning home. These tests are designed to be run in CI and to BLOCK any commit that reintroduces cloud defaults. """ from __future__ import annotations import json from pathlib import Path import yaml import pytest REPO_ROOT = Path(__file__).parent.parent CONFIG_PATH = REPO_ROOT / "config.yaml" CRON_PATH = REPO_ROOT / "cron" / "jobs.json" # Cloud URLs that should never appear in default/fallback paths CLOUD_URLS = [ "generativelanguage.googleapis.com", "api.openai.com", "chatgpt.com", "api.anthropic.com", "openrouter.ai", ] CLOUD_MODELS = [ "gpt-4", "gpt-5", "gpt-4o", "claude", "gemini", ] @pytest.fixture def config(): return yaml.safe_load(CONFIG_PATH.read_text()) @pytest.fixture def cron_jobs(): data = json.loads(CRON_PATH.read_text()) return data.get("jobs", data) if isinstance(data, dict) else data # ── Config defaults ────────────────────────────────────────────────── class TestDefaultModelIsLocal: """The default model must point to localhost.""" def test_default_model_is_not_cloud(self, config): """model.default should be a local model identifier.""" model = config["model"]["default"] for cloud in CLOUD_MODELS: assert cloud not in model.lower(), \ f"Default model '{model}' looks like a cloud model" def test_default_base_url_is_localhost(self, config): """model.base_url should point to localhost.""" base_url = config["model"]["base_url"] assert "localhost" in base_url or "127.0.0.1" in base_url, \ f"Default base_url '{base_url}' is not local" def test_default_provider_is_local(self, config): """model.provider should be 'custom' or 'ollama'.""" provider = config["model"]["provider"] assert provider in ("custom", "ollama", "local"), \ f"Default provider '{provider}' may route to cloud" class TestFallbackIsLocal: """The fallback model must also be local — this is the #94 fix.""" def test_fallback_base_url_is_localhost(self, config): """fallback_model.base_url must point to localhost.""" fb = config.get("fallback_model", {}) base_url = fb.get("base_url", "") if base_url: assert "localhost" in base_url or "127.0.0.1" in base_url, \ f"Fallback base_url '{base_url}' is not local — cloud leak!" def test_fallback_has_no_cloud_url(self, config): """fallback_model must not contain any cloud API URLs.""" fb = config.get("fallback_model", {}) base_url = fb.get("base_url", "") for cloud_url in CLOUD_URLS: assert cloud_url not in base_url, \ f"Fallback model routes to cloud: {cloud_url}" def test_fallback_model_name_is_local(self, config): """fallback_model.model should not be a cloud model name.""" fb = config.get("fallback_model", {}) model = fb.get("model", "") for cloud in CLOUD_MODELS: assert cloud not in model.lower(), \ f"Fallback model name '{model}' looks like cloud" # ── Cron jobs ──────────────────────────────────────────────────────── class TestCronSovereignty: """Enabled cron jobs must never inherit cloud defaults.""" def test_enabled_crons_have_explicit_model(self, cron_jobs): """Every enabled cron job must have a non-null model field. When model is null, the job inherits from config.yaml's default. Even if the default is local today, a future edit could change it. Explicit is always safer than implicit. """ for job in cron_jobs: if not isinstance(job, dict): continue if not job.get("enabled", False): continue model = job.get("model") name = job.get("name", job.get("id", "?")) assert model is not None and model != "", \ f"Enabled cron job '{name}' has null model — will inherit default" def test_enabled_crons_have_explicit_provider(self, cron_jobs): """Every enabled cron job must have a non-null provider field.""" for job in cron_jobs: if not isinstance(job, dict): continue if not job.get("enabled", False): continue provider = job.get("provider") name = job.get("name", job.get("id", "?")) assert provider is not None and provider != "", \ f"Enabled cron job '{name}' has null provider — will inherit default" def test_no_enabled_cron_uses_cloud_url(self, cron_jobs): """No enabled cron job should have a cloud base_url.""" for job in cron_jobs: if not isinstance(job, dict): continue if not job.get("enabled", False): continue base_url = job.get("base_url", "") name = job.get("name", job.get("id", "?")) for cloud_url in CLOUD_URLS: assert cloud_url not in (base_url or ""), \ f"Cron '{name}' routes to cloud: {cloud_url}" # ── Custom providers ───────────────────────────────────────────────── class TestCustomProviders: """Cloud providers can exist but must not be the default path.""" def test_local_provider_exists(self, config): """At least one custom provider must be local.""" providers = config.get("custom_providers", []) has_local = any( "localhost" in p.get("base_url", "") or "127.0.0.1" in p.get("base_url", "") for p in providers ) assert has_local, "No local custom provider defined" def test_first_provider_is_local(self, config): """The first custom_provider should be the local one. Hermes resolves 'custom' provider by scanning the list in order. If a cloud provider is listed first, it becomes the implicit default. """ providers = config.get("custom_providers", []) if providers: first = providers[0] base_url = first.get("base_url", "") assert "localhost" in base_url or "127.0.0.1" in base_url, \ f"First custom_provider '{first.get('name')}' is not local" # ── TTS/STT ────────────────────────────────────────────────────────── class TestVoiceSovereignty: """Voice services should prefer local providers.""" def test_tts_default_is_local(self, config): """TTS provider should be local (edge or neutts).""" tts_provider = config.get("tts", {}).get("provider", "") assert tts_provider in ("edge", "neutts", "local"), \ f"TTS provider '{tts_provider}' may use cloud" def test_stt_default_is_local(self, config): """STT provider should be local.""" stt_provider = config.get("stt", {}).get("provider", "") assert stt_provider in ("local", "whisper", ""), \ f"STT provider '{stt_provider}' may use cloud"