fix(agent99): recover external host through full closure chain

This commit is contained in:
ogt
2026-07-14 13:47:55 +08:00
parent abff2c89e0
commit 3b0d6a2ed3
8 changed files with 462 additions and 11 deletions

View File

@@ -73,7 +73,7 @@ def test_agent99_telegram_v5_hides_raw_evidence_and_has_operator_fields() -> Non
assert "kmPath" not in formatter
assert "verifierName=" not in formatter
assert 'messageFormat = "incident_card_zh_tw_v5"' in source
for phase_label in ("VMware", "主機連線", "Kali 112", "公開網站", "Full SOP"):
for phase_label in ("VMware", "Host111 WOL", "主機連線", "Kali 112", "公開網站", "Full SOP"):
assert f'{{ "{phase_label}" }}' in source
@@ -169,6 +169,71 @@ def test_agent99_99_config_has_bounded_transport_and_recovery_defaults() -> None
assert config["autoRecovery"]["enabled"] is True
assert config["autoRecovery"]["cooldownMinutes"] == 10
assert config["recoverySlo"]["targetMinutes"] == 10
assert "192.168.0.111" in config["hosts"]
assert "192.168.0.111" in config["selfHealth"]["requiredHosts"]
host111 = next(item for item in config["externalHosts"] if item["host"] == "192.168.0.111")
assert host111["recoveryAction"] == "wake_on_lan"
assert host111["required"] is True
assert host111["enabled"] is True
assert host111["verifyTcpPorts"] == [22]
assert host111["verifyTimeoutSeconds"] <= 300
host112 = next(item for item in config["vms"] if item["host"] == "192.168.0.112")
assert host112["vmx"] == r"S:\VMs\host112\kali-linux-2025.4-vmware-amd64.vmx"
def test_agent99_host111_recovery_is_allowlisted_and_independently_verified() -> None:
source = CONTROL.read_text(encoding="utf-8")
function = source[source.index("function Test-AgentTcpPort") :]
function = function[: function.index("function Test-HostReachability")]
assert "function Send-AgentWakeOnLan" in function
assert "System.Net.Sockets.UdpClient" in function
assert 'transport = "windows99_udp_broadcast"' in function
assert "Invoke-AgentExternalHostRecovery" in function
assert "Test-AgentExternalHostReadiness" in function
assert 'terminal = if ($after.ready) { "verified_ready" }' in function
assert 'verifier = "icmp_and_required_tcp_ports"' in function
for prohibited in (
"host_reboot",
"power_cut",
"service_restart",
"secret_read",
"firewall_change",
"database_write",
):
assert f'"{prohibited}"' in function
recover = source[source.index('if ($Mode -eq "Recover") {\n $externalHostRecovery') :]
assert 'New-AgentRecoveryPhase "external_host_recovery"' in recover
assert "$requiredExternalHostFailures.Count -eq 0" in recover
assert 'New-AgentOutcomeCheck "external_hosts_recovered"' in source
def test_agent99_startup_recovery_uses_full_outcome_callback_and_learning_chain() -> None:
tasks = (ROOT / "agent99-register-tasks.ps1").read_text(encoding="utf-8")
assert '$submitRequest = Join-Path $AgentRoot "bin\\agent99-submit-request.ps1"' in tasks
assert '-Mode Recover -ControlledApply -RunNow' in tasks
assert '-Source agent99-startup-recovery' in tasks
assert '-ExternalId windows99-startup' in tasks
recover_action = tasks[tasks.index("$recoverInstruction") : tasks.index("$heartbeatArgs")]
assert '$launcher`" -Mode Recover' not in recover_action
assert "callback, Telegram lifecycle, and KM/RAG" in tasks
def test_agent99_deployer_migrates_and_verifies_host111_and_host112_sources() -> None:
deploy = DEPLOY.read_text(encoding="utf-8")
for migration in (
"host111_canonical_recovery_target",
"host111_wol_executor",
"host111_self_health_coverage",
"host112_ssd_vmx_source",
):
assert migration in deploy
assert '"S:\\VMs\\host112\\kali-linux-2025.4-vmware-amd64.vmx"' in deploy
assert "$host111ExternalReady" in deploy
assert "$host112VmxReady" in deploy
def test_agent99_deployer_verifies_promoted_runtime_and_writes_revision() -> None: