Files
felhom.eu/scripts/decoy_coverage_gate.py
T
admin 574f5df107
gates / gates (push) Failing after 17s
the decoy sweep: 29 gates read, 16 fooled, 10 fixed - and a gate that refuses the next one (R-421)
THE CLASS, now a row: an instrument that matches a LABEL rather than the fact it names. Five
instances - R-410, R-400, R-378, R-419, R-94 - and EVERY ONE was found by accident, by someone
looking at something else. The gates enforce every other rule in this project, including the rule
that findings must be written down rather than left in prose. Nothing had ever checked the gates.

METHOD, and it is the transferable part: for each gate, construct the label WITHOUT the fact - a
directory with the right name and no bake log, a handler case that exists only in a comment, a note
whose prose mentions the marker it lacks - run the gate, record what it says. No verdict was reached
by reading. Reading is how all five hid.

RESULT: 29 distinct scripts (35 registrations; three are shared across three runners). 19 sound, 4
holes left OPEN with rows, 6 that no plausible decoy could be built for and are named UNTESTED rather
than called sound. A gate nobody tried to fool is UNKNOWN.

SCOPE IS A FACT TOO - the largest single cause, and mundane. Eight gates decided what to look at with
os.listdir, one level. Every one was green AND CORRECT today, and every one would have gone blind the
moment anyone added a subdirectory. mojibake and docker-v already used os.walk, caught the identical
planted file, and are the control that proves the cause was the listing and not the decoy.

IN THIS REPO: hub-confirm and manifest-bearer now walk. observations_gate (R-419, CLOSED) requires a
marker at a line start or after a sentence boundary and strips inline code spans - a note SAYING it
carries no marker no longer satisfies the marker test. closed-register now CONVICTS on a row it
cannot parse instead of warning: FOUR rows were in that state, TWO of them written by the session
that closed them the day before, and every one was exempt from the only check that reads that file.
The rows were repaired first and the conviction added second - registering a failing gate refuses
every push.

THE META-GATE: decoy_coverage_gate.py refuses a gate registered without a decoy or a named exemption.
It convicted ITSELF the moment it was registered, which is how it came to have one. Coverage is a
DECLARATION the gate AST-parses, never a grep - searching a test file for a gate's name would be the
very shape this sweep exists to find. The 20 uncovered gates are listed by name (R-426).

NOT FIXED, each with a row and a decoy asserting TODAY's behaviour so the fix must be deliberate:
R-422 reuse-refs (only 7 extensions; a rotted .md citation is invisible), R-423 site (PAGES is a
hardcoded list of 7), R-424 one-register (a defect parked as `idea`), R-425 offbox-rename (fixed
FILES list). R-427: closed_register_gate checks ONE direction - twelve open rows carry a closed
verdict and were NOT moved, because telling finished from partly-finished is a judgement and R-378
is the record of a machine getting it wrong.

FIVE DECOYS WITHDRAWN AS ILLEGITIMATE, mine, named in the audit. A decoy nobody would write proves
nothing, and manufacturing a finding to fill a row is worse than an honest NO.

No product code. No version bump. No image. No golden owed. All four runners green.
Register: OPEN 172 -> 178, CLOSED 160 -> 161.
2026-09-01 12:39:45 +02:00

221 lines
11 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""decoy_coverage_gate.py — every registered gate ships with a decoy test (R-421).
Usage: python3 scripts/decoy_coverage_gate.py <repo-root> [<repo-root> ...]
Exit 0 covered-or-registered · 1 a gate has neither a decoy nor an exemption · 2 inconclusive.
WHY THIS 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 answering
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 here, and they were the one part nothing checked.** The 2026-09-01 sweep
read all 29 and fooled 16 of them.
A survey fixes what it finds once. This makes the next one impossible to add quietly: **a NEW gate
with no decoy fails immediately**, and the gates not yet covered are listed BY NAME below, each with
its row, so the remaining debt is visible and shrinking rather than forgotten in a Python literal.
R-329's shape, deliberately, because this project already trusts it: walk everything, register the
exceptions by name, and let a new one fail rather than slip through.
⚠ COVERAGE IS A DECLARATION, NOT A GREP. Each decoy suite exports `COVERS = {gate: why}` and this
gate AST-parses that literal. Searching the test file for a gate's name would be exactly the
substring-for-fact shape this whole sweep exists to find — the gate that checks for label-matching
must not itself match on a label.
"""
import ast
import io
import os
import sys
# ── EXEMPTIONS — each carries its row and one line saying why it is not covered YET ─────────────
# Dated 2026-09-01. This list is DEBT, not a settled state: R-426 owns it and names every entry.
EXEMPT = {
# felhom.eu
("felhom.eu", "site"):
"R-423 — PAGES is a hardcoded list of 7 files; a new page is unscanned. The decoy passes "
"TODAY, so a test asserting rejection would be a lie. Fix is to glob website/*.html.",
("felhom.eu", "one-register"):
"R-424 — a real defect parked under state `idea` is invisible. Declared in the gate's own "
"docstring as residual hole 1; the decoy passes today.",
("felhom.eu", "hostinstall"):
"R-426 — no plausible decoy constructed yet. It asserts installer invariants against a "
"shell script; a legitimate decoy needs a shape a real edit would produce.",
("felhom.eu", "wire-contract"):
"R-426 — 607 lines comparing emitted fields to receiver structs across two repos; a decoy "
"needs a Go edit, which this sweep was forbidden from making.",
("felhom.eu", "hub-copy"):
"COVERED IN THE SWEEP, not yet in a suite: a retired name planted in a NEW hub template was "
"REJECTED (it uses os.walk). R-426 tracks moving that decoy into the suite.",
("felhom.eu", "due-checks"):
"R-426 — no legitimate decoy constructed; the attempt in the sweep was a no-op and its "
"verdict was withdrawn rather than reported.",
("felhom.eu", "instructions"):
"COVERED IN THE SWEEP, not yet in a suite: a version literal in effective text was REJECTED. "
"R-426 tracks moving it in.",
# felhom-controller
("felhom-controller", "docker-v"):
"COVERED IN THE SWEEP, not yet in a suite: an unallowlisted `-v` host path in a new Go file "
"was REJECTED (it uses os.walk). R-426 tracks moving it in.",
("felhom-controller", "offbox-rename"):
"R-425 — the FILES list is fixed, so banned branding in a NEW offbox file is unscanned. The "
"decoy passes today.",
("felhom-controller", "reuse-refs"):
"shared script; its decoy lives in felhom.eu/scripts/test_gate_decoys.py.",
("felhom-controller", "instructions"):
"shared script; see felhom.eu. R-426.",
("felhom-controller", "observations"):
"shared script; its decoy (R-419) lives in felhom.eu/scripts/test_gate_decoys.py.",
# felhom-agent — no decoy suite yet
("felhom-agent", "reuse-refs"): "shared script; decoy in felhom.eu. R-426.",
("felhom-agent", "instructions"): "shared script; decoy in felhom.eu. R-426.",
("felhom-agent", "observations"): "shared script; decoy in felhom.eu. R-426.",
("felhom-agent", "published"):
"R-426 — a network gate; a decoy needs a fake registry, which this sweep did not build.",
("felhom-agent", "release-complete"):
"R-426 — the sweep's decoy (a non-version heading on top of CHANGELOG.md) was WITHDRAWN: "
"HEAD_RE.search scans the whole file, so the real release is still found and named. The "
"gate looked sound; no legitimate decoy has been constructed yet.",
# app-catalog
("app-catalog-felhom.eu", "image-pins"):
"COVERED IN THE SWEEP: a real untagged `image:` line was REJECTED, and the `x-image:` decoy "
"was WITHDRAWN as illegitimate (x- fields are inert in Compose). R-426 tracks a suite.",
("app-catalog-felhom.eu", "image-resolvable"):
"R-426 — needs a container runtime and the network; not a --fast gate.",
("app-catalog-felhom.eu", "volume-persistence"):
"R-426 — 877 lines that actually run containers and diff them; not a --fast gate.",
}
RUNNERS = {
"felhom.eu": os.path.join("scripts", "repo_gates.py"),
"felhom-controller": os.path.join("controller", "scripts", "controller_gates.py"),
"felhom-agent": os.path.join("scripts", "agent_gates.py"),
"app-catalog-felhom.eu": os.path.join("scripts", "catalog_gates.py"),
}
SUITES = {
"felhom.eu": os.path.join("scripts", "test_gate_decoys.py"),
"felhom-controller": os.path.join("controller", "scripts", "test_gate_decoys.py"),
"felhom-agent": os.path.join("scripts", "test_gate_decoys.py"),
"app-catalog-felhom.eu": os.path.join("scripts", "test_gate_decoys.py"),
}
def literal_from(path, name):
"""AST-parse a module-level literal assignment. Never imports — importing a test RUNS it."""
if not os.path.isfile(path):
return None
try:
tree = ast.parse(io.open(path, encoding="utf-8").read())
except SyntaxError as e:
return ("ERROR", "%s does not parse: %s" % (path, e))
for node in tree.body:
if isinstance(node, ast.Assign):
for t in node.targets:
if isinstance(t, ast.Name) and t.id == name:
try:
return ast.literal_eval(node.value)
except Exception:
return ("ERROR", "%s in %s is not a literal" % (name, path))
return None
def gates_of(runner_path):
"""The GATES table's LABELS, AST-read — the runner is never imported and never literal_eval'd.
Only the label is taken, deliberately. Three of the four runners build their script paths with
`os.path.join(...)`, so the table as a whole is not a literal — but every label is a plain string
constant, which is all this gate needs. Evaluating the whole row would make this gate fail on
three repos for a reason that has nothing to do with coverage.
"""
if not os.path.isfile(runner_path):
return None
try:
tree = ast.parse(io.open(runner_path, encoding="utf-8").read())
except SyntaxError:
return None
for node in tree.body:
if not isinstance(node, ast.Assign):
continue
if not any(isinstance(t, ast.Name) and t.id == "GATES" for t in node.targets):
continue
if not isinstance(node.value, (ast.List, ast.Tuple)):
return None
out = []
for row in node.value.elts:
if isinstance(row, (ast.Tuple, ast.List)) and row.elts:
first = row.elts[0]
if isinstance(first, ast.Constant) and isinstance(first.value, str):
out.append(first.value)
return out
return None
def main(argv):
roots = argv[1:] or ["."]
problems, inconclusive, lines = [], [], []
total = covered = exempt = 0
for root in roots:
root = os.path.abspath(root)
name = os.path.basename(root)
if name not in RUNNERS:
inconclusive.append("unknown repo %r — no runner registered for it" % name)
continue
runner = os.path.join(root, RUNNERS[name])
if not os.path.isfile(runner):
inconclusive.append("%s: runner not found at %s" % (name, runner))
continue
labels = gates_of(runner)
if labels is None:
inconclusive.append("%s: could not read the GATES table from %s" % (name, runner))
continue
covers = literal_from(os.path.join(root, SUITES[name]), "COVERS") or {}
if isinstance(covers, tuple):
inconclusive.append("%s: %s" % (name, covers[1]))
covers = {}
for label in labels:
total += 1
if label in covers:
covered += 1
lines.append(" COVERED %-22s %-20s %s" % (name, label, covers[label][:60]))
elif (name, label) in EXEMPT:
exempt += 1
lines.append(" exempt %-22s %-20s %s" % (name, label, EXEMPT[(name, label)][:60]))
else:
problems.append((name, label))
print("decoy-coverage gate — %d registered gate(s): %d with a decoy, %d registered exempt, "
"%d UNACCOUNTED" % (total, covered, exempt, len(problems)))
for l in sorted(lines):
print(l)
if inconclusive:
print()
for i in inconclusive:
print(" INCONCLUSIVE: %s" % i)
if not problems:
print("\ndecoy-coverage gate INCONCLUSIVE — an undetermined result is never a pass")
return 2
if problems:
print()
print("CONVICTED — these gates have neither a decoy test nor a registered exemption:")
for repo, label in problems:
print(" %s / %s" % (repo, label))
print()
print("A gate ships with a decoy test that has been SEEN TO FAIL. Construct the label")
print("without the fact, run the gate, and assert it convicts — then name the gate in that")
print("repo's scripts/test_gate_decoys.py COVERS map.")
print("If no plausible decoy exists, say so: add it to EXEMPT here with a row number and one")
print("line of reason. An honest exemption is a result; a silent gap is how R-410 happened.")
return 1
print("\ndecoy-coverage gate OK — every registered gate has a decoy or a named exemption (R-426)")
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))