fix(windows99): surface scoped relink receipt readback
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 1m41s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped

This commit is contained in:
ogt
2026-07-10 10:11:09 +08:00
parent 42ce3d54be
commit acbf26e089
3 changed files with 338 additions and 21 deletions

View File

@@ -48,6 +48,9 @@ _SCOPED_RELINK_DRY_RUN_RECEIPT_SCHEMA_VERSION = (
"windows99_vmx_source_scoped_relink_dry_run_receipt_v1"
)
_RECEIPT_FILE = "windows99-vmx-source-locator-receipt.snapshot.json"
_SCOPED_RELINK_DRY_RUN_RECEIPT_FILE = (
"windows99-vmx-source-scoped-relink-dry-run-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"
_CANDIDATE_CONFIRMATION_REQUIRED_FIELDS = [
@@ -200,6 +203,23 @@ def _load_locator_receipt(operations_dir: Path | None = None) -> dict[str, Any]:
return payload
def _load_scoped_relink_dry_run_receipt(
operations_dir: Path | None = None,
) -> dict[str, Any]:
path = (operations_dir or _DEFAULT_OPERATIONS_DIR) / _SCOPED_RELINK_DRY_RUN_RECEIPT_FILE
if not path.exists():
return {}
payload = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(payload, dict):
raise ValueError(f"{path}: expected JSON object")
if payload.get("schema_version") != _SCOPED_RELINK_DRY_RUN_RECEIPT_SCHEMA_VERSION:
raise ValueError(f"{path}: unexpected schema_version")
leaked = _forbidden_fragments(payload)
if leaked:
raise ValueError(f"{path}: unsafe scoped relink receipt fragments: {leaked}")
return payload
def _vm_rows_for_aliases(
repair_package: dict[str, Any],
target_aliases: list[str],
@@ -821,6 +841,7 @@ def build_windows99_vmx_source_locator_readback(
*,
repair_package: dict[str, Any] | None = None,
collector_receipt: dict[str, Any] | None = None,
scoped_relink_dry_run_receipt: dict[str, Any] | None = None,
operations_dir: Path | None = None,
generated_at: str | None = None,
) -> dict[str, Any]:
@@ -873,8 +894,21 @@ def build_windows99_vmx_source_locator_readback(
"--collect"
)
receipt = collector_receipt if collector_receipt is not None else _load_locator_receipt(operations_dir)
receipt = (
collector_receipt
if collector_receipt is not None
else _load_locator_receipt(operations_dir)
)
receipt = _dict(receipt)
scoped_receipt = (
_dict(scoped_relink_dry_run_receipt)
if scoped_relink_dry_run_receipt is not None
else (
_load_scoped_relink_dry_run_receipt(operations_dir)
if collector_receipt is None
else {}
)
)
receipt_aliases = _strings(receipt.get("target_vm_aliases"))
receipt_applies = bool(
receipt.get("collector_readback_present") is True
@@ -882,6 +916,51 @@ def build_windows99_vmx_source_locator_readback(
and receipt_aliases
and set(receipt_aliases).issubset(set(target_aliases or receipt_aliases))
)
scoped_receipt_target_selector = _dict(scoped_receipt.get("target_selector"))
scoped_receipt_source_diff = _dict(scoped_receipt.get("source_of_truth_diff"))
scoped_receipt_operation_boundaries = _dict(
scoped_receipt.get("operation_boundaries")
)
scoped_receipt_aliases = _strings(scoped_receipt.get("target_vm_aliases")) or _strings(
scoped_receipt_target_selector.get("vm_aliases")
)
scoped_receipt_false_flags = [
"request_payload_saved",
"secret_value_read",
"raw_path_output",
"vmx_file_content_read",
"remote_write_performed",
"runtime_write_performed",
"host_reboot_performed",
"vm_power_change_performed",
"windows_registry_apply_performed",
"scheduled_task_write_performed",
"apply_gate_opened",
]
scoped_receipt_boundaries_clean = all(
scoped_receipt_operation_boundaries.get(
field,
scoped_receipt.get(field, False),
)
is False
for field in scoped_receipt_false_flags
)
scoped_receipt_applies = bool(
scoped_receipt
and scoped_receipt.get("schema_version")
== _SCOPED_RELINK_DRY_RUN_RECEIPT_SCHEMA_VERSION
and scoped_receipt.get("target_host_alias") == "99"
and scoped_receipt_aliases
and set(scoped_receipt_aliases).issubset(
set(missing_aliases or target_aliases or scoped_receipt_aliases)
)
and str(scoped_receipt.get("plan_id") or "")
== _scoped_relink_plan_id(scoped_receipt_aliases)
and scoped_receipt.get("ready") is True
and scoped_receipt.get("reviewer_validation_passed") is True
and scoped_receipt.get("controlled_relink_dry_run_ready") is True
and scoped_receipt_boundaries_clean
)
receipt_locate_status = str(receipt.get("locate_status") or "")
expected_vmx_path_present_count = (
_int(receipt.get("expected_vmx_path_present_count"))
@@ -920,7 +999,8 @@ def build_windows99_vmx_source_locator_readback(
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
(receipt_applies and receipt.get("controlled_relink_dry_run_ready") is True)
or scoped_receipt_applies
)
verified_source_or_identity_evidence_required = bool(
receipt_applies
@@ -928,13 +1008,19 @@ def build_windows99_vmx_source_locator_readback(
and not controlled_relink_dry_run_ready
)
controlled_relink_dry_run_blocker = ""
if receipt_applies:
if receipt_applies or scoped_receipt_applies:
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"
)
if scoped_receipt_applies:
next_step = "rerun_no_secret_collector_and_scorecard"
safe_next_step = (
"rerun_no_secret_collector_and_scorecard_no_vm_power_change"
)
else:
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 = (
@@ -957,11 +1043,21 @@ def build_windows99_vmx_source_locator_readback(
)
controlled_relink_dry_run_blocker = "scoped_relink_candidate_not_verified"
relink_dry_run_plan_aliases = (
scoped_receipt_aliases if scoped_receipt_applies else missing_aliases
)
relink_dry_run_plan = {
"plan_id": f"windows99-vmx-source-relink-dry-run-{','.join(missing_aliases) or 'none'}",
"plan_id": _scoped_relink_plan_id(relink_dry_run_plan_aliases),
"mode": "dry_run_no_windows_or_vm_write",
"ready": controlled_relink_dry_run_ready,
"blocker": controlled_relink_dry_run_blocker,
"reviewer_validation_passed": scoped_receipt_applies,
"reviewer_validation_status": str(scoped_receipt.get("status") or "")
if scoped_receipt_applies
else "",
"receipt_ref": str(scoped_receipt.get("receipt_ref") or "")
if scoped_receipt_applies
else "",
"target_selector": {
"host_alias": "99",
"vm_aliases": target_aliases,
@@ -982,6 +1078,19 @@ def build_windows99_vmx_source_locator_readback(
"identity_unassigned_ubuntu_candidate_count": (
identity_unassigned_ubuntu_candidate_count
),
"candidate_identity_evidence_ref": str(
scoped_receipt_source_diff.get("candidate_identity_evidence_ref") or ""
)
if scoped_receipt_applies
else "",
"source_fingerprint_ref": str(
scoped_receipt_source_diff.get("source_fingerprint_ref") or ""
)
if scoped_receipt_applies
else "",
"backup_ref": str(scoped_receipt_source_diff.get("backup_ref") or "")
if scoped_receipt_applies
else "",
"raw_path_output": False,
"path_fingerprint_only": True,
"bounded_identity_metadata_only": bool(
@@ -1075,6 +1184,20 @@ def build_windows99_vmx_source_locator_readback(
),
"controlled_relink_dry_run_ready": controlled_relink_dry_run_ready,
"controlled_relink_dry_run_blocker": controlled_relink_dry_run_blocker,
"controlled_relink_dry_run_receipt_present": scoped_receipt_applies,
"controlled_relink_dry_run_receipt_ref": str(
scoped_receipt.get("receipt_ref") or ""
)
if scoped_receipt_applies
else "",
"controlled_relink_dry_run_reviewer_validation_passed": (
scoped_receipt_applies
),
"controlled_relink_dry_run_validation_status": str(
scoped_receipt.get("status") or ""
)
if scoped_receipt_applies
else "",
"controlled_relink_dry_run_plan": relink_dry_run_plan,
"candidate_confirmation_gate": candidate_confirmation_gate,
"candidate_confirmation_status": str(
@@ -1085,10 +1208,12 @@ def build_windows99_vmx_source_locator_readback(
),
"candidate_identity_evidence_sufficient": bool(
candidate_confirmation_gate.get("identity_evidence_sufficient") is True
or scoped_receipt_applies
),
"candidate_relink_dry_run_ready": bool(
candidate_confirmation_gate.get("can_prepare_scoped_relink_dry_run")
is True
or scoped_receipt_applies
),
"candidate_confirmation_missing_evidence": _strings(
candidate_confirmation_gate.get("missing_evidence")

View File

@@ -133,6 +133,56 @@ def _valid_scoped_relink_dry_run_packet() -> dict:
}
def _valid_scoped_relink_dry_run_receipt() -> dict:
return {
"schema_version": "windows99_vmx_source_scoped_relink_dry_run_receipt_v1",
"receipt_ref": (
"receipt://awoooi/windows99/windows99-vmx-source-relink-dry-run-111"
),
"status": "reviewer_validation_passed_no_write",
"ready": True,
"reviewer_validation_passed": True,
"controlled_relink_dry_run_ready": True,
"plan_id": "windows99-vmx-source-relink-dry-run-111",
"target_host_alias": "99",
"target_vm_aliases": ["111"],
"target_selector": {
"host_alias": "99",
"vm_aliases": ["111"],
"missing_vmx_aliases": ["111"],
"secret_collection_allowed": False,
"vm_power_change_allowed": False,
"runtime_write_allowed": False,
},
"source_of_truth_diff": {
"expected_vmx_path_present_count": 0,
"candidate_source_count": 1,
"candidate_source_fingerprint_count": 1,
"candidate_identity_evidence_ref": (
"console://awoooi/windows99/candidate-identity-redacted-111"
),
"source_fingerprint_ref": "fingerprint://awoooi/windows99/vmx-source-111",
"backup_ref": "backup://awoooi/windows99/pre-relink-redacted-111",
"raw_path_output": False,
"path_fingerprint_only": True,
"bounded_identity_metadata_only": True,
},
"operation_boundaries": {
"request_payload_saved": False,
"secret_value_read": False,
"raw_path_output": False,
"vmx_file_content_read": False,
"remote_write_performed": False,
"runtime_write_performed": False,
"host_reboot_performed": False,
"vm_power_change_performed": False,
"windows_registry_apply_performed": False,
"scheduled_task_write_performed": False,
"apply_gate_opened": False,
},
}
def test_windows99_vmx_source_locator_builder_is_no_secret_check_mode() -> None:
repair_package = build_windows99_vmx_source_repair_package(
_scorecard(),
@@ -284,6 +334,74 @@ def test_windows99_vmx_source_locator_builder_projects_redacted_receipt() -> Non
assert payload["vm_power_change_performed"] is False
def test_windows99_vmx_source_locator_builder_projects_scoped_relink_receipt() -> None:
repair_package = build_windows99_vmx_source_repair_package(
_scorecard(),
generated_at="2026-07-03T11:21:00+08:00",
)
payload = build_windows99_vmx_source_locator_readback(
_scorecard(),
repair_package=repair_package,
collector_receipt={
"schema_version": "windows99_vmx_source_locator_receipt_v1",
"collector_readback_present": True,
"collector_collection_status": (
"collected_windows99_vmx_source_locator_stdout"
),
"target_host_alias": "99",
"target_vm_aliases": ["111"],
"expected_vmx_path_present_count": 0,
"candidate_source_count": 1,
"candidate_source_fingerprint_count": 1,
"alias_hint_candidate_count": 0,
"identity_metadata_read": True,
"bounded_identity_metadata_only": True,
"identity_target_hint_count": 0,
"identity_unassigned_ubuntu_candidate_count": 1,
"locate_status": (
"single_identity_unassigned_ubuntu_candidate_requires_confirmation"
),
},
scoped_relink_dry_run_receipt=_valid_scoped_relink_dry_run_receipt(),
generated_at="2026-07-10T10:16:00+08:00",
)
assert payload["status"] == "collector_readback_scoped_relink_dry_run_ready"
assert payload["safe_next_step"] == (
"rerun_no_secret_collector_and_scorecard_no_vm_power_change"
)
assert payload["controlled_relink_dry_run_ready"] is True
assert payload["controlled_relink_dry_run_blocker"] == ""
assert payload["controlled_relink_dry_run_receipt_present"] is True
assert payload["controlled_relink_dry_run_reviewer_validation_passed"] is True
assert payload["controlled_relink_dry_run_validation_status"] == (
"reviewer_validation_passed_no_write"
)
assert payload["verified_source_or_identity_evidence_required"] is False
assert payload["candidate_relink_dry_run_ready"] is True
assert payload["candidate_identity_evidence_sufficient"] is True
plan = payload["controlled_relink_dry_run_plan"]
assert plan["ready"] is True
assert plan["reviewer_validation_passed"] is True
assert plan["receipt_ref"] == (
"receipt://awoooi/windows99/windows99-vmx-source-relink-dry-run-111"
)
assert plan["source_diff"]["source_fingerprint_ref"] == (
"fingerprint://awoooi/windows99/vmx-source-111"
)
assert plan["source_diff"]["backup_ref"] == (
"backup://awoooi/windows99/pre-relink-redacted-111"
)
assert plan["source_diff"]["raw_path_output"] is False
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 payload["remote_write_performed"] is False
assert payload["vm_power_change_performed"] is False
assert payload["secret_value_read"] is False
def test_windows99_candidate_confirmation_validator_accepts_redacted_packet() -> None:
repair_package = build_windows99_vmx_source_repair_package(
_scorecard(),
@@ -614,8 +732,9 @@ def test_windows99_vmx_source_locator_endpoint_returns_public_safe_readback(
assert response.status_code == 200
payload = response.json()
assert payload["schema_version"] == "windows99_vmx_source_locator_readback_v1"
assert payload["status"] == (
"collector_readback_verified_source_required_before_relink_dry_run"
assert payload["status"] == "collector_readback_scoped_relink_dry_run_ready"
assert payload["safe_next_step"] == (
"rerun_no_secret_collector_and_scorecard_no_vm_power_change"
)
assert payload["target_vm_aliases"] == ["111"]
assert payload["locator_check_mode_ready"] is True
@@ -628,20 +747,22 @@ def test_windows99_vmx_source_locator_endpoint_returns_public_safe_readback(
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"]["plan_id"] == (
"windows99-vmx-source-relink-dry-run-111"
)
assert payload["controlled_relink_dry_run_plan"]["remote_write_performed"] is False
assert payload["verified_source_or_identity_evidence_required"] is False
assert payload["controlled_relink_dry_run_ready"] is True
assert payload["controlled_relink_dry_run_blocker"] == ""
assert payload["controlled_relink_dry_run_receipt_present"] is True
assert payload["controlled_relink_dry_run_reviewer_validation_passed"] is True
plan = payload["controlled_relink_dry_run_plan"]
assert plan["plan_id"] == "windows99-vmx-source-relink-dry-run-111"
assert plan["ready"] is True
assert plan["reviewer_validation_passed"] is True
assert plan["remote_write_performed"] is False
assert plan["vm_power_change_performed"] is False
assert payload["candidate_confirmation_status"] == (
"blocked_single_unassigned_ubuntu_candidate_identity_not_confirmed"
)
assert payload["candidate_identity_evidence_sufficient"] is False
assert payload["candidate_relink_dry_run_ready"] is False
assert payload["candidate_identity_evidence_sufficient"] is True
assert payload["candidate_relink_dry_run_ready"] is True
assert payload["apply_allowed_by_candidate_confirmation_gate"] is False
assert payload["public_lan_topology_redacted"] is True
assert payload["raw_path_output"] is False