fix(reboot): expose runtime scorecard freshness
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 52s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled

This commit is contained in:
Your Name
2026-07-02 18:27:06 +08:00
parent e97af7951a
commit 1f251d06f4
5 changed files with 100 additions and 3 deletions

View File

@@ -7,7 +7,9 @@ need to fan out across five separate endpoints or duplicate blocker math.
from __future__ import annotations
from datetime import datetime
from typing import Any
from zoneinfo import ZoneInfo
from src.services.awoooi_gitea_onboarding_warning_step_runtime_enablement_gate import (
load_latest_awoooi_gitea_onboarding_warning_step_runtime_enablement_gate,
@@ -1322,14 +1324,28 @@ def build_delivery_closure_workbench(
_source_status("runtime_surface", runtime),
_source_status("backup_dr", backup),
]
generated_candidates = [
source_generated_candidates = [
source["generated_at"] for source in source_statuses if source["generated_at"]
]
snapshot_generated_candidates = [
source["snapshot_generated_at"]
for source in source_statuses
if source["snapshot_generated_at"]
]
loaded_source_count = sum(1 for source in source_statuses if source["loaded"])
high_risk_blocker_count = sum(_int(lane["blocker_count"]) for lane in lanes)
average_completion = _percent(
sum(_int(lane["completion_percent"]) for lane in lanes) / max(len(lanes), 1)
)
source_generated_at = (
max(source_generated_candidates) if source_generated_candidates else ""
)
snapshot_generated_at = (
max(snapshot_generated_candidates)
if snapshot_generated_candidates
else source_generated_at
)
runtime_readback_generated_at = _taipei_now_iso()
next_focus = [
{
"lane_id": lane["id"],
@@ -1350,12 +1366,19 @@ def build_delivery_closure_workbench(
return {
"schema_version": _SCHEMA_VERSION,
"generated_at": max(generated_candidates) if generated_candidates else "",
"generated_at": runtime_readback_generated_at,
"runtime_readback_generated_at": runtime_readback_generated_at,
"source_generated_at": source_generated_at,
"snapshot_generated_at": snapshot_generated_at,
"status": delivery_status,
"safe_next_step": current_p0_safe_next_step,
"active_blockers": current_p0_active_blockers,
"readback": {
"workbench_status": delivery_status,
"generated_at": runtime_readback_generated_at,
"runtime_readback_generated_at": runtime_readback_generated_at,
"source_generated_at": source_generated_at,
"snapshot_generated_at": snapshot_generated_at,
"current_p0_workplan_id": current_p0_workplan_id,
"current_p0_source_id": "reboot_auto_recovery_slo_scorecard",
"current_p0_status": str(reboot_slo.get("status") or ""),
@@ -1442,8 +1465,14 @@ def build_delivery_closure_workbench(
"secret_values_collected": False,
"workflow_trigger_authorized": False,
"runtime_write_authorized": False,
"runtime_readback_generated_at_present": True,
"source_generated_at_present": bool(source_generated_at),
},
"summary": {
"generated_at": runtime_readback_generated_at,
"runtime_readback_generated_at": runtime_readback_generated_at,
"source_generated_at": source_generated_at,
"snapshot_generated_at": snapshot_generated_at,
"source_count": len(source_statuses),
"loaded_source_count": loaded_source_count,
"average_completion_percent": average_completion,
@@ -2266,6 +2295,10 @@ def _source_status(source_id: str, payload: dict[str, Any]) -> dict[str, Any]:
"loaded": not source_missing,
"schema_version": str(payload.get("schema_version") or ""),
"generated_at": str(payload.get("generated_at") or ""),
"runtime_readback_generated_at": str(
payload.get("runtime_readback_generated_at") or ""
),
"snapshot_generated_at": str(payload.get("snapshot_generated_at") or ""),
"missing_reason": str(payload.get("missing_reason") or "")
if source_missing
else "",
@@ -2302,6 +2335,10 @@ def _strings(value: Any) -> list[str]:
return [str(item) for item in value if item is not None]
def _taipei_now_iso() -> str:
return datetime.now(ZoneInfo("Asia/Taipei")).isoformat(timespec="seconds")
def _first_string(value: Any) -> str:
if isinstance(value, list) and value:
return str(value[0])

View File

@@ -8,8 +8,10 @@ write StockPlatform data; it only reads the committed JSON scorecard.
from __future__ import annotations
import json
from datetime import datetime
from pathlib import Path
from typing import Any
from zoneinfo import ZoneInfo
from src.services.snapshot_paths import default_operations_dir
@@ -80,6 +82,8 @@ def _build_payload(scorecard: dict[str, Any], path: Path) -> dict[str, Any]:
)
if can_claim_slo and not active_blockers:
readiness_percent = 100
snapshot_generated_at = str(scorecard.get("generated_at") or "")
runtime_readback_generated_at = _taipei_now_iso()
latest_verify_metric = _dict(scorecard.get("latest_verify_only_metric"))
active_blocker_count = len(active_blockers)
observed_host_count = len(_strings(host_boot_detection.get("observed_hosts")))
@@ -228,10 +232,13 @@ def _build_payload(scorecard: dict[str, Any], path: Path) -> dict[str, Any]:
"windows99_rdp_console_reachable": (
windows99_management.get("rdp_console_reachable") is True
),
"runtime_readback_generated_at_present": True,
}
return {
"schema_version": _API_SCHEMA_VERSION,
"generated_at": str(scorecard.get("generated_at") or ""),
"generated_at": runtime_readback_generated_at,
"runtime_readback_generated_at": runtime_readback_generated_at,
"snapshot_generated_at": snapshot_generated_at,
"priority": "P0-006",
"scope": "reboot_auto_recovery_slo_scorecard",
"status": str(scorecard.get("status") or "unknown"),
@@ -277,6 +284,9 @@ def _build_payload(scorecard: dict[str, Any], path: Path) -> dict[str, Any]:
"workplan_id": "P0-006",
"workplan_title": "主機重啟自動偵測、自動觸發與 10 分鐘恢復 SLO",
"source_scorecard_ref": f"docs/operations/{path.name}",
"generated_at": runtime_readback_generated_at,
"runtime_readback_generated_at": runtime_readback_generated_at,
"snapshot_generated_at": snapshot_generated_at,
"target_minutes": _int(scorecard.get("target_minutes")),
"current_phase": sop_progress["current_phase"],
"eta_or_wait_reason": sop_progress["eta_or_wait_reason"],
@@ -734,3 +744,7 @@ def _strings(value: Any) -> list[str]:
if not isinstance(value, list):
return []
return [str(item) for item in value if item is not None]
def _taipei_now_iso() -> str:
return datetime.now(ZoneInfo("Asia/Taipei")).isoformat(timespec="seconds")