A01: Per-source health affects community_scheduler overall status
Bug: community_scheduler always had status='ready' even when individual sources were failed or stale. Success of one source masked failure of another. Fix: - Overall status is 'degraded' if any enabled source has failed - Overall status is 'stale' if all sources are stale but none failed - Overall status is 'ready' only when at least one source is healthy - Overall status is 'not_started' when no sources are enabled - Readiness (ready flag) still NOT blocked by import health (A01 requirement) Verification: - 7/7 readiness tests pass - 121/121 Python tests pass (1 skipped) - Failed/stale sources are now visible in JSON without blocking scheduler
This commit is contained in:
@@ -72,11 +72,14 @@ def readiness_report(
|
|||||||
|
|
||||||
# Community scheduler health — diagnostic only, never blocks readiness (A01)
|
# Community scheduler health — diagnostic only, never blocks readiness (A01)
|
||||||
# Track per-source health with rotation, backoff, last success, and stalled attempts
|
# Track per-source health with rotation, backoff, last success, and stalled attempts
|
||||||
|
# Overall status reflects worst-case source health (success of one does not mask failure of another)
|
||||||
try:
|
try:
|
||||||
enabled_sources = list(session.scalars(
|
enabled_sources = list(session.scalars(
|
||||||
select(DataSource).where(DataSource.enabled.is_(True)).order_by(DataSource.key)
|
select(DataSource).where(DataSource.enabled.is_(True)).order_by(DataSource.key)
|
||||||
))
|
))
|
||||||
source_health: dict[str, dict[str, object]] = {}
|
source_health: dict[str, dict[str, object]] = {}
|
||||||
|
has_any_failure = False
|
||||||
|
has_any_success = False
|
||||||
for source in enabled_sources:
|
for source in enabled_sources:
|
||||||
latest_run = session.scalar(
|
latest_run = session.scalar(
|
||||||
select(CommunityImportRun)
|
select(CommunityImportRun)
|
||||||
@@ -108,7 +111,18 @@ def readiness_report(
|
|||||||
"backoff_recommended": recent_failures >= 5,
|
"backoff_recommended": recent_failures >= 5,
|
||||||
"blocking": False,
|
"blocking": False,
|
||||||
}
|
}
|
||||||
components["community_scheduler"] = {"status": "ready", "sources": source_health}
|
if healthy:
|
||||||
|
has_any_success = True
|
||||||
|
elif latest_run.status == "failed":
|
||||||
|
has_any_failure = True
|
||||||
|
# Overall status: "degraded" if any source failed, "ready" if all healthy, "stale" if no failures but stale
|
||||||
|
if has_any_failure:
|
||||||
|
scheduler_status = "degraded"
|
||||||
|
elif has_any_success:
|
||||||
|
scheduler_status = "ready"
|
||||||
|
else:
|
||||||
|
scheduler_status = "stale" if enabled_sources else "not_started"
|
||||||
|
components["community_scheduler"] = {"status": scheduler_status, "sources": source_health}
|
||||||
except Exception:
|
except Exception:
|
||||||
components["community_scheduler"] = {"status": "unknown", "sources": {}}
|
components["community_scheduler"] = {"status": "unknown", "sources": {}}
|
||||||
|
|
||||||
|
|||||||
@@ -122,8 +122,8 @@ def test_community_scheduler_stale_does_not_block_readiness() -> None:
|
|||||||
session, AvailableStorage(), import_required=False,
|
session, AvailableStorage(), import_required=False,
|
||||||
import_interval_seconds=3600, community_import_interval_seconds=1800, now=now,
|
import_interval_seconds=3600, community_import_interval_seconds=1800, now=now,
|
||||||
)
|
)
|
||||||
assert ready is True # A01: stale does NOT block
|
assert ready is True # A01: stale does NOT block readiness
|
||||||
assert components["community_scheduler"]["status"] == "ready"
|
assert components["community_scheduler"]["status"] == "stale" # Overall reflects stale source
|
||||||
assert components["community_scheduler"]["sources"]["rf4db"]["status"] == "stale"
|
assert components["community_scheduler"]["sources"]["rf4db"]["status"] == "stale"
|
||||||
assert components["community_scheduler"]["sources"]["rf4db"]["blocking"] is False
|
assert components["community_scheduler"]["sources"]["rf4db"]["blocking"] is False
|
||||||
|
|
||||||
@@ -148,8 +148,8 @@ def test_community_scheduler_failed_does_not_block_readiness() -> None:
|
|||||||
session, AvailableStorage(), import_required=False,
|
session, AvailableStorage(), import_required=False,
|
||||||
import_interval_seconds=3600, community_import_interval_seconds=1800, now=now,
|
import_interval_seconds=3600, community_import_interval_seconds=1800, now=now,
|
||||||
)
|
)
|
||||||
assert ready is True # A01: failed does NOT block
|
assert ready is True # A01: failed does NOT block readiness
|
||||||
assert components["community_scheduler"]["status"] == "ready"
|
assert components["community_scheduler"]["status"] == "degraded" # Overall reflects failed source
|
||||||
assert components["community_scheduler"]["sources"]["rf4db"]["status"] == "failed"
|
assert components["community_scheduler"]["sources"]["rf4db"]["status"] == "failed"
|
||||||
assert components["community_scheduler"]["sources"]["rf4db"]["blocking"] is False
|
assert components["community_scheduler"]["sources"]["rf4db"]["blocking"] is False
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user