fix(windows99): project scoped relink dry run
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 1m14s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
AWOOOI Harbor 110 Local Repair / workflow-shape (push) Successful in 1s
AWOOOI Harbor 110 Local Repair / harbor-110-local-repair (push) Successful in 43s

This commit is contained in:
ogt
2026-07-10 02:13:40 +08:00
parent e240573c34
commit f29bc594ea
2 changed files with 168 additions and 0 deletions

View File

@@ -35,6 +35,9 @@ _CANDIDATE_CONFIRMATION_VALIDATION_SCHEMA_VERSION = (
_CANDIDATE_CONFIRMATION_PACKET_SCHEMA_VERSION = (
"windows99_vmx_source_candidate_confirmation_packet_v1"
)
_SCOPED_RELINK_DRY_RUN_SCHEMA_VERSION = (
"windows99_vmx_source_scoped_relink_dry_run_v1"
)
_RECEIPT_FILE = "windows99-vmx-source-locator-receipt.snapshot.json"
_COLLECTOR_SCRIPT_PATH = "scripts/reboot-recovery/locate-windows99-vmx-source.sh"
_LOCATOR_SCRIPT_PATH = "scripts/reboot-recovery/windows99-vmx-source-locator.ps1"
@@ -289,6 +292,104 @@ def _build_candidate_confirmation_gate(
}
def _build_projected_scoped_relink_dry_run_plan(
*,
accepted: bool,
locator: dict[str, Any],
packet: dict[str, Any],
) -> dict[str, Any]:
target_aliases = _strings(locator.get("target_vm_aliases"))
missing_aliases = _strings(locator.get("missing_vmx_aliases")) or target_aliases
evidence_ref = (
str(packet.get("candidate_identity_evidence_ref") or "").strip()
if accepted
else ""
)
candidate_identity_kind = (
str(packet.get("candidate_identity_kind") or "").strip() if accepted else ""
)
ready = bool(accepted)
existing_plan = _dict(locator.get("controlled_relink_dry_run_plan"))
return {
"schema_version": _SCOPED_RELINK_DRY_RUN_SCHEMA_VERSION,
"plan_id": (
"windows99-vmx-source-relink-dry-run-"
f"{','.join(missing_aliases) or 'none'}"
),
"status": (
"ready_for_no_write_review"
if ready
else "blocked_candidate_confirmation_not_accepted"
),
"ready": ready,
"mode": "dry_run_no_windows_or_vm_write",
"target_selector": {
"host_alias": "99",
"vm_aliases": target_aliases,
"missing_vmx_aliases": missing_aliases,
"candidate_identity_kind": candidate_identity_kind,
"secret_collection_allowed": False,
"vm_power_change_allowed": False,
"runtime_write_allowed": False,
},
"source_diff": {
"expected_vmx_path_count": locator.get("expected_vmx_path_count"),
"expected_vmx_path_present_count": locator.get(
"expected_vmx_path_present_count"
),
"candidate_source_count": locator.get("candidate_source_count"),
"candidate_source_fingerprint_count": locator.get(
"candidate_source_fingerprint_count"
),
"alias_hint_candidate_count": locator.get("alias_hint_candidate_count"),
"identity_target_hint_count": locator.get("identity_target_hint_count"),
"identity_unassigned_ubuntu_candidate_count": locator.get(
"identity_unassigned_ubuntu_candidate_count"
),
"candidate_identity_evidence_ref": evidence_ref,
"raw_path_output": False,
"path_fingerprint_only": True,
"bounded_identity_metadata_only": True,
},
"pre_apply_backup_plan": {
"required": True,
"status": (
"review_required_before_apply"
if ready
else "blocked_candidate_confirmation_not_accepted"
),
"backup_ref_required": True,
"rollback_ref": "rollback://awoooi/windows99-vmx-source-relink-dry-run",
},
"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(existing_plan.get("km_writeback_ref") or "")
or "km://awoooi/reboot-slo/windows99-vmx-source-locator-111"
),
"playbook_trust_writeback_ref": (
str(existing_plan.get("playbook_trust_writeback_ref") or "")
or "playbook://awoooi/windows99-vmx-source-locator/windows99-vmx-source-locator-111"
),
"apply_allowed_by_this_plan": False,
"remote_write_performed": False,
"vm_power_change_performed": False,
"secret_value_read": False,
"request_payload_saved": False,
"safe_next_step": (
"review_scoped_relink_dry_run_then_run_post_verifier_no_vm_power_change"
if ready
else "submit_redacted_single_candidate_identity_evidence_without_raw_paths"
),
}
def validate_windows99_vmx_source_candidate_confirmation_packet(
packet: dict[str, Any],
*,
@@ -362,6 +463,13 @@ def validate_windows99_vmx_source_candidate_confirmation_packet(
if accepted
else "submit_redacted_single_candidate_identity_evidence_without_raw_paths"
)
projected_scoped_relink_dry_run_plan = (
_build_projected_scoped_relink_dry_run_plan(
accepted=accepted,
locator=locator,
packet=packet,
)
)
return {
"schema_version": _CANDIDATE_CONFIRMATION_VALIDATION_SCHEMA_VERSION,
@@ -398,6 +506,14 @@ def validate_windows99_vmx_source_candidate_confirmation_packet(
"secret_value_read": False,
"safe_next_step": next_safe_step,
},
"scoped_relink_dry_run_ready": accepted,
"pre_apply_backup_plan_ready": accepted,
"post_verifier_ready": accepted,
"km_writeback_ready": accepted,
"playbook_trust_writeback_ready": accepted,
"projected_scoped_relink_dry_run_plan": (
projected_scoped_relink_dry_run_plan
),
"operation_boundaries": {
"no_persist_validator": True,
"request_payload_saved": False,

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
import json
import os
os.environ.setdefault("DATABASE_URL", "postgresql+asyncpg://test:test@localhost/test")
@@ -273,6 +274,40 @@ def test_windows99_candidate_confirmation_validator_accepts_redacted_packet() ->
assert payload["projected_confirmation_gate"][
"apply_allowed_by_confirmation_gate"
] is False
assert payload["scoped_relink_dry_run_ready"] is True
assert payload["pre_apply_backup_plan_ready"] is True
assert payload["post_verifier_ready"] is True
assert payload["km_writeback_ready"] is True
assert payload["playbook_trust_writeback_ready"] is True
plan = payload["projected_scoped_relink_dry_run_plan"]
assert plan["schema_version"] == "windows99_vmx_source_scoped_relink_dry_run_v1"
assert plan["status"] == "ready_for_no_write_review"
assert plan["ready"] is True
assert plan["mode"] == "dry_run_no_windows_or_vm_write"
assert plan["target_selector"]["host_alias"] == "99"
assert plan["target_selector"]["vm_aliases"] == ["111"]
assert plan["target_selector"]["missing_vmx_aliases"] == ["111"]
assert plan["target_selector"]["secret_collection_allowed"] is False
assert plan["target_selector"]["vm_power_change_allowed"] is False
assert plan["target_selector"]["runtime_write_allowed"] is False
assert plan["source_diff"]["candidate_identity_evidence_ref"] == (
"console://awoooi/windows99/candidate-identity-redacted-111"
)
assert plan["source_diff"]["raw_path_output"] is False
assert plan["source_diff"]["path_fingerprint_only"] is True
assert plan["pre_apply_backup_plan"]["required"] is True
assert plan["pre_apply_backup_plan"]["backup_ref_required"] is True
assert plan["rollback_ref"] == (
"rollback://awoooi/windows99-vmx-source-relink-dry-run"
)
assert "GET /api/v1/agents/windows99-vmx-source-locator-readback" in plan[
"post_verifier"
]
assert plan["apply_allowed_by_this_plan"] is False
assert plan["remote_write_performed"] is False
assert plan["vm_power_change_performed"] is False
assert plan["secret_value_read"] is False
assert plan["request_payload_saved"] is False
assert payload["operation_boundaries"]["request_payload_saved"] is False
assert payload["operation_boundaries"]["remote_write_performed"] is False
assert payload["operation_boundaries"]["vm_power_change_performed"] is False
@@ -325,6 +360,14 @@ def test_windows99_candidate_confirmation_validator_rejects_sensitive_payload()
assert payload["projected_confirmation_gate"][
"can_prepare_scoped_relink_dry_run"
] is False
assert payload["scoped_relink_dry_run_ready"] is False
plan = payload["projected_scoped_relink_dry_run_plan"]
assert plan["ready"] is False
assert plan["status"] == "blocked_candidate_confirmation_not_accepted"
assert plan["source_diff"]["candidate_identity_evidence_ref"] == ""
serialized = json.dumps(payload)
assert "D:\\" not in serialized
assert "192.168.0." not in serialized
assert payload["operation_boundaries"]["apply_gate_opened"] is False
@@ -453,5 +496,14 @@ def test_windows99_candidate_confirmation_endpoint_is_no_persist_validator(
assert payload["operation_boundaries"]["request_payload_saved"] is False
assert payload["operation_boundaries"]["apply_gate_opened"] is False
assert payload["operation_boundaries"]["vm_power_change_performed"] is False
assert payload["scoped_relink_dry_run_ready"] is True
plan = payload["projected_scoped_relink_dry_run_plan"]
assert plan["ready"] is True
assert plan["mode"] == "dry_run_no_windows_or_vm_write"
assert plan["source_diff"]["candidate_identity_evidence_ref"] == (
"backup://awoooi/windows99/redacted-fingerprint-111"
)
assert plan["apply_allowed_by_this_plan"] is False
assert plan["request_payload_saved"] is False
assert "D:\\" not in response.text
assert "192.168.0." not in response.text