"""Health-evidence selection policy. The policy is deliberately small and pure. Storage retains every provider claim; this module decides which *current component-scoped* claim can answer the direct-health question. It keeps aggregate AppOps health useful in the inspector without ever treating it as a component probe. """ from __future__ import annotations from datetime import UTC, datetime, timedelta from .models import ( EvidenceFreshness, EvidenceProvider, EvidenceScope, HealthEvidence, HealthResolution, HealthResolutionReason, HealthStatus, ) _PROVIDER_PRIORITY = { EvidenceProvider.MANUAL: 100, EvidenceProvider.PROBE: 90, EvidenceProvider.DOCKER: 80, EvidenceProvider.APPOPS: 40, EvidenceProvider.RUNTIME: 20, } def appops_health_status(value: str | None) -> HealthStatus | None: """Keep AppOps aggregate health scoped to the application it describes.""" return { "unhealthy": HealthStatus.DOWN, "down": HealthStatus.DOWN, "warning": HealthStatus.DEGRADED, "degraded": HealthStatus.DEGRADED, "healthy": HealthStatus.HEALTHY, }.get((value or "").casefold()) def utc_now() -> datetime: return datetime.now(UTC) def freshness_for(evidence: HealthEvidence, *, now: datetime | None = None) -> EvidenceFreshness: """Return freshness without mutating the persisted observation. The bounded stale window is solely explanatory: stale and expired claims are both excluded from health selection. Showing the distinction helps an operator understand whether a provider is merely late or genuinely old. """ now = now or utc_now() if evidence.valid_until >= now: return EvidenceFreshness.FRESH ttl = max(evidence.valid_until - evidence.observed_at, timedelta(seconds=1)) return ( EvidenceFreshness.STALE if evidence.valid_until + ttl >= now else EvidenceFreshness.EXPIRED ) def with_freshness(evidence: HealthEvidence, *, now: datetime | None = None) -> HealthEvidence: return evidence.model_copy(update={"freshness": freshness_for(evidence, now=now)}) def resolve_health( evidence: list[HealthEvidence], *, now: datetime | None = None ) -> HealthResolution: """Resolve current direct health from independently persisted evidence.""" now = now or utc_now() annotated = [with_freshness(item, now=now) for item in evidence] eligible = [ item for item in annotated if item.active and item.scope == EvidenceScope.COMPONENT and item.freshness == EvidenceFreshness.FRESH ] if not eligible: component_claims = [ item for item in annotated if item.scope == EvidenceScope.COMPONENT and item.active ] app_claims = [ item for item in annotated if item.scope == EvidenceScope.APPLICATION and item.active ] if component_claims: reason_code = ( HealthResolutionReason.STALE_EVIDENCE if any(item.freshness == EvidenceFreshness.STALE for item in component_claims) else HealthResolutionReason.EXPIRED_EVIDENCE ) elif app_claims: reason_code = HealthResolutionReason.APPLICATION_SCOPED_ONLY else: reason_code = HealthResolutionReason.NO_CURRENT_COMPONENT_EVIDENCE explanation = ( "Only application-scoped evidence is current; it is shown for provenance and is not " "used as component health." if app_claims else "No current component-scoped health evidence is available." ) return HealthResolution(reason_code=reason_code, explanation=explanation, evidence=annotated) ordered = sorted( eligible, key=lambda item: ( _PROVIDER_PRIORITY[item.provider], item.confidence, item.observed_at, item.received_at, item.id, ), reverse=True, ) selected = ordered[0] conflict = len({item.status for item in eligible}) > 1 scope_note = " Conflicting current component signals are retained below." if conflict else "" return HealthResolution( status=selected.status, selected_evidence_id=selected.id, selected_provider=selected.provider, confidence=selected.confidence, reason_code=HealthResolutionReason.SELECTED_COMPONENT_EVIDENCE, explanation=( f"{selected.provider.value} selected from current component-scoped evidence " f"(confidence {selected.confidence:.2f}).{scope_note}" ), conflict=conflict, evidence=annotated, )