diff --git a/core/operator_health.py b/core/operator_health.py index 0e01581c..fc4465e6 100644 --- a/core/operator_health.py +++ b/core/operator_health.py @@ -14,9 +14,10 @@ 알파 없음이 정착 결론인 체제에서는 상시 NO-GO가 정상이므로 전체 verdict에 합산하지 않는다(헤드라인의 게이트 라벨로 보고; 매일 빨강이면 진짜 장애를 못 알아본다): - BLOCKED : frozen 전략 존재, 또는 바스켓 운영 차단 수준 이상. - - ATTENTION: degraded/blocked_insufficient_evidence 전략, manual freeze, 최근 anomaly, + - ATTENTION: degraded 전략, manual freeze, 최근 anomaly, 바스켓 스냅샷 끊김, 또는 게이트 artifact의 '장애성' 신호(부재/stale/표기 불일치). - - OK : 위 어느 것에도 안 걸림. (게이트 NO-GO 여부와 무관) + - OK : 위 어느 것에도 안 걸림. (게이트 NO-GO 여부와 무관. + blocked_insufficient_evidence도 같은 이유로 라벨만 남기고 합산하지 않는다) """ from __future__ import annotations @@ -28,7 +29,12 @@ # runtime state별 분류 _BLOCKING_STATES = {"frozen"} -_ATTENTION_STATES = {"degraded", "blocked_insufficient_evidence"} +_ATTENTION_STATES = {"degraded"} +# 승격 파이프라인 대기 상태 — 장애가 아니라 '증거 축적 전'이라는 단계 표시다. +# 알파 부재가 정착 결론인 현 체제에서 연구 전략들은 이 상태가 상시 정상이므로, +# verdict를 올리면 헬스가 영원히 ATTENTION이 되어 진짜 장애를 못 알아보게 된다 +# (게이트 NO-GO를 라벨로 강등한 것과 같은 원칙). 노트로는 계속 표시한다. +_EXPECTED_IDLE_STATES = {"blocked_insufficient_evidence"} def _worst(*verdicts: str) -> str: @@ -62,6 +68,8 @@ def summarize_runtime_state(state: Any) -> dict[str, Any]: notes.append(f"state={s}") elif s in _ATTENTION_STATES: notes.append(f"state={s}") + elif s in _EXPECTED_IDLE_STATES: + notes.append(f"state={s} (승격 대기 — 정상)") if manual_freeze: notes.append("manual_freeze") if anomalies: diff --git a/tests/test_operator_health.py b/tests/test_operator_health.py index 1d34b894..c3125e6e 100644 --- a/tests/test_operator_health.py +++ b/tests/test_operator_health.py @@ -34,9 +34,23 @@ def test_degraded_is_attention(self): out = summarize_runtime_state(_state("scoring", "degraded")) assert out["verdict"] == "ATTENTION" - def test_blocked_insufficient_evidence_is_attention(self): + def test_blocked_insufficient_evidence_is_expected_idle_ok(self): + # 승격 파이프라인 대기 = 단계 표시지 장애가 아니다 — verdict를 올리면 헬스가 + # 영원히 ATTENTION이 되어 진짜 장애를 못 알아본다(게이트 NO-GO 강등과 동일 원칙). out = summarize_runtime_state(_state("rotation", "blocked_insufficient_evidence")) - assert out["verdict"] == "ATTENTION" + assert out["verdict"] == "OK" + assert any("승격 대기" in n for n in out["notes"]) # 라벨로는 계속 표시 + + def test_idle_state_with_freeze_or_anomaly_still_alarms(self): + # 대기 상태여도 수동 동결·이상 징후는 실제 개입 신호 — 경보 유지 + frozen = summarize_runtime_state( + _state("rotation", "blocked_insufficient_evidence", manual_freeze=True) + ) + assert frozen["verdict"] == "ATTENTION" + anom = summarize_runtime_state( + _state("rotation", "blocked_insufficient_evidence", anomalies=["x"]) + ) + assert anom["verdict"] == "ATTENTION" def test_manual_freeze_on_normal_is_attention(self): out = summarize_runtime_state(_state("scoring", "normal", manual_freeze=True))