681cc663ef
gates / gates (push) Failing after 13s
Every gate was DECOYED - the label constructed without the fact, the gate run, the verdict recorded.
No verdict here was reached by reading, because reading is exactly how the five prior instances hid.
SCOPE IS A FACT TOO, and it was the big one. Six gates decided what to look at with os.listdir - one
directory level. Every one was green AND CORRECT, because no template subdirectory exists today; every
one would have gone blind the moment anyone added templates/partials/, which is an ordinary act. A
single planted file carrying an emoji, a native confirm(), hand-rolled row markup, a dangling JS id
reference, a templated secret and an unregistered retrieval promise passed all six.
THE CONTROL IS WHAT MAKES THAT A MEASUREMENT: mojibake and docker-v already used os.walk, saw the
identical planted file, and convicted. So the cause was the listing, not the decoy.
COMMENTS ARE NOT CODE, AND COMMENTS ARE NOT CONTROLS. debug-routes matched `case subpath == "x"` in
raw text, so a case left in a commented-out block counted as a live handler - which is R-400's
original defect (seven dead controls on the page an operator opens when something is already wrong)
reached through the one door its own gate could not see. app-row-dedup's MUST_USE check had the same
shape: a commented-out {{template "app_list_row"}} satisfied it.
Stripping is deliberately crude in debug_route_gate, and that is correct there: its own docstring
insists on ten lines that cannot rot. A // inside a string literal truncates that line, which can
only ever HIDE a reference, never invent one - it fails in the safe direction.
NOT FIXED, and left open with its decoy rather than quietly patched: R-425, offbox-rename scans a
fixed three-entry FILES list, so banned NAS branding in a NEW offbox template passes. The scope was
correct when written and silently narrows every time the feature grows a file.
test_gate_decoys.py holds 10 decoys and declares COVERS, which felhom.eu's new decoy-coverage gate
AST-parses - a substring search for coverage would be the very shape this sweep exists to find.
No Go code. No version bump. No image. No golden owed.
Survey: felhom.eu/documentation/audits/AUDIT-gate-decoys-2026-09-01.md
90 lines
3.8 KiB
Python
90 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""Debug-route gate (R-400) — every control on the debug page must resolve to a handler, and every
|
|
handler must be reachable from the page.
|
|
|
|
WHY IT EXISTS. On 2026-08-31 the shipped debug page referenced 24 `/api/debug/...` addresses and the
|
|
dispatcher answered 17. Seven controls did nothing, and three of those seven were not buttons at all:
|
|
they fetch on page LOAD, so whole panels had been permanently empty and nobody had to click anything
|
|
to be misled. This is the page an operator opens when something is already wrong.
|
|
|
|
BOTH DIRECTIONS FAIL. A reference with no case is a dead control. A case with no reference is a
|
|
handler nothing reaches — the same defect mirrored, and the shape this project has now shipped eight
|
|
times. Neither is a warning here.
|
|
|
|
DELIBERATELY TEN LINES OF LOGIC. Two lists and a difference. Its value is that it cannot rot: a
|
|
cleverer gate that understood routing would need maintaining, and an unmaintained gate is how the
|
|
class hides in the first place. The dispatcher's EXACT-match switch with a NotFound default is what
|
|
made the original defect visible, and this gate assumes exactly that shape — do not make either clever.
|
|
|
|
Run from controller/: python3 scripts/debug_route_gate.py
|
|
"""
|
|
import io
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
TEMPLATE = os.path.join("internal", "web", "templates", "debug.html")
|
|
DISPATCH = os.path.join("internal", "web", "handler_debug.go")
|
|
|
|
REF_RE = re.compile(r"/api/debug/([A-Za-z0-9/_-]+)")
|
|
CASE_RE = re.compile(r'subpath\s*==\s*"([A-Za-z0-9/_-]+)"')
|
|
|
|
|
|
# R-421 (2026-09-01) — COMMENTS ARE NOT CODE, AND COMMENTS ARE NOT CONTROLS.
|
|
#
|
|
# Both sides of this gate were plain regexes over raw file text, so a `case subpath == "x":` left
|
|
# behind in a commented-out block counted as a live handler, and a `/api/debug/x` inside an HTML
|
|
# comment counted as a live control. Measured 2026-09-01: commenting out one dispatcher case while
|
|
# adding the matching button made this gate report OK on a control that does nothing — which is
|
|
# R-400's original defect, reachable again through the one door the gate could not see.
|
|
#
|
|
# Stripping is deliberately crude and that is correct here: this gate's own docstring insists on ten
|
|
# lines of logic that cannot rot. A `//` inside a string literal (a URL, say) would truncate that
|
|
# line — which can only ever HIDE a reference, never invent one, so it fails in the safe direction.
|
|
GO_COMMENT_RE = re.compile(r"//[^\n]*")
|
|
GO_BLOCK_RE = re.compile(r"/\*.*?\*/", re.S)
|
|
HTML_COMMENT_RE = re.compile(r"<!--.*?-->", re.S)
|
|
|
|
|
|
def strip_go_comments(src):
|
|
return GO_COMMENT_RE.sub("", GO_BLOCK_RE.sub("", src))
|
|
|
|
|
|
def strip_html_comments(src):
|
|
return HTML_COMMENT_RE.sub("", src)
|
|
|
|
|
|
def read(path):
|
|
if not os.path.exists(path):
|
|
print("DEBUG ROUTE GATE INCONCLUSIVE: %s not found (run from controller/)" % path)
|
|
sys.exit(2)
|
|
return io.open(path, encoding="utf-8").read()
|
|
|
|
|
|
def main():
|
|
# Sets, not lists: the same address referenced by two controls is satisfied by one case (§8).
|
|
refs = set(REF_RE.findall(strip_html_comments(read(TEMPLATE))))
|
|
cases = set(CASE_RE.findall(strip_go_comments(read(DISPATCH))))
|
|
|
|
dead = sorted(refs - cases)
|
|
unreached = sorted(cases - refs)
|
|
|
|
for name in dead:
|
|
print("DEAD CONTROL %s references /api/debug/%s and %s has no case for it"
|
|
% (TEMPLATE, name, DISPATCH))
|
|
for name in unreached:
|
|
print("UNREACHED %s dispatches %r and %s never references it"
|
|
% (DISPATCH, name, TEMPLATE))
|
|
|
|
if dead or unreached:
|
|
print("DEBUG ROUTE GATE FAILED: %d dead control(s), %d unreached handler(s)"
|
|
% (len(dead), len(unreached)))
|
|
sys.exit(1)
|
|
print("debug route gate OK - %d referenced address(es), all dispatched, none orphaned"
|
|
% len(refs))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|