#!/usr/bin/env python3 # -*- coding: utf-8 -*- """host_swap_rule_gate.py — the hub and the box judge „too little host swap" by ONE rule (R-1216, D28 = A). Run from the repo root: python3 scripts/host_swap_rule_gate.py Exit 0 clean · 1 convicted · 2 inconclusive (the felhom-controller clone is not beside this one — never a silent pass). WHY. The rule lives twice: the hub raises `host_swap_short` for the operator (hub/internal/api/host_swap.go), and the controller's install page tells the household „A gépen nincs elég cserehely (swap)…" (internal/web/ system_memory_handlers.go). Two Go modules cannot share a function, so they share a CASE TABLE: each repo carries `testdata/host_swap_rule_cases.json` and a Go test that runs its own implementation over every row. This gate is the contract between them: 1. the two tables are BYTE-IDENTICAL (a row changed on one side only is drift); 2. each table is READ by a Go test in its own package (a table no test opens proves nothing — the label without the fact: the file name present, the run absent). The decoys are in scripts/test_host_swap_rule_gate.py, run from scripts/test_gate_decoys.py. """ from __future__ import annotations import glob import io import os import sys HERE = os.path.dirname(os.path.abspath(__file__)) REPO = os.environ.get("HOST_SWAP_RULE_REPO") or os.path.dirname(HERE) WORKSPACE = os.path.dirname(REPO) NAME = "host_swap_rule_cases.json" HUB_DIR = os.path.join(REPO, "hub", "internal", "api") CTRL_DIR = os.path.join(WORKSPACE, "felhom-controller", "controller", "internal", "web") def read_by_a_test(pkg_dir: str) -> bool: for f in glob.glob(os.path.join(pkg_dir, "*_test.go")): if NAME in io.open(f, encoding="utf-8").read(): return True return False def main() -> int: hub = os.path.join(HUB_DIR, "testdata", NAME) ctrl = os.path.join(CTRL_DIR, "testdata", NAME) if not os.path.exists(hub): print("host-swap-rule: CONVICTS — the hub's case table is gone (%s)" % os.path.relpath(hub, REPO)) return 1 if not os.path.isdir(os.path.join(WORKSPACE, "felhom-controller")): print("host-swap-rule: INCONCLUSIVE — no felhom-controller clone beside this one; the contract cannot be read", file=sys.stderr) return 2 problems = [] if not os.path.exists(ctrl): problems.append("the controller's case table is gone (felhom-controller/controller/internal/web/testdata/%s)" % NAME) else: a, b = open(hub, "rb").read(), open(ctrl, "rb").read() if a != b: problems.append("the two case tables differ (%d vs %d bytes) — the hub and the box no longer judge host " "swap by the same rule" % (len(a), len(b))) for label, d in (("hub", HUB_DIR), ("controller", CTRL_DIR)): if not read_by_a_test(d): problems.append("no Go test in the %s package reads %s — a table nothing runs proves nothing" % (label, NAME)) print("host-swap-rule: compared the hub's and the controller's %s, and looked for the test that runs each" % NAME) if problems: print("host-swap-rule gate CONVICTS (%d):" % len(problems)) for p in problems: print(" " + p) return 1 print("host-swap-rule gate OK: one rule, byte-identical tables, both run by a test.") return 0 if __name__ == "__main__": sys.exit(main())