"""Regression tests for the disk-gc report-only gate (CT 111 / tdunna / .129). WHY THIS FILE EXISTS: `disk-gc-threat-response.prose.md` defined AMBER as "GC scheduled for next run" and its Execution loop called `gc-executor` for EVERY threat, with no guest-level exclusion. CT 111 (tdunna, 192.168.68.129) belongs to Theo and is report-only per the captain (2026-08-17, re-confirmed 2026-09-10) — so a single AMBER reading on that guest would have scheduled GC commands (apt clean, journal vacuum, log/tmp deletion, snap removal) against someone else's box. The only marker was frontmatter `report_only_agents`, which names an AGENT while the scan unit is a GUEST. These tests execute the real planner (`scripts/disk-gc-plan.py`) and assert observable behaviour: an excluded guest never produces a `gc-executor` action at any level, while our own guests still do. """ from __future__ import annotations import json import pathlib import subprocess import sys ROOT = pathlib.Path(__file__).resolve().parent.parent PLAN = ROOT / "scripts" / "disk-gc-plan.py" def _plan(scan, tmp_path): scan_file = tmp_path / "scan.json" scan_file.write_text(json.dumps(scan)) proc = subprocess.run( [sys.executable, str(PLAN), "--scan", str(scan_file), "--json"], capture_output=True, text=True, ) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) def _actions_for(plan, target): return [row for row in plan if str(row["target"]) == str(target)] def test_excluded_guest_never_gets_gc_at_any_level(tmp_path): """CT 111 at AMBER, RED and CRITICAL — always report-only, never gc-executor.""" for pct, level in ((84, "AMBER"), (90, "RED"), (97, "CRITICAL")): plan = _plan([{"id": 111, "hostname": "tdunna", "ip": "192.168.68.129", "usage_pct": pct}], tmp_path) rows = _actions_for(plan, 111) assert rows, f"CT 111 must still be reported at {level}" assert rows[0]["level"] == level assert rows[0]["action"] == "report-only", rows assert not any(r["action"] == "gc-executor" for r in rows) def test_exclusion_matches_on_any_identity_key(tmp_path): """The gate is keyed on guest/host, so id, hostname or IP all match.""" for entry in ({"id": 111, "usage_pct": 95}, {"hostname": "tdunna", "usage_pct": 95}, {"ip": "192.168.68.129", "usage_pct": 95}): plan = _plan([entry], tmp_path) assert all(r["action"] == "report-only" for r in plan), (entry, plan) def test_our_own_guests_still_get_gc(tmp_path): """acerpve .9 and amdpve .15 are ours — they must still be acted on.""" plan = _plan([{"hostname": "acerpve", "ip": "192.168.68.9", "usage_pct": 77}, {"hostname": "amdpve", "ip": "192.168.68.15", "usage_pct": 76}], tmp_path) assert len(plan) == 2 assert all(r["action"] == "gc-executor" for r in plan), plan def test_below_threshold_emits_nothing(tmp_path): """GREEN guests produce no action at all.""" assert _plan([{"id": 111, "usage_pct": 40}], tmp_path) == [] def test_agent_name_alone_does_not_gate_a_guest(tmp_path): """An agent-name marker must not be the gate: an unrelated guest still gets GC.""" plan = _plan([{"id": 999, "hostname": "koby", "usage_pct": 95}], tmp_path) assert plan and plan[0]["action"] == "gc-executor" def test_contract_carries_the_guest_keyed_exclusion(tmp_path): """The authoritative gate must exist and identify CT 111 by guest/host.""" plan = _plan([{"id": 111, "usage_pct": 95}], tmp_path) reason = _actions_for(plan, 111)[0]["reason"] assert reason, "the exclusion must carry a reason for the alert" contract = (ROOT / "disk-gc-threat-response.prose.md").read_text() assert "report_only_guests:" in contract assert "192.168.68.129" in contract assert "tdunna" in contract