From df42c32b22932d0386befb46639bea5e513238a9 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 3 Jul 2026 12:01:51 +0800 Subject: [PATCH] fix(reboot): surface windows99 vmx execution readiness --- .../windows99_vmx_source_repair_package.py | 42 +++++++++++++++++ ...windows99_vmx_source_repair_package_api.py | 47 ++++++++++++++++++- docs/LOGBOOK.md | 15 ++++++ 3 files changed, 103 insertions(+), 1 deletion(-) diff --git a/apps/api/src/services/windows99_vmx_source_repair_package.py b/apps/api/src/services/windows99_vmx_source_repair_package.py index 6edf75107..00634ecc6 100644 --- a/apps/api/src/services/windows99_vmx_source_repair_package.py +++ b/apps/api/src/services/windows99_vmx_source_repair_package.py @@ -37,6 +37,8 @@ def _with_ui_counts( missing_vmx_aliases = _strings(payload.get("missing_vmx_aliases")) powered_off_aliases = _strings(payload.get("powered_off_aliases")) readback = _dict(scorecard.get("readback")) + windows99_management = _dict(scorecard.get("windows99_management_channel")) + ssh_batch = _dict(windows99_management.get("ssh_batch")) 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") @@ -44,6 +46,46 @@ def _with_ui_counts( payload["collector_status"] = str( readback.get("windows99_no_secret_collector_status") or "unknown" ) + payload["collector_readback_present"] = ( + readback.get("windows99_no_secret_collector_readback_present") is True + ) + payload["collector_ssh_batchmode_auth_ready"] = ( + readback.get("windows99_no_secret_collector_ssh_batchmode_auth_ready") + is True + ) + payload["collector_remote_verify_attempted"] = ( + readback.get("windows99_no_secret_collector_remote_verify_attempted") + is True + ) + payload["collector_safe_next_step"] = str( + readback.get("windows99_no_secret_collector_safe_next_step") or "" + ) + payload["management_channel_ready"] = ( + windows99_management.get("remote_execution_channel_ready") is True + ) + payload["can_collect_vmware_verify_without_secret"] = ( + windows99_management.get("can_collect_vmware_verify_without_secret") is True + ) + payload["ssh_batch_ready"] = ssh_batch.get("ready") is True + payload["ssh_batch_status"] = str(ssh_batch.get("status") or "unknown") + payload["winrm_http_open"] = windows99_management.get("winrm_http_open") is True + payload["winrm_https_open"] = windows99_management.get("winrm_https_open") is True + payload["local_console_channel_reachable"] = ( + windows99_management.get("local_console_channel_reachable") is True + ) + payload["console_collection_channels"] = _strings( + windows99_management.get("console_collection_channels") + ) + payload["collector_management_channel_conflict"] = bool( + payload["collector_ssh_batchmode_auth_ready"] + and not payload["management_channel_ready"] + ) + payload["effective_check_mode_execution_ready"] = bool( + payload.get("package_ready") is True + and payload.get("check_mode_allowed_by_this_package") is True + and payload["management_channel_ready"] + and not payload["collector_management_channel_conflict"] + ) return payload 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 index 9641d32ab..c0eb1f7b0 100644 --- a/apps/api/tests/test_windows99_vmx_source_repair_package_api.py +++ b/apps/api/tests/test_windows99_vmx_source_repair_package_api.py @@ -23,7 +23,27 @@ 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_no_secret_collector_safe_next_step": ( + "commit_no_secret_verify_artifact_then_rerun_reboot_auto_" + "recovery_slo_scorecard" + ), + }, + "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", + }, }, } @@ -94,6 +114,27 @@ def test_windows99_vmx_source_repair_package_builder_is_check_mode_only() -> Non 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 payload["collector_status"] == "collected_windows99_vmware_verify_stdout" + assert payload["collector_readback_present"] is True + assert payload["collector_ssh_batchmode_auth_ready"] is True + assert payload["collector_remote_verify_attempted"] is True + assert payload["collector_safe_next_step"] == ( + "commit_no_secret_verify_artifact_then_rerun_reboot_auto_recovery_" + "slo_scorecard" + ) + 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["winrm_http_open"] is False + assert payload["winrm_https_open"] is False + assert payload["local_console_channel_reachable"] is True + assert payload["console_collection_channels"] == [ + "rdp_console", + "hyperv_vmconnect", + ] + assert payload["collector_management_channel_conflict"] is True + assert payload["effective_check_mode_execution_ready"] is False assert "read_windows_password_or_secret" in payload["forbidden_actions"] assert "vm_power_change" in payload["forbidden_actions"] @@ -138,6 +179,10 @@ def test_windows99_vmx_source_repair_package_endpoint_returns_readback( assert payload["redacted_public_display_only"] is True assert payload["check_mode_probe_commands_executable"] is False assert payload["internal_console_package_required_for_execution"] is True + assert payload["management_channel_ready"] is False + assert payload["ssh_batch_status"] == "permission_denied" + assert payload["collector_management_channel_conflict"] is True + assert payload["effective_check_mode_execution_ready"] is False assert "192.168.0." not in response.text assert "host:dev-a_Ubuntu_64-bit" in response.text vm_rows = {row["alias"]: row for row in payload["vm_rows"]} diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 36f050a65..e5af00a4f 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,18 @@ +## 2026-07-03 — 11:58 Windows99 VMX package execution readiness 防誤判 + +**完成內容**: +- Production `/api/v1/agents/windows99-vmx-source-repair-package` 已讀回 `package_ready=true`、`missing_vmx_aliases=["111"]`、`work_order_id=windows99-vmx-source-repair-111`、`check_mode_allowed_by_this_package=true`、`apply_allowed_by_this_package=false`、`vm_power_change_allowed=false`、`destructive_restore_allowed=false`。 +- no-secret collector check artifact:`/tmp/awoooi-windows99-vmx-checkmode-20260703-115534`。預設 5 個使用者與擴大 19 個使用者候選均固定 `BatchMode=yes`、no password prompt、no remote write、no VM power change,結果仍為 `verify_collection_status=blocked_ssh_publickey_auth_missing`、`ssh_batchmode_auth_ready=0`。 +- Windows99 management-channel probe 交叉確認:host / RDP / Hyper-V VMConnect 可達,WinRM 關閉,SSH BatchMode `permission_denied`,`remote_execution_channel_ready=false`、`can_collect_vmware_verify_without_secret=false`。 +- `windows99-vmx-source-repair-package` 補上 execution readiness 欄位:collector readback、management channel readiness、SSH batch status、console channel、`collector_management_channel_conflict` 與 `effective_check_mode_execution_ready`。避免 operator 看到 `package_ready=true` 就誤判為可執行 apply。 + +**已跑驗證**: +- Production package / priority / reboot readback:VMX package ready 且 priority/reboot rollup 同步。 +- `collect-windows99-vmware-verify.sh --check`、expanded users check 與 `windows99-management-channel-probe.py`:均為 no-secret / no-write / no-VM-power。 + +**仍維持**: +- 未讀 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:05 Windows99 VMX package CLI 收斂到 scorecard API **完成內容**: