feat(iwooos): show controlled cd lane live metric proof
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 42s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 42s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
This commit is contained in:
@@ -340,9 +340,11 @@ from src.services.awoooi_gitea_onboarding_warning_step_template_copy_receipt imp
|
||||
)
|
||||
from src.services.awoooi_priority_work_order_readback import (
|
||||
apply_ai_loop_current_blocker_execution_queue,
|
||||
apply_controlled_cd_lane_live_metric_readback,
|
||||
apply_harbor_registry_controlled_recovery_preflight,
|
||||
apply_stockplatform_public_api_controlled_recovery_preflight,
|
||||
apply_stockplatform_public_api_runtime_readback,
|
||||
load_controlled_cd_lane_live_metric_readback,
|
||||
load_latest_awoooi_priority_work_order_readback,
|
||||
)
|
||||
from src.services.awoooi_status_cleanup_dashboard import (
|
||||
@@ -1089,7 +1091,8 @@ async def get_delivery_closure_workbench() -> dict[str, Any]:
|
||||
"讀取已提交的 AWOOOI P0/P1 主線工作順序快照;此端點只回傳"
|
||||
"目前 active P0、已關閉 P0、下一步順序、禁止事項與 evidence refs。"
|
||||
"它會讀取 StockPlatform public HTTPS health/freshness/ingestion live "
|
||||
"readback;不讀 raw sessions / SQLite、不呼叫 GitHub / Gitea live API、"
|
||||
"readback,並讀取 110 node_exporter 的 controlled CD lane textfile metrics;"
|
||||
"不讀 raw sessions / SQLite、不呼叫 GitHub / Gitea live API、不 SSH、"
|
||||
"不讀 secret、不註冊 runner、不觸發 workflow、不操作 host / Docker / K8s / DB / firewall。"
|
||||
),
|
||||
)
|
||||
@@ -1122,6 +1125,13 @@ async def get_awoooi_priority_work_order_readback() -> dict[str, Any]:
|
||||
load_latest_ai_agent_log_controlled_writeback_executor_readback
|
||||
)
|
||||
apply_ai_loop_current_blocker_execution_queue(payload, log_executor)
|
||||
controlled_cd_lane_metrics = await asyncio.to_thread(
|
||||
load_controlled_cd_lane_live_metric_readback
|
||||
)
|
||||
apply_controlled_cd_lane_live_metric_readback(
|
||||
payload,
|
||||
controlled_cd_lane_metrics,
|
||||
)
|
||||
return redact_public_lan_topology(payload)
|
||||
except FileNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
|
||||
@@ -9,6 +9,8 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -19,7 +21,15 @@ from src.services.snapshot_paths import default_operations_dir
|
||||
_DEFAULT_OPERATIONS_DIR = default_operations_dir(Path(__file__))
|
||||
_SNAPSHOT_FILE = "awoooi-priority-work-order-readback.snapshot.json"
|
||||
_SCHEMA_VERSION = "awoooi_priority_work_order_readback_v1"
|
||||
_CONTROLLED_CD_LANE_LIVE_METRIC_SCHEMA_VERSION = (
|
||||
"controlled_cd_lane_live_metric_readback_v1"
|
||||
)
|
||||
_CONTROLLED_CD_LANE_NODE_EXPORTER_URL = "http://192.168.0.110:9100/metrics"
|
||||
_SHA_RE = re.compile(r"^[0-9a-f]{40}$")
|
||||
_METRIC_LINE_RE = re.compile(
|
||||
r"^(?P<name>[a-zA-Z_:][a-zA-Z0-9_:]*)(?:\{[^}]*\})?\s+"
|
||||
r"(?P<value>[-+]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][-+]?[0-9]+)?)$"
|
||||
)
|
||||
|
||||
|
||||
def load_latest_awoooi_priority_work_order_readback(
|
||||
@@ -41,6 +51,141 @@ def load_latest_awoooi_priority_work_order_readback(
|
||||
return payload
|
||||
|
||||
|
||||
def load_controlled_cd_lane_live_metric_readback(
|
||||
metrics_url: str = _CONTROLLED_CD_LANE_NODE_EXPORTER_URL,
|
||||
timeout_seconds: float = 3.0,
|
||||
) -> dict[str, Any]:
|
||||
"""Read 110 node_exporter text metrics for the controlled CD lane.
|
||||
|
||||
This is a read-only HTTP metrics probe. It does not SSH, read runner
|
||||
registration contents, touch Docker/systemd, or perform runtime writes.
|
||||
"""
|
||||
try:
|
||||
request = urllib.request.Request(metrics_url, method="GET")
|
||||
with urllib.request.urlopen(request, timeout=timeout_seconds) as response:
|
||||
raw = response.read(2_000_000).decode("utf-8", errors="replace")
|
||||
except (OSError, TimeoutError, urllib.error.URLError) as exc:
|
||||
return {
|
||||
"schema_version": _CONTROLLED_CD_LANE_LIVE_METRIC_SCHEMA_VERSION,
|
||||
"status": "controlled_cd_lane_live_metric_unavailable",
|
||||
"source": "node_exporter_text_metrics",
|
||||
"source_url": metrics_url,
|
||||
"metadata_only": True,
|
||||
"active_blockers": ["controlled_cd_lane_live_metric_unavailable"],
|
||||
"error_class": exc.__class__.__name__,
|
||||
"readback": {
|
||||
"enforcer_metric_present": False,
|
||||
"controlled_drain_active_allowed": False,
|
||||
"controlled_drain_staging_allowed": False,
|
||||
"enforcer_last_run_fresh": False,
|
||||
},
|
||||
}
|
||||
|
||||
metrics = _parse_prometheus_text_metrics(raw)
|
||||
readback = _build_controlled_cd_lane_metric_readback(metrics)
|
||||
active_blockers: list[str] = []
|
||||
if not readback["enforcer_metric_present"]:
|
||||
active_blockers.append("controlled_cd_lane_enforcer_metric_missing")
|
||||
if not readback["enforcer_last_run_fresh"]:
|
||||
active_blockers.append("controlled_cd_lane_enforcer_metric_stale")
|
||||
if not readback["controlled_drain_active_allowed"]:
|
||||
active_blockers.append("controlled_cd_lane_active_not_allowed_yet")
|
||||
if not readback["controlled_drain_staging_allowed"]:
|
||||
active_blockers.append("controlled_cd_lane_staging_not_allowed_yet")
|
||||
|
||||
return {
|
||||
"schema_version": _CONTROLLED_CD_LANE_LIVE_METRIC_SCHEMA_VERSION,
|
||||
"status": (
|
||||
"controlled_cd_lane_live_metric_readback_ready"
|
||||
if readback["enforcer_metric_present"]
|
||||
else "controlled_cd_lane_live_metric_missing"
|
||||
),
|
||||
"source": "node_exporter_text_metrics",
|
||||
"source_url": metrics_url,
|
||||
"metadata_only": True,
|
||||
"active_blockers": active_blockers,
|
||||
"readback": readback,
|
||||
}
|
||||
|
||||
|
||||
def apply_controlled_cd_lane_live_metric_readback(
|
||||
payload: dict[str, Any],
|
||||
live_metric_readback: dict[str, Any],
|
||||
) -> None:
|
||||
"""Overlay live controlled CD lane metric truth onto P0-006 readback."""
|
||||
readback = _dict(live_metric_readback.get("readback"))
|
||||
state = _dict(payload.setdefault("mainline_execution_state", {}))
|
||||
active_blockers = _strings(live_metric_readback.get("active_blockers"))
|
||||
|
||||
metric_fields = {
|
||||
"controlled_cd_lane_live_metric_status": str(
|
||||
live_metric_readback.get("status") or "unknown"
|
||||
),
|
||||
"controlled_cd_lane_live_metric_source": str(
|
||||
live_metric_readback.get("source") or "node_exporter_text_metrics"
|
||||
),
|
||||
"controlled_cd_lane_live_metric_metadata_only": bool(
|
||||
live_metric_readback.get("metadata_only") is True
|
||||
),
|
||||
"controlled_cd_lane_live_enforcer_metric_present": bool(
|
||||
readback.get("enforcer_metric_present") is True
|
||||
),
|
||||
"controlled_cd_lane_live_enforcer_last_run_timestamp": _float(
|
||||
readback.get("enforcer_last_run_timestamp")
|
||||
),
|
||||
"controlled_cd_lane_live_enforcer_last_run_fresh": bool(
|
||||
readback.get("enforcer_last_run_fresh") is True
|
||||
),
|
||||
"controlled_cd_lane_live_enforcer_apply_performed": _int(
|
||||
readback.get("enforcer_apply_performed")
|
||||
),
|
||||
"controlled_cd_lane_live_active_job_containers": _int(
|
||||
readback.get("active_job_containers")
|
||||
),
|
||||
"controlled_cd_lane_live_lane_process_count": _int(
|
||||
readback.get("lane_process_count")
|
||||
),
|
||||
"controlled_cd_lane_live_root_restore_sources_left": _int(
|
||||
readback.get("root_restore_sources_left")
|
||||
),
|
||||
"controlled_cd_lane_live_controlled_drain_active_allowed": bool(
|
||||
readback.get("controlled_drain_active_allowed") is True
|
||||
),
|
||||
"controlled_cd_lane_live_controlled_drain_staging_allowed": bool(
|
||||
readback.get("controlled_drain_staging_allowed") is True
|
||||
),
|
||||
"controlled_cd_lane_live_host_load5_per_core": _float(
|
||||
readback.get("host_load5_per_core")
|
||||
),
|
||||
"controlled_cd_lane_live_gitea_actions_active_container_count": _int(
|
||||
readback.get("gitea_actions_active_container_count")
|
||||
),
|
||||
"controlled_cd_lane_live_gitea_actions_active_process_count": _int(
|
||||
readback.get("gitea_actions_active_process_count")
|
||||
),
|
||||
"controlled_cd_lane_live_metric_blockers": active_blockers,
|
||||
}
|
||||
state.update(metric_fields)
|
||||
|
||||
rollups = _dict(payload.setdefault("rollups", {}))
|
||||
rollups.update(metric_fields)
|
||||
rollups["controlled_cd_lane_live_metric_blocker_count"] = len(active_blockers)
|
||||
|
||||
summary = _dict(payload.setdefault("summary", {}))
|
||||
summary.update(metric_fields)
|
||||
summary["controlled_cd_lane_live_metric_blocker_count"] = len(active_blockers)
|
||||
|
||||
for item in _list(payload.get("in_progress_or_blocked_in_priority_order")):
|
||||
workplan = _dict(item)
|
||||
if workplan.get("workplan_id") != "P0-006":
|
||||
continue
|
||||
evidence = _dict(workplan.setdefault("evidence", {}))
|
||||
evidence.update(metric_fields)
|
||||
evidence["controlled_cd_lane_live_metric_blocker_count"] = len(
|
||||
active_blockers
|
||||
)
|
||||
|
||||
|
||||
def apply_stockplatform_public_api_runtime_readback(
|
||||
payload: dict[str, Any],
|
||||
runtime_readback: dict[str, Any],
|
||||
@@ -2116,6 +2261,87 @@ def _require_mainline_consistency(payload: dict[str, Any], label: str) -> None:
|
||||
raise ValueError(f"{label}: next_execution_order must start with P0-006")
|
||||
|
||||
|
||||
def _parse_prometheus_text_metrics(raw: str) -> dict[str, float]:
|
||||
metrics: dict[str, float] = {}
|
||||
allowed_names = {
|
||||
"awoooi_host_gitea_actions_active_container_count",
|
||||
"awoooi_host_gitea_actions_active_process_count",
|
||||
"awoooi_host_load5_per_core",
|
||||
"awoooi_runner_failclosed_enforcer_active_job_containers",
|
||||
"awoooi_runner_failclosed_enforcer_apply_performed",
|
||||
"awoooi_runner_failclosed_enforcer_controlled_drain_active_allowed",
|
||||
"awoooi_runner_failclosed_enforcer_controlled_drain_staging_allowed",
|
||||
"awoooi_runner_failclosed_enforcer_lane_process_count",
|
||||
"awoooi_runner_failclosed_enforcer_last_run_timestamp",
|
||||
"awoooi_runner_failclosed_enforcer_root_restore_sources_left",
|
||||
}
|
||||
for line in raw.splitlines():
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
match = _METRIC_LINE_RE.match(line.strip())
|
||||
if not match:
|
||||
continue
|
||||
name = match.group("name")
|
||||
if name not in allowed_names:
|
||||
continue
|
||||
try:
|
||||
metrics[name] = float(match.group("value"))
|
||||
except ValueError:
|
||||
continue
|
||||
return metrics
|
||||
|
||||
|
||||
def _build_controlled_cd_lane_metric_readback(
|
||||
metrics: dict[str, float],
|
||||
) -> dict[str, Any]:
|
||||
last_run = metrics.get(
|
||||
"awoooi_runner_failclosed_enforcer_last_run_timestamp", 0.0
|
||||
)
|
||||
now_seconds = datetime.now(ZoneInfo("Asia/Taipei")).timestamp()
|
||||
enforcer_metric_present = (
|
||||
"awoooi_runner_failclosed_enforcer_last_run_timestamp" in metrics
|
||||
)
|
||||
return {
|
||||
"enforcer_metric_present": enforcer_metric_present,
|
||||
"enforcer_last_run_timestamp": last_run,
|
||||
"enforcer_last_run_age_seconds": max(0, int(now_seconds - last_run))
|
||||
if last_run
|
||||
else 0,
|
||||
"enforcer_last_run_fresh": bool(last_run and now_seconds - last_run <= 900),
|
||||
"enforcer_apply_performed": _int(
|
||||
metrics.get("awoooi_runner_failclosed_enforcer_apply_performed")
|
||||
),
|
||||
"active_job_containers": _int(
|
||||
metrics.get("awoooi_runner_failclosed_enforcer_active_job_containers")
|
||||
),
|
||||
"lane_process_count": _int(
|
||||
metrics.get("awoooi_runner_failclosed_enforcer_lane_process_count")
|
||||
),
|
||||
"root_restore_sources_left": _int(
|
||||
metrics.get("awoooi_runner_failclosed_enforcer_root_restore_sources_left")
|
||||
),
|
||||
"controlled_drain_active_allowed": _int(
|
||||
metrics.get(
|
||||
"awoooi_runner_failclosed_enforcer_controlled_drain_active_allowed"
|
||||
)
|
||||
)
|
||||
== 1,
|
||||
"controlled_drain_staging_allowed": _int(
|
||||
metrics.get(
|
||||
"awoooi_runner_failclosed_enforcer_controlled_drain_staging_allowed"
|
||||
)
|
||||
)
|
||||
== 1,
|
||||
"host_load5_per_core": _float(metrics.get("awoooi_host_load5_per_core")),
|
||||
"gitea_actions_active_container_count": _int(
|
||||
metrics.get("awoooi_host_gitea_actions_active_container_count")
|
||||
),
|
||||
"gitea_actions_active_process_count": _int(
|
||||
metrics.get("awoooi_host_gitea_actions_active_process_count")
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _dict(value: Any) -> dict[str, Any]:
|
||||
return value if isinstance(value, dict) else {}
|
||||
|
||||
@@ -2131,6 +2357,13 @@ def _int(value: Any) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _float(value: Any) -> float:
|
||||
try:
|
||||
return float(str(value))
|
||||
except (TypeError, ValueError):
|
||||
return 0.0
|
||||
|
||||
|
||||
def _strings(value: Any) -> list[str]:
|
||||
return [str(item) for item in _list(value)]
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ from src.services.ai_agent_log_controlled_writeback_executor_readback import (
|
||||
)
|
||||
from src.services.awoooi_priority_work_order_readback import (
|
||||
apply_ai_loop_current_blocker_execution_queue,
|
||||
apply_controlled_cd_lane_live_metric_readback,
|
||||
apply_harbor_registry_controlled_recovery_preflight,
|
||||
apply_stockplatform_public_api_controlled_recovery_preflight,
|
||||
apply_stockplatform_public_api_runtime_readback,
|
||||
@@ -104,6 +105,11 @@ def test_awoooi_priority_work_order_readback_endpoint_returns_snapshot(
|
||||
"load_latest_harbor_registry_controlled_recovery_preflight",
|
||||
_harbor_registry_ready,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
agents,
|
||||
"load_controlled_cd_lane_live_metric_readback",
|
||||
_controlled_cd_lane_live_metrics,
|
||||
)
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/api/v1")
|
||||
client = TestClient(app)
|
||||
@@ -124,6 +130,28 @@ def test_awoooi_priority_work_order_readback_endpoint_returns_snapshot(
|
||||
data["mainline_execution_state"]["ai_loop_current_blocker_registry_v2_ready"]
|
||||
is True
|
||||
)
|
||||
assert (
|
||||
data["mainline_execution_state"][
|
||||
"controlled_cd_lane_live_enforcer_metric_present"
|
||||
]
|
||||
is True
|
||||
)
|
||||
assert (
|
||||
data["mainline_execution_state"][
|
||||
"controlled_cd_lane_live_enforcer_last_run_fresh"
|
||||
]
|
||||
is True
|
||||
)
|
||||
assert (
|
||||
data["mainline_execution_state"][
|
||||
"controlled_cd_lane_live_controlled_drain_active_allowed"
|
||||
]
|
||||
is False
|
||||
)
|
||||
assert data["summary"]["controlled_cd_lane_live_metric_status"] == (
|
||||
"controlled_cd_lane_live_metric_readback_ready"
|
||||
)
|
||||
assert data["summary"]["controlled_cd_lane_live_metric_metadata_only"] is True
|
||||
assert data["mainline_execution_state"][
|
||||
"ai_loop_current_blocker_deploy_marker_readback_required"
|
||||
] is False
|
||||
@@ -158,6 +186,11 @@ def test_awoooi_priority_work_order_readback_endpoint_redacts_ai_loop_queue(
|
||||
"load_latest_harbor_registry_controlled_recovery_preflight",
|
||||
_harbor_registry_upstream_502,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
agents,
|
||||
"load_controlled_cd_lane_live_metric_readback",
|
||||
_controlled_cd_lane_live_metrics,
|
||||
)
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/api/v1")
|
||||
client = TestClient(app)
|
||||
@@ -929,6 +962,33 @@ def _stockplatform_runtime_ready() -> dict:
|
||||
}
|
||||
|
||||
|
||||
def _controlled_cd_lane_live_metrics() -> dict:
|
||||
return {
|
||||
"schema_version": "controlled_cd_lane_live_metric_readback_v1",
|
||||
"status": "controlled_cd_lane_live_metric_readback_ready",
|
||||
"source": "node_exporter_text_metrics",
|
||||
"metadata_only": True,
|
||||
"active_blockers": [
|
||||
"controlled_cd_lane_active_not_allowed_yet",
|
||||
"controlled_cd_lane_staging_not_allowed_yet",
|
||||
],
|
||||
"readback": {
|
||||
"enforcer_metric_present": True,
|
||||
"enforcer_last_run_timestamp": 1782961969.0,
|
||||
"enforcer_last_run_fresh": True,
|
||||
"enforcer_apply_performed": 1,
|
||||
"active_job_containers": 0,
|
||||
"lane_process_count": 0,
|
||||
"root_restore_sources_left": 0,
|
||||
"controlled_drain_active_allowed": False,
|
||||
"controlled_drain_staging_allowed": False,
|
||||
"host_load5_per_core": 0.64,
|
||||
"gitea_actions_active_container_count": 0,
|
||||
"gitea_actions_active_process_count": 0,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _stockplatform_runtime_blocked() -> dict:
|
||||
return {
|
||||
"schema_version": "stockplatform_public_api_runtime_readback_v1",
|
||||
|
||||
Reference in New Issue
Block a user