From df70a0233f0d838f92f193e10b7c399c397f2f46 Mon Sep 17 00:00:00 2001 From: ogt Date: Fri, 10 Jul 2026 10:33:47 +0800 Subject: [PATCH] fix(reboot): follow scoped relink receipt safe next --- .../reboot_auto_recovery_slo_scorecard.py | 33 +++++++++++++++++++ ..._reboot_auto_recovery_slo_scorecard_api.py | 13 +++++--- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/apps/api/src/services/reboot_auto_recovery_slo_scorecard.py b/apps/api/src/services/reboot_auto_recovery_slo_scorecard.py index a7943d7e3..68936efc8 100644 --- a/apps/api/src/services/reboot_auto_recovery_slo_scorecard.py +++ b/apps/api/src/services/reboot_auto_recovery_slo_scorecard.py @@ -127,6 +127,15 @@ _WINDOWS99_MISSING_VMX_NEXT_SAFE_ACTION = ( "restore_windows99_missing_vmx_source_for_aliases_then_rerun_no_secret_" "collector_and_scorecard_no_vm_power_change" ) +_WINDOWS99_SCOPED_RELINK_RECEIPT_FILE = ( + "windows99-vmx-source-scoped-relink-dry-run-receipt.snapshot.json" +) +_WINDOWS99_SCOPED_RELINK_RECEIPT_SCHEMA_VERSION = ( + "windows99_vmx_source_scoped_relink_dry_run_receipt_v1" +) +_WINDOWS99_SCOPED_RELINK_READY_NEXT_SAFE_ACTION = ( + "rerun_no_secret_collector_and_scorecard_no_vm_power_change" +) _WINDOWS99_POWERED_OFF_NEXT_SAFE_ACTION = ( "run_windows99_vmware_autostart_check_mode_package_for_powered_off_aliases_" "then_rerun_no_secret_collector_no_vm_power_change" @@ -1386,6 +1395,8 @@ def _windows99_collected_runtime_next_safe_action( ): return "" if _strings(metric_readback.get("windows99_missing_vmx_aliases")): + if _windows99_scoped_relink_receipt_ready(): + return _WINDOWS99_SCOPED_RELINK_READY_NEXT_SAFE_ACTION return _WINDOWS99_MISSING_VMX_NEXT_SAFE_ACTION if _strings(metric_readback.get("windows99_powered_off_aliases")): return _WINDOWS99_POWERED_OFF_NEXT_SAFE_ACTION @@ -1406,6 +1417,28 @@ def _read_json_file(path: Path) -> dict[str, Any]: return payload if isinstance(payload, dict) else {} +def _windows99_scoped_relink_receipt_ready( + operations_dir: Path | None = None, +) -> bool: + receipt = _read_json_file( + (operations_dir or _DEFAULT_OPERATIONS_DIR) + / _WINDOWS99_SCOPED_RELINK_RECEIPT_FILE + ) + aliases = _strings(receipt.get("target_vm_aliases")) + return bool( + receipt.get("schema_version") + == _WINDOWS99_SCOPED_RELINK_RECEIPT_SCHEMA_VERSION + and receipt.get("target_host_alias") == "99" + and aliases == ["111"] + and receipt.get("ready") is True + and receipt.get("reviewer_validation_passed") is True + and receipt.get("controlled_relink_dry_run_ready") is True + and receipt.get("remote_write_performed") is not True + and receipt.get("vm_power_change_performed") is not True + and receipt.get("secret_value_read") is not True + ) + + def apply_public_maintenance_runtime_readback( payload: dict[str, Any], runtime_readback: dict[str, Any], diff --git a/apps/api/tests/test_reboot_auto_recovery_slo_scorecard_api.py b/apps/api/tests/test_reboot_auto_recovery_slo_scorecard_api.py index 4fe10814c..8c6541508 100644 --- a/apps/api/tests/test_reboot_auto_recovery_slo_scorecard_api.py +++ b/apps/api/tests/test_reboot_auto_recovery_slo_scorecard_api.py @@ -46,6 +46,9 @@ EXPECTED_WINDOWS99_MISSING_VMX_NEXT_STEP = ( "restore_windows99_missing_vmx_source_for_aliases_then_rerun_no_secret_" "collector_and_scorecard_no_vm_power_change" ) +EXPECTED_WINDOWS99_SCOPED_RELINK_READY_NEXT_STEP = ( + "rerun_no_secret_collector_and_scorecard_no_vm_power_change" +) EXPECTED_WINDOWS99_POWERED_OFF_NEXT_STEP = ( "run_windows99_vmware_autostart_check_mode_package_for_powered_off_aliases_" "then_rerun_no_secret_collector_no_vm_power_change" @@ -1257,16 +1260,16 @@ def test_reboot_auto_recovery_slo_scorecard_promotes_collected_windows99_runtime ) assert payload["primary_blocker"] == "reboot_event_required_host_unreachable" - assert payload["safe_next_step"] == EXPECTED_WINDOWS99_MISSING_VMX_NEXT_STEP - assert payload["next_safe_action"] == EXPECTED_WINDOWS99_MISSING_VMX_NEXT_STEP + assert payload["safe_next_step"] == EXPECTED_WINDOWS99_SCOPED_RELINK_READY_NEXT_STEP + assert payload["next_safe_action"] == EXPECTED_WINDOWS99_SCOPED_RELINK_READY_NEXT_STEP assert payload["readback"]["safe_next_step"] == ( - EXPECTED_WINDOWS99_MISSING_VMX_NEXT_STEP + EXPECTED_WINDOWS99_SCOPED_RELINK_READY_NEXT_STEP ) assert payload["readback"]["next_safe_action"] == ( - EXPECTED_WINDOWS99_MISSING_VMX_NEXT_STEP + EXPECTED_WINDOWS99_SCOPED_RELINK_READY_NEXT_STEP ) assert payload["reboot_sop_progress"]["next_safe_action"] == ( - EXPECTED_WINDOWS99_MISSING_VMX_NEXT_STEP + EXPECTED_WINDOWS99_SCOPED_RELINK_READY_NEXT_STEP ) assert payload["windows99_verify_collection"][ "no_secret_collector_status"