fix(agent99): gate reboot recovery on full SOP

This commit is contained in:
ogt
2026-07-11 15:43:08 +08:00
parent 276410fbb6
commit dbbf651f0a
13 changed files with 864 additions and 91 deletions

View File

@@ -0,0 +1,96 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
def read(relative: str) -> str:
return (ROOT / relative).read_text(encoding="utf-8")
def test_recover_requires_full_sop_coordinator_before_verified_resolution() -> None:
control = read("agent99-control-plane.ps1")
assert "Invoke-AgentRecoveryCoordinatorReadback" in control
assert 'New-AgentOutcomeCheck "full_sop_coordinator_verified"' in control
assert "-and $coordinatorOk" in control
assert 'New-AgentRecoveryPhase "full_sop_scorecard"' in control
assert 'schemaVersion = "agent99_recovery_slo_v2"' in control
assert 'scope = $recoveryScope' in control
assert 'rebootSloClaimed = $rebootSloClaimed' in control
def test_coordinator_covers_all_hosts_and_full_recovery_evidence() -> None:
control = read("agent99-control-plane.ps1")
for alias in ("99", "110", "111", "112", "120", "121", "188"):
assert f'"{alias}"' in control
for field in (
"SERVICE_GREEN",
"PRODUCT_DATA_GREEN",
"STOCK_FRESHNESS_STATUS",
"BACKUP_CORE_GREEN",
"HOST_188_SERVICE_GREEN",
"HOST_188_HYGIENE_BLOCKED",
"EDGE_FALLBACK_READY",
"VMWARE_AUTOSTART_VERIFY_READY",
"WINDOWS_UPDATE_NO_AUTO_REBOOT_READY",
"SCORECARD_FRESH_REBOOT_WINDOW",
"SCORECARD_CAN_CLAIM",
"SCORECARD_BLOCKER_COUNT",
):
assert field in control
def test_fresh_reboot_claim_requires_fresh_artifact_and_zero_blockers() -> None:
control = read("agent99-control-plane.ps1")
assert 'name = "artifact_fresh_for_recovery"' in control
assert 'name = "fresh_reboot_window_observed"' in control
assert 'name = "scorecard_can_claim_slo"' in control
assert 'name = "scorecard_has_no_blockers"' in control
assert "$artifactEpoch -ge ($startedEpoch - $CoordinatorConfig.artifactClockSkewSeconds)" in control
assert "-not $requiresFreshWindow -or $rebootSloClaimed" in control
def test_runtime_deployer_migrates_recovery_coordinator_defaults() -> None:
deployer = read("agent99-deploy.ps1")
config = read("agent99.config.99.example.json")
assert 'Add-DefaultProperty $config "recoveryCoordinator"' in deployer
assert 'Add-DefaultProperty $config.recoveryCoordinator "controllerHost" "192.168.0.110"' in deployer
assert 'Add-DefaultProperty $config.recoveryCoordinator "requiredHostAliases"' in deployer
assert '"recoveryCoordinator"' in config
assert '"freshWaitTimeoutSeconds": 420' in config
def test_coordinator_reads_only_bounded_summary_artifacts() -> None:
control = read("agent99-control-plane.ps1")
coordinator = control.split("function Invoke-AgentRecoveryCoordinatorReadback", 1)[1]
coordinator = coordinator.split("function Convert-AgentDouble", 1)[0]
assert "summary.txt" in coordinator
assert "host-probe.txt" in coordinator
assert "public-maintenance-edge-fallback.txt" in coordinator
assert "windows99-vmware-verify.txt" in coordinator
assert "scorecard.json" in coordinator
assert "*.log" not in coordinator
assert ".env" not in coordinator
assert "auth.json" not in coordinator
assert "session" not in coordinator.lower()
def test_live_preflight_is_no_secret_and_no_remote_write() -> None:
preflight = read("scripts/reboot-recovery/agent99-live-preflight.ps1")
assert 'secretValueRead = $false' in preflight
assert 'remoteWritePerformed = $false' in preflight
assert 'Write-Output "SECRET_VALUE_READ=0"' in preflight
assert 'Write-Output "REMOTE_WRITE_PERFORMED=0"' in preflight
assert "runtime-manifest.json" in preflight
assert "Get-ScheduledTask" in preflight
assert "Get-NetTCPConnection -LocalPort 8787" in preflight
assert ".env" not in preflight
assert "auth.json" not in preflight
assert "agent99.config.json" not in preflight