From d74516f2cd897afb3c3f0c6ec483275bed231412 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 3 Jul 2026 12:40:36 +0800 Subject: [PATCH] fix(reboot): surface vmx locator execution readiness --- .../awoooi_priority_work_order_readback.py | 49 +++++++++++++++++++ .../windows99_vmx_source_locator_readback.py | 32 ++++++++++++ ...awoooi_priority_work_order_readback_api.py | 10 +++- ...ndows99_vmx_source_locator_readback_api.py | 35 ++++++++++++- docs/LOGBOOK.md | 15 ++++++ 5 files changed, 138 insertions(+), 3 deletions(-) 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 f87327091..b4335895f 100644 --- a/apps/api/src/services/awoooi_priority_work_order_readback.py +++ b/apps/api/src/services/awoooi_priority_work_order_readback.py @@ -3435,6 +3435,19 @@ def _enrich_from_current_readbacks( state["windows99_vmx_source_locator_check_mode_ready"] = bool( windows99_vmx_source_locator_readback.get("locator_check_mode_ready") is True ) + state["windows99_vmx_source_locator_effective_collect_ready"] = bool( + windows99_vmx_source_locator_readback.get("effective_locator_collect_ready") + is True + ) + state["windows99_vmx_source_locator_collect_blocker"] = str( + windows99_vmx_source_locator_readback.get("locator_collect_blocker") or "" + ) + state["windows99_vmx_source_locator_ssh_batch_ready"] = bool( + windows99_vmx_source_locator_readback.get("ssh_batch_ready") is True + ) + state["windows99_vmx_source_locator_ssh_batch_status"] = str( + windows99_vmx_source_locator_readback.get("ssh_batch_status") or "unknown" + ) state["windows99_vmx_source_locator_target_aliases"] = _strings( windows99_vmx_source_locator_readback.get("target_vm_aliases") ) @@ -3822,6 +3835,18 @@ def _enrich_from_current_readbacks( evidence["windows99_vmx_source_locator_check_mode_ready"] = state[ "windows99_vmx_source_locator_check_mode_ready" ] + evidence["windows99_vmx_source_locator_effective_collect_ready"] = state[ + "windows99_vmx_source_locator_effective_collect_ready" + ] + evidence["windows99_vmx_source_locator_collect_blocker"] = state[ + "windows99_vmx_source_locator_collect_blocker" + ] + evidence["windows99_vmx_source_locator_ssh_batch_ready"] = state[ + "windows99_vmx_source_locator_ssh_batch_ready" + ] + evidence["windows99_vmx_source_locator_ssh_batch_status"] = state[ + "windows99_vmx_source_locator_ssh_batch_status" + ] evidence["windows99_vmx_source_locator_target_aliases"] = state[ "windows99_vmx_source_locator_target_aliases" ] @@ -4584,6 +4609,18 @@ def _set_rollups_and_summary( "windows99_vmx_source_locator_check_mode_ready": ( state.get("windows99_vmx_source_locator_check_mode_ready") is True ), + "windows99_vmx_source_locator_effective_collect_ready": ( + state.get("windows99_vmx_source_locator_effective_collect_ready") is True + ), + "windows99_vmx_source_locator_collect_blocker": str( + state.get("windows99_vmx_source_locator_collect_blocker") or "" + ), + "windows99_vmx_source_locator_ssh_batch_ready": ( + state.get("windows99_vmx_source_locator_ssh_batch_ready") is True + ), + "windows99_vmx_source_locator_ssh_batch_status": str( + state.get("windows99_vmx_source_locator_ssh_batch_status") or "unknown" + ), "windows99_vmx_source_locator_target_aliases": _strings( state.get("windows99_vmx_source_locator_target_aliases") ), @@ -4876,6 +4913,18 @@ def _set_rollups_and_summary( "windows99_vmx_source_locator_check_mode_ready": ( state.get("windows99_vmx_source_locator_check_mode_ready") is True ), + "windows99_vmx_source_locator_effective_collect_ready": ( + state.get("windows99_vmx_source_locator_effective_collect_ready") is True + ), + "windows99_vmx_source_locator_collect_blocker": str( + state.get("windows99_vmx_source_locator_collect_blocker") or "" + ), + "windows99_vmx_source_locator_ssh_batch_ready": ( + state.get("windows99_vmx_source_locator_ssh_batch_ready") is True + ), + "windows99_vmx_source_locator_ssh_batch_status": str( + state.get("windows99_vmx_source_locator_ssh_batch_status") or "unknown" + ), "windows99_vmx_source_locator_target_aliases": _strings( state.get("windows99_vmx_source_locator_target_aliases") ), diff --git a/apps/api/src/services/windows99_vmx_source_locator_readback.py b/apps/api/src/services/windows99_vmx_source_locator_readback.py index ea0b10f69..17d37e00a 100644 --- a/apps/api/src/services/windows99_vmx_source_locator_readback.py +++ b/apps/api/src/services/windows99_vmx_source_locator_readback.py @@ -75,6 +75,17 @@ def build_windows99_vmx_source_locator_readback( and package.get("package_ready") is True and package.get("check_mode_allowed_by_this_package") is True ) + management_channel_ready = package.get("management_channel_ready") is True + ssh_batch_ready = package.get("ssh_batch_ready") is True + ssh_batch_status = str(package.get("ssh_batch_status") or "unknown") + can_collect_vmware_verify_without_secret = ( + package.get("can_collect_vmware_verify_without_secret") is True + ) + local_console_channel_reachable = ( + package.get("local_console_channel_reachable") is True + ) + console_collection_channels = _strings(package.get("console_collection_channels")) + effective_locator_collect_ready = bool(locator_check_mode_ready and ssh_batch_ready) if locator_check_mode_ready: status = "check_mode_locator_ready_windows99_vmx_source_required" next_step = "run_no_secret_windows99_vmx_source_locator_collect" @@ -82,6 +93,12 @@ def build_windows99_vmx_source_locator_readback( "bash scripts/reboot-recovery/locate-windows99-vmx-source.sh " "--collect" ) + if not effective_locator_collect_ready: + next_step = "restore_windows99_ssh_publickey_channel_then_rerun_locator" + safe_next_step = ( + "select_existing_authorized_public_key_user_or_set_WINDOWS99_SSH_USERS_" + "then_rerun_locator_no_password" + ) elif source_locator_required: status = "blocked_locator_package_not_ready" next_step = "repair_package_first" @@ -112,6 +129,18 @@ def build_windows99_vmx_source_locator_readback( "locator_schema_version": _LOCATOR_SCHEMA_VERSION, "collector_readback_present": False, "collector_collection_status": "not_collected_by_api_readback", + "management_channel_ready": management_channel_ready, + "can_collect_vmware_verify_without_secret": ( + can_collect_vmware_verify_without_secret + ), + "ssh_batch_ready": ssh_batch_ready, + "ssh_batch_status": ssh_batch_status, + "local_console_channel_reachable": local_console_channel_reachable, + "console_collection_channels": console_collection_channels, + "effective_locator_collect_ready": effective_locator_collect_ready, + "locator_collect_blocker": ( + "" if effective_locator_collect_ready else f"ssh_batch_{ssh_batch_status}" + ), "remote_locator_mode": "in_memory_stdin_scriptblock", "expected_vmx_path_count": expected_vmx_path_count, "expected_vmx_path_present_count": None, @@ -146,11 +175,14 @@ def build_windows99_vmx_source_locator_readback( }, "check_mode": { "allowed": locator_check_mode_ready, + "effective_collect_ready": effective_locator_collect_ready, "path_existence_only": True, "vmx_file_content_read": False, "raw_path_output": False, "path_fingerprint_only": True, "collector_command": safe_next_step if locator_check_mode_ready else "", + "ssh_batch_ready": ssh_batch_ready, + "ssh_batch_status": ssh_batch_status, }, "target_selector": { "host_alias": "99", 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 26add1b57..e025a0b91 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 @@ -742,9 +742,17 @@ def test_awoooi_priority_work_order_readback_routes_closed_backup_to_host_boot_a ) assert state["windows99_vmx_source_locator_required"] is True assert state["windows99_vmx_source_locator_check_mode_ready"] is True + assert state["windows99_vmx_source_locator_effective_collect_ready"] is False + assert state["windows99_vmx_source_locator_collect_blocker"] == ( + "ssh_batch_permission_denied" + ) + assert state["windows99_vmx_source_locator_ssh_batch_ready"] is False + assert state["windows99_vmx_source_locator_ssh_batch_status"] == ( + "permission_denied" + ) assert state["windows99_vmx_source_locator_target_aliases"] == ["111"] assert state["windows99_vmx_source_locator_next_executable_step"] == ( - "run_no_secret_windows99_vmx_source_locator_collect" + "restore_windows99_ssh_publickey_channel_then_rerun_locator" ) assert state["windows99_vmx_source_locator_expected_vmx_path_count"] == 1 assert state["windows99_vmx_source_locator_candidate_source_count_known"] is False diff --git a/apps/api/tests/test_windows99_vmx_source_locator_readback_api.py b/apps/api/tests/test_windows99_vmx_source_locator_readback_api.py index 22646acf5..f6a731729 100644 --- a/apps/api/tests/test_windows99_vmx_source_locator_readback_api.py +++ b/apps/api/tests/test_windows99_vmx_source_locator_readback_api.py @@ -26,7 +26,23 @@ def _scorecard() -> dict: "readback": { "windows99_no_secret_collector_status": ( "collected_windows99_vmware_verify_stdout" - ) + ), + "windows99_no_secret_collector_readback_present": True, + "windows99_no_secret_collector_ssh_batchmode_auth_ready": True, + "windows99_no_secret_collector_remote_verify_attempted": True, + }, + "windows99_management_channel": { + "remote_execution_channel_ready": False, + "can_collect_vmware_verify_without_secret": False, + "winrm_http_open": False, + "winrm_https_open": False, + "local_console_channel_reachable": True, + "console_collection_channels": ["rdp_console", "hyperv_vmconnect"], + "ssh_batch": { + "checked": True, + "ready": False, + "status": "permission_denied", + }, }, } @@ -52,8 +68,19 @@ def test_windows99_vmx_source_locator_builder_is_no_secret_check_mode() -> None: assert payload["source_locator_required"] is True assert payload["locator_check_mode_ready"] is True assert payload["next_executable_step"] == ( - "run_no_secret_windows99_vmx_source_locator_collect" + "restore_windows99_ssh_publickey_channel_then_rerun_locator" ) + assert payload["management_channel_ready"] is False + assert payload["can_collect_vmware_verify_without_secret"] is False + assert payload["ssh_batch_ready"] is False + assert payload["ssh_batch_status"] == "permission_denied" + assert payload["local_console_channel_reachable"] is True + assert payload["console_collection_channels"] == [ + "rdp_console", + "hyperv_vmconnect", + ] + assert payload["effective_locator_collect_ready"] is False + assert payload["locator_collect_blocker"] == "ssh_batch_permission_denied" assert payload["expected_vmx_path_count"] == 1 assert payload["candidate_source_count_known"] is False assert payload["collector_readback_present"] is False @@ -64,6 +91,8 @@ def test_windows99_vmx_source_locator_builder_is_no_secret_check_mode() -> None: "scripts/reboot-recovery/windows99-vmx-source-locator.ps1" ) assert payload["check_mode"]["allowed"] is True + assert payload["check_mode"]["effective_collect_ready"] is False + assert payload["check_mode"]["ssh_batch_status"] == "permission_denied" assert payload["check_mode"]["vmx_file_content_read"] is False assert payload["raw_path_output"] is False assert payload["path_fingerprint_only"] is True @@ -102,6 +131,8 @@ def test_windows99_vmx_source_locator_endpoint_returns_public_safe_readback( ) assert payload["target_vm_aliases"] == ["111"] assert payload["locator_check_mode_ready"] is True + assert payload["effective_locator_collect_ready"] is False + assert payload["locator_collect_blocker"] == "ssh_batch_permission_denied" assert payload["public_lan_topology_redacted"] is True assert payload["raw_path_output"] is False assert payload["file_content_read"] is False diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index ada899e0a..0b3a0ae27 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,18 @@ +## 2026-07-03 — 12:36 Windows99 VMX locator effective collect readiness 防誤判 + +**完成內容**: +- Production `/api/v1/agents/windows99-vmx-source-locator-readback` 已讀回 locator package:`status=check_mode_locator_ready_windows99_vmx_source_required`、`target_vm_aliases=["111"]`、`next_executable_step=run_no_secret_windows99_vmx_source_locator_collect`、`raw_path_output=false`、`path_fingerprint_only=true`、`file_content_read=false`、`remote_write_performed=false`、`vm_power_change_performed=false`。 +- 實跑 no-secret locator check artifact:`/tmp/awoooi-windows99-vmx-source-locator-20260703-123620`。結果:`port_22_open=1`、`port_3389_open=1`、`ssh_auth_probed_users=19`、`ssh_batchmode_auth_ready=0`、`locator_collection_status=blocked_ssh_publickey_auth_missing`。 +- `windows99-vmx-source-locator-readback` 補上 `management_channel_ready`、`ssh_batch_ready`、`ssh_batch_status`、`effective_locator_collect_ready` 與 `locator_collect_blocker`;`awoooi-priority-work-order-readback` 同步上卷,避免 operator 看到 `locator_check_mode_ready=true` 就誤判為可執行 collect。 + +**已跑驗證**: +- `DATABASE_URL=postgresql+asyncpg://test:test@localhost/test PYTHONPATH=apps/api python3.11 -m pytest apps/api/tests/test_windows99_vmx_source_locator_readback_api.py apps/api/tests/test_awoooi_priority_work_order_readback_api.py apps/api/tests/test_windows99_vmx_source_repair_package_api.py -q -p no:cacheprovider`:`23 passed`。 +- `python3.11 -m py_compile apps/api/src/services/windows99_vmx_source_locator_readback.py apps/api/src/services/awoooi_priority_work_order_readback.py apps/api/tests/test_windows99_vmx_source_locator_readback_api.py apps/api/tests/test_awoooi_priority_work_order_readback_api.py`:通過。 +- `git diff --check`、`python3.11 ops/runner/guard-gitea-runner-pressure.py --root .`:通過。 + +**仍維持**: +- 未讀 secret / token / `.env` / raw sessions / SQLite / auth;未使用 GitHub / gh;未 workflow_dispatch;未啟動或關閉 VM;未重啟 host / service;未 Docker / Nginx / K3s / DB / firewall restart;未 DROP / TRUNCATE / restore / prune / delete / force push。 + ## 2026-07-03 — 11:58 Windows99 VMX package execution readiness 防誤判 **完成內容**: