diff --git a/apps/api/src/api/v1/agents.py b/apps/api/src/api/v1/agents.py index 47f731b35..80911b215 100644 --- a/apps/api/src/api/v1/agents.py +++ b/apps/api/src/api/v1/agents.py @@ -483,6 +483,9 @@ from src.services.telegram_alert_monitoring_live_receipt_apply import ( apply_latest_telegram_alert_monitoring_live_receipts, load_latest_telegram_alert_monitoring_live_receipt_apply_readback, ) +from src.services.windows99_vmx_source_repair_package import ( + load_latest_windows99_vmx_source_repair_package, +) router = APIRouter(prefix="/agents", tags=["Agent Teams"]) logger = get_logger("awoooi.agents") @@ -1532,6 +1535,37 @@ async def get_reboot_auto_recovery_slo_scorecard() -> dict[str, Any]: ) from exc +@router.get( + "/windows99-vmx-source-repair-package", + response_model=dict[str, Any], + summary="取得 Windows99 VMX source repair check-mode package", + description=( + "讀取 P0-006 scorecard 的 Windows99 VMware autostart/VMX blockers," + "產生只讀 check-mode package。此端點只回傳缺 VMX alias、powered-off alias、" + "safe next step、post-verifier 與禁止 runtime action;不讀 secret、不 SSH、" + "不啟停 VM、不寫 Windows、不改 registry/task、不重啟主機、不 restart service。" + ), +) +async def get_windows99_vmx_source_repair_package() -> dict[str, Any]: + """回傳 Windows99 VMX source repair check-mode package。""" + try: + payload = await asyncio.to_thread( + load_latest_windows99_vmx_source_repair_package + ) + return redact_public_lan_topology(payload) + except FileNotFoundError as exc: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=str(exc), + ) from exc + except (json.JSONDecodeError, ValueError) as exc: + logger.error("windows99_vmx_source_repair_package_invalid", error=str(exc)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Windows99 VMX source repair check-mode package 無效", + ) from exc + + @router.get( "/reboot-auto-recovery-drill-preflight", response_model=dict[str, Any], diff --git a/apps/api/src/services/awoooi_priority_work_order_readback.py b/apps/api/src/services/awoooi_priority_work_order_readback.py index 1785e38e9..a2314a6e9 100644 --- a/apps/api/src/services/awoooi_priority_work_order_readback.py +++ b/apps/api/src/services/awoooi_priority_work_order_readback.py @@ -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 ), diff --git a/apps/api/src/services/windows99_vmx_source_repair_package.py b/apps/api/src/services/windows99_vmx_source_repair_package.py new file mode 100644 index 000000000..53cd96731 --- /dev/null +++ b/apps/api/src/services/windows99_vmx_source_repair_package.py @@ -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() + ) diff --git a/apps/api/tests/test_awoooi_priority_work_order_readback_api.py b/apps/api/tests/test_awoooi_priority_work_order_readback_api.py index bc911ef20..d97482567 100644 --- a/apps/api/tests/test_awoooi_priority_work_order_readback_api.py +++ b/apps/api/tests/test_awoooi_priority_work_order_readback_api.py @@ -620,6 +620,13 @@ def test_awoooi_priority_work_order_readback_routes_closed_backup_to_host_boot_a "host99_uptime_known": False, } ) + payload["windows99_vmware_autostart"].update( + { + "required_vm_aliases": ["111", "188", "120", "121", "112"], + "missing_vmx_aliases": ["111"], + "powered_off_aliases": ["111", "112", "120", "121", "188"], + } + ) payload["rollups"].update( { "active_blocker_count": len(active_blockers), @@ -677,6 +684,17 @@ def test_awoooi_priority_work_order_readback_routes_closed_backup_to_host_boot_a "windows99_vmware_guest_power_not_ready", "windows99_vmware_vmx_missing", ] + assert state["windows99_vmx_source_repair_package_ready"] is True + assert state["windows99_vmx_source_repair_missing_aliases"] == ["111"] + assert state["windows99_vmx_source_repair_powered_off_count"] == 5 + assert ( + state["windows99_vmx_source_repair_controlled_restore_required_count"] + == 1 + ) + assert ( + state["windows99_vmx_source_repair_apply_allowed_by_this_package"] + is False + ) assert payload["summary"]["controlled_service_data_backup_blocker_count"] == 0 assert payload["summary"]["active_p0_primary_blocker"] == ( "reboot_event_required_host_unreachable" @@ -702,6 +720,22 @@ def test_awoooi_priority_work_order_readback_routes_closed_backup_to_host_boot_a "blocked_reboot_slo_scorecard_host_boot_and_windows99_verify_" "collection_not_ready" ) + assert payload["summary"]["windows99_vmx_source_repair_package_ready"] is True + assert payload["summary"]["windows99_vmx_source_repair_missing_aliases"] == [ + "111" + ] + assert ( + payload["summary"][ + "windows99_vmx_source_repair_controlled_restore_required_count" + ] + == 1 + ) + assert ( + payload["rollups"][ + "windows99_vmx_source_repair_apply_allowed_by_this_package" + ] + is False + ) assert "service/data/backup readback is green" in payload[ "next_execution_order" ][0] @@ -716,6 +750,19 @@ def test_awoooi_priority_work_order_readback_routes_closed_backup_to_host_boot_a assert in_progress["evidence"]["next_safe_action"] == ( "rerun_reboot_event_detector_and_host_probe_verify_only_no_reboot" ) + assert ( + in_progress["evidence"]["windows99_vmx_source_repair_package_status"] + == "check_mode_package_ready_windows99_vmx_source_repair_required" + ) + assert in_progress["evidence"]["windows99_vmx_source_repair_missing_aliases"] == [ + "111" + ] + assert ( + in_progress["evidence"][ + "windows99_vmx_source_repair_apply_allowed_by_this_package" + ] + is False + ) assert "Windows99 verify collection" in in_progress["professional_fix"][ "action" ] diff --git a/apps/api/tests/test_windows99_vmx_source_repair_package_api.py b/apps/api/tests/test_windows99_vmx_source_repair_package_api.py new file mode 100644 index 000000000..8eb129f5c --- /dev/null +++ b/apps/api/tests/test_windows99_vmx_source_repair_package_api.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from src.api.v1 import agents +from src.api.v1.agents import router +from src.services.windows99_vmx_source_repair_package import ( + build_windows99_vmx_source_repair_package, +) + + +def _scorecard() -> dict: + return { + "status": "blocked_reboot_auto_recovery_slo_not_ready", + "generated_at": "2026-07-03T10:45:28+08:00", + "windows99_vmware_autostart": { + "required_vm_aliases": ["111", "188", "120", "121", "112"], + "missing_vmx_aliases": ["111"], + "powered_off_aliases": ["111", "112", "120", "121", "188"], + }, + "readback": { + "windows99_no_secret_collector_status": ( + "collected_windows99_vmware_verify_stdout" + ) + }, + } + + +def test_windows99_vmx_source_repair_package_builder_is_check_mode_only() -> None: + payload = build_windows99_vmx_source_repair_package( + _scorecard(), + generated_at="2026-07-03T10:46:00+08:00", + ) + + assert payload["schema_version"] == ( + "windows99_vmx_source_repair_check_mode_package_v1" + ) + assert payload["status"] == ( + "check_mode_package_ready_windows99_vmx_source_repair_required" + ) + assert payload["package_ready"] is True + assert payload["missing_vmx_aliases"] == ["111"] + assert payload["powered_off_count"] == 5 + assert payload["controlled_restore_required_count"] == 1 + assert payload["safe_next_step"] == ( + "restore_windows99_missing_vmx_source_for_aliases_then_rerun_no_secret_" + "collector_and_scorecard_no_vm_power_change" + ) + assert payload["controlled_apply_mode"] == ( + "check_mode_package_only_no_windows_or_vm_write" + ) + assert payload["apply_allowed_by_this_package"] is False + assert payload["operation_boundaries"]["secret_value_read"] is False + assert payload["operation_boundaries"]["remote_write_performed"] is False + assert payload["operation_boundaries"]["vm_power_change_performed"] is False + assert "read_windows_password_or_secret" in payload["forbidden_actions"] + assert "vm_power_change" in payload["forbidden_actions"] + + +def test_windows99_vmx_source_repair_package_endpoint_returns_readback( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setattr( + agents, + "load_latest_windows99_vmx_source_repair_package", + lambda: build_windows99_vmx_source_repair_package( + _scorecard(), + generated_at="2026-07-03T10:46:00+08:00", + ), + ) + app = FastAPI() + app.include_router(router, prefix="/api/v1") + client = TestClient(app) + + response = client.get("/api/v1/agents/windows99-vmx-source-repair-package") + + assert response.status_code == 200 + payload = response.json() + assert payload["schema_version"] == ( + "windows99_vmx_source_repair_check_mode_package_v1" + ) + assert payload["target_host_alias"] == "99" + assert payload["controlled_apply_mode"] == ( + "check_mode_package_only_no_windows_or_vm_write" + ) + assert payload["apply_allowed_by_this_package"] is False + assert payload["operation_boundaries"]["host_reboot_performed"] is False diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index a944bcf05..d7a263029 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -9337,6 +9337,17 @@ "noSecret": "no secret", "publicVerifier": "public verifier" }, + "vmxRepair": { + "label": "VMX repair", + "ready": "package ready", + "waiting": "waiting package", + "missing": "VMX {aliases}", + "missingNone": "VMX 0", + "poweredOff": "power off {count}", + "checkMode": "check-mode", + "applyClosed": "apply 0", + "applyOpen": "apply 1" + }, "blockerLanes": { "label": "Blocker lanes", "count": "{count}", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 1e2b0cf6f..407ca32f0 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -9337,6 +9337,17 @@ "noSecret": "不讀 secret", "publicVerifier": "公開驗證器" }, + "vmxRepair": { + "label": "VMX repair", + "ready": "package ready", + "waiting": "等待 package", + "missing": "VMX {aliases}", + "missingNone": "VMX 0", + "poweredOff": "power off {count}", + "checkMode": "check-mode", + "applyClosed": "apply 0", + "applyOpen": "apply 1" + }, "blockerLanes": { "label": "卡點分群", "count": "{count} 個", diff --git a/apps/web/src/app/[locale]/awooop/work-items/page.tsx b/apps/web/src/app/[locale]/awooop/work-items/page.tsx index 417a6be29..adf8b5265 100644 --- a/apps/web/src/app/[locale]/awooop/work-items/page.tsx +++ b/apps/web/src/app/[locale]/awooop/work-items/page.tsx @@ -1154,6 +1154,15 @@ type PriorityWorkOrderResponse = { controlled_service_data_backup_next_safe_action?: string | null; windows99_verify_collection_blocker_count?: number | null; windows99_verify_collection_blockers?: string[] | null; + windows99_vmx_source_repair_package_status?: string | null; + windows99_vmx_source_repair_package_ready?: boolean | null; + windows99_vmx_source_repair_missing_aliases?: string[] | null; + windows99_vmx_source_repair_powered_off_aliases?: string[] | null; + windows99_vmx_source_repair_controlled_restore_required_count?: number | null; + windows99_vmx_source_repair_powered_off_count?: number | null; + windows99_vmx_source_repair_safe_next_step?: string | null; + windows99_vmx_source_repair_controlled_apply_mode?: string | null; + windows99_vmx_source_repair_apply_allowed_by_this_package?: boolean | null; ai_loop_log_source_grouping_key_count?: number | null; ai_loop_log_source_grouping_keys?: string[] | null; ai_loop_log_source_tagging_contract_count?: number | null; @@ -1191,6 +1200,15 @@ type PriorityWorkOrderResponse = { controlled_service_data_backup_blocker_count?: number | null; controlled_service_data_backup_can_clear_blockers?: boolean | null; windows99_verify_collection_blockers?: string[] | null; + windows99_vmx_source_repair_package_status?: string | null; + windows99_vmx_source_repair_package_ready?: boolean | null; + windows99_vmx_source_repair_missing_aliases?: string[] | null; + windows99_vmx_source_repair_powered_off_aliases?: string[] | null; + windows99_vmx_source_repair_controlled_restore_required_count?: number | null; + windows99_vmx_source_repair_powered_off_count?: number | null; + windows99_vmx_source_repair_safe_next_step?: string | null; + windows99_vmx_source_repair_controlled_apply_mode?: string | null; + windows99_vmx_source_repair_apply_allowed_by_this_package?: boolean | null; drill_preflight_blocker_count?: number | null; ai_loop_current_blocker_log_source_tags?: AiLoopLogSourceTag[] | null; ai_loop_log_source_tagging_contract?: AiLoopLogSourceContract[] | null; @@ -1286,6 +1304,18 @@ const COMMANDER_INSERTED_REQUIREMENT_FALLBACK: PriorityWorkOrderResponse = { "windows99_vmware_vmx_missing", "windows99_uptime_unknown", ], + windows99_vmx_source_repair_package_status: + "check_mode_package_ready_windows99_vmx_source_repair_required", + windows99_vmx_source_repair_package_ready: true, + windows99_vmx_source_repair_missing_aliases: ["111"], + windows99_vmx_source_repair_powered_off_aliases: ["111", "112", "120", "121", "188"], + windows99_vmx_source_repair_controlled_restore_required_count: 1, + windows99_vmx_source_repair_powered_off_count: 5, + windows99_vmx_source_repair_safe_next_step: + "restore_windows99_missing_vmx_source_for_aliases_then_rerun_no_secret_collector_and_scorecard_no_vm_power_change", + windows99_vmx_source_repair_controlled_apply_mode: + "check_mode_package_only_no_windows_or_vm_write", + windows99_vmx_source_repair_apply_allowed_by_this_package: false, stockplatform_public_api_runtime_status: "stockplatform_public_api_runtime_ready", stockplatform_public_api_controlled_recovery_preflight_status: "not_required_stockplatform_runtime_ready", @@ -1342,6 +1372,18 @@ const COMMANDER_INSERTED_REQUIREMENT_FALLBACK: PriorityWorkOrderResponse = { "windows99_vmware_vmx_missing", "windows99_uptime_unknown", ], + windows99_vmx_source_repair_package_status: + "check_mode_package_ready_windows99_vmx_source_repair_required", + windows99_vmx_source_repair_package_ready: true, + windows99_vmx_source_repair_missing_aliases: ["111"], + windows99_vmx_source_repair_powered_off_aliases: ["111", "112", "120", "121", "188"], + windows99_vmx_source_repair_controlled_restore_required_count: 1, + windows99_vmx_source_repair_powered_off_count: 5, + windows99_vmx_source_repair_safe_next_step: + "restore_windows99_missing_vmx_source_for_aliases_then_rerun_no_secret_collector_and_scorecard_no_vm_power_change", + windows99_vmx_source_repair_controlled_apply_mode: + "check_mode_package_only_no_windows_or_vm_write", + windows99_vmx_source_repair_apply_allowed_by_this_package: false, drill_preflight_blocker_count: 5, stockplatform_public_api_runtime_status: "stockplatform_public_api_runtime_ready", stockplatform_public_api_runtime_ready: true, @@ -8898,7 +8940,9 @@ function compactBlockerLabel(value: string | null | undefined) { service_green_not_1: "service green", wazuh_dashboard_degraded: "Wazuh dashboard", windows99_remote_execution_channel_unavailable: "windows99 remote", + windows99_update_no_auto_reboot_policy_not_ready: "update policy", windows99_vmware_autostart_readback_missing: "VMware autostart", + windows99_vmware_autostart_config_not_ready: "autostart config", windows99_vmware_guest_power_not_ready: "VM power", windows99_vmware_vmx_missing: "VMX missing", windows99_uptime_unknown: "Windows99 uptime", @@ -9050,6 +9094,35 @@ function ManagerSituationBoard({ summary?.controlled_service_data_backup_blocker_count ?? evidence?.controlled_service_data_backup_blocker_count ?? 0; + const vmxRepairMissingAliases = ( + summary?.windows99_vmx_source_repair_missing_aliases ?? + evidence?.windows99_vmx_source_repair_missing_aliases ?? + [] + ).filter(Boolean); + const vmxRepairPoweredOffAliases = ( + summary?.windows99_vmx_source_repair_powered_off_aliases ?? + evidence?.windows99_vmx_source_repair_powered_off_aliases ?? + [] + ).filter(Boolean); + const vmxRepairPackageReady = + summary?.windows99_vmx_source_repair_package_ready ?? + evidence?.windows99_vmx_source_repair_package_ready ?? + false; + const vmxRepairRestoreCount = + summary?.windows99_vmx_source_repair_controlled_restore_required_count ?? + evidence?.windows99_vmx_source_repair_controlled_restore_required_count ?? + vmxRepairMissingAliases.length; + const vmxRepairPoweredOffCount = + summary?.windows99_vmx_source_repair_powered_off_count ?? + evidence?.windows99_vmx_source_repair_powered_off_count ?? + vmxRepairPoweredOffAliases.length; + const vmxRepairApplyAllowed = + summary?.windows99_vmx_source_repair_apply_allowed_by_this_package ?? + evidence?.windows99_vmx_source_repair_apply_allowed_by_this_package ?? + false; + const vmxRepairMissingLabel = vmxRepairMissingAliases.length + ? vmxRepairMissingAliases.join(", ") + : "0"; const readinessPercent = summary?.active_p0_readiness_percent ?? null; const rebootEvidenceReady = evidence?.live_slo_metric_present === true && @@ -9248,6 +9321,43 @@ function ManagerSituationBoard({ {isRebootP0 ? (
+
+ + + + {vmxRepairPackageReady + ? t("vmxRepair.ready") + : t("vmxRepair.waiting")} + + + {vmxRepairRestoreCount > 0 + ? t("vmxRepair.missing", { aliases: vmxRepairMissingLabel }) + : t("vmxRepair.missingNone")} + + + {t("vmxRepair.poweredOff", { count: vmxRepairPoweredOffCount })} + + + {t("vmxRepair.checkMode")} + + + {vmxRepairApplyAllowed + ? t("vmxRepair.applyOpen") + : t("vmxRepair.applyClosed")} + +
{t("blockerLanes.label")}