#!/usr/bin/env python3 # -*- coding: utf-8 -*- """test_gate_decoys.py — can this gate be fooled by a LABEL? (R-421) WHY THIS FILE EXISTS. Four times in one week a gate turned out to be matching a name instead of the thing it named — R-410 (a `mkdir` turned the release gate green), R-400 (seven debug controls that answered nothing), R-378 (a status word inside a sentence), R-419 (a phrase inside prose, including prose saying the marker was absent). **All four were found by accident.** The gates are the machinery that enforces everything else in this project, and they were the one part nothing checked. A DECOY IS THE LABEL WITHOUT THE FACT. Each test below constructs one, runs the real gate, and asserts it CONVICTS. Where a decoy would pass, that is a live hole. ⚠ A DECOY MUST BE THE SHAPE A REAL SESSION WOULD PRODUCE. R-419 was not found by an absurd input — it was found by a genuine note explaining that it carried no marker. That is the standard. A decoy nobody would ever write proves nothing, and saying so is a result. ⚠ EVERY TEST ASSERTS BOTH DIRECTIONS where it can. A gate that rejects the decoy AND rejects the genuine article is worse than the hole it replaced. Fixtures are planted in the real tree and removed in a `finally`. The suite asserts the tree is unchanged at the end. Run from the repo root: python3 scripts/test_gate_decoys.py Exit 0 all decoys rejected · 1 a decoy passed (a live hole). """ import io import json import os import shutil import subprocess import sys ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # ── WHAT THIS FILE COVERS ──────────────────────────────────────────────────────────────────────── # Read by scripts/decoy_coverage_gate.py, which AST-parses this literal rather than grepping for # gate names — a substring search for coverage would be the very shape this sweep exists to find. # A gate named here MUST have a decoy below that has been seen to fail. COVERS = { "hub-confirm": "a native confirm() in templates/partials/ (scope was os.listdir)", "manifest-bearer": "a bearer literal in manifests/overlays/ (scope was os.listdir)", "observations": "R-419: prose SAYING it carries no marker, plus both genuine markers", "reuse-refs": "a cited .go path that does not exist; the .md hole is asserted as R-422", "golden-currency": "R-410: an empty directory with a perfect name, checked by what it COUNTED", "closed-register": ("a verdict cell reading open, a row with no state cell at all, and (RULE 3, " "2026-10-03) a FINISHED row left in OPEN-ITEMS.md — with R-87's shape, a READY " "row whose prose says 'closed' about another row, asserted to PASS"), "register-shape": ("R-627: a row whose state cell was EATEN (no trailing pipe), a DUPLICATED " "id, and a blank line splitting the table — each the exact shape the " "2026-09-21 append produced; plus the genuine article, which must pass; and " "(2026-10-03) an old-shape row under the new header, a near-miss category, an " "old rank tag as Sev, an undefined state word, and a pipe outside backticks"), "decoy-coverage": "a gate registered in a runner with no decoy and no exemption (its red-proof)", "guide-quote": ("R-596: SEVEN cases in scripts/test_guide_quote_gate.py, run from here so " "this suite stays the single entry point. The load-bearing one is " "name-for-fact: a guide that lists every key in a table and quotes none of " "their sentences. Also: a prefix of the real sentence, the OLD Hungarian " "quote, a reworded bundle, a deleted key, and the scope case — an absent " "controller clone must be INCONCLUSIVE, never a pass"), "hub-copy": ("R-558: an English retrieval promise in the NEW bundle (the sentences moved " "out of templates.go, so the surface list had to move with them), the same " "in Hungarian, and an INNOCENT control using the identical verbs without the " "capability claim — the control is the half that matters, because the first " "stem list convicted 141 honest sentences"), } fails = [] ran = 0 def gate(script, args=()): p = subprocess.run([sys.executable, os.path.join("scripts", script)] + list(args), cwd=ROOT, capture_output=True, text=True) return p.returncode, p.stdout + p.stderr def decoy(name, script, plant, args=(), expect="convict"): """plant() is a callable returning a cleanup callable.""" global ran ran += 1 cleanup = plant() try: rc, out = gate(script, args) finally: cleanup() want_nonzero = (expect == "convict") if (rc != 0) != want_nonzero: fails.append("%s: decoy %s (rc=%d)\n%s" % (name, "PASSED - LIVE HOLE" if want_nonzero else "was wrongly convicted", rc, out[-700:])) else: print(" ok %-20s %s" % (name, "decoy rejected" if want_nonzero else "genuine accepted")) def plant_file(path, content): def _plant(): made = [] d = os.path.dirname(path) if d and not os.path.isdir(d): os.makedirs(d) made.append(d) io.open(path, "w", encoding="utf-8").write(content) def _clean(): if os.path.exists(path): os.remove(path) for m in reversed(made): if os.path.isdir(m) and not os.listdir(m): os.rmdir(m) return _clean return _plant def append_to(path, extra): def _plant(): backup = io.open(path, encoding="utf-8").read() io.open(path, "w", encoding="utf-8").write(backup + extra) return lambda: io.open(path, "w", encoding="utf-8").write(backup) return _plant T = os.path.join(ROOT, "hub", "internal", "web", "templates") M = os.path.join(ROOT, "manifests") REP = os.path.join(ROOT, "REPORT.md") print("decoys — felhom.eu") # --- hub-confirm: a native confirm() one directory down (R-421) ------------------------------- # Shape 1, name-for-fact: the gate used os.listdir, so its SCOPE was a directory listing rather # than the set of templates. There are no subdirectories today; adding templates/partials/ is an # ordinary act and the gate would have stayed green. decoy("hub-confirm/subdir", "hub_confirm_gate.py", plant_file(os.path.join(T, "partials", "decoy.html"), u'\n')) # --- manifest-bearer: a bearer literal one directory down -------------------------------------- decoy("manifest-bearer/subdir", "manifest_bearer_gate.py", plant_file(os.path.join(M, "overlays", "decoy.yaml"), u"apiVersion: v1\ndata:\n token: %s\n" % ("a1b2c3d4" * 8))) # --- observations: R-419 itself, and the genuine markers beside it ----------------------------- # Shape 2, substring-for-field. THE decoy that found this class: an honest note SAYING it has no # marker satisfied the marker test. decoy("observations/R-419", "observations_gate.py", append_to(REP, u"\n## Observations\n\n1. **A real finding.** It carries no `FILED:` marker " u"and no `NOT-A-FINDING:` marker, deliberately.\n"), args=(ROOT,)) decoy("observations/genuine-FILED", "observations_gate.py", append_to(REP, u"\n## Observations\n\n1. **A real finding.** Something broke. " u"**FILED: R-419**\n"), args=(ROOT,), expect="accept") decoy("observations/genuine-NAF", "observations_gate.py", append_to(REP, u"\n## Observations\n\n1. **A real finding.** Odd. **NOT-A-FINDING: my own " u"typo, corrected in the same minute.**\n"), args=(ROOT,), expect="accept") # --- reuse-refs: a cited path that does not exist ---------------------------------------------- # The .go case is REJECTED. The .md case is a KNOWN HOLE (R-422) and is asserted as such below, so # this file records the hole rather than pretending it is covered. decoy("reuse-refs/missing-go", "reuse_refs_check.py", append_to(os.path.join(ROOT, "REUSE.md"), u"\n- see `hub/internal/api/does_not_exist.go`\n"), args=(ROOT,)) # --- KNOWN HOLE, asserted so it cannot be forgotten (R-422) ------------------------------------ # reuse_refs_check.py's PATH_RE matches only go|py|html|css|yml|yaml|sh. A rotted .md citation is # invisible. This asserts the CURRENT behaviour so the day it is fixed, this test fails and is # updated deliberately — a hole that nothing asserts is a hole nobody remembers. decoy("reuse-refs/missing-md (KNOWN HOLE R-422)", "reuse_refs_check.py", append_to(os.path.join(ROOT, "REUSE.md"), u"\n- see `documentation/architecture/99-does-not-exist.md`\n"), args=(ROOT,), expect="accept") # --- golden-currency: R-410's own decoy, re-run here so the sweep owns it too ------------------ def _mkdir_decoy(): d = os.path.join(ROOT, "documentation", "tests", "golden-9.9.9-2026-01-01") os.makedirs(d) return lambda: os.path.isdir(d) and os.rmdir(d) def check_golden_names_the_fake(): """golden-currency exits 0 either way when currency is fine — the QUESTION is what it counted.""" global ran ran += 1 cleanup = _mkdir_decoy() try: _rc, out = gate("golden_currency_gate.py") finally: cleanup() if "newest golden baked : 9.9.9" in out: fails.append("golden-currency: an EMPTY directory was counted as a bake — R-410 has regressed") elif "NOT counted as bakes" not in out: fails.append("golden-currency: the empty directory was neither counted nor REPORTED; a " "half-finished bake must be visible, not silently ignored") else: print(" ok %-20s empty dir rejected AND named" % "golden-currency") check_golden_names_the_fake() # --- closed-register: the verdict cell is the predicate, deliberately (R-378) ------------------- # NOT a hole: R-378's whole lesson is that an open word ANYWHERE in a row convicts rows that are # genuinely closed. This asserts the deliberate behaviour so a future "fix" has to argue with it. decoy("closed-register/body-word (BY DESIGN)", "closed_register_gate.py", append_to(os.path.join(ROOT, "documentation", "backlog", "CLOSED-ITEMS.md"), u"\n| **R-903** | Work continues and it is still READY in the body. | CLOSED 2026-09-01 | none |\n"), expect="accept") # A 4-column row (| ID | Title | Shipped | Evidence |) whose VERDICT cell reads open. decoy("closed-register/verdict-word", "closed_register_gate.py", append_to(os.path.join(ROOT, "documentation", "backlog", "CLOSED-ITEMS.md"), u"\n| **R-904** | A finished thing. | READY - still being worked on | none |\n")) # R-421: a row this gate cannot PARSE used to be a warning, and the gate then printed OK. Four rows # were in that state — two of them written by the session that closed them the day before this # sweep — so they were exempt from the only check that reads this file. An unreadable row is now a # conviction. This is the sweep's own shape one level up and it is why the decoy is kept. decoy("closed-register/unreadable-row", "closed_register_gate.py", append_to(os.path.join(ROOT, "documentation", "backlog", "CLOSED-ITEMS.md"), u"\n| **R-905** | A row with no state cell at all. |\n")) # --- closed-register RULE 3 (2026-10-03): a FINISHED row left in the OPEN register --------------- # On 2026-10-03 the open register held 113 rows whose leading verdict was finished — a quarter of the # file. The decoy is that exact shape: a row a session closed in place and never moved. The genuine # article beside it is R-87's shape from R-378: a READY row whose prose says "closed" about ANOTHER # row — it must pass, or the gate repeats the 2026-08-22 sweep's mistake one file over. # The row is built from the register's CURRENT header, so the decoy follows a column change rather # than silently testing a shape the file no longer has. _OPEN_REG = os.path.join(ROOT, "documentation", "backlog", "OPEN-ITEMS.md") def _open_row(rid, state): cols = None for line in io.open(_OPEN_REG, encoding="utf-8"): if line.startswith("| ID |"): cols = [c.strip() for c in line.strip().strip("|").split("|")] fill = {"ID": "**%s**" % rid, "Category": "Process & tooling", "Sev": "P4", "What": "**A decoy finding.**", "State": state, "Owner": "CC"} return u"\n" + u"| " + u" | ".join(fill.get(c, u"—") for c in cols) + u" |\n" decoy("closed-register/finished-row-in-open", "closed_register_gate.py", append_to(_OPEN_REG, _open_row("R-906", "**CLOSED 2026-10-03 — shipped in v9.9.9**"))) decoy("closed-register/open-row-closed-word (BY DESIGN)", "closed_register_gate.py", append_to(_OPEN_REG, _open_row("R-907", "**READY — RE-RANKED UP 2026-08-03 (R-86 closed)**")), expect="accept") # --- one-register (2026-10-03): an open ROADMAP row whose id carries a LETTER SUFFIX ------------ # The gate's id pattern was `R-(\d+)`, so R-27b, R-27c and R-50b were never read. Fixing it convicted # R-50b at once — a finding that had lived only in ROADMAP.md. This decoy is that shape. (one-register # stays in decoy_coverage_gate's EXEMPT list for R-424's hole — a finding filed under `idea` — which # this decoy does not close.) decoy("one-register/suffix-id-row", "one_register_gate.py", append_to(os.path.join(ROOT, "documentation", "backlog", "ROADMAP.md"), u"\n| R-905b | **A finding filed only here.** | S | READY — 2026-10-03 | none |\n")) # --- hub-copy: an ENGLISH retrieval promise, planted in the bundle (R-558) ----------------------- # # TWO SHAPES AT ONCE, and the second is why this decoy exists at all. # # Shape "scope is a fact": until hub v0.118.0 every customer sentence was a Go literal in # templates.go. Slice 3 moved all of them into locales/*.json. If the gate's CUSTOMER_SURFACES had # not moved with them, all four declared files would still exist, the gate would still report # success, and it would be scanning a file with no customer sentences left in it. # # Shape "half a guard": the stems were Hungarian only. A promise written in the new English bundle # would have been invisible to a gate that had just been taught to read the file it sits in. # # The planted sentence is the shape a real translator would write — the English of a claim this # product genuinely cannot keep (R-304: nothing reads a retained key, and a customer's correct old # code is reported as wrong). _EN_BUNDLE = os.path.join(ROOT, "hub", "internal", "i18n", "locales", "en.json") def _plant_en_promise(): backup = io.open(_EN_BUNDLE, encoding="utf-8").read() d = json.loads(backup) d["mail.event.offbox_repo_reset"] = ("The remote backup store has been reset. Your earlier " "backups can still be restored with your old recovery code.") io.open(_EN_BUNDLE, "w", encoding="utf-8").write(json.dumps(d, ensure_ascii=False, indent=2) + "\n") return lambda: io.open(_EN_BUNDLE, "w", encoding="utf-8").write(backup) decoy("hub-copy/en-promise", "hub_copy_gate.py", _plant_en_promise) # ...and the CONTROL, which is the half that makes the decoy mean something: an ordinary English # sentence using the very same words WITHOUT the capability claim must NOT be convicted. The first # version of these stems matched the bare verbs and convicted 141 honest sentences, including # "Disaster recovery has started" and the name of the Restore page. def _plant_en_innocent(): backup = io.open(_EN_BUNDLE, encoding="utf-8").read() d = json.loads(backup) d["mail.event.offbox_repo_reset"] = ("The remote backup store has been reset. Disaster recovery " "has finished and the Restore page is available again.") io.open(_EN_BUNDLE, "w", encoding="utf-8").write(json.dumps(d, ensure_ascii=False, indent=2) + "\n") return lambda: io.open(_EN_BUNDLE, "w", encoding="utf-8").write(backup) decoy("hub-copy/en-innocent", "hub_copy_gate.py", _plant_en_innocent, expect="accept") # And the scope half, stated as its own decoy: a HUNGARIAN promise in the bundle. Before v0.118.0 # this text lived in templates.go and was scanned; if the bundle were not a declared surface, moving # the sentence would have moved it out of reach of a gate that was already watching it. def _plant_hu_promise(): hu = os.path.join(ROOT, "hub", "internal", "i18n", "locales", "hu.json") backup = io.open(hu, encoding="utf-8").read() d = json.loads(backup) d["mail.event.offbox_repo_reset"] = ("A tárolót visszaállítottuk. A régi mentéseidet a korábbi " "helyreállítási kóddal visszaállíthatod.") io.open(hu, "w", encoding="utf-8").write(json.dumps(d, ensure_ascii=False, indent=2) + "\n") return lambda: io.open(hu, "w", encoding="utf-8").write(backup) decoy("hub-copy/hu-in-bundle", "hub_copy_gate.py", _plant_hu_promise) # --- decoy-coverage: the meta-gate's own red-proof, kept as a test -------------------------------- # It must convict a gate registered in a runner with no decoy and no exemption. Without this the # meta-gate is itself an unchecked instrument, which is the joke this whole sweep exists to avoid. # It also convicted ITSELF the moment it was registered, which is how this decoy came to be written. ran += 1 _RUNNER = os.path.join(ROOT, "scripts", "repo_gates.py") _b = io.open(_RUNNER, encoding="utf-8").read() _anchor = ' ("observations", os.path.join(SCRIPTS, "observations_gate.py"), [ROOT], True, False),' try: assert _anchor in _b, "the runner's shape changed — this decoy can no longer be built" io.open(_RUNNER, "w", encoding="utf-8").write(_b.replace( _anchor, _anchor + '\n ("decoy-red-proof", os.path.join(SCRIPTS, "nope.py"), [], True, False),', 1)) _rc, _out = gate("decoy_coverage_gate.py", (ROOT,)) finally: io.open(_RUNNER, "w", encoding="utf-8").write(_b) if _rc == 0: fails.append("decoy-coverage: a NEW gate with no decoy and no exemption was ACCEPTED — the " "meta-gate cannot see the thing it exists for\n%s" % _out[-500:]) elif "decoy-red-proof" not in _out: fails.append("decoy-coverage: it convicted, but did not NAME the uncovered gate") else: print(" ok %-20s a new gate with no decoy is convicted BY NAME" % "decoy-coverage") # ── guide-quote (R-596) ────────────────────────────────────────────────────────────────────────── # # Its decoys build whole fake workspaces (a guide plus a sibling controller clone), which does not # fit the plant/restore shape above — so they live in their own file and are RUN from here. The # decoy-coverage gate reads COVERS in this file, so this is the seam that keeps that entry honest: # if the separate suite stops passing, this one fails, and the COVERS line stops being a label. ran += 1 _gq = subprocess.run([sys.executable, os.path.join("scripts", "test_guide_quote_gate.py")], cwd=ROOT, capture_output=True, text=True) if _gq.returncode == 2: fails.append("guide-quote: its decoy suite could not run (no controller clone beside this one) — " "INCONCLUSIVE is not coverage\n%s" % (_gq.stdout + _gq.stderr)[-500:]) elif _gq.returncode != 0: fails.append("guide-quote: its decoy suite FAILED — a decoy did not convict\n%s" % (_gq.stdout + _gq.stderr)[-800:]) else: _n = _gq.stdout.strip().splitlines()[-1] if _gq.stdout.strip() else "?" print(" ok %-20s %s" % ("guide-quote", _n)) # ── register-shape (R-627) ─────────────────────────────────────────────────────────────────────── # # THE DECOYS ARE THE REAL DAMAGE, not invented shapes. On 2026-09-21 an append regex ate two rows' # state cells, left the cell behind as a stray extra cell on a DUPLICATE of another row, and put a # blank line between each pair. The register then reported 317 rows for 315 findings. Each decoy # below is one of those three shapes, written as a session would actually produce it. # # AND THE GENUINE ARTICLE IS ASSERTED TOO: a gate that rejects the damage and also rejects a healthy # register is worse than the hole it replaces. import importlib.util as _ilu _spec = _ilu.spec_from_file_location("rsg", os.path.join(ROOT, "scripts", "register_shape_gate.py")) _rsg = _ilu.module_from_spec(_spec) _spec.loader.exec_module(_rsg) _HEADER = "| ID | Category | Sev | What | State | Blocked on | Next action | Owner |" _HEALTHY = "\n".join([ _HEADER, "|---|---|---|---|---|---|---|---|", "| **R-901** | Backup & restore | P3 | **A finding.** Its text mentions `owner: CC | and a literal pipe` on purpose. | **READY — owner: CC** | — | — | CC |", "| **R-902** | Process & tooling | P4 | **Another finding.** | **CLOSED 2026-09-01** | — | — | CC |", "", ]) def _rsg_case(name, text, must_convict, expect_rule=None): global ran ran += 1 fp = os.path.join(ROOT, "scripts", ".decoy-register-%s.md" % name) try: with open(fp, "w", encoding="utf-8") as fh: fh.write(text) rows, ids, bad = _rsg.check(fp) convicted = bool(bad) if convicted != must_convict: fails.append("register-shape [%s]: expected %s, got %s (%s)" % (name, "CONVICT" if must_convict else "PASS", "CONVICT" if convicted else "PASS", "; ".join(w for _, _, w in bad)[:200] or "no findings")) elif expect_rule and not any(expect_rule in w for _, _, w in bad): fails.append("register-shape [%s]: convicted, but not on %s — on %s" % (name, expect_rule, "; ".join(w.split(" — ")[0] for _, _, w in bad))) else: print(" ok %-20s %s" % ("register-shape", name)) finally: if os.path.exists(fp): os.remove(fp) # the genuine article must PASS — including a row carrying a literal `|` in its prose, which is why # there is no cell-count rule _rsg_case("genuine", _HEALTHY, must_convict=False) # decoy 1 — the state cell eaten (R-446 / R-458 / R-254's shape) _rsg_case("eaten-state-cell", _HEALTHY.replace("| **CLOSED 2026-09-01** |", "").rstrip().rstrip("|").rstrip() + "\n", must_convict=True, expect_rule="RULE 1") # decoy 2 — the same finding filed twice (R-625 / R-626's shape) _rsg_case("duplicate-id", _HEALTHY.rstrip() + "\n| **R-901** | **The same finding again.** | **READY** |\n", must_convict=True, expect_rule="RULE 3") # decoy 3 — a blank line splitting the table _rsg_case("blank-splits-table", _HEALTHY.replace("| **R-902**", "\n| **R-902**"), must_convict=True, expect_rule="RULE 4") # decoy 4 (2026-09-30) — a row whose id carries a LETTER SUFFIX (R-88a, R-88b, R-209a are real) with its state cell # eaten: the old `R-\d+` pattern did not read it as a row at all, so it passed unchecked _rsg_case("suffix-row-eaten-state", _HEALTHY.rstrip() + "\n| **R-903a** | **A split finding.** |\n".replace(" |\n", "\n"), must_convict=True, expect_rule="RULE 1") # 2026-10-03 — RULES 5–8: the columns, the category, the severity and the state word. Each decoy is a # shape a session filing a row by hand would really produce. _R901 = "| **R-901** | Backup & restore | P3 |" # decoy 5 — a row filed with no category (the old 3-column habit, under the new header) _rsg_case("old-shape-row-under-new-header", _HEALTHY.rstrip() + "\n| **R-903** | **A finding filed the old way.** | **READY** |\n", must_convict=True, expect_rule="RULE 5") # decoy 6 — a near-miss category (the brief's own short name, not the defined one) _rsg_case("unknown-category", _HEALTHY.replace(_R901, "| **R-901** | Backup | P3 |"), must_convict=True, expect_rule="RULE 6") # decoy 7 — an old rank tag in the Sev cell instead of P1–P4 _rsg_case("old-rank-tag-as-sev", _HEALTHY.replace(_R901, "| **R-901** | Backup & restore | P3-LOW |"), must_convict=True, expect_rule="RULE 7") # decoy 8 — a state word nobody defined (one of the 18 real ones the triage found) _rsg_case("undefined-state-word", _HEALTHY.replace("**READY — owner: CC**", "**PLUMBING COMPLETE** (controller v0.196.0)"), must_convict=True, expect_rule="RULE 8") # decoy 9 — a literal pipe in prose OUTSIDE backticks, which renders as an extra column _rsg_case("pipe-outside-backticks", _HEALTHY.replace("**A finding.**", "**A finding.** owner: CC | operator"), must_convict=True, expect_rule="RULE 5") print() if fails: for f in fails: print("FAIL: %s" % f) print("\n%d decoy(s) of %d exposed a hole" % (len(fails), ran)) sys.exit(1) print("all %d felhom.eu decoys behaved — labels do not satisfy these gates" % ran)