fix(reboot): surface windows99 vmx repair package
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m13s
CD Pipeline / build-and-deploy (push) Successful in 5m15s
CD Pipeline / post-deploy-checks (push) Successful in 1m51s

This commit is contained in:
Your Name
2026-07-03 11:16:10 +08:00
parent ccc6741c76
commit 92edb3ab8a
8 changed files with 490 additions and 0 deletions

View File

@@ -2984,6 +2984,9 @@ def _enrich_from_current_readbacks(
apply_gitea_repo_bundle_backup_readback,
load_latest_reboot_auto_recovery_slo_scorecard,
)
from src.services.windows99_vmx_source_repair_package import (
build_windows99_vmx_source_repair_package,
)
workbench = load_delivery_closure_workbench()
workbench_summary = _dict(workbench.get("summary"))
@@ -3023,6 +3026,9 @@ def _enrich_from_current_readbacks(
windows99_verify_collection_blockers = _strings(
windows99_verify_collection.get("collection_blockers")
)
windows99_vmx_source_repair_package = (
build_windows99_vmx_source_repair_package(reboot_slo)
)
p0_006_event_gated = bool(
reboot_slo_rollups.get("blocked_by_fresh_reboot_window_only") is True
)
@@ -3231,6 +3237,38 @@ def _enrich_from_current_readbacks(
state["windows99_winrm_https_open"] = bool(
windows99_management.get("winrm_https_open") is True
)
state["windows99_vmx_source_repair_package_status"] = str(
windows99_vmx_source_repair_package.get("status") or "unknown"
)
state["windows99_vmx_source_repair_package_ready"] = bool(
windows99_vmx_source_repair_package.get("package_ready") is True
)
state["windows99_vmx_source_repair_missing_aliases"] = _strings(
windows99_vmx_source_repair_package.get("missing_vmx_aliases")
)
state["windows99_vmx_source_repair_powered_off_aliases"] = _strings(
windows99_vmx_source_repair_package.get("powered_off_aliases")
)
state["windows99_vmx_source_repair_controlled_restore_required_count"] = (
_int(
windows99_vmx_source_repair_package.get(
"controlled_restore_required_count"
)
)
)
state["windows99_vmx_source_repair_powered_off_count"] = _int(
windows99_vmx_source_repair_package.get("powered_off_count")
)
state["windows99_vmx_source_repair_safe_next_step"] = str(
windows99_vmx_source_repair_package.get("safe_next_step") or ""
)
state["windows99_vmx_source_repair_controlled_apply_mode"] = str(
windows99_vmx_source_repair_package.get("controlled_apply_mode") or ""
)
state["windows99_vmx_source_repair_apply_allowed_by_this_package"] = bool(
windows99_vmx_source_repair_package.get("apply_allowed_by_this_package")
is True
)
state["stale_snapshot_or_old_cd_runs_must_not_reopen_closed_work"] = True
state["p0_004_template_copy_apply_gate_production_http_status"] = 200
state["p0_004_template_copy_apply_gate_runtime_readback_state"] = (
@@ -3480,6 +3518,33 @@ def _enrich_from_current_readbacks(
]
evidence["windows99_winrm_http_open"] = state["windows99_winrm_http_open"]
evidence["windows99_winrm_https_open"] = state["windows99_winrm_https_open"]
evidence["windows99_vmx_source_repair_package_status"] = state[
"windows99_vmx_source_repair_package_status"
]
evidence["windows99_vmx_source_repair_package_ready"] = state[
"windows99_vmx_source_repair_package_ready"
]
evidence["windows99_vmx_source_repair_missing_aliases"] = state[
"windows99_vmx_source_repair_missing_aliases"
]
evidence["windows99_vmx_source_repair_powered_off_aliases"] = state[
"windows99_vmx_source_repair_powered_off_aliases"
]
evidence["windows99_vmx_source_repair_controlled_restore_required_count"] = (
state["windows99_vmx_source_repair_controlled_restore_required_count"]
)
evidence["windows99_vmx_source_repair_powered_off_count"] = state[
"windows99_vmx_source_repair_powered_off_count"
]
evidence["windows99_vmx_source_repair_safe_next_step"] = state[
"windows99_vmx_source_repair_safe_next_step"
]
evidence["windows99_vmx_source_repair_controlled_apply_mode"] = state[
"windows99_vmx_source_repair_controlled_apply_mode"
]
evidence["windows99_vmx_source_repair_apply_allowed_by_this_package"] = (
state["windows99_vmx_source_repair_apply_allowed_by_this_package"]
)
evidence["drill_preflight_status"] = str(reboot_preflight.get("status") or "")
evidence["drill_preflight_ready"] = (
reboot_preflight_rollups.get("preflight_ready") is True
@@ -4116,6 +4181,19 @@ def _set_rollups_and_summary(
"windows99_console_collection_channels": _strings(
state.get("windows99_console_collection_channels")
),
"windows99_vmx_source_repair_package_ready": (
state.get("windows99_vmx_source_repair_package_ready") is True
),
"windows99_vmx_source_repair_controlled_restore_required_count": _int(
state.get("windows99_vmx_source_repair_controlled_restore_required_count")
),
"windows99_vmx_source_repair_powered_off_count": _int(
state.get("windows99_vmx_source_repair_powered_off_count")
),
"windows99_vmx_source_repair_apply_allowed_by_this_package": (
state.get("windows99_vmx_source_repair_apply_allowed_by_this_package")
is True
),
"p0_004_runtime_readback_ready": p0_004_ready,
"reboot_drill_preflight_runtime_readback_ready": (
state.get("reboot_drill_preflight_runtime_readback_state") == "ready"
@@ -4263,6 +4341,34 @@ def _set_rollups_and_summary(
"windows99_console_collection_channels": _strings(
state.get("windows99_console_collection_channels")
),
"windows99_vmx_source_repair_package_status": str(
state.get("windows99_vmx_source_repair_package_status") or "unknown"
),
"windows99_vmx_source_repair_package_ready": (
state.get("windows99_vmx_source_repair_package_ready") is True
),
"windows99_vmx_source_repair_missing_aliases": _strings(
state.get("windows99_vmx_source_repair_missing_aliases")
),
"windows99_vmx_source_repair_powered_off_aliases": _strings(
state.get("windows99_vmx_source_repair_powered_off_aliases")
),
"windows99_vmx_source_repair_controlled_restore_required_count": _int(
state.get("windows99_vmx_source_repair_controlled_restore_required_count")
),
"windows99_vmx_source_repair_powered_off_count": _int(
state.get("windows99_vmx_source_repair_powered_off_count")
),
"windows99_vmx_source_repair_safe_next_step": str(
state.get("windows99_vmx_source_repair_safe_next_step") or ""
),
"windows99_vmx_source_repair_controlled_apply_mode": str(
state.get("windows99_vmx_source_repair_controlled_apply_mode") or ""
),
"windows99_vmx_source_repair_apply_allowed_by_this_package": (
state.get("windows99_vmx_source_repair_apply_allowed_by_this_package")
is True
),
"windows99_winrm_http_open": (
state.get("windows99_winrm_http_open") is True
),

View File

@@ -0,0 +1,82 @@
"""Windows99 VMX source repair check-mode package readback.
This API-facing wrapper reuses the reboot SLO scorecard's canonical package
builder, then adds compact count/metadata fields for manager UI consumption.
It does not read secrets, start VMs, write Windows state, restart services, or
reboot hosts.
"""
from __future__ import annotations
from typing import Any
from src.services.reboot_auto_recovery_slo_scorecard import (
_build_windows99_vmx_source_repair_package,
_taipei_now_iso,
load_latest_reboot_auto_recovery_slo_scorecard,
)
def _dict(value: Any) -> dict[str, Any]:
return value if isinstance(value, dict) else {}
def _strings(value: Any) -> list[str]:
if isinstance(value, list):
return [str(item).strip() for item in value if str(item).strip()]
if isinstance(value, str):
return [part.strip() for part in value.split(",") if part.strip()]
return []
def _with_ui_counts(
package: dict[str, Any],
scorecard: dict[str, Any],
) -> dict[str, Any]:
payload = dict(package)
missing_vmx_aliases = _strings(payload.get("missing_vmx_aliases"))
powered_off_aliases = _strings(payload.get("powered_off_aliases"))
readback = _dict(scorecard.get("readback"))
payload["controlled_restore_required_count"] = len(missing_vmx_aliases)
payload["powered_off_count"] = len(powered_off_aliases)
payload["scorecard_status"] = str(scorecard.get("status") or "unknown")
payload["scorecard_generated_at"] = str(scorecard.get("generated_at") or "")
payload["collector_status"] = str(
readback.get("windows99_no_secret_collector_status") or "unknown"
)
return payload
def _alias_lists_match(package: dict[str, Any], windows99: dict[str, Any]) -> bool:
return _strings(package.get("missing_vmx_aliases")) == _strings(
windows99.get("missing_vmx_aliases")
) and _strings(package.get("powered_off_aliases")) == _strings(
windows99.get("powered_off_aliases")
)
def build_windows99_vmx_source_repair_package(
scorecard: dict[str, Any],
*,
generated_at: str | None = None,
) -> dict[str, Any]:
existing_package = _dict(scorecard.get("windows99_vmx_source_repair_package"))
windows99 = _dict(scorecard.get("windows99_vmware_autostart"))
package = (
dict(existing_package)
if existing_package
and generated_at is None
and _alias_lists_match(existing_package, windows99)
else _build_windows99_vmx_source_repair_package(
windows99,
generated_at=generated_at
or str(scorecard.get("generated_at") or _taipei_now_iso()),
)
)
return _with_ui_counts(package, scorecard)
def load_latest_windows99_vmx_source_repair_package() -> dict[str, Any]:
return build_windows99_vmx_source_repair_package(
load_latest_reboot_auto_recovery_slo_scorecard()
)