diff --git a/controller/scripts/test_gate_decoys.py b/controller/scripts/test_gate_decoys.py index 76bb82c..257b3b5 100644 --- a/controller/scripts/test_gate_decoys.py +++ b/controller/scripts/test_gate_decoys.py @@ -52,6 +52,14 @@ COVERS = { "gofmt": "R-454: a planted unformatted .go file in internal/ is convicted; the clean tree passes", "offbox-rename": "R-425: NAS branding in a NEW backups*.html, and in a bundle value an offbox Go file " "names; control: the same token in the network-storage feature's copy is accepted", + "docker-v": "R-426: an unallowlisted `-v /etc:/x` in a NEW .go file two directories down " + "(internal/ and cmd/) convicts; controls: the clean tree, the same line in a _test.go", + "reuse-refs": "R-426: a cited .go and a cited .md path that do not exist, planted in THIS repo's " + "REUSE.md - vs the real file", + "instructions": "R-426: a component version literal in THIS repo's CLAUDE.md effective text - vs the " + "same sentence inside an HTML comment", + "observations": "R-426: R-419 in THIS repo's REPORT.md - an Observations note SAYING it carries no " + "marker - vs the two genuine markers", } fails = [] @@ -344,6 +352,136 @@ if os.path.isdir(os.path.dirname(EV)): else: print(" -- %-20s SKIPPED: no felhom.eu sibling clone" % "golden-notice") +# --- docker-v (R-426): a NEW file with an unreviewed host-path mount, anywhere under the roots ------ +def _plant_go(rel, body): + path = os.path.join(CTRL, rel) + made = [] + d = os.path.dirname(path) + while not os.path.isdir(d): + made.append(d) + d = os.path.dirname(d) + for m in reversed(made): + os.mkdir(m) + io.open(path, "w", encoding="utf-8").write(body) + return path, made + + +def _unplant(path, made): + os.remove(path) + for m in made: + os.rmdir(m) + + +DOCKER_V_LINE = u'package decoy\n\nvar args = []string{"run", "-v", "/etc:/x", "alpine"}\n' +for _label, _rel, _expect in ( + ("docker-v/new-internal-file", os.path.join("internal", "decoydockerv", "deep", "decoy.go"), "convict"), + ("docker-v/new-cmd-file", os.path.join("cmd", "decoydockerv", "decoy.go"), "convict"), + ("docker-v/_test.go (CONTROL)", os.path.join("internal", "decoydockerv", "decoy_test.go"), "accept")): + ran += 1 + _p, _made = _plant_go(_rel, DOCKER_V_LINE) + try: + _rc, _out = gate("docker_run_volume_path_gate.py") + finally: + _unplant(_p, _made) + if (_rc != 0) != (_expect == "convict") or (_expect == "convict" and "/etc:/x" not in _out): + fails.append("%s: rc=%d, expected %s\n%s" % (_label, _rc, _expect, _out[-400:])) + else: + print(" ok %-30s %s" % (_label, "decoy rejected" if _expect == "convict" else "genuine accepted")) +ran += 1 +_rc, _out = gate("docker_run_volume_path_gate.py") +if _rc != 0: + fails.append("docker-v/clean tree (CONTROL): rc=%d\n%s" % (_rc, _out[-400:])) +else: + print(" ok %-30s %s" % ("docker-v/clean tree (CONTROL)", "genuine accepted")) + + +# --- the SHARED felhom.eu gates (R-426), against THIS repo's inputs -------------------------------- +# A scratch WORKSPACE: a clone of this repo, named as the main clone, beside symlinks to the siblings, +# because the shared scripts reach across (REUSE.md cites felhom.eu paths; instructions_gate reads the +# workspace CLAUDE.md). The plants go into the clone, never into this tree. Mirrors felhom-agent b78a0ff. +# DECOY_SHARED_DIR exists for ONE purpose, the red-proof: it lets a mutated COPY of the shared scripts be +# judged without editing the felhom.eu clone. Unset, the suite judges the real shared scripts. +import shutil +import tempfile + +REPO = os.path.dirname(CTRL) +PARENT = os.path.dirname(REPO) +SHARED = os.environ.get("DECOY_SHARED_DIR") or os.path.join(PARENT, "felhom.eu", "scripts") + + +def _run(argv, cwd): + p = subprocess.run(argv, cwd=cwd, capture_output=True, text=True, input="") + return p.returncode, p.stdout + p.stderr + + +def shared_cases(): + global ran + for g in ("reuse_refs_check.py", "instructions_gate.py", "observations_gate.py"): + if not os.path.isfile(os.path.join(SHARED, g)): + fails.append("shared gate %s is MISSING beside this clone (tried %s) - a failure, never a skip" + % (g, SHARED)) + return + ws = tempfile.mkdtemp(prefix="ctrl-decoy-shared-") + try: + space = os.path.join(ws, "workspace") + os.makedirs(space) + for entry in sorted(os.listdir(PARENT)): + if entry in ("felhom.eu", "felhom-agent", "app-catalog-felhom.eu", "homelab-manifests", + "CLAUDE.md", ".claude-memory"): + os.symlink(os.path.join(PARENT, entry), os.path.join(space, entry)) + repo = os.path.join(space, "felhom-controller") + rc, out = _run(["git", "clone", "-q", "--no-tags", "file://" + REPO, repo], ws) + if rc != 0: + fails.append("shared: could not clone this repo into the scratch workspace\n" + out[-400:]) + return + # the WORKING-TREE inputs the plants go into, so a case judges today's file + for f in ("REUSE.md", "CLAUDE.md", "REPORT.md"): + shutil.copy(os.path.join(REPO, f), os.path.join(repo, f)) + + def case(name, script, relpath, extra, expect_rc, must=()): + global ran + ran += 1 + p = os.path.join(repo, relpath) + backup = io.open(p, encoding="utf-8").read() + try: + if extra: + io.open(p, "w", encoding="utf-8").write(backup + extra) + rc, out = _run([sys.executable, os.path.join(SHARED, script), repo], repo) + finally: + io.open(p, "w", encoding="utf-8").write(backup) + missing = [m for m in must if m not in out] + if rc == expect_rc and not missing: + print(" ok %-62s rc=%d" % (name, rc)) + else: + hole = " - LIVE HOLE" if expect_rc != 0 and rc == 0 else "" + fails.append("%s: rc=%d expected %d%s; missing %s\n%s" % (name, rc, expect_rc, hole, missing, + out[-900:])) + + case("reuse-refs: GENUINE: this repo's REUSE.md", "reuse_refs_check.py", "REUSE.md", "", 0, ("FAILED 0",)) + case("reuse-refs: FACT: a cited .go path that does not exist", "reuse_refs_check.py", "REUSE.md", + u"\n- see `controller/internal/web/does_not_exist.go`\n", 1, ("does_not_exist.go",)) + case("reuse-refs: FACT: a cited .md path that does not exist", "reuse_refs_check.py", "REUSE.md", + u"\n- see `docs/99-does-not-exist.md`\n", 1, ("99-does-not-exist.md",)) + case("instructions: GENUINE: this repo's CLAUDE.md", "instructions_gate.py", "CLAUDE.md", "", 0, + ("instructions_gate: OK",)) + case("instructions: FACT: a version literal in effective text", "instructions_gate.py", "CLAUDE.md", + u"\nThe controller runs v0.298.0 today.\n", 1, ("v0.298.0",)) + case("instructions: GENUINE: the same sentence in an HTML comment", "instructions_gate.py", "CLAUDE.md", + u"\n\n", 0, ("instructions_gate: OK",)) + case("observations: FACT: R-419, prose SAYING it has no marker", "observations_gate.py", "REPORT.md", + u"\n## Observations\n\n1. **A real finding.** It carries no `FILED:` marker and no " + u"`NOT-A-FINDING:` marker, deliberately.\n", 1) + case("observations: GENUINE: a FILED marker", "observations_gate.py", "REPORT.md", + u"\n## Observations\n\n1. **A real finding.** Something broke. **FILED: R-419**\n", 0) + case("observations: GENUINE: a NOT-A-FINDING marker", "observations_gate.py", "REPORT.md", + u"\n## Observations\n\n1. **A real finding.** Odd. **NOT-A-FINDING: my own typo, corrected in " + u"the same minute.**\n", 0) + finally: + shutil.rmtree(ws, ignore_errors=True) + + +shared_cases() + print() if fails: for f in fails: