"""Compose bounded Gitea and controller runner-fleet telemetry. AppOps is intentionally a consumer of this projection. The root-owned controller remains the only component that may perform an allowlisted recovery or cleanup inside the isolated CI Docker daemon. """ from __future__ import annotations from typing import Any from ..connectors.gitea_actions import GiteaActionsConnector from .deployment_broker import DeploymentBrokerClient _CAPABILITIES = ( "linux-validation", "docker-validation", "browser-validation", "security-validation", "windows-native", ) _SEVERITY_RANK = {"healthy": 0, "warning": 1, "critical": 2, "unknown": 3} def _severity(*states: object) -> str: normalized = [str(state) for state in states] return max(normalized, key=lambda value: _SEVERITY_RANK.get(value, 3), default="unknown") def _capabilities(runners: list[dict[str, Any]]) -> list[dict[str, object]]: rows = [] for capability in _CAPABILITIES: compatible = [runner for runner in runners if capability in runner.get("labels", [])] online = [ runner for runner in compatible if runner.get("status") == "online" and not runner.get("disabled") ] rows.append( { "name": capability, "total": len(compatible), "online": len(online), "busy": sum(bool(runner.get("busy")) for runner in online), "status": "healthy" if online else "critical", } ) return rows class RunnerFleetService: """Provide a read-only, explainable CI fleet projection.""" def snapshot(self) -> dict[str, object]: actions = GiteaActionsConnector().snapshot() controller = DeploymentBrokerClient().runner_fleet_status() runners = actions.get("runners") if isinstance(actions.get("runners"), list) else [] typed_runners = [runner for runner in runners if isinstance(runner, dict)] capabilities = _capabilities(typed_runners) controller_status = str(controller.get("status") or "unavailable") controller_severity = ( controller_status if controller_status in _SEVERITY_RANK else "warning" if controller_status != "unavailable" else "unknown" ) action_severity = str(actions.get("severity") or "unknown") capability_severity = _severity(*(row["status"] for row in capabilities)) return { "status": _severity(action_severity, controller_severity, capability_severity), "generatedAt": actions.get("generatedAt"), "runners": typed_runners, "capabilities": capabilities, "queue": actions.get("queue") if isinstance(actions.get("queue"), dict) else {}, "controller": controller, "actions": [ {"intent": "runner.inspect", "mode": "read_only"}, {"intent": "runner.cleanup.preview", "mode": "read_only"}, {"intent": "runner.restart", "mode": "root_allowlisted_and_bounded"}, {"intent": "runner.drain", "mode": "operator_controlled"}, ], }