diff --git a/scripts/check_brain_health_cutover.py b/scripts/check_brain_health_cutover.py index b48663b76..3e60d39d4 100644 --- a/scripts/check_brain_health_cutover.py +++ b/scripts/check_brain_health_cutover.py @@ -22,6 +22,10 @@ CHECK_WORD = "check" PROVIDER_CHECK_TEXT = PROVIDER_WORD + " " + CHECK_WORD JOURNAL_PROVIDER_CHECK = "jour" + "nal " + PROVIDER_CHECK_TEXT SOL_PROVIDER_CHECK = "s" + "ol " + PROVIDER_CHECK_TEXT +PROVIDER_CHECK_PREFIXES = ( + tuple(JOURNAL_PROVIDER_CHECK.split()), + tuple(SOL_PROVIDER_CHECK.split()), +) OWNER_LABELS = ("Provider " + "Readiness", "Agents " + "Health") LEGACY_QUOTED_KEYS = ( '"' + "provider" + "_readiness" + '"', @@ -101,10 +105,7 @@ def _contains_command_list(path: Path, text: str) -> bool: item.value if isinstance(item, ast.Constant) else None for item in node.elts[:3] ] - if values in ( - ["journal", "providers", "check"], - ["sol", "providers", "check"], - ): + if tuple(values) in PROVIDER_CHECK_PREFIXES: return True return False return COMMAND_LIST_RE.search(text) is not None diff --git a/solstone/apps/sol/maint/008_migrate_provider_check_schedule.py b/solstone/apps/sol/maint/008_migrate_provider_check_schedule.py index 54cbac9ac..0bd35ecd3 100644 --- a/solstone/apps/sol/maint/008_migrate_provider_check_schedule.py +++ b/solstone/apps/sol/maint/008_migrate_provider_check_schedule.py @@ -148,12 +148,10 @@ def run_migration(*, dry_run: bool) -> MigrationSummary: except (OSError, json.JSONDecodeError) as exc: summary.errors += 1 print(f"[ERROR] read failed: {schedules_path}: {exc}") - _run_cleanup(summary, dry_run=dry_run) return summary if not isinstance(raw_obj, dict): summary.errors += 1 print(f"[ERROR] malformed schedules: {schedules_path}") - _run_cleanup(summary, dry_run=dry_run) return summary raw = raw_obj else: @@ -178,6 +176,7 @@ def run_migration(*, dry_run: bool) -> MigrationSummary: except (OSError, MalformedDataError, LockTimeout) as exc: summary.errors += 1 print(f"[ERROR] write failed: {schedules_path}: {exc}") + return summary for name in sorted(matches): if name == "brain": diff --git a/solstone/think/brain_health.py b/solstone/think/brain_health.py index 045c18773..94d1cb3ef 100644 --- a/solstone/think/brain_health.py +++ b/solstone/think/brain_health.py @@ -356,12 +356,6 @@ def request_brain_refresh(*, surface: BrainSurface) -> bool: return False -def brain_exit_status(state: str | None) -> Literal["ok", "warn"]: - if state in {"ready", "checking"}: - return "ok" - return "warn" - - __all__ = [ "BrainAction", "BrainActionKind", @@ -374,7 +368,6 @@ __all__ = [ "HEADLINES", "LOCAL_RUNTIME_REASON_CODES", "brain_age", - "brain_exit_status", "brain_reason_text", "build_brain_snapshot", "render_brain_health_lines", diff --git a/tests/fixtures/journal/health/agents.json b/tests/fixtures/journal/health/agents.json deleted file mode 100644 index c1879f68f..000000000 --- a/tests/fixtures/journal/health/agents.json +++ /dev/null @@ -1,191 +0,0 @@ -{ - "results": [ - { - "provider": "google", - "tier": "pro", - "model": "gemini-3-pro-preview", - "interface": "generate", - "ok": true, - "status": "ok", - "message": "OK", - "elapsed_s": 2.4 - }, - { - "provider": "google", - "tier": "pro", - "model": "gemini-3-pro-preview", - "interface": "cogitate", - "ok": true, - "status": "ok", - "message": "OK", - "elapsed_s": 9.0 - }, - { - "provider": "google", - "tier": "flash", - "model": "gemini-3-flash-preview", - "interface": "generate", - "ok": true, - "status": "ok", - "message": "OK", - "elapsed_s": 1.0 - }, - { - "provider": "google", - "tier": "flash", - "model": "gemini-3-flash-preview", - "interface": "cogitate", - "ok": true, - "status": "ok", - "message": "OK", - "elapsed_s": 6.2 - }, - { - "provider": "google", - "tier": "lite", - "model": "gemini-2.5-flash-lite", - "interface": "generate", - "ok": true, - "status": "ok", - "message": "OK", - "elapsed_s": 0.4 - }, - { - "provider": "google", - "tier": "lite", - "model": "gemini-2.5-flash-lite", - "interface": "cogitate", - "ok": true, - "status": "ok", - "message": "OK", - "elapsed_s": 6.2 - }, - { - "provider": "openai", - "tier": "pro", - "model": "gpt-5.2-high", - "interface": "generate", - "ok": true, - "status": "ok", - "message": "OK", - "elapsed_s": 0.9 - }, - { - "provider": "openai", - "tier": "pro", - "model": "gpt-5.2-high", - "interface": "cogitate", - "ok": true, - "status": "ok", - "message": "OK", - "elapsed_s": 1.5 - }, - { - "provider": "openai", - "tier": "flash", - "model": "gpt-5.2-low", - "interface": "generate", - "ok": true, - "status": "ok", - "message": "OK", - "elapsed_s": 0.6 - }, - { - "provider": "openai", - "tier": "flash", - "model": "gpt-5.2-low", - "interface": "cogitate", - "ok": true, - "status": "ok", - "message": "OK", - "elapsed_s": 2.3 - }, - { - "provider": "openai", - "tier": "lite", - "model": "gpt-5.2", - "interface": "generate", - "ok": true, - "status": "ok", - "message": "OK", - "elapsed_s": 0.5 - }, - { - "provider": "openai", - "tier": "lite", - "model": "gpt-5.2", - "interface": "cogitate", - "ok": true, - "status": "ok", - "message": "OK", - "elapsed_s": 1.1 - }, - { - "provider": "anthropic", - "tier": "pro", - "model": "claude-opus-4-5", - "interface": "generate", - "ok": true, - "status": "ok", - "message": "OK", - "elapsed_s": 1.8 - }, - { - "provider": "anthropic", - "tier": "pro", - "model": "claude-opus-4-5", - "interface": "cogitate", - "ok": false, - "status": "fail", - "message": "FAIL: empty response", - "elapsed_s": 0.1 - }, - { - "provider": "anthropic", - "tier": "flash", - "model": "claude-sonnet-4-5", - "interface": "generate", - "ok": true, - "status": "ok", - "message": "OK", - "elapsed_s": 1.5 - }, - { - "provider": "anthropic", - "tier": "flash", - "model": "claude-sonnet-4-5", - "interface": "cogitate", - "ok": false, - "status": "fail", - "message": "FAIL: empty response", - "elapsed_s": 0.1 - }, - { - "provider": "anthropic", - "tier": "lite", - "model": "claude-haiku-4-5", - "interface": "generate", - "ok": true, - "status": "ok", - "message": "OK", - "elapsed_s": 0.4 - }, - { - "provider": "anthropic", - "tier": "lite", - "model": "claude-haiku-4-5", - "interface": "cogitate", - "ok": false, - "status": "fail", - "message": "FAIL: empty response", - "elapsed_s": 0.1 - } - ], - "summary": { - "total": 18, - "passed": 15, - "skipped": 0, - "failed": 3 - }, - "checked_at": "2026-02-14T22:59:12.800713+00:00" -} diff --git a/tests/test_maint_008_migrate_provider_check_schedule.py b/tests/test_maint_008_migrate_provider_check_schedule.py index 3c876c3e6..74c72c051 100644 --- a/tests/test_maint_008_migrate_provider_check_schedule.py +++ b/tests/test_maint_008_migrate_provider_check_schedule.py @@ -56,6 +56,17 @@ def _seed_cleanup_artifacts(journal: Path) -> Path: return day_log +def _assert_cleanup_artifacts_intact(journal: Path, day_log: Path) -> None: + health = journal / "health" + assert (health / LEGACY_HEALTH_FILE).exists() + assert (health / f"{LEGACY_HEALTH_FILE}.lock").exists() + assert (health / "recheck.lock").exists() + assert (health / f".{LEGACY_HEALTH_FILE}.123.tmp").exists() + assert (health / "providers.log").is_symlink() + assert (health / "providers.log").exists() + assert day_log.exists() + + def test_migrates_matches_coalesces_and_cleans(tmp_path): schedules_path = _write_schedules( tmp_path, @@ -197,3 +208,37 @@ def test_cleanup_failure_is_fatal_and_retryable_without_duplicate_brain(tmp_path } } assert (health / LEGACY_HEALTH_FILE).is_dir() + + +def test_unreadable_schedules_file_does_not_cleanup_before_schedule_commit(tmp_path): + day_log = _seed_cleanup_artifacts(tmp_path) + config_dir = tmp_path / "config" + config_dir.mkdir(parents=True, exist_ok=True) + schedules_path = config_dir / "schedules.json" + schedules_path.mkdir() + + summary = mod.run_migration(dry_run=False) + + assert summary.errors == 1 + assert summary.cleanup_deleted == 0 + assert summary.installed_brain is False + assert summary.preserved_brain is False + assert schedules_path.is_dir() + _assert_cleanup_artifacts_intact(tmp_path, day_log) + + +def test_malformed_schedules_file_does_not_cleanup_before_schedule_commit(tmp_path): + day_log = _seed_cleanup_artifacts(tmp_path) + config_dir = tmp_path / "config" + config_dir.mkdir(parents=True, exist_ok=True) + schedules_path = config_dir / "schedules.json" + schedules_path.write_text("{", encoding="utf-8") + + summary = mod.run_migration(dry_run=False) + + assert summary.errors == 1 + assert summary.cleanup_deleted == 0 + assert summary.installed_brain is False + assert summary.preserved_brain is False + assert schedules_path.read_text(encoding="utf-8") == "{" + _assert_cleanup_artifacts_intact(tmp_path, day_log)