fix(reboot): load runtime slo scorecard artifact
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 1m5s
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-03 00:28:06 +08:00
parent d896139ec5
commit f700489bb9
2 changed files with 91 additions and 9 deletions

View File

@@ -1,8 +1,8 @@
"""P0-006 reboot auto-recovery SLO scorecard readback.
This loader promotes the committed reboot recovery scorecard into a stable API
contract. It does not reboot hosts, restart services, query runtime secrets, or
write StockPlatform data; it only reads the committed JSON scorecard.
This loader promotes the committed reboot recovery scorecard and the latest
runtime verifier artifact into a stable API contract. It does not reboot hosts,
restart services, query runtime secrets, or write StockPlatform data.
"""
from __future__ import annotations
@@ -23,8 +23,8 @@ _API_SCHEMA_VERSION = "reboot_auto_recovery_slo_scorecard_readback_v1"
_PUBLIC_MAINTENANCE_RUNTIME_FILE_ENV = (
"AWOOOI_PUBLIC_MAINTENANCE_FALLBACK_READBACK_FILE"
)
_PUBLIC_MAINTENANCE_RUNTIME_DIR_ENV = "AWOOOI_REBOOT_RECOVERY_LOG_DIR"
_RUNTIME_SCORECARD_FILE_ENV = "AWOOOI_REBOOT_RECOVERY_SLO_SCORECARD_FILE"
_PUBLIC_MAINTENANCE_RUNTIME_DIR_ENV = "AWOOOI_REBOOT_RECOVERY_LOG_DIR"
_RUNTIME_SCORECARD_DIR_ENV = "AWOOOI_REBOOT_RECOVERY_LOG_DIR"
_DEFAULT_REBOOT_RECOVERY_LOG_DIR = Path("/home/wooo/reboot-recovery")
_PUBLIC_MAINTENANCE_RUNTIME_PATTERN = (
@@ -45,7 +45,7 @@ def load_latest_reboot_auto_recovery_slo_scorecard(
runtime_scorecard_path: Path | None = None,
runtime_scorecard_dir: Path | None = None,
) -> dict[str, Any]:
"""Load and validate the committed P0-006 reboot recovery scorecard."""
"""Load the committed P0-006 scorecard and overlay trusted runtime artifacts."""
directory = operations_dir or _DEFAULT_OPERATIONS_DIR
path = directory / _SCORECARD_FILE
with path.open(encoding="utf-8") as handle:
@@ -106,9 +106,15 @@ def _load_latest_runtime_scorecard(
return {}, None
if not candidates:
return {}, None
latest = max(candidates, key=lambda path: path.stat().st_mtime)
payload = _read_json_file(latest)
return _runtime_scorecard_if_supported(payload), latest
for latest in sorted(
candidates,
key=lambda path: path.stat().st_mtime,
reverse=True,
):
payload = _runtime_scorecard_if_supported(_read_json_file(latest))
if payload:
return payload, latest
return {}, None
def _runtime_scorecard_if_supported(payload: dict[str, Any]) -> dict[str, Any]:
@@ -446,6 +452,11 @@ def _build_payload(scorecard: dict[str, Any], path: Path) -> dict[str, Any]:
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"))
source_scorecard_ref = (
f"docs/operations/{path.name}"
if path.name == _SCORECARD_FILE
else str(path)
)
active_blocker_count = len(active_blockers)
observed_host_count = len(_strings(host_boot_detection.get("observed_hosts")))
missing_host_count = len(_strings(host_boot_detection.get("missing_hosts")))
@@ -673,7 +684,7 @@ def _build_payload(scorecard: dict[str, Any], path: Path) -> dict[str, Any]:
"readback": {
"workplan_id": "P0-006",
"workplan_title": "主機重啟自動偵測、自動觸發與 10 分鐘恢復 SLO",
"source_scorecard_ref": f"docs/operations/{path.name}",
"source_scorecard_ref": source_scorecard_ref,
"generated_at": runtime_readback_generated_at,
"runtime_readback_generated_at": runtime_readback_generated_at,
"snapshot_generated_at": snapshot_generated_at,

View File

@@ -71,6 +71,14 @@ PUBLIC_MAINTENANCE_RUNTIME_READY = {
],
}
_REPO_ROOT = Path(__file__).resolve().parents[3]
_SOURCE_SCORECARD = (
_REPO_ROOT
/ "docs"
/ "operations"
/ "awoooi-reboot-auto-recovery-slo-scorecard.snapshot.json"
)
def test_reboot_auto_recovery_slo_scorecard_loader_exposes_stockplatform_gate():
payload = load_latest_reboot_auto_recovery_slo_scorecard()
@@ -155,6 +163,69 @@ def test_reboot_auto_recovery_slo_scorecard_overlays_runtime_scorecard_artifact(
assert payload["rollups"]["runtime_scorecard_readback_present"] is True
def test_reboot_auto_recovery_slo_scorecard_uses_latest_runtime_scorecard_from_dir(
tmp_path,
):
runtime_dir = tmp_path / "reboot-recovery"
artifact_dir = runtime_dir / "reboot-auto-recovery-slo-20260703-002055"
artifact_dir.mkdir(parents=True)
runtime_scorecard = json.loads(_SOURCE_SCORECARD.read_text(encoding="utf-8"))
runtime_scorecard["generated_at"] = "2026-07-03T00:20:55+08:00"
runtime_scorecard["active_blockers"] = [
"all_required_hosts_not_in_10_minute_reboot_window",
"conversation_event_hot_path_index_migration_source_missing",
"host_boot_observation_older_than_target_window",
"host_unreachable_after_reboot",
"host_uptime_unknown",
"public_maintenance_fallback_runtime_readback_missing",
"reboot_event_required_host_unreachable",
"windows99_vmware_autostart_readback_missing",
]
runtime_scorecard["windows99_management_channel"]["ssh_users"] = [
"administrator",
"wooo",
]
runtime_scorecard["windows99_management_channel"]["ssh_batch_candidates"] = [
{
"user": "administrator",
"checked": True,
"ready": False,
"status": "permission_denied",
},
{
"user": "wooo",
"checked": True,
"ready": False,
"status": "permission_denied",
},
]
(artifact_dir / "scorecard.json").write_text(
json.dumps(runtime_scorecard),
encoding="utf-8",
)
payload = load_latest_reboot_auto_recovery_slo_scorecard(
runtime_scorecard_dir=runtime_dir,
public_maintenance_runtime_dir=runtime_dir,
)
assert payload["runtime_scorecard_readback_present"] is True
assert payload["runtime_scorecard_generated_at"] == (
"2026-07-03T00:20:55+08:00"
)
assert payload["snapshot_generated_at"] == "2026-07-02T15:08:44+08:00"
assert payload["active_blocker_count"] == 8
assert payload["readback"]["runtime_scorecard_source_ref"].endswith(
"reboot-auto-recovery-slo-20260703-002055/scorecard.json"
)
assert (
payload["windows99_management_channel"]["ssh_batch_candidates"][1][
"user"
]
== "wooo"
)
def test_reboot_auto_recovery_slo_scorecard_endpoint_returns_readback(monkeypatch):
monkeypatch.setattr(
agents,