from __future__ import annotations import hashlib import os import shutil import subprocess from pathlib import Path ROOT = Path(__file__).resolve().parents[3] READINESS = ROOT / "scripts/reboot-recovery/host112-guest-readiness.sh" INSTALLER = ROOT / "scripts/reboot-recovery/install-host112-guest-recovery.sh" SERVICE = ROOT / "scripts/reboot-recovery/awoooi-host112-guest-recovery.service" CONTROL = ROOT / "agent99-control-plane.ps1" BOOTSTRAP = ROOT / "agent99-bootstrap.ps1" def read(path: Path) -> str: return path.read_text(encoding="utf-8") def isolated_env(tmp_path: Path) -> dict[str, str]: env = os.environ.copy() env.update( { "HOST112_RECOVERY_STATE_DIR": str(tmp_path / "state"), "HOST112_INDEXER_RETRY_MARKER": str(tmp_path / "indexer.epoch"), "HOST112_MANAGER_RETRY_MARKER": str(tmp_path / "manager.epoch"), "HOST112_MANAGER_LOCK_FILE": str(tmp_path / "manager.lock"), } ) return env def test_check_and_dry_run_are_real_no_write_execution_paths(tmp_path: Path) -> None: identity = [ "--trace-id", "trace:D037-host112-dry-run", "--run-id", "run:D037-host112-dry-run", "--work-item-id", "D037-WAZUH-HOST112", ] env = isolated_env(tmp_path) check = subprocess.run( ["bash", str(READINESS), "--check", *identity], text=True, capture_output=True, env=env, check=False, timeout=20, ) dry_run = subprocess.run( ["bash", str(READINESS), "--dry-run", *identity], text=True, capture_output=True, env=env, check=False, timeout=20, ) assert check.returncode in {0, 1} assert dry_run.returncode in {0, 1} for result, mode in ((check, "check"), (dry_run, "dry_run")): output = result.stdout + result.stderr assert f"mode={mode}" in output assert "trace_id=trace:D037-host112-dry-run" in output assert "run_id=run:D037-host112-dry-run" in output assert "work_item_id=D037-WAZUH-HOST112" in output assert "runtime_write_performed=0" in output assert "manager_runtime_write_performed=0" in output assert "receipt_write_performed=0" in output assert "manager_preflight_status=" in output assert "apply_phase_timeout_budget_seconds=370" in output assert "executor_deadline_seconds=420" in output assert "durable_receipt_present=0" in output assert not (tmp_path / "state").exists() assert not (tmp_path / "indexer.epoch").exists() assert not (tmp_path / "manager.epoch").exists() assert not (tmp_path / "manager.lock").exists() def test_unsafe_or_incomplete_identity_fails_before_any_state_path(tmp_path: Path) -> None: env = isolated_env(tmp_path) unsafe = subprocess.run( [ "bash", str(READINESS), "--dry-run", "--trace-id", "unsafe value", "--run-id", "run-safe", "--work-item-id", "D037-WAZUH-HOST112", ], text=True, capture_output=True, env=env, check=False, timeout=10, ) incomplete = subprocess.run( [ "bash", str(READINESS), "--dry-run", "--trace-id", "trace-safe", ], text=True, capture_output=True, env=env, check=False, timeout=10, ) assert unsafe.returncode == 64 assert "BLOCKER unsafe_control_identity" in (unsafe.stdout + unsafe.stderr) assert incomplete.returncode == 64 assert "BLOCKER incomplete_control_identity" in ( incomplete.stdout + incomplete.stderr ) assert not any(tmp_path.iterdir()) def test_manager_preflight_verifier_receipt_and_rollback_are_complete() -> None: source = read(READINESS) for field in ( "manager_failed_state_observed", "manager_orphan_analysisd", "manager_memory_available_kb", "manager_disk_available_kb", "manager_disk_used_percent", "wazuh_analysisd_process_count", "wazuh_agent_event_port_1514_tcp", "wazuh_agent_event_port_1514_udp", "wazuh_agent_enrollment_port_1515_tcp", "wazuh_manager_api_port_55000_tcp", "manager_independent_verifier_ready", ): assert field in source assert 'manager_active_state="$(unit_property wazuh-manager.service ActiveState)"' in source assert 'manager_result="$(unit_property wazuh-manager.service Result)"' in source assert 'analysisd_count="$(process_count wazuh-analysisd)"' in source assert source.count( 'timeout "$MANAGER_START_TIMEOUT_SECONDS" systemctl start wazuh-manager.service' ) == 1 assert "manager_attempt_count=1" in source assert 'if [ "$manager_start_exit" = "0" ]; then' in source assert 'manager_terminal="manager_start_nonzero_verifier_healthy_unattributed"' in source assert 'timeout "$MANAGER_ROLLBACK_TIMEOUT_SECONDS" systemctl stop wazuh-manager.service' in source assert 'rollback_terminal="bounded_stop_verified"' in source assert "schema_version=host112_wazuh_manager_recovery_receipt_v2" in source assert "boot_id=$boot_id" in source assert "trace_id=$TRACE_ID run_id=$RUN_ID work_item_id=$WORK_ITEM_ID" in source assert 'receipt_status="write_failed"' in source assert "prohibited_actions=host_reboot,vm_power_change,vm_reset,firewall_change,secret_read,database_write" in source preflight = source[ source.index("evaluate_manager_preflight() {") : source.index( "wait_for_manager_verifier() {" ) ] assert 'elif [ "$(unit_active wazuh-indexer.service)" != "active" ]; then' in preflight assert 'manager_preflight_status="blocked_indexer_dependency_no_write"' in preflight def test_cooldown_marker_write_is_atomic_and_fails_closed_before_start() -> None: source = read(READINESS) marker_function = source[ source.index("write_epoch_marker() {") : source.index("unit_active() {") ] assert 'tmp="${marker}.tmp"' in marker_function assert 'printf \'%s\\n\' "$epoch" >"$tmp"' in marker_function assert 'chmod 0644 "$tmp"' in marker_function assert 'mv "$tmp" "$marker"' in marker_function assert "return 1" in marker_function manager_start = source.index( ' if write_epoch_marker "$MANAGER_RETRY_MARKER" "$now_epoch"; then' ) manager_end = source.index(" ;;", manager_start) manager_branch = source[manager_start:manager_end] marker_failure_at = manager_branch.index( ' else\n record_action "wazuh_manager_marker_write_failed_no_write"' ) success = manager_branch[:marker_failure_at] failure = manager_branch[marker_failure_at:] marker_success_at = success.index( ' manager_marker_write_performed=1' ) reset_at = success.index("systemctl reset-failed wazuh-manager.service") assert marker_success_at < reset_at assert "runtime_write_performed=1" in success[:reset_at] assert "state_write_performed=1" in success[:reset_at] assert "systemctl reset-failed wazuh-manager.service" in success assert "systemctl start wazuh-manager.service" in success assert 'bounded_timeout "$MANAGER_RESET_TIMEOUT_SECONDS" systemctl reset-failed wazuh-manager.service' in success assert "manager_attempt_count=1" in success assert "systemctl reset-failed wazuh-manager.service" not in failure assert "systemctl start wazuh-manager.service" not in failure assert "wazuh_manager_marker_write_failed_no_write" in failure assert 'manager_terminal="no_write_cooldown_marker_write_failed"' in failure assert "manager_marker_write_performed=1" not in failure assert "state_write_performed=$state_write_performed" in source assert "manager_marker_write_performed=$manager_marker_write_performed" in source indexer_start = source.index( ' if write_epoch_marker "$INDEXER_RETRY_MARKER" "$now_epoch"; then' ) indexer_end = source.index(" else\n record_action", indexer_start) indexer_branch = source[indexer_start:indexer_end] indexer_success, indexer_failure = indexer_branch.split(" else\n", maxsplit=1) assert "systemctl reset-failed wazuh-indexer.service" in indexer_success assert "systemctl start wazuh-indexer.service" in indexer_success assert "systemctl reset-failed wazuh-indexer.service" not in indexer_failure assert "systemctl start wazuh-indexer.service" not in indexer_failure assert "wazuh_indexer_marker_write_failed_no_write" in indexer_failure def test_phase_budget_and_systemd_outer_timeout_are_aligned() -> None: source = read(READINESS) service = read(SERVICE) control = read(CONTROL) assert 'SERVICE_START_TIMEOUT_SECONDS="${HOST112_SERVICE_START_TIMEOUT_SECONDS:-15}"' in source assert 'INDEXER_START_TIMEOUT_SECONDS="${HOST112_INDEXER_START_TIMEOUT_SECONDS:-90}"' in source assert 'MANAGER_START_TIMEOUT_SECONDS="${HOST112_MANAGER_START_TIMEOUT_SECONDS:-90}"' in source assert 'MANAGER_RESET_TIMEOUT_SECONDS="${HOST112_MANAGER_RESET_TIMEOUT_SECONDS:-5}"' in source assert 'MANAGER_VERIFY_TIMEOUT_SECONDS="${HOST112_MANAGER_VERIFY_TIMEOUT_SECONDS:-30}"' in source assert 'MANAGER_ROLLBACK_TIMEOUT_SECONDS="${HOST112_MANAGER_ROLLBACK_TIMEOUT_SECONDS:-30}"' in source assert 'EXECUTOR_DEADLINE_SECONDS="${HOST112_EXECUTOR_DEADLINE_SECONDS:-420}"' in source assert 'if [ "$APPLY_PHASE_TIMEOUT_BUDGET_SECONDS" -gt "$EXECUTOR_DEADLINE_SECONDS" ]; then' in source assert 'if [ "$MANAGER_VERIFY_INTERVAL_SECONDS" -gt "$MANAGER_VERIFY_TIMEOUT_SECONDS" ]; then' in source assert "TimeoutStartSec=480" in service assert "$script:Agent99Host112ExecutorBudgetSeconds = 420" in control assert "$script:Agent99Host112ApplyTransportTimeoutSeconds = 510" in control assert "Invoke-AgentHost112SshText $applyCommand $script:Agent99Host112ApplyTransportTimeoutSeconds 1" in control assert "executorReserveSeconds -ge 60" in control assert "queueReserveSeconds -ge 300" in control assert "clientReserveSeconds -ge 120" in control assert '$process.WaitForExit($script:Agent99QueueOuterDeadlineSeconds * 1000)' in control def test_receipts_and_readbacks_are_immutable_boot_bound_and_replay_safe() -> None: source = read(READINESS) assert "receipt_name_for_identity()" in source assert "printf '%s\\0%s\\0%s\\0'" in source assert 'boot_id=$boot_id' in source assert 'ln "$tmp" "$receipt_path"' in source receipt_writer = source[ source.index("write_public_receipt() {") : source.index( "write_signal_readback() {", ) ] assert 'if [ "$MODE" = "timer_apply" ]' in receipt_writer assert 'mv "$tmp" "$receipt_path"' in receipt_writer assert 'elif [ "$MODE" != "timer_apply" ]' in receipt_writer assert 'ln "$tmp" "$receipt_path"' in receipt_writer assert 'receipt_status="reused_immutable"' in source assert 'manager_terminal="immutable_receipt_replay"' in source assert "immutable_receipt_identity_or_boot_mismatch" in source assert "durable_receipt_terminal=$durable_receipt_terminal" in source assert 'ln "$tmp" "$durable_readback_path"' in source assert "durable_readback_sha256=%s" in source assert "latest_readback_pointer_written=%s" in source def test_check_reads_exact_immutable_receipt_and_rejects_boot_mismatch( tmp_path: Path, ) -> None: trace_id = "trace-D037-receipt" run_id = "run-D037-receipt" work_item_id = "D037-WAZUH-HOST112" digest = hashlib.sha256( f"{trace_id}\0{run_id}\0{work_item_id}\0".encode() ).hexdigest() receipt_dir = tmp_path / "state" / "receipts" receipt_dir.mkdir(parents=True) receipt = receipt_dir / f"{run_id}-{digest}.txt" receipt.write_text( "schema_version=host112_wazuh_manager_recovery_receipt_v2 " f"trace_id={trace_id} run_id={run_id} work_item_id={work_item_id} " "boot_id=unknown terminal=verified_healthy\n", encoding="utf-8", ) readback_dir = tmp_path / "state" / "readbacks" readback_dir.mkdir(parents=True) durable_readback = readback_dir / f"{run_id}-{digest}.txt" receipt_sha = hashlib.sha256(receipt.read_bytes()).hexdigest() durable_readback.write_text( "schema_version=host112_guest_recovery_v2 " f"trace_id={trace_id} run_id={run_id} work_item_id={work_item_id} " "boot_id=unknown durable_receipt_terminal=verified_healthy " f"receipt_path={receipt} receipt_sha256={receipt_sha}\n", encoding="utf-8", ) original = receipt.read_bytes() env = isolated_env(tmp_path) args = [ "bash", str(READINESS), "--check", "--trace-id", trace_id, "--run-id", run_id, "--work-item-id", work_item_id, ] matched = subprocess.run( args, text=True, capture_output=True, env=env, check=False, timeout=20 ) matched_output = matched.stdout + matched.stderr assert "durable_receipt_present=1" in matched_output assert "durable_receipt_identity_match=1" in matched_output assert "durable_receipt_boot_match=1" in matched_output assert "durable_receipt_terminal=verified_healthy" in matched_output assert "durable_readback_present=1" in matched_output assert "durable_readback_receipt_link_match=1" in matched_output assert "durable_readback_terminal_match=1" in matched_output assert receipt.read_bytes() == original receipt.write_text( receipt.read_text(encoding="utf-8").replace( "boot_id=unknown", "boot_id=previous-boot" ), encoding="utf-8", ) mismatched = subprocess.run( args, text=True, capture_output=True, env=env, check=False, timeout=20 ) assert mismatched.returncode == 65 assert "immutable_receipt_identity_or_boot_mismatch" in ( mismatched.stdout + mismatched.stderr ) def test_executor_has_absolute_deadline_bounded_reads_and_signal_receipt() -> None: source = read(READINESS) assert "remaining_executor_seconds()" in source assert "bounded_timeout()" in source assert 'timeout --signal=TERM --kill-after=2 "$effective" "$@"' in source assert "handle_executor_signal()" in source assert "trap 'handle_executor_signal TERM' TERM" in source assert "trap 'handle_executor_signal INT' INT" in source assert "trap 'handle_executor_signal HUP' HUP" in source assert "if write_public_receipt && write_signal_readback; then" in source assert "action_count=${#actions[@]} actions=$receipt_action_text" in source assert "schema_version=host112_guest_recovery_signal_readback_v1" in source assert 'ln "$tmp" "$signal_readback_path"' in source assert "signal_readback_status=\"written_immutable\"" in source assert 'bounded_timeout "$READ_TIMEOUT_SECONDS" systemctl show' in source assert 'bounded_timeout "$READ_TIMEOUT_SECONDS" ss -H -ltn' in source assert 'bounded_timeout "$READ_TIMEOUT_SECONDS" df -Pk' in source def test_timer_noop_uses_one_bounded_aggregate_instead_of_per_minute_artifacts() -> None: source = read(READINESS) function = source[ source.index("write_bounded_timer_observation() {") : source.index( 'boot_id="$(cat /proc/sys/kernel/random/boot_id', ) ] assert 'timer_observation_path="$STATE_DIR/timer-observation.latest"' in function assert "schema_version=host112_timer_observation_aggregate_v1" in function assert "aggregation=fixed_single_atomic_pointer" in function assert 'mv "$tmp" "$timer_observation_path"' in function assert "repeat_count=$timer_observation_repeat_count" in function assert 'if [ "$MODE" = "apply" ] || [ "$runtime_write_performed" -eq 1 ]; then' in source assert 'receipt_status="timer_no_runtime_mutation_aggregated"' in source assert 'durable_readback_status="skipped_bounded_timer_no_runtime_mutation"' in source assert "timer_ledger_capacity=128" in source assert "write_bounded_timer_mutation_readback()" in source assert "retention=rolling_atomic_overwrite" in source assert 'timer_artifact_policy="bounded_ring_128_for_runtime_mutation"' in source assert 'timer_artifact_policy="bounded_mutable_observation_no_immutable_artifact"' in source def test_apply_success_is_bound_to_same_run_durable_terminal() -> None: source = read(READINESS) terminal_gate = source[source.rindex('if [ "$MODE" = "apply" ]') :] assert 'successful_terminal="$run_terminal"' in terminal_gate assert 'successful_terminal="$durable_receipt_terminal"' in terminal_gate assert '[ "$successful_terminal" = "verified_healthy" ]' in terminal_gate assert '[ "$successful_terminal" = "idempotent_already_verified_healthy" ]' in terminal_gate guest_verifier = source.index('guest_ready=0\nif [ "$console_ready" -eq 1 ]') receipt_commit = source.index("if ! write_public_receipt; then", guest_verifier) assert guest_verifier < receipt_commit assert 'run_terminal="guest_verification_failed_${manager_terminal}"' in source assert 'run_terminal="execution_failed_or_unattributed_${manager_terminal}"' in source assert 'action_failure_observed=1' in source assert "manager_terminal=$manager_terminal terminal=$run_terminal" in source def test_general_guest_repair_includes_recovery_timer_independent_of_manager() -> None: source = read(READINESS) general_start = source.index( ' current_default="$(bounded_timeout "$READ_TIMEOUT_SECONDS"' ) manager_start = source.index("\nfi\n\ncapture_manager_state", general_start) general = source[general_start:manager_start] assert "unit_loaded awoooi-host112-guest-recovery.timer" in general assert ( "enable_if_disabled awoooi-host112-guest-recovery.timer " "enable_recovery_timer" ) in general assert ( "start_if_inactive awoooi-host112-guest-recovery.timer " "start_recovery_timer" ) in general assert 'record_action "recovery_timer_missing"' in general def test_installer_uses_argument_regex_and_post_install_policy_readback() -> None: source = read(INSTALLER) assert "SUDOERS_ARGUMENT_REGEX='^--apply --trace-id " in source assert "--run-id [A-Za-z0-9]" in source assert "--work-item-id [A-Za-z0-9]" in source assert '"$TARGET_USER" "$READINESS_TARGET" "$SUDOERS_ARGUMENT_REGEX"' in source assert 'visudo -cf "$sudoers_tmp"' in source assert "SUDO_CORRELATED_APPLY_NOPASSWD" in source assert "SUDO_LEGACY_UNCORRELATED_APPLY_NOPASSWD" in source assert "SUDO_UNSAFE_IDENTITY_NOPASSWD" in source assert "--trace-id 'unsafe value'" in source assert "--rollback" in source def test_exact_sudoers_argument_regex_parses_when_visudo_is_available() -> None: visudo = shutil.which("visudo") if not visudo: return candidate = ( "kali ALL=(root) NOPASSWD: " "/usr/local/sbin/awoooi-host112-guest-readiness " "^--apply --trace-id [A-Za-z0-9][A-Za-z0-9._:@+-]{0,127} " "--run-id [A-Za-z0-9][A-Za-z0-9._:@+-]{0,127} " "--work-item-id [A-Za-z0-9][A-Za-z0-9._:@+-]{0,127}$\n" ) result = subprocess.run( [visudo, "-cf", "-"], input=candidate, text=True, capture_output=True, check=False, timeout=10, ) assert result.returncode == 0, result.stdout + result.stderr def test_agent99_propagates_identity_and_requires_independent_receipt() -> None: control = read(CONTROL) bootstrap = read(BOOTSTRAP) function = control[control.index("function Convert-AgentHost112GuestReadback") :] function = function[: function.index("function Test-HostReachability")] for parameter in ("AutomationRunId", "TraceId", "WorkItemId"): assert f"[string]${parameter} = \"\"" in control assert f'[string]`${parameter} = ""' in bootstrap assert 'reason = "controlled_dispatch_identity_unsafe"' in control assert 'applyBlockedReason = "control_identity_incomplete_or_unsafe"' in function assert '$dryRun.managerPreflightStatus -eq "ready"' in function assert 'receiptStatus -eq "written_immutable"' in function assert '$after.managerVerified' in function assert 'name = "wazuh_manager_service_active"' in function assert 'name = "wazuh_analysisd_process_present"' in function assert 'name = "wazuh_agent_event_1514_ready"' in function assert 'name = "wazuh_agent_enrollment_1515_tcp"' in function assert 'name = "wazuh_manager_api_55000_tcp"' in function assert "host112_readback_transport_unavailable" in function assert 'terminal = "no_write_control_identity_incomplete_or_unsafe"' in function def test_agent99_manager_success_requires_apply_and_immutable_receipt_chain() -> None: control = read(CONTROL) function = control[control.index("function Invoke-AgentHost112GuestRecovery") :] function = function[: function.index("function Test-HostReachability")] assert "$applyTransport -and $applyTransport.ok" in function assert '$applyReceipt.durableReceiptTerminal -eq "verified_healthy"' in function assert "$applyReceipt.managerIndependentVerifierReady" in function assert "$applyReceipt.managerRuntimeWritePerformed" in function assert '[string]$applyReceipt.managerAttemptCount -eq "1"' in function assert '[string]$applyReceipt.managerStartExit -eq "0"' in function assert "$durableReceiptReadback.receiptPath -eq $applyReceipt.receiptPath" in function assert "$durableReceiptReadback.receiptSha256 -eq $applyReceipt.receiptSha256" in function assert "$durableReceiptReadback.durableReceiptTerminal -eq $applyReceipt.durableReceiptTerminal" in function assert '$durableReceiptReadback.durableReceiptTerminal -eq "verified_healthy"' in function assert "$applyReceipt.durableReadbackReceiptLinkMatch" in function assert "$applyReceipt.durableReadbackTerminalMatch" in function assert "$durableReceiptReadback.durableReadbackReceiptLinkMatch" in function assert "$durableReceiptReadback.durableReadbackTerminalMatch" in function assert "$before.durableReadbackReceiptLinkMatch" in function assert "$before.durableReadbackTerminalMatch" in function assert "$afterManagerVerifierVerified" in function assert 'afterVerifierRole = "independent_second_verifier_only"' in function assert "$after.ready -and" in function assert "$managerApplyVerified" in function assert "existingReceiptTerminalAccepted" in function assert "immutable_receipt_replay_not_successful" in function assert "host112_failed_apply_cannot_be_washed_green" in read( ROOT / "agent99-synthetic-tests.ps1" ) def test_agent99_uses_correlated_sudo_dry_run_over_canonical_direct_key() -> None: control = read(CONTROL) assert "function Invoke-AgentHost112SshText" in control assert 'expectedUser = "kali"' in control assert 'route = "direct_host112_kali"' in control assert "Invoke-SshText -TargetHost $transportContract.targetHost" in control assert "--dry-run --trace-id {0} --run-id {1} --work-item-id {2}" in control assert "Invoke-AgentHost112SshText $dryRunCommand" in control assert "$dryRun.identityMatches" in control assert "$dryRun.managerPreflightPresent" in control assert "$dryRun.transportOk" in control assert "-not $dryRun.durableReceiptPresent" in control def test_unhealthy_executor_exit_one_is_delivered_readback_not_transport_failure() -> None: control = read(CONTROL) function = control[control.index("function Invoke-AgentHost112GuestRecovery") :] function = function[: function.index("function Test-HostReachability")] synthetic = read(ROOT / "agent99-synthetic-tests.ps1") assert function.count('case "$executor_exit" in 0|1) exit 0') == 3 assert "$before.executorExitCode -in @(\"0\", \"1\")" in function assert "$before.readbackPresent -and" in function assert "$before.identityMatches -and" in function assert '$beforeTransport = Invoke-AgentHost112SshText $checkCommand' in function assert 'New-Host112SyntheticRow "check" 0 0 "none" 0 "not_requested" 0 "unknown" 1' in synthetic assert "host112_general_candidate_not_blocked_by_healthy_manager" in synthetic def test_general_guest_candidate_is_not_blocked_by_manager_candidate() -> None: control = read(CONTROL) function = control[control.index("function Invoke-AgentHost112GuestRecovery") :] function = function[: function.index("function Test-HostReachability")] synthetic = read(ROOT / "agent99-synthetic-tests.ps1") assert "$generalCandidateRequired = [bool]($beforeVerified -and -not $before.generalReady)" in function assert "$managerCandidateRequired = [bool]($beforeVerified -and -not $before.managerVerified)" in function assert "$generalApplyEligible = [bool]($generalCandidateRequired -and $generalDryRunVerified)" in function assert "$managerApplyEligible = [bool]($managerCandidateRequired -and $managerDryRunVerified)" in function assert "$applyEligible = [bool]($generalApplyEligible -or $managerApplyEligible)" in function assert "managerNoAttemptPreserved" in function assert "host112_general_candidate_not_blocked_by_healthy_manager" in synthetic assert '-not $generalOnly.managerCandidateRequired' in synthetic assert '[string]$generalOnly.applyReceipt.managerAttemptCount -eq "0"' in synthetic assert '-not $generalOnly.applyReceipt.managerRuntimeWritePerformed' in synthetic def test_deploy_and_bootstrap_canonicalize_host112_direct_transport() -> None: deploy = read(ROOT / "agent99-deploy.ps1") bootstrap = read(BOOTSTRAP) generic_config = read(ROOT / "agent99.config.example.json") host_config = read(ROOT / "agent99.config.99.example.json") assert 'Set-AgentProperty $config.sshUsers "192.168.0.112" "kali"' in deploy assert 'name = "host112_canonical_direct_route"' in deploy assert 'Host112 canonical direct SSH config post-verifier failed.' in deploy assert "Ensure-AgentHost112CanonicalSshConfig" in bootstrap assert 'Set-AgentBootstrapProperty $config.sshUsers "192.168.0.112" "kali"' in bootstrap assert 'Host112 canonical direct SSH config bootstrap verifier failed.' in bootstrap for config in (generic_config, host_config): assert '"192.168.0.112": "kali"' in config assert '"directHosts"' in config assert '"192.168.0.112"' in config