From 6e17be006f9ea8641e5a81cfcad6ca427ea8e0d2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 3 Jul 2026 12:05:28 +0800 Subject: [PATCH] fix(reboot): expose windows99 vmx executor chain --- .../awoooi_priority_work_order_readback.py | 222 ++++++++++++++++++ .../reboot_auto_recovery_slo_scorecard.py | 130 +++++++--- ...awoooi_priority_work_order_readback_api.py | 56 +++++ ...windows99_vmx_source_repair_package_api.py | 30 +++ apps/web/messages/en.json | 14 +- apps/web/messages/zh-TW.json | 14 +- .../app/[locale]/awooop/work-items/page.tsx | 146 ++++++++++++ docs/LOGBOOK.md | 20 ++ ...est_windows99_vmx_source_repair_package.py | 20 ++ 9 files changed, 623 insertions(+), 29 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 0bc5aa79d..a5d3db17e 100644 --- a/apps/api/src/services/awoooi_priority_work_order_readback.py +++ b/apps/api/src/services/awoooi_priority_work_order_readback.py @@ -3038,6 +3038,12 @@ def _enrich_from_current_readbacks( windows99_vmx_source_repair_check_mode = _dict( windows99_vmx_source_repair_action_chain.get("check_mode") ) + windows99_vmx_source_repair_candidate = _dict( + windows99_vmx_source_repair_action_chain.get("controlled_apply_candidate") + ) + windows99_vmx_source_repair_execution_boundary = _dict( + windows99_vmx_source_repair_action_chain.get("execution_boundary") + ) windows99_vmx_source_repair_rollback = _dict( windows99_vmx_source_repair_action_chain.get("rollback") ) @@ -3049,6 +3055,14 @@ def _enrich_from_current_readbacks( ) else [] ) + windows99_vmx_source_repair_post_apply_verifier = _strings( + windows99_vmx_source_repair_package.get("post_apply_verifier") + or windows99_vmx_source_repair_package.get("post_verifier") + or windows99_vmx_source_repair_action_chain.get("post_apply_verifier") + ) + windows99_vmx_source_repair_learning_refs = _dict( + windows99_vmx_source_repair_package.get("learning_writeback_refs") + ) or _dict(windows99_vmx_source_repair_action_chain.get("learning_writeback_refs")) p0_006_event_gated = bool( reboot_slo_rollups.get("blocked_by_fresh_reboot_window_only") is True ) @@ -3329,6 +3343,80 @@ def _enrich_from_current_readbacks( state["windows99_vmx_source_repair_source_diff_alias_count"] = len( windows99_vmx_source_repair_diff ) + state["windows99_vmx_source_repair_target_vm_aliases"] = _strings( + windows99_vmx_source_repair_package.get("target_vm_aliases") + or windows99_vmx_source_repair_target_selector.get("vm_aliases") + ) + state["windows99_vmx_source_repair_check_mode_probe_command_count"] = _int( + windows99_vmx_source_repair_package.get("check_mode_probe_command_count") + or windows99_vmx_source_repair_check_mode.get("probe_command_count") + ) + state["windows99_vmx_source_repair_controlled_apply_candidate_status"] = str( + windows99_vmx_source_repair_package.get( + "controlled_apply_candidate_status" + ) + or windows99_vmx_source_repair_candidate.get("status") + or "" + ) + state["windows99_vmx_source_repair_controlled_apply_candidate_executor"] = str( + windows99_vmx_source_repair_package.get( + "controlled_apply_candidate_executor" + ) + or windows99_vmx_source_repair_candidate.get("executor") + or windows99_vmx_source_repair_execution_boundary.get("executor") + or "" + ) + state["windows99_vmx_source_repair_controlled_apply_candidate_command_id"] = str( + windows99_vmx_source_repair_package.get( + "controlled_apply_candidate_command_id" + ) + or windows99_vmx_source_repair_candidate.get("command_id") + or windows99_vmx_source_repair_execution_boundary.get("command_id") + or "" + ) + state["windows99_vmx_source_repair_post_apply_verifier_count"] = len( + windows99_vmx_source_repair_post_apply_verifier + ) + state["windows99_vmx_source_repair_learning_writeback_ref_count"] = _int( + windows99_vmx_source_repair_package.get("learning_writeback_ref_count") + or windows99_vmx_source_repair_action_chain.get( + "learning_writeback_ref_count" + ) + or len( + [ + value + for value in windows99_vmx_source_repair_learning_refs.values() + if str(value or "").strip() + ] + ) + ) + state["windows99_vmx_source_repair_log_receipt_ref"] = str( + windows99_vmx_source_repair_package.get("log_receipt_ref") + or windows99_vmx_source_repair_learning_refs.get("log_receipt_ref") + or "" + ) + state["windows99_vmx_source_repair_km_writeback_ref"] = str( + windows99_vmx_source_repair_package.get("km_writeback_ref") + or windows99_vmx_source_repair_learning_refs.get("km_writeback_ref") + or "" + ) + state["windows99_vmx_source_repair_rag_chunk_ref"] = str( + windows99_vmx_source_repair_package.get("rag_chunk_ref") + or windows99_vmx_source_repair_learning_refs.get("rag_chunk_ref") + or "" + ) + state["windows99_vmx_source_repair_mcp_evidence_ref"] = str( + windows99_vmx_source_repair_package.get("mcp_evidence_ref") + or windows99_vmx_source_repair_learning_refs.get("mcp_evidence_ref") + or "" + ) + state["windows99_vmx_source_repair_playbook_trust_writeback_ref"] = str( + windows99_vmx_source_repair_package.get("playbook_trust_writeback_ref") + or windows99_vmx_source_repair_learning_refs.get( + "playbook_trust_writeback_ref" + ) + or "" + ) 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"] = ( @@ -3629,6 +3717,48 @@ def _enrich_from_current_readbacks( evidence["windows99_vmx_source_repair_source_diff_alias_count"] = state[ "windows99_vmx_source_repair_source_diff_alias_count" ] + evidence["windows99_vmx_source_repair_target_vm_aliases"] = state[ + "windows99_vmx_source_repair_target_vm_aliases" + ] + evidence["windows99_vmx_source_repair_check_mode_probe_command_count"] = ( + state["windows99_vmx_source_repair_check_mode_probe_command_count"] + ) + evidence[ + "windows99_vmx_source_repair_controlled_apply_candidate_status" + ] = state[ + "windows99_vmx_source_repair_controlled_apply_candidate_status" + ] + evidence[ + "windows99_vmx_source_repair_controlled_apply_candidate_executor" + ] = state[ + "windows99_vmx_source_repair_controlled_apply_candidate_executor" + ] + evidence[ + "windows99_vmx_source_repair_controlled_apply_candidate_command_id" + ] = state[ + "windows99_vmx_source_repair_controlled_apply_candidate_command_id" + ] + evidence["windows99_vmx_source_repair_post_apply_verifier_count"] = state[ + "windows99_vmx_source_repair_post_apply_verifier_count" + ] + evidence["windows99_vmx_source_repair_learning_writeback_ref_count"] = ( + state["windows99_vmx_source_repair_learning_writeback_ref_count"] + ) + evidence["windows99_vmx_source_repair_log_receipt_ref"] = state[ + "windows99_vmx_source_repair_log_receipt_ref" + ] + evidence["windows99_vmx_source_repair_km_writeback_ref"] = state[ + "windows99_vmx_source_repair_km_writeback_ref" + ] + evidence["windows99_vmx_source_repair_rag_chunk_ref"] = state[ + "windows99_vmx_source_repair_rag_chunk_ref" + ] + evidence["windows99_vmx_source_repair_mcp_evidence_ref"] = state[ + "windows99_vmx_source_repair_mcp_evidence_ref" + ] + evidence["windows99_vmx_source_repair_playbook_trust_writeback_ref"] = ( + state["windows99_vmx_source_repair_playbook_trust_writeback_ref"] + ) evidence["drill_preflight_status"] = str(reboot_preflight.get("status") or "") evidence["drill_preflight_ready"] = ( reboot_preflight_rollups.get("preflight_ready") is True @@ -4303,6 +4433,52 @@ def _set_rollups_and_summary( "windows99_vmx_source_repair_source_diff_alias_count": _int( state.get("windows99_vmx_source_repair_source_diff_alias_count") ), + "windows99_vmx_source_repair_target_vm_aliases": _strings( + state.get("windows99_vmx_source_repair_target_vm_aliases") + ), + "windows99_vmx_source_repair_check_mode_probe_command_count": _int( + state.get("windows99_vmx_source_repair_check_mode_probe_command_count") + ), + "windows99_vmx_source_repair_controlled_apply_candidate_status": str( + state.get( + "windows99_vmx_source_repair_controlled_apply_candidate_status" + ) + or "" + ), + "windows99_vmx_source_repair_controlled_apply_candidate_executor": str( + state.get( + "windows99_vmx_source_repair_controlled_apply_candidate_executor" + ) + or "" + ), + "windows99_vmx_source_repair_controlled_apply_candidate_command_id": str( + state.get( + "windows99_vmx_source_repair_controlled_apply_candidate_command_id" + ) + or "" + ), + "windows99_vmx_source_repair_post_apply_verifier_count": _int( + state.get("windows99_vmx_source_repair_post_apply_verifier_count") + ), + "windows99_vmx_source_repair_learning_writeback_ref_count": _int( + state.get("windows99_vmx_source_repair_learning_writeback_ref_count") + ), + "windows99_vmx_source_repair_log_receipt_ref": str( + state.get("windows99_vmx_source_repair_log_receipt_ref") or "" + ), + "windows99_vmx_source_repair_km_writeback_ref": str( + state.get("windows99_vmx_source_repair_km_writeback_ref") or "" + ), + "windows99_vmx_source_repair_rag_chunk_ref": str( + state.get("windows99_vmx_source_repair_rag_chunk_ref") or "" + ), + "windows99_vmx_source_repair_mcp_evidence_ref": str( + state.get("windows99_vmx_source_repair_mcp_evidence_ref") or "" + ), + "windows99_vmx_source_repair_playbook_trust_writeback_ref": str( + state.get("windows99_vmx_source_repair_playbook_trust_writeback_ref") + or "" + ), "p0_004_runtime_readback_ready": p0_004_ready, "reboot_drill_preflight_runtime_readback_ready": ( state.get("reboot_drill_preflight_runtime_readback_state") == "ready" @@ -4503,6 +4679,52 @@ def _set_rollups_and_summary( "windows99_vmx_source_repair_source_diff_alias_count": _int( state.get("windows99_vmx_source_repair_source_diff_alias_count") ), + "windows99_vmx_source_repair_target_vm_aliases": _strings( + state.get("windows99_vmx_source_repair_target_vm_aliases") + ), + "windows99_vmx_source_repair_check_mode_probe_command_count": _int( + state.get("windows99_vmx_source_repair_check_mode_probe_command_count") + ), + "windows99_vmx_source_repair_controlled_apply_candidate_status": str( + state.get( + "windows99_vmx_source_repair_controlled_apply_candidate_status" + ) + or "" + ), + "windows99_vmx_source_repair_controlled_apply_candidate_executor": str( + state.get( + "windows99_vmx_source_repair_controlled_apply_candidate_executor" + ) + or "" + ), + "windows99_vmx_source_repair_controlled_apply_candidate_command_id": str( + state.get( + "windows99_vmx_source_repair_controlled_apply_candidate_command_id" + ) + or "" + ), + "windows99_vmx_source_repair_post_apply_verifier_count": _int( + state.get("windows99_vmx_source_repair_post_apply_verifier_count") + ), + "windows99_vmx_source_repair_learning_writeback_ref_count": _int( + state.get("windows99_vmx_source_repair_learning_writeback_ref_count") + ), + "windows99_vmx_source_repair_log_receipt_ref": str( + state.get("windows99_vmx_source_repair_log_receipt_ref") or "" + ), + "windows99_vmx_source_repair_km_writeback_ref": str( + state.get("windows99_vmx_source_repair_km_writeback_ref") or "" + ), + "windows99_vmx_source_repair_rag_chunk_ref": str( + state.get("windows99_vmx_source_repair_rag_chunk_ref") or "" + ), + "windows99_vmx_source_repair_mcp_evidence_ref": str( + state.get("windows99_vmx_source_repair_mcp_evidence_ref") or "" + ), + "windows99_vmx_source_repair_playbook_trust_writeback_ref": str( + state.get("windows99_vmx_source_repair_playbook_trust_writeback_ref") + or "" + ), "windows99_winrm_http_open": ( state.get("windows99_winrm_http_open") is True ), 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 9ecfc0755..65ee6ded8 100644 --- a/apps/api/src/services/reboot_auto_recovery_slo_scorecard.py +++ b/apps/api/src/services/reboot_auto_recovery_slo_scorecard.py @@ -2453,6 +2453,11 @@ def _build_windows99_vmx_source_repair_action_chain( stage = "verify_only_ready" next_executable_step = "rerun_no_secret_collector_and_scorecard" candidate_status = "no_repair_required" + work_order_id = ( + f"windows99-vmx-source-repair-{primary_alias}" + if primary_alias + else "windows99-vmx-source-repair-verify" + ) source_diffs = [] for alias in repair_aliases: @@ -2476,12 +2481,38 @@ def _build_windows99_vmx_source_repair_action_chain( } ) - return { - "work_order_id": ( - f"windows99-vmx-source-repair-{primary_alias}" - if primary_alias - else "windows99-vmx-source-repair-verify" + post_apply_verifier = [ + "bash scripts/reboot-recovery/collect-windows99-vmware-verify.sh --collect", + "bash scripts/reboot-recovery/install-reboot-auto-recovery-slo-110.sh --verify-only", + "GET /api/v1/agents/reboot-auto-recovery-slo-scorecard", + "GET /api/v1/agents/windows99-vmx-source-repair-package", + "GET /api/v1/agents/awoooi-priority-work-order-readback", + ] + learning_writeback_refs = { + "log_receipt_ref": f"log://awoooi/p0-006/{work_order_id}", + "km_writeback_ref": f"km://awoooi/reboot-slo/{work_order_id}", + "rag_chunk_ref": f"rag://awoooi/reboot-slo/{work_order_id}", + "mcp_evidence_ref": f"mcp://awoooi/windows99/{work_order_id}", + "playbook_trust_writeback_ref": ( + f"playbook://awoooi/windows99-vmx-source-repair/{work_order_id}" ), + "work_item_receipt_ref": f"awooop://work-items/{work_order_id}", + } + controlled_apply_candidate = { + "status": candidate_status, + "executor": "windows99_no_secret_management_channel_or_local_console", + "command_id": "windows99_vmx_source_relink_or_restore_scoped", + "apply_allowed_by_this_package": False, + "allowed_when": [ + "existing_vmx_source_or_backup_artifact_is_identified", + "pre_apply_backup_or_copy_plan_is_recorded", + "dry_run_path_check_passed", + "post_apply_no_secret_verifier_is_available", + ], + } + + return { + "work_order_id": work_order_id, "stage": stage, "next_executable_step": next_executable_step, "target_selector": { @@ -2501,34 +2532,34 @@ def _build_windows99_vmx_source_repair_action_chain( for alias in repair_aliases ), }, - "controlled_apply_candidate": { - "status": candidate_status, - "executor": "windows99_no_secret_management_channel_or_local_console", - "command_id": "windows99_vmx_source_relink_or_restore_scoped", - "apply_allowed_by_this_package": False, - "allowed_when": [ - "existing_vmx_source_or_backup_artifact_is_identified", - "pre_apply_backup_or_copy_plan_is_recorded", - "dry_run_path_check_passed", - "post_apply_no_secret_verifier_is_available", - ], + "controlled_apply_candidate": controlled_apply_candidate, + "execution_boundary": { + "executor": controlled_apply_candidate["executor"], + "command_id": controlled_apply_candidate["command_id"], + "execution_channel": "authorized_internal_console_or_no_secret_collector", + "execution_authorized_by_this_package": False, + "public_endpoint_display_only": True, + "requires_internal_unredacted_path_package": True, + "host_write_allowed_by_this_package": False, + "vm_power_change_allowed_by_this_package": False, }, "rollback": { "required": True, "mode": "revert_scoped_vmx_source_link_or_file_copy", "backup_required_before_apply": True, }, - "post_apply_verifier": [ - "bash scripts/reboot-recovery/collect-windows99-vmware-verify.sh --collect", - "bash scripts/reboot-recovery/install-reboot-auto-recovery-slo-110.sh --verify-only", - "GET /api/v1/agents/reboot-auto-recovery-slo-scorecard", - "GET /api/v1/agents/windows99-vmx-source-repair-package", - ], + "post_apply_verifier": post_apply_verifier, + "post_apply_verifier_count": len(post_apply_verifier), "learning_writeback": [ "awooop_work_item_receipt", "reboot_slo_scorecard_rollups", + "log_receipt_ref", + "mcp_evidence_ref", + "rag_chunk_ref", "km_playbook_trust_writeback_after_verifier", ], + "learning_writeback_refs": learning_writeback_refs, + "learning_writeback_ref_count": len(learning_writeback_refs), "manager_summary": { "primary_alias": primary_alias or "none", "missing_vmx_count": len(missing_vmx_aliases), @@ -2536,6 +2567,8 @@ def _build_windows99_vmx_source_repair_action_chain( "check_mode_allowed": True, "apply_allowed": False, "vm_power_change_allowed": False, + "post_apply_verifier_count": len(post_apply_verifier), + "learning_writeback_ref_count": len(learning_writeback_refs), }, } @@ -2591,6 +2624,9 @@ def _build_windows99_vmx_source_repair_package( missing_vmx_aliases=missing_vmx_aliases, powered_off_aliases=powered_off_aliases, ) + controlled_apply_candidate = _dict(action_chain.get("controlled_apply_candidate")) + learning_writeback_refs = _dict(action_chain.get("learning_writeback_refs")) + post_apply_verifier = _strings(action_chain.get("post_apply_verifier")) return { "schema_version": "windows99_vmx_source_repair_check_mode_package_v1", @@ -2606,9 +2642,15 @@ def _build_windows99_vmx_source_repair_package( "work_order_id": action_chain["work_order_id"], "action_stage": action_chain["stage"], "next_executable_step": action_chain["next_executable_step"], + "target_vm_aliases": _strings( + _dict(action_chain.get("target_selector")).get("vm_aliases") + ), "check_mode_allowed_by_this_package": action_chain["check_mode"][ "allowed_by_this_package" ], + "check_mode_probe_command_count": _int( + _dict(action_chain.get("check_mode")).get("probe_command_count") + ), "rollback_required": action_chain["rollback"]["required"], "vm_power_change_allowed": action_chain["target_selector"][ "vm_power_change_allowed" @@ -2616,14 +2658,30 @@ def _build_windows99_vmx_source_repair_package( "destructive_restore_allowed": action_chain["target_selector"][ "destructive_restore_allowed" ], + "controlled_apply_candidate_status": str( + controlled_apply_candidate.get("status") or "" + ), + "controlled_apply_candidate_executor": str( + controlled_apply_candidate.get("executor") or "" + ), + "controlled_apply_candidate_command_id": str( + controlled_apply_candidate.get("command_id") or "" + ), "safe_next_step": safe_next_step, "controlled_apply_mode": "check_mode_package_only_no_windows_or_vm_write", "apply_allowed_by_this_package": False, - "post_verifier": [ - "bash scripts/reboot-recovery/collect-windows99-vmware-verify.sh --collect", - "bash scripts/reboot-recovery/install-reboot-auto-recovery-slo-110.sh --verify-only", - "GET /api/v1/agents/reboot-auto-recovery-slo-scorecard", - ], + "post_apply_verifier": post_apply_verifier, + "post_verifier": post_apply_verifier, + "post_apply_verifier_count": len(post_apply_verifier), + "learning_writeback_refs": learning_writeback_refs, + "learning_writeback_ref_count": len(learning_writeback_refs), + "log_receipt_ref": str(learning_writeback_refs.get("log_receipt_ref") or ""), + "km_writeback_ref": str(learning_writeback_refs.get("km_writeback_ref") or ""), + "rag_chunk_ref": str(learning_writeback_refs.get("rag_chunk_ref") or ""), + "mcp_evidence_ref": str(learning_writeback_refs.get("mcp_evidence_ref") or ""), + "playbook_trust_writeback_ref": str( + learning_writeback_refs.get("playbook_trust_writeback_ref") or "" + ), "forbidden_actions": _WINDOWS99_VMX_REPAIR_FORBIDDEN_ACTIONS, "operation_boundaries": { "secret_value_read": False, @@ -2662,6 +2720,15 @@ def _sync_windows99_vmx_source_repair_package( readback["windows99_vmx_source_repair_missing_vmx_aliases"] = _strings( package.get("missing_vmx_aliases") ) + readback["windows99_vmx_source_repair_post_apply_verifier_count"] = _int( + package.get("post_apply_verifier_count") + ) + readback["windows99_vmx_source_repair_learning_writeback_ref_count"] = _int( + package.get("learning_writeback_ref_count") + ) + readback["windows99_vmx_source_repair_controlled_apply_candidate_executor"] = str( + package.get("controlled_apply_candidate_executor") or "" + ) rollups = _dict(payload.setdefault("rollups", {})) rollups["windows99_vmx_source_repair_package_ready"] = ( @@ -2677,6 +2744,15 @@ def _sync_windows99_vmx_source_repair_package( rollups["windows99_vmx_source_repair_missing_vmx_aliases"] = _strings( package.get("missing_vmx_aliases") ) + rollups["windows99_vmx_source_repair_post_apply_verifier_count"] = _int( + package.get("post_apply_verifier_count") + ) + rollups["windows99_vmx_source_repair_learning_writeback_ref_count"] = _int( + package.get("learning_writeback_ref_count") + ) + rollups["windows99_vmx_source_repair_controlled_apply_candidate_executor"] = str( + package.get("controlled_apply_candidate_executor") or "" + ) return package 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 da672d0af..edf65b888 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 @@ -709,6 +709,34 @@ def test_awoooi_priority_work_order_readback_routes_closed_backup_to_host_boot_a assert state["windows99_vmx_source_repair_vm_power_change_allowed"] is False assert state["windows99_vmx_source_repair_destructive_restore_allowed"] is False assert state["windows99_vmx_source_repair_source_diff_alias_count"] == 1 + assert state["windows99_vmx_source_repair_target_vm_aliases"] == ["111"] + assert state["windows99_vmx_source_repair_check_mode_probe_command_count"] == 1 + assert state[ + "windows99_vmx_source_repair_controlled_apply_candidate_status" + ] == "candidate_waiting_existing_vmx_source_or_backup_path" + assert state[ + "windows99_vmx_source_repair_controlled_apply_candidate_executor" + ] == "windows99_no_secret_management_channel_or_local_console" + assert state[ + "windows99_vmx_source_repair_controlled_apply_candidate_command_id" + ] == "windows99_vmx_source_relink_or_restore_scoped" + assert state["windows99_vmx_source_repair_post_apply_verifier_count"] == 5 + assert state["windows99_vmx_source_repair_learning_writeback_ref_count"] == 6 + assert state["windows99_vmx_source_repair_log_receipt_ref"].endswith( + "windows99-vmx-source-repair-111" + ) + assert state["windows99_vmx_source_repair_km_writeback_ref"].startswith( + "km://awoooi/reboot-slo/" + ) + assert state["windows99_vmx_source_repair_rag_chunk_ref"].startswith( + "rag://awoooi/reboot-slo/" + ) + assert state["windows99_vmx_source_repair_mcp_evidence_ref"].startswith( + "mcp://awoooi/windows99/" + ) + assert state[ + "windows99_vmx_source_repair_playbook_trust_writeback_ref" + ].startswith("playbook://awoooi/windows99-vmx-source-repair/") assert payload["summary"]["controlled_service_data_backup_blocker_count"] == 0 assert payload["summary"]["active_p0_primary_blocker"] == ( "reboot_event_required_host_unreachable" @@ -757,6 +785,16 @@ def test_awoooi_priority_work_order_readback_routes_closed_backup_to_host_boot_a "source_repair_check_mode_ready" ) assert payload["summary"]["windows99_vmx_source_repair_check_mode_allowed"] is True + assert payload["summary"][ + "windows99_vmx_source_repair_controlled_apply_candidate_executor" + ] == "windows99_no_secret_management_channel_or_local_console" + assert payload["summary"]["windows99_vmx_source_repair_post_apply_verifier_count"] == 5 + assert payload["summary"][ + "windows99_vmx_source_repair_learning_writeback_ref_count" + ] == 6 + assert payload["summary"]["windows99_vmx_source_repair_mcp_evidence_ref"].startswith( + "mcp://awoooi/windows99/" + ) assert ( payload["rollups"]["windows99_vmx_source_repair_rollback_required"] is True ) @@ -770,6 +808,12 @@ def test_awoooi_priority_work_order_readback_routes_closed_backup_to_host_boot_a ] is False ) + assert payload["rollups"][ + "windows99_vmx_source_repair_controlled_apply_candidate_status" + ] == "candidate_waiting_existing_vmx_source_or_backup_path" + assert payload["rollups"]["windows99_vmx_source_repair_target_vm_aliases"] == [ + "111" + ] assert "service/data/backup readback is green" in payload[ "next_execution_order" ][0] @@ -807,6 +851,18 @@ def test_awoooi_priority_work_order_readback_routes_closed_backup_to_host_boot_a in_progress["evidence"]["windows99_vmx_source_repair_check_mode_allowed"] is True ) + assert in_progress["evidence"][ + "windows99_vmx_source_repair_controlled_apply_candidate_executor" + ] == "windows99_no_secret_management_channel_or_local_console" + assert in_progress["evidence"][ + "windows99_vmx_source_repair_post_apply_verifier_count" + ] == 5 + assert in_progress["evidence"][ + "windows99_vmx_source_repair_learning_writeback_ref_count" + ] == 6 + assert in_progress["evidence"][ + "windows99_vmx_source_repair_playbook_trust_writeback_ref" + ].startswith("playbook://awoooi/windows99-vmx-source-repair/") 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 index c204e0c48..9641d32ab 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 @@ -58,16 +58,39 @@ def test_windows99_vmx_source_repair_package_builder_is_check_mode_only() -> Non payload["next_executable_step"] == "locate_or_relink_missing_vmx_source_check_mode" ) + assert payload["target_vm_aliases"] == ["111"] assert payload["check_mode_allowed_by_this_package"] is True + assert payload["check_mode_probe_command_count"] == 1 assert payload["rollback_required"] is True assert payload["vm_power_change_allowed"] is False assert payload["destructive_restore_allowed"] is False + assert payload["controlled_apply_candidate_status"] == ( + "candidate_waiting_existing_vmx_source_or_backup_path" + ) + assert payload["controlled_apply_candidate_executor"] == ( + "windows99_no_secret_management_channel_or_local_console" + ) + assert payload["controlled_apply_candidate_command_id"] == ( + "windows99_vmx_source_relink_or_restore_scoped" + ) + assert payload["post_apply_verifier_count"] == 5 + assert payload["learning_writeback_ref_count"] == 6 + assert payload["log_receipt_ref"].endswith("windows99-vmx-source-repair-111") + assert payload["mcp_evidence_ref"].startswith("mcp://awoooi/windows99/") + assert payload["rag_chunk_ref"].startswith("rag://awoooi/reboot-slo/") + assert payload["km_writeback_ref"].startswith("km://awoooi/reboot-slo/") + assert payload["playbook_trust_writeback_ref"].startswith( + "playbook://awoooi/windows99-vmx-source-repair/" + ) action_chain = payload["action_chain"] assert action_chain["target_selector"]["host_alias"] == "99" assert action_chain["target_selector"]["vm_aliases"] == ["111"] assert action_chain["source_of_truth_diff"][0]["alias"] == "111" assert action_chain["source_of_truth_diff"][0]["destructive_restore_allowed"] is False assert action_chain["controlled_apply_candidate"]["apply_allowed_by_this_package"] is False + assert action_chain["execution_boundary"]["execution_authorized_by_this_package"] is False + assert action_chain["post_apply_verifier_count"] == 5 + assert action_chain["learning_writeback_ref_count"] == 6 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 @@ -104,6 +127,13 @@ def test_windows99_vmx_source_repair_package_endpoint_returns_readback( "check_mode_package_only_no_windows_or_vm_write" ) assert payload["apply_allowed_by_this_package"] is False + assert payload["controlled_apply_candidate_executor"] == ( + "windows99_no_secret_management_channel_or_local_console" + ) + assert payload["post_apply_verifier_count"] == 5 + assert payload["learning_writeback_ref_count"] == 6 + assert payload["km_writeback_ref"].startswith("km://awoooi/reboot-slo/") + assert payload["mcp_evidence_ref"].startswith("mcp://awoooi/windows99/") assert payload["public_lan_topology_redacted"] is True assert payload["redacted_public_display_only"] is True assert payload["check_mode_probe_commands_executable"] is False diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 498a2cff8..f27bf6ed0 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -9367,7 +9367,19 @@ "noPower": "no VM power", "powerOpen": "VM power open", "noDestructive": "no destructive", - "destructiveOpen": "destructive open" + "destructiveOpen": "destructive open", + "executor": "executor {value}", + "executorConsole": "console/no-secret", + "executorReady": "executor ready", + "executorWaiting": "executor waiting", + "target": "target {aliases}", + "probes": "probe {count}", + "candidateWaiting": "candidate waiting", + "candidateReady": "candidate ready", + "verifiers": "verifier {count}", + "learning": "learning refs {count}", + "learningTargets": "LOG / KM / RAG / MCP / PlayBook", + "refs": "refs {count}" }, "blockerLanes": { "label": "Blocker lanes", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 9f9ff79db..eb474a51f 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -9367,7 +9367,19 @@ "noPower": "no VM power", "powerOpen": "VM power open", "noDestructive": "no destructive", - "destructiveOpen": "destructive open" + "destructiveOpen": "destructive open", + "executor": "executor {value}", + "executorConsole": "console/no-secret", + "executorReady": "executor ready", + "executorWaiting": "executor waiting", + "target": "target {aliases}", + "probes": "probe {count}", + "candidateWaiting": "candidate waiting", + "candidateReady": "candidate ready", + "verifiers": "verifier {count}", + "learning": "learning refs {count}", + "learningTargets": "LOG / KM / RAG / MCP / PlayBook", + "refs": "refs {count}" }, "blockerLanes": { "label": "卡點分群", 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 a9d094c80..a786b2a31 100644 --- a/apps/web/src/app/[locale]/awooop/work-items/page.tsx +++ b/apps/web/src/app/[locale]/awooop/work-items/page.tsx @@ -1171,6 +1171,18 @@ type PriorityWorkOrderResponse = { windows99_vmx_source_repair_vm_power_change_allowed?: boolean | null; windows99_vmx_source_repair_destructive_restore_allowed?: boolean | null; windows99_vmx_source_repair_source_diff_alias_count?: number | null; + windows99_vmx_source_repair_target_vm_aliases?: string[] | null; + windows99_vmx_source_repair_check_mode_probe_command_count?: number | null; + windows99_vmx_source_repair_controlled_apply_candidate_status?: string | null; + windows99_vmx_source_repair_controlled_apply_candidate_executor?: string | null; + windows99_vmx_source_repair_controlled_apply_candidate_command_id?: string | null; + windows99_vmx_source_repair_post_apply_verifier_count?: number | null; + windows99_vmx_source_repair_learning_writeback_ref_count?: number | null; + windows99_vmx_source_repair_log_receipt_ref?: string | null; + windows99_vmx_source_repair_km_writeback_ref?: string | null; + windows99_vmx_source_repair_rag_chunk_ref?: string | null; + windows99_vmx_source_repair_mcp_evidence_ref?: string | null; + windows99_vmx_source_repair_playbook_trust_writeback_ref?: string | 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; @@ -1225,6 +1237,18 @@ type PriorityWorkOrderResponse = { windows99_vmx_source_repair_vm_power_change_allowed?: boolean | null; windows99_vmx_source_repair_destructive_restore_allowed?: boolean | null; windows99_vmx_source_repair_source_diff_alias_count?: number | null; + windows99_vmx_source_repair_target_vm_aliases?: string[] | null; + windows99_vmx_source_repair_check_mode_probe_command_count?: number | null; + windows99_vmx_source_repair_controlled_apply_candidate_status?: string | null; + windows99_vmx_source_repair_controlled_apply_candidate_executor?: string | null; + windows99_vmx_source_repair_controlled_apply_candidate_command_id?: string | null; + windows99_vmx_source_repair_post_apply_verifier_count?: number | null; + windows99_vmx_source_repair_learning_writeback_ref_count?: number | null; + windows99_vmx_source_repair_log_receipt_ref?: string | null; + windows99_vmx_source_repair_km_writeback_ref?: string | null; + windows99_vmx_source_repair_rag_chunk_ref?: string | null; + windows99_vmx_source_repair_mcp_evidence_ref?: string | null; + windows99_vmx_source_repair_playbook_trust_writeback_ref?: string | null; drill_preflight_blocker_count?: number | null; ai_loop_current_blocker_log_source_tags?: AiLoopLogSourceTag[] | null; ai_loop_log_source_tagging_contract?: AiLoopLogSourceContract[] | null; @@ -1341,6 +1365,26 @@ const COMMANDER_INSERTED_REQUIREMENT_FALLBACK: PriorityWorkOrderResponse = { windows99_vmx_source_repair_vm_power_change_allowed: false, windows99_vmx_source_repair_destructive_restore_allowed: false, windows99_vmx_source_repair_source_diff_alias_count: 1, + windows99_vmx_source_repair_target_vm_aliases: ["111"], + windows99_vmx_source_repair_check_mode_probe_command_count: 1, + windows99_vmx_source_repair_controlled_apply_candidate_status: + "candidate_waiting_existing_vmx_source_or_backup_path", + windows99_vmx_source_repair_controlled_apply_candidate_executor: + "windows99_no_secret_management_channel_or_local_console", + windows99_vmx_source_repair_controlled_apply_candidate_command_id: + "windows99_vmx_source_relink_or_restore_scoped", + windows99_vmx_source_repair_post_apply_verifier_count: 5, + windows99_vmx_source_repair_learning_writeback_ref_count: 6, + windows99_vmx_source_repair_log_receipt_ref: + "log://awoooi/p0-006/windows99-vmx-source-repair-111", + windows99_vmx_source_repair_km_writeback_ref: + "km://awoooi/reboot-slo/windows99-vmx-source-repair-111", + windows99_vmx_source_repair_rag_chunk_ref: + "rag://awoooi/reboot-slo/windows99-vmx-source-repair-111", + windows99_vmx_source_repair_mcp_evidence_ref: + "mcp://awoooi/windows99/windows99-vmx-source-repair-111", + windows99_vmx_source_repair_playbook_trust_writeback_ref: + "playbook://awoooi/windows99-vmx-source-repair/windows99-vmx-source-repair-111", stockplatform_public_api_runtime_status: "stockplatform_public_api_runtime_ready", stockplatform_public_api_controlled_recovery_preflight_status: "not_required_stockplatform_runtime_ready", @@ -1418,6 +1462,26 @@ const COMMANDER_INSERTED_REQUIREMENT_FALLBACK: PriorityWorkOrderResponse = { windows99_vmx_source_repair_vm_power_change_allowed: false, windows99_vmx_source_repair_destructive_restore_allowed: false, windows99_vmx_source_repair_source_diff_alias_count: 1, + windows99_vmx_source_repair_target_vm_aliases: ["111"], + windows99_vmx_source_repair_check_mode_probe_command_count: 1, + windows99_vmx_source_repair_controlled_apply_candidate_status: + "candidate_waiting_existing_vmx_source_or_backup_path", + windows99_vmx_source_repair_controlled_apply_candidate_executor: + "windows99_no_secret_management_channel_or_local_console", + windows99_vmx_source_repair_controlled_apply_candidate_command_id: + "windows99_vmx_source_relink_or_restore_scoped", + windows99_vmx_source_repair_post_apply_verifier_count: 5, + windows99_vmx_source_repair_learning_writeback_ref_count: 6, + windows99_vmx_source_repair_log_receipt_ref: + "log://awoooi/p0-006/windows99-vmx-source-repair-111", + windows99_vmx_source_repair_km_writeback_ref: + "km://awoooi/reboot-slo/windows99-vmx-source-repair-111", + windows99_vmx_source_repair_rag_chunk_ref: + "rag://awoooi/reboot-slo/windows99-vmx-source-repair-111", + windows99_vmx_source_repair_mcp_evidence_ref: + "mcp://awoooi/windows99/windows99-vmx-source-repair-111", + windows99_vmx_source_repair_playbook_trust_writeback_ref: + "playbook://awoooi/windows99-vmx-source-repair/windows99-vmx-source-repair-111", drill_preflight_blocker_count: 5, stockplatform_public_api_runtime_status: "stockplatform_public_api_runtime_ready", stockplatform_public_api_runtime_ready: true, @@ -9178,9 +9242,57 @@ function ManagerSituationBoard({ summary?.windows99_vmx_source_repair_destructive_restore_allowed ?? evidence?.windows99_vmx_source_repair_destructive_restore_allowed ?? false; + const vmxRepairTargetAliases = ( + summary?.windows99_vmx_source_repair_target_vm_aliases ?? + evidence?.windows99_vmx_source_repair_target_vm_aliases ?? + vmxRepairMissingAliases + ).filter(Boolean); + const vmxRepairProbeCount = + summary?.windows99_vmx_source_repair_check_mode_probe_command_count ?? + evidence?.windows99_vmx_source_repair_check_mode_probe_command_count ?? + 0; + const vmxRepairExecutor = + summary?.windows99_vmx_source_repair_controlled_apply_candidate_executor ?? + evidence?.windows99_vmx_source_repair_controlled_apply_candidate_executor ?? + ""; + const vmxRepairCandidateStatus = + summary?.windows99_vmx_source_repair_controlled_apply_candidate_status ?? + evidence?.windows99_vmx_source_repair_controlled_apply_candidate_status ?? + ""; + const vmxRepairVerifierCount = + summary?.windows99_vmx_source_repair_post_apply_verifier_count ?? + evidence?.windows99_vmx_source_repair_post_apply_verifier_count ?? + 0; + const vmxRepairWritebackCount = + summary?.windows99_vmx_source_repair_learning_writeback_ref_count ?? + evidence?.windows99_vmx_source_repair_learning_writeback_ref_count ?? + 0; + const vmxRepairWritebackRefs = [ + summary?.windows99_vmx_source_repair_log_receipt_ref ?? + evidence?.windows99_vmx_source_repair_log_receipt_ref, + summary?.windows99_vmx_source_repair_km_writeback_ref ?? + evidence?.windows99_vmx_source_repair_km_writeback_ref, + summary?.windows99_vmx_source_repair_rag_chunk_ref ?? + evidence?.windows99_vmx_source_repair_rag_chunk_ref, + summary?.windows99_vmx_source_repair_mcp_evidence_ref ?? + evidence?.windows99_vmx_source_repair_mcp_evidence_ref, + summary?.windows99_vmx_source_repair_playbook_trust_writeback_ref ?? + evidence?.windows99_vmx_source_repair_playbook_trust_writeback_ref, + ].filter((value): value is string => Boolean(value)); const vmxRepairMissingLabel = vmxRepairMissingAliases.length ? vmxRepairMissingAliases.join(", ") : "0"; + const vmxRepairTargetLabel = vmxRepairTargetAliases.length + ? vmxRepairTargetAliases.join(", ") + : "0"; + const vmxRepairExecutorLabel = vmxRepairExecutor.includes("local_console") + ? t("vmxRepair.executorConsole") + : vmxRepairExecutor + ? t("vmxRepair.executorReady") + : t("vmxRepair.executorWaiting"); + const vmxRepairCandidateLabel = vmxRepairCandidateStatus.includes("waiting") + ? t("vmxRepair.candidateWaiting") + : t("vmxRepair.candidateReady"); const vmxRepairStageLabel = vmxRepairActionStage.includes("source") ? t("vmxRepair.stageSource") : vmxRepairActionStage.includes("power") @@ -9460,6 +9572,40 @@ function ManagerSituationBoard({ : t("vmxRepair.noDestructive")} +
+ + {t("vmxRepair.executor", { value: vmxRepairExecutorLabel })} + + + {t("vmxRepair.target", { aliases: vmxRepairTargetLabel })} + + + {t("vmxRepair.probes", { count: vmxRepairProbeCount })} + + + {vmxRepairCandidateLabel} + + + {t("vmxRepair.verifiers", { count: vmxRepairVerifierCount })} + +
+
+ + {t("vmxRepair.learning", { count: vmxRepairWritebackCount })} + + + {t("vmxRepair.learningTargets")} + + + {t("vmxRepair.refs", { count: vmxRepairWritebackRefs.length })} + +
{t("blockerLanes.label")}
diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index c32eca07f..36f050a65 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -54225,3 +54225,23 @@ production browser smoke: **下一步**: - 通過 focused verifier 後 commit / push 到 Gitea main;等待 CD deploy marker 後讀回兩個 production API route,確認 public package 帶 `check_mode_probe_commands_executable=false`,且 SLO 仍以 111 VMX source / guest power 等 active blocker 為下一步,不宣稱 10 分鐘 SLA 已完成。 + +## 2026-07-03 — P0-006 Windows99 VMX action chain executor / learning refs + +**完成內容**: +- `windows99_vmx_source_repair_package` 從 check-mode package 補齊為 executor 可消化的 action-chain contract:新增 target VM aliases、check-mode probe count、controlled apply candidate status / executor / command id、execution boundary、post-apply verifier count 與 LOG / KM / RAG / MCP / PlayBook writeback refs。 +- `awoooi-priority-work-order-readback` 同步投影上述欄位到 `mainline_execution_state`、P0-006 evidence、summary 與 rollups,避免 Work Items 只看見 package ready 卻看不見 executor / verifier / learning loop。 +- AwoooP Work Items 的 Windows99 VMX 區塊新增 executor chain 與 learning chain chips:顯示 console/no-secret executor、target alias、probe count、candidate status、verifier count、LOG / KM / RAG / MCP / PlayBook refs count。 + +**本地驗證結果**: +- `DATABASE_URL=postgresql+asyncpg://test:test@localhost/test PYTHONPATH=apps/api python3.11 -m pytest apps/api/tests/test_windows99_vmx_source_repair_package_api.py scripts/reboot-recovery/tests/test_windows99_vmx_source_repair_package.py apps/api/tests/test_awoooi_priority_work_order_readback_api.py -q -p no:cacheprovider`:`25 passed`。 +- `python3.11 -m py_compile apps/api/src/services/reboot_auto_recovery_slo_scorecard.py apps/api/src/services/awoooi_priority_work_order_readback.py apps/api/src/services/windows99_vmx_source_repair_package.py scripts/reboot-recovery/build-windows99-vmx-source-repair-package.py`:通過。 +- `pnpm --dir apps/web typecheck`、`python3.11 ops/runner/guard-gitea-runner-pressure.py --root .`、`git diff --check`:通過。 + +**仍維持**: +- 沒有讀 secret / token / `.env` / raw sessions / SQLite / auth。 +- 沒有使用 GitHub / gh / GitHub API / GitHub Actions。 +- 沒有重啟主機,沒有 Docker / Nginx / K3s / DB / firewall restart,沒有 workflow_dispatch,沒有 DROP / TRUNCATE / restore / prune,沒有啟動或關閉 VM。 + +**下一步**: +- commit / push 到 Gitea main;等待 CD deploy marker 後讀回 production `/api/v1/agents/windows99-vmx-source-repair-package`、`/api/v1/agents/awoooi-priority-work-order-readback` 與 `/zh-TW/awooop/work-items`,確認 executor / verifier / learning refs 在 API 與 UI 均可見。 diff --git a/scripts/reboot-recovery/tests/test_windows99_vmx_source_repair_package.py b/scripts/reboot-recovery/tests/test_windows99_vmx_source_repair_package.py index 2fcd25d76..6c8e24e1b 100644 --- a/scripts/reboot-recovery/tests/test_windows99_vmx_source_repair_package.py +++ b/scripts/reboot-recovery/tests/test_windows99_vmx_source_repair_package.py @@ -90,6 +90,26 @@ def test_package_routes_missing_vmx_to_source_repair_check_mode() -> None: "expected_vmx_source_missing" ) assert action_chain["check_mode"]["probe_command_count"] == 1 + assert action_chain["controlled_apply_candidate"]["executor"] == ( + "windows99_no_secret_management_channel_or_local_console" + ) + assert action_chain["execution_boundary"]["execution_authorized_by_this_package"] is False + assert action_chain["post_apply_verifier_count"] == 5 + assert action_chain["learning_writeback_ref_count"] == 6 + assert payload["target_vm_aliases"] == ["111"] + assert payload["check_mode_probe_command_count"] == 1 + assert payload["controlled_apply_candidate_status"] == ( + "candidate_waiting_existing_vmx_source_or_backup_path" + ) + assert payload["controlled_apply_candidate_executor"] == ( + "windows99_no_secret_management_channel_or_local_console" + ) + assert payload["post_apply_verifier_count"] == 5 + assert payload["learning_writeback_ref_count"] == 6 + assert payload["km_writeback_ref"].startswith("km://awoooi/reboot-slo/") + assert payload["playbook_trust_writeback_ref"].startswith( + "playbook://awoooi/windows99-vmx-source-repair/" + ) assert row_by_alias["188"]["missing_vmx_source"] is False assert payload["operation_boundaries"]["vm_power_change_performed"] is False assert payload["operation_boundaries"]["secret_value_read"] is False