From bcaf6c3a265ab1b7560b32a72316f673e73151ab Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 3 Jul 2026 13:13:13 +0800 Subject: [PATCH] fix(reboot): surface vmx relink dry-run evidence gate --- .../awoooi_priority_work_order_readback.py | 107 +++++++++++ .../windows99_vmx_source_locator_readback.py | 174 ++++++++++++++---- ...awoooi_priority_work_order_readback_api.py | 69 ++++++- ...ndows99_vmx_source_locator_readback_api.py | 27 ++- docs/LOGBOOK.md | 18 ++ 5 files changed, 352 insertions(+), 43 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 19c409d17..6f0617539 100644 --- a/apps/api/src/services/awoooi_priority_work_order_readback.py +++ b/apps/api/src/services/awoooi_priority_work_order_readback.py @@ -3475,6 +3475,40 @@ def _enrich_from_current_readbacks( windows99_vmx_source_locator_readback.get("candidate_confirmation_required") is True ) + state[ + "windows99_vmx_source_locator_human_confirmation_required_for_dry_run" + ] = bool( + windows99_vmx_source_locator_readback.get( + "human_confirmation_required_for_dry_run" + ) + is True + ) + state[ + "windows99_vmx_source_locator_verified_source_or_identity_evidence_required" + ] = bool( + windows99_vmx_source_locator_readback.get( + "verified_source_or_identity_evidence_required" + ) + is True + ) + state["windows99_vmx_source_locator_controlled_relink_dry_run_ready"] = bool( + windows99_vmx_source_locator_readback.get( + "controlled_relink_dry_run_ready" + ) + is True + ) + state["windows99_vmx_source_locator_controlled_relink_dry_run_blocker"] = str( + windows99_vmx_source_locator_readback.get( + "controlled_relink_dry_run_blocker" + ) + or "" + ) + locator_relink_plan = _dict( + windows99_vmx_source_locator_readback.get("controlled_relink_dry_run_plan") + ) + state["windows99_vmx_source_locator_relink_dry_run_plan_id"] = str( + locator_relink_plan.get("plan_id") or "" + ) state["windows99_vmx_source_locator_candidate_source_count_known"] = bool( windows99_vmx_source_locator_readback.get("candidate_source_count_known") is True @@ -3891,6 +3925,29 @@ def _enrich_from_current_readbacks( evidence["windows99_vmx_source_locator_candidate_confirmation_required"] = state[ "windows99_vmx_source_locator_candidate_confirmation_required" ] + evidence[ + "windows99_vmx_source_locator_human_confirmation_required_for_dry_run" + ] = state[ + "windows99_vmx_source_locator_human_confirmation_required_for_dry_run" + ] + evidence[ + "windows99_vmx_source_locator_verified_source_or_identity_evidence_required" + ] = state[ + "windows99_vmx_source_locator_verified_source_or_identity_evidence_required" + ] + evidence[ + "windows99_vmx_source_locator_controlled_relink_dry_run_ready" + ] = state[ + "windows99_vmx_source_locator_controlled_relink_dry_run_ready" + ] + evidence[ + "windows99_vmx_source_locator_controlled_relink_dry_run_blocker" + ] = state[ + "windows99_vmx_source_locator_controlled_relink_dry_run_blocker" + ] + evidence["windows99_vmx_source_locator_relink_dry_run_plan_id"] = state[ + "windows99_vmx_source_locator_relink_dry_run_plan_id" + ] evidence["windows99_vmx_source_locator_candidate_source_count_known"] = state[ "windows99_vmx_source_locator_candidate_source_count_known" ] @@ -4683,6 +4740,31 @@ def _set_rollups_and_summary( state.get("windows99_vmx_source_locator_candidate_confirmation_required") is True ), + "windows99_vmx_source_locator_human_confirmation_required_for_dry_run": ( + state.get( + "windows99_vmx_source_locator_human_confirmation_required_for_dry_run" + ) + is True + ), + "windows99_vmx_source_locator_verified_source_or_identity_evidence_required": ( + state.get( + "windows99_vmx_source_locator_verified_source_or_identity_evidence_required" + ) + is True + ), + "windows99_vmx_source_locator_controlled_relink_dry_run_ready": ( + state.get("windows99_vmx_source_locator_controlled_relink_dry_run_ready") + is True + ), + "windows99_vmx_source_locator_controlled_relink_dry_run_blocker": str( + state.get( + "windows99_vmx_source_locator_controlled_relink_dry_run_blocker" + ) + or "" + ), + "windows99_vmx_source_locator_relink_dry_run_plan_id": str( + state.get("windows99_vmx_source_locator_relink_dry_run_plan_id") or "" + ), "windows99_vmx_source_locator_candidate_source_count_known": ( state.get("windows99_vmx_source_locator_candidate_source_count_known") is True @@ -5005,6 +5087,31 @@ def _set_rollups_and_summary( state.get("windows99_vmx_source_locator_candidate_confirmation_required") is True ), + "windows99_vmx_source_locator_human_confirmation_required_for_dry_run": ( + state.get( + "windows99_vmx_source_locator_human_confirmation_required_for_dry_run" + ) + is True + ), + "windows99_vmx_source_locator_verified_source_or_identity_evidence_required": ( + state.get( + "windows99_vmx_source_locator_verified_source_or_identity_evidence_required" + ) + is True + ), + "windows99_vmx_source_locator_controlled_relink_dry_run_ready": ( + state.get("windows99_vmx_source_locator_controlled_relink_dry_run_ready") + is True + ), + "windows99_vmx_source_locator_controlled_relink_dry_run_blocker": str( + state.get( + "windows99_vmx_source_locator_controlled_relink_dry_run_blocker" + ) + or "" + ), + "windows99_vmx_source_locator_relink_dry_run_plan_id": str( + state.get("windows99_vmx_source_locator_relink_dry_run_plan_id") or "" + ), "windows99_vmx_source_locator_candidate_source_count_known": ( state.get("windows99_vmx_source_locator_candidate_source_count_known") is True 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 8979deda4..55c949e03 100644 --- a/apps/api/src/services/windows99_vmx_source_locator_readback.py +++ b/apps/api/src/services/windows99_vmx_source_locator_readback.py @@ -156,10 +156,131 @@ def build_windows99_vmx_source_locator_readback( and set(receipt_aliases).issubset(set(target_aliases or receipt_aliases)) ) receipt_locate_status = str(receipt.get("locate_status") or "") + expected_vmx_path_present_count = ( + _int(receipt.get("expected_vmx_path_present_count")) + if receipt_applies + else None + ) + candidate_source_count = ( + _int(receipt.get("candidate_source_count")) if receipt_applies else None + ) + candidate_source_fingerprint_count = ( + _int(receipt.get("candidate_source_fingerprint_count"), 0) + if receipt_applies + else None + ) + alias_hint_candidate_count = ( + _int(receipt.get("alias_hint_candidate_count"), 0) + if receipt_applies + else None + ) + unassigned_ubuntu_candidate_count = ( + _int(receipt.get("unassigned_ubuntu_candidate_count"), 0) + if receipt_applies + else None + ) + identity_target_hint_count = ( + _int(receipt.get("identity_target_hint_count"), 0) + if receipt_applies + else None + ) + identity_unassigned_ubuntu_candidate_count = ( + _int(receipt.get("identity_unassigned_ubuntu_candidate_count"), 0) + if receipt_applies + else None + ) + candidate_confirmation_required = bool( + receipt_applies and "requires_confirmation" in receipt_locate_status + ) + controlled_relink_dry_run_ready = bool( + receipt_applies and receipt.get("controlled_relink_dry_run_ready") is True + ) + verified_source_or_identity_evidence_required = bool( + receipt_applies + and candidate_confirmation_required + and not controlled_relink_dry_run_ready + ) + controlled_relink_dry_run_blocker = "" if receipt_applies: - status = "collector_readback_single_identity_candidate_confirmation_required" - next_step = "confirm_single_unassigned_ubuntu_candidate_before_scoped_relink_dry_run" - safe_next_step = str(receipt.get("safe_next_step") or safe_next_step) + if controlled_relink_dry_run_ready: + status = "collector_readback_scoped_relink_dry_run_ready" + next_step = "run_scoped_relink_dry_run_no_windows_or_vm_write" + safe_next_step = ( + "run_scoped_relink_dry_run_no_windows_or_vm_write_then_post_verifier" + ) + elif verified_source_or_identity_evidence_required: + status = "collector_readback_verified_source_required_before_relink_dry_run" + next_step = ( + "provide_verified_vmx_source_or_identity_evidence_then_" + "scoped_relink_dry_run" + ) + safe_next_step = ( + "provide_existing_vmx_source_or_verified_backup_path_then_build_" + "scoped_relink_dry_run_no_write" + ) + controlled_relink_dry_run_blocker = ( + "verified_vmx_source_or_identity_evidence_missing" + ) + else: + status = "collector_readback_no_scoped_relink_candidate" + next_step = "provide_existing_vmx_source_or_backup_path" + safe_next_step = ( + "provide_existing_vmx_source_or_verified_backup_path_then_rerun_" + "locator_check_mode_no_write" + ) + controlled_relink_dry_run_blocker = "scoped_relink_candidate_not_verified" + + relink_dry_run_plan = { + "plan_id": f"windows99-vmx-source-relink-dry-run-{','.join(missing_aliases) or 'none'}", + "mode": "dry_run_no_windows_or_vm_write", + "ready": controlled_relink_dry_run_ready, + "blocker": controlled_relink_dry_run_blocker, + "target_selector": { + "host_alias": "99", + "vm_aliases": target_aliases, + "missing_vmx_aliases": missing_aliases, + "candidate_requires_verified_identity": ( + verified_source_or_identity_evidence_required + ), + }, + "source_diff": { + "expected_vmx_path_count": expected_vmx_path_count, + "expected_vmx_path_present_count": expected_vmx_path_present_count, + "candidate_source_count": candidate_source_count, + "candidate_source_fingerprint_count": ( + candidate_source_fingerprint_count + ), + "alias_hint_candidate_count": alias_hint_candidate_count, + "identity_target_hint_count": identity_target_hint_count, + "identity_unassigned_ubuntu_candidate_count": ( + identity_unassigned_ubuntu_candidate_count + ), + "raw_path_output": False, + "path_fingerprint_only": True, + "bounded_identity_metadata_only": bool( + receipt_applies + and receipt.get("bounded_identity_metadata_only") is True + ), + }, + "rollback_ref": "rollback://awoooi/windows99-vmx-source-relink-dry-run", + "post_verifier": [ + "bash scripts/reboot-recovery/collect-windows99-vmware-verify.sh --collect", + "GET /api/v1/agents/reboot-auto-recovery-slo-scorecard", + "GET /api/v1/agents/windows99-vmx-source-repair-package", + "GET /api/v1/agents/windows99-vmx-source-locator-readback", + ], + "km_writeback_ref": str(receipt.get("km_writeback_ref") or "") + if receipt_applies + else "", + "playbook_trust_writeback_ref": str( + receipt.get("playbook_trust_writeback_ref") or "" + ) + if receipt_applies + else "", + "apply_allowed_by_this_plan": False, + "remote_write_performed": False, + "vm_power_change_performed": False, + } return { "schema_version": _SCHEMA_VERSION, @@ -199,47 +320,28 @@ def build_windows99_vmx_source_locator_readback( ), "remote_locator_mode": "in_memory_stdin_scriptblock", "expected_vmx_path_count": expected_vmx_path_count, - "expected_vmx_path_present_count": ( - _int(receipt.get("expected_vmx_path_present_count")) - if receipt_applies - else None - ), - "candidate_source_count": ( - _int(receipt.get("candidate_source_count")) if receipt_applies else None - ), + "expected_vmx_path_present_count": expected_vmx_path_present_count, + "candidate_source_count": candidate_source_count, "candidate_source_count_known": bool(receipt_applies), - "candidate_source_fingerprint_count": ( - _int(receipt.get("candidate_source_fingerprint_count"), 0) - if receipt_applies - else None - ), - "alias_hint_candidate_count": ( - _int(receipt.get("alias_hint_candidate_count"), 0) - if receipt_applies - else None - ), - "unassigned_ubuntu_candidate_count": ( - _int(receipt.get("unassigned_ubuntu_candidate_count"), 0) - if receipt_applies - else None - ), + "candidate_source_fingerprint_count": candidate_source_fingerprint_count, + "alias_hint_candidate_count": alias_hint_candidate_count, + "unassigned_ubuntu_candidate_count": unassigned_ubuntu_candidate_count, "identity_metadata_read": bool( receipt_applies and receipt.get("identity_metadata_read") is True ), - "identity_target_hint_count": ( - _int(receipt.get("identity_target_hint_count"), 0) - if receipt_applies - else None - ), + "identity_target_hint_count": identity_target_hint_count, "identity_unassigned_ubuntu_candidate_count": ( - _int(receipt.get("identity_unassigned_ubuntu_candidate_count"), 0) - if receipt_applies - else None + identity_unassigned_ubuntu_candidate_count ), "locate_status": receipt_locate_status if receipt_applies else "", - "candidate_confirmation_required": bool( - receipt_applies and "requires_confirmation" in receipt_locate_status + "candidate_confirmation_required": candidate_confirmation_required, + "human_confirmation_required_for_dry_run": False, + "verified_source_or_identity_evidence_required": ( + verified_source_or_identity_evidence_required ), + "controlled_relink_dry_run_ready": controlled_relink_dry_run_ready, + "controlled_relink_dry_run_blocker": controlled_relink_dry_run_blocker, + "controlled_relink_dry_run_plan": relink_dry_run_plan, "public_lan_topology_redacted": True, "redacted_public_display_only": True, "raw_path_output": False, 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 6961b19ea..3ca434c29 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 @@ -738,7 +738,7 @@ def test_awoooi_priority_work_order_readback_routes_closed_backup_to_host_boot_a "windows99_vmx_source_repair_playbook_trust_writeback_ref" ].startswith("playbook://awoooi/windows99-vmx-source-repair/") assert state["windows99_vmx_source_locator_status"] == ( - "collector_readback_single_identity_candidate_confirmation_required" + "collector_readback_verified_source_required_before_relink_dry_run" ) assert state["windows99_vmx_source_locator_required"] is True assert state["windows99_vmx_source_locator_check_mode_ready"] is True @@ -752,7 +752,7 @@ def test_awoooi_priority_work_order_readback_routes_closed_backup_to_host_boot_a ) assert state["windows99_vmx_source_locator_target_aliases"] == ["111"] assert state["windows99_vmx_source_locator_next_executable_step"] == ( - "confirm_single_unassigned_ubuntu_candidate_before_scoped_relink_dry_run" + "provide_verified_vmx_source_or_identity_evidence_then_scoped_relink_dry_run" ) assert state["windows99_vmx_source_locator_expected_vmx_path_count"] == 1 assert state["windows99_vmx_source_locator_candidate_source_count"] == 6 @@ -765,6 +765,28 @@ def test_awoooi_priority_work_order_readback_routes_closed_backup_to_host_boot_a == 1 ) assert state["windows99_vmx_source_locator_candidate_confirmation_required"] is True + assert ( + state[ + "windows99_vmx_source_locator_human_confirmation_required_for_dry_run" + ] + is False + ) + assert ( + state[ + "windows99_vmx_source_locator_verified_source_or_identity_evidence_required" + ] + is True + ) + assert ( + state["windows99_vmx_source_locator_controlled_relink_dry_run_ready"] + is False + ) + assert state[ + "windows99_vmx_source_locator_controlled_relink_dry_run_blocker" + ] == "verified_vmx_source_or_identity_evidence_missing" + assert state["windows99_vmx_source_locator_relink_dry_run_plan_id"] == ( + "windows99-vmx-source-relink-dry-run-111" + ) assert state["windows99_vmx_source_locator_candidate_source_count_known"] is True assert state["windows99_vmx_source_locator_collector_readback_present"] is True assert state["windows99_vmx_source_locator_file_content_read"] is True @@ -848,7 +870,7 @@ def test_awoooi_priority_work_order_readback_routes_closed_backup_to_host_boot_a "111" ] assert payload["summary"]["windows99_vmx_source_locator_status"] == ( - "collector_readback_single_identity_candidate_confirmation_required" + "collector_readback_verified_source_required_before_relink_dry_run" ) assert payload["summary"]["windows99_vmx_source_locator_check_mode_ready"] is True assert payload["summary"]["windows99_vmx_source_locator_target_aliases"] == [ @@ -867,6 +889,27 @@ def test_awoooi_priority_work_order_readback_routes_closed_backup_to_host_boot_a ] is True ) + assert ( + payload["summary"][ + "windows99_vmx_source_locator_human_confirmation_required_for_dry_run" + ] + is False + ) + assert ( + payload["summary"][ + "windows99_vmx_source_locator_verified_source_or_identity_evidence_required" + ] + is True + ) + assert ( + payload["rollups"][ + "windows99_vmx_source_locator_controlled_relink_dry_run_ready" + ] + is False + ) + assert payload["rollups"][ + "windows99_vmx_source_locator_controlled_relink_dry_run_blocker" + ] == "verified_vmx_source_or_identity_evidence_missing" assert ( payload["rollups"]["windows99_vmx_source_locator_remote_write_performed"] is False @@ -925,7 +968,7 @@ def test_awoooi_priority_work_order_readback_routes_closed_backup_to_host_boot_a "windows99_vmx_source_repair_playbook_trust_writeback_ref" ].startswith("playbook://awoooi/windows99-vmx-source-repair/") assert in_progress["evidence"]["windows99_vmx_source_locator_status"] == ( - "collector_readback_single_identity_candidate_confirmation_required" + "collector_readback_verified_source_required_before_relink_dry_run" ) assert ( in_progress["evidence"]["windows99_vmx_source_locator_check_mode_ready"] @@ -945,6 +988,24 @@ def test_awoooi_priority_work_order_readback_routes_closed_backup_to_host_boot_a ] is True ) + assert ( + in_progress["evidence"][ + "windows99_vmx_source_locator_human_confirmation_required_for_dry_run" + ] + is False + ) + assert ( + in_progress["evidence"][ + "windows99_vmx_source_locator_verified_source_or_identity_evidence_required" + ] + is True + ) + assert ( + in_progress["evidence"][ + "windows99_vmx_source_locator_controlled_relink_dry_run_ready" + ] + is False + ) assert ( in_progress["evidence"]["windows99_vmx_source_locator_remote_write_performed"] 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 0ea872787..250bd7480 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 @@ -146,7 +146,7 @@ def test_windows99_vmx_source_locator_builder_projects_redacted_receipt() -> Non ) assert payload["status"] == ( - "collector_readback_single_identity_candidate_confirmation_required" + "collector_readback_verified_source_required_before_relink_dry_run" ) assert payload["collector_readback_present"] is True assert payload["collector_collection_status"] == ( @@ -161,8 +161,19 @@ def test_windows99_vmx_source_locator_builder_projects_redacted_receipt() -> Non assert payload["identity_target_hint_count"] == 0 assert payload["identity_unassigned_ubuntu_candidate_count"] == 1 assert payload["candidate_confirmation_required"] is True + assert payload["human_confirmation_required_for_dry_run"] is False + assert payload["verified_source_or_identity_evidence_required"] is True + assert payload["controlled_relink_dry_run_ready"] is False + assert payload["controlled_relink_dry_run_blocker"] == ( + "verified_vmx_source_or_identity_evidence_missing" + ) + assert payload["controlled_relink_dry_run_plan"]["ready"] is False + assert payload["controlled_relink_dry_run_plan"]["apply_allowed_by_this_plan"] is False + assert payload["controlled_relink_dry_run_plan"]["source_diff"][ + "raw_path_output" + ] is False assert payload["next_executable_step"] == ( - "confirm_single_unassigned_ubuntu_candidate_before_scoped_relink_dry_run" + "provide_verified_vmx_source_or_identity_evidence_then_scoped_relink_dry_run" ) assert payload["raw_path_output"] is False assert payload["secret_value_read"] is False @@ -191,7 +202,7 @@ def test_windows99_vmx_source_locator_endpoint_returns_public_safe_readback( payload = response.json() assert payload["schema_version"] == "windows99_vmx_source_locator_readback_v1" assert payload["status"] == ( - "collector_readback_single_identity_candidate_confirmation_required" + "collector_readback_verified_source_required_before_relink_dry_run" ) assert payload["target_vm_aliases"] == ["111"] assert payload["locator_check_mode_ready"] is True @@ -200,6 +211,16 @@ def test_windows99_vmx_source_locator_endpoint_returns_public_safe_readback( assert payload["collector_readback_present"] is True assert payload["candidate_source_count"] == 6 assert payload["candidate_confirmation_required"] is True + assert payload["human_confirmation_required_for_dry_run"] is False + assert payload["verified_source_or_identity_evidence_required"] is True + assert payload["controlled_relink_dry_run_ready"] is False + assert payload["controlled_relink_dry_run_blocker"] == ( + "verified_vmx_source_or_identity_evidence_missing" + ) + assert payload["controlled_relink_dry_run_plan"]["plan_id"] == ( + "windows99-vmx-source-relink-dry-run-111" + ) + assert payload["controlled_relink_dry_run_plan"]["remote_write_performed"] is False assert payload["public_lan_topology_redacted"] is True assert payload["raw_path_output"] is False assert payload["file_content_read"] is True diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 883a02e98..eb7d933bd 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,21 @@ +## 2026-07-03 — 13:12 Windows99 VMX relink dry-run evidence gate 上卷 + +**完成內容**: +- Gitea CD `#4582` 已完成最新 main `ba0849f70` 部署,deploy marker 為 `558b9e916 chore(cd): deploy ba0849f [skip ci]`;job3 讀回 `ArgoCD: sync=Synced health=Healthy`、API/Web/Worker rollout completed、`Production deploy readback matches this build`、`Job succeeded`。 +- Production `/api/v1/agents/windows99-vmx-source-locator-readback` 已讀回 collector receipt:`collector_readback_present=true`、`candidate_source_count=6`、`identity_unassigned_ubuntu_candidate_count=1`、`raw_path_output=false`、`path_fingerprint_only=true`、`remote_write_performed=false`、`vm_power_change_performed=false`。 +- 本輪 verifier-only artifact:`/tmp/awoooi-p0-006-reboot-slo-20260703-130633`。host probe 顯示 99 reachable-unknown-boot、111 unreachable、112/120/121 reachable、188 reachable 但 `awoooi-startup.service failed/degraded`;本工作機對 99 的 no-secret SSH collector 固定 `ssh_batchmode_auth_ready=0`、`verify_collection_status=blocked_ssh_publickey_auth_missing`。 +- 將 VMX locator 的「confirmation」字眼改成 machine-readable evidence gate:`verified_source_or_identity_evidence_required=true`、`human_confirmation_required_for_dry_run=false`、`controlled_relink_dry_run_ready=false`、`controlled_relink_dry_run_blocker=verified_vmx_source_or_identity_evidence_missing`,並新增 `controlled_relink_dry_run_plan`。此 plan 只允許 dry-run evidence,不允許 apply / remote write / VM power change。 +- `awoooi-priority-work-order-readback` 同步投影上述 dry-run blocker 到 `mainline_execution_state`、in-progress evidence、`summary` 與 `rollups`,避免再把 P0-006 卡成模糊的人工確認。 + +**已跑驗證**: +- `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`:通過。 +- `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 -q -p no:cacheprovider`:`22 passed`。 +- `git diff --check`、`python3.11 ops/runner/guard-gitea-runner-pressure.py --root .`:通過。 + +**仍維持**: +- P0-006 尚未完成:缺 verified 111 VMX source / backup path / stronger identity evidence,因此不得 relink generic Ubuntu candidate、不得 restore、不得 power on,也不得宣稱 10 分鐘 SLO 完成。 +- 未讀 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 — 12:47 Windows99 VMX locator receipt readback 上卷 **完成內容**: