Files
felhom.eu/scripts/test_gate_decoys.py
T
admin a411cde7c4 R-444: hub half — the System page shows each box's last disk trim
sysfacts reads the agent's top-level guest_disk_trim stanza (schedule + per-guest
last attempt: vmid, last_attempt_at, ok, bytes_trimmed, mounts, duration_seconds,
last_ok_at, error) into a field-by-field mirror. The System page's new 'Last disk
trim' column shows the last successful trim and the GiB it freed; amber when the
newest attempt failed (error shown) or last_ok_at is older than 14 days (judged on
the success time, never the attempt time); '—' when the agent sends no stanza.
wire_contract_gate: SUBTREE_MIRRORS checks guest_disk_trim field by field BOTH
ways against sysfacts.DiskTrim; decoys (ok renamed, last_ok_at dropped) in
test_gate_decoys.py. Decision 139.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-06 11:29:17 +02:00

816 lines
48 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 re
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, and (R-422) a cited .md path, that does not exist",
"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)",
"iso-bootstrap": ("R-502: SIX decoys + the genuine article in scripts/test_iso_bootstrap_gate.py, run from here, docker-free (a "
"fake docker on a one-directory PATH): a BLIND harness that passes a bootstrap whose "
"pairing banner never paints (the R-496 shape), a failing harness, the pass line with no "
"checks behind it, and no docker / no image / a docker error, each 'not checked' (exit 2); "
"the gate also plants that broken banner itself in the real container on every full run"),
"instructions": "R-426: a version literal in a CLAUDE.md's effective text; the same in an HTML comment must pass",
"wire-contract": ("R-555: an emitted tag whose name the receiver carries ONLY in a // and a /* */ comment "
"must convict (it passed for months as `language` did); the genuine article — the same "
"name in a struct tag beside a `//` inside a string literal — must pass; and R-315: the "
"agent RENAMING its mirror's `superseded_at` tag (the old name still occurs as a local-API "
"map key) must convict on the field-by-field mirror check; and R-444: the agent renaming "
"`guest_disk_trim.guests.ok` to `succeeded` (a name the hub carries elsewhere) or DROPPING `last_ok_at` (a field the "
"hub reads and would show as never-ok for ever) must convict on the subtree mirror"),
"stands": ("R-819: a stand citing a register id that exists in NEITHER register, and a stand "
"marked 'walked' whose only source is a register row (a green dot from a label); "
"plus the genuine article — a stand citing only a CLOSED row, which must PASS"),
"site": "R-423: a NEW page under website/ that PAGES does not list (the gate used to scan 7 of 9)",
"script-tests": ("R-885: FIVE cases in scripts/test_script_tests_gate.py, run from here: a suite that PRINTS "
"OK and exits 1 (label without fact), a failing suite three levels deep (scope is a walk), "
"an empty scripts/ tree (checked nothing), the genuine article (must pass), the nested mark"),
"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"),
"hostinstall": ("R-426: R-185's regression with only its dry-run echo left, the age install commented "
"out, and a version const in a new hub sub-package; a comment naming the identifier passes"),
"due-checks": ("R-426: a dated check whose id survives only in prose (orphan), the due-TODAY boundary "
"(and due-tomorrow, which must pass), and the block's marker named in prose (exit 2)"),
"one-register": ("R-426: an open roadmap row whose state NAMES another row's CLOSED (a live hole until "
"2026-10-06), its id only in register prose, a suffix id; genuine: a row with a counterpart, "
"and an `idea` row (R-424's accepted hole, asserted BY DESIGN)"),
"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):
# The file may not exist: the operator removed every REPORT*.md on 2026-10-06 (a fresh session writes its own). A
# missing file is planted with the decoy text alone and removed again afterwards — never left behind.
def _plant():
if not os.path.exists(path):
io.open(path, "w", encoding="utf-8").write(extra)
return lambda: os.remove(path)
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
def _two_appends(path_a, extra_a, path_b, extra_b):
def _plant():
ua = append_to(path_a, extra_a)()
ub = append_to(path_b, extra_b)()
def _clean():
ub()
ua()
return _clean
return _plant
def _open_reg_first_id():
"""The first row id of the OPEN register, read at run time so the genuine article follows the file."""
for line in io.open(os.path.join(ROOT, "documentation", "backlog", "OPEN-ITEMS.md"), encoding="utf-8"):
m = re.match(r"^\| \*\*(R-\d+[a-z]?)\*\* \|", line)
if m:
return m.group(1)
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'<button onclick="confirm(\'biztos?\')">x</button>\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 ----------------------------------------------
# Both the .go and (since R-422, 2026-10-05) the .md case are REJECTED.
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,))
# R-422 CLOSED THE HOLE this decoy used to assert (expect="accept"): PATH_RE now includes .md, so a
# rotted document citation is convicted. Updated deliberately, as the old comment asked.
decoy("reuse-refs/missing-md (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,))
# --- instructions (R-426): moved in from the 2026-09-01 sweep --------------------------------------
# A component version literal in EFFECTIVE text of a CLAUDE.md is convicted; the same sentence inside an
# HTML comment (stripped before injection) is the genuine article and passes.
decoy("instructions/version-literal", "instructions_gate.py",
append_to(os.path.join(ROOT, "CLAUDE.md"), u"\nThe hub runs v0.137.0 today.\n"), args=(ROOT,))
decoy("instructions/version-in-comment", "instructions_gate.py",
append_to(os.path.join(ROOT, "CLAUDE.md"), u"\n<!--\nThe hub ran v0.137.0 on 2026-10-05.\n-->\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 4 (2026-10-05, R-416): the same id twice in CLOSED-ITEMS.md ---------------
# The id is read from the file's FIRST closed row at run time, so the decoy duplicates a real id rather than an
# invented one that could never collide.
def _first_closed_id():
for line in io.open(os.path.join(ROOT, "documentation", "backlog", "CLOSED-ITEMS.md"), encoding="utf-8"):
m = re.match(r"^\| \*\*(R-\d+[a-z]?)\*\* \|", line)
if m:
return m.group(1)
decoy("closed-register/duplicate-closed-id", "closed_register_gate.py",
append_to(os.path.join(ROOT, "documentation", "backlog", "CLOSED-ITEMS.md"),
u"\n| **%s** | The same id again. | CLOSED 2026-10-05 | none |\n" % _first_closed_id()))
# --- 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.
_ROADMAP = os.path.join(ROOT, "documentation", "backlog", "ROADMAP.md")
decoy("one-register/suffix-id-row", "one_register_gate.py",
append_to(_ROADMAP, u"\n| R-905b | **A finding filed only here.** | S | READY — 2026-10-03 | none |\n"))
# R-426 (2026-10-06): the rest of the gate's decoys, which took it off the EXEMPT list.
# * done-word-about-another-row PASSED before 2026-10-06 (a live hole): DONE was searched over the whole
# state cell, so an open row whose state NAMED another row's verdict escaped as shipped. R-87's shape
# from R-378, one file over. The gate now reads the leading verdict.
# * id-only-in-register-prose: the open register MENTIONS the id but has no row for it — a label.
# * the genuine articles: an open roadmap row WITH a register row passes; and a row filed under `idea`
# passes BY DESIGN — that is R-424's hole, which the operator accepted on 2026-10-05 (no mechanical
# fix; a person reads the roadmap). Asserted so a future change to it is a decision, not a drift.
decoy("one-register/done-word-about-another-row", "one_register_gate.py",
append_to(_ROADMAP, u"\n| R-905c | **A finding filed only here.** | S | READY — split out after R-86 CLOSED | none |\n"))
decoy("one-register/id-only-in-register-prose", "one_register_gate.py",
_two_appends(_ROADMAP, u"\n| R-905d | **A finding filed only here.** | S | READY — 2026-10-06 | none |\n",
_OPEN_REG, u"\nNote: R-905d is tracked on the roadmap.\n"))
decoy("one-register/genuine-counterpart", "one_register_gate.py",
lambda: append_to(_ROADMAP, u"\n| %s | **The roadmap half of a registered finding.** | S | READY — 2026-10-06 | none |\n"
% _open_reg_first_id())(),
expect="accept")
decoy("one-register/idea-row (BY DESIGN, R-424 accepted)", "one_register_gate.py",
append_to(_ROADMAP, u"\n| R-905e | **A proposal.** | S | idea — 2026-10-06 | none |\n"),
expect="accept")
# --- 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")
# ...and its STALE-EXEMPTION notice (R-426, 2026-10-06): an EXEMPT entry for a gate that has a decoy must be
# NAMED, or the debt list reads longer than the debt (controller/offbox-rename sat there after R-425).
ran += 1
_DCG = os.path.join(ROOT, "scripts", "decoy_coverage_gate.py")
_dcg = io.open(_DCG, encoding="utf-8").read()
try:
assert "EXEMPT = {\n" in _dcg, "the EXEMPT literal's shape changed — this check can no longer be built"
io.open(_DCG, "w", encoding="utf-8").write(_dcg.replace(
"EXEMPT = {\n", 'EXEMPT = {\n ("felhom.eu", "site"): "decoy: a stale entry for a covered gate",\n', 1))
_rc, _out = gate("decoy_coverage_gate.py", (ROOT,))
finally:
io.open(_DCG, "w", encoding="utf-8").write(_dcg)
if "STALE EXEMPTION: felhom.eu / site" not in _out:
fails.append("decoy-coverage: an EXEMPT entry for a COVERED gate was not named as stale\n%s" % _out[-400:])
else:
print(" ok %-20s a stale exemption is NAMED" % "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))
# ── iso-bootstrap (R-502) ────────────────────────────────────────────────────────────────────────
#
# Its decoys need a fake docker on a one-directory PATH, so they live in their own file and are RUN from
# here (guide-quote's shape). The suite never reaches the real docker, so this stays CI-safe.
ran += 1
_ib = subprocess.run([sys.executable, os.path.join("scripts", "test_iso_bootstrap_gate.py")],
cwd=ROOT, capture_output=True, text=True)
if _ib.returncode != 0:
fails.append("iso-bootstrap: its decoy suite FAILED — a decoy did not convict\n%s"
% (_ib.stdout + _ib.stderr)[-800:])
else:
print(" ok %-20s %s" % ("iso-bootstrap", (_ib.stderr.strip().splitlines() or ["?"])[-1]))
# ── site (R-423) ─────────────────────────────────────────────────────────────────────────────────
# A page that exists but is not in PAGES. The content is a perfectly valid page, so only the scope rule can convict.
decoy("site/unlisted-page", "site_gates.py",
plant_file(os.path.join(ROOT, "website", "zz-r423-decoy.html"),
u"\ufeff<!DOCTYPE html><html><body><nav></nav><footer></footer></body></html>\n"))
def replace_in(path, old, new):
"""Mutate one exact span of a real file; restore it byte-for-byte. A span that is no longer there makes
the decoy UNBUILDABLE, which is a failure of this suite — never a quietly skipped case."""
def _plant():
backup = io.open(path, encoding="utf-8").read()
assert backup.count(old) == 1, "%s: the decoy's anchor is gone or ambiguous — rebuild the decoy" % path
io.open(path, "w", encoding="utf-8").write(backup.replace(old, new, 1))
return lambda: io.open(path, "w", encoding="utf-8").write(backup)
return _plant
# ── hostinstall (R-426, 2026-10-06) ──────────────────────────────────────────────────────────────
#
# Each decoy is a one-line edit a real session would make to felhom-host-install.sh, leaving the LABEL
# the gate used to read while removing the fact. The first two PASSED before 2026-10-06 (live holes):
# * R-185's own regression — the Scenario-F arm loses its real grant but keeps its dry-run echo. The
# gate counted the bare word, so the echo stood in for the grant (2 resolutions, 2 "grants").
# * the `age` install commented out while debugging — the bare search matched the comment.
# * a host-install version const in a NEW hub sub-package — the scope is a walk, not a file list.
# The genuine article is the unmodified tree, which the runner's own `hostinstall` row passes on every push.
_HI = os.path.join(ROOT, "scripts", "felhom-host-install.sh")
decoy("hostinstall/R-185-dry-run-echo-only", "hostinstall_gates.py",
replace_in(_HI,
' /usr/local/sbin/felhom-backup-target-apply grant "$BACKUP_TARGET_ID" \\\n'
' || die "backup target grant failed on the pre-existing target',
' true \\\n'
' || die "backup target grant failed on the pre-existing target'))
_AGE_CMD = (" DEBIAN_FRONTEND=noninteractive apt-get install -y -q age >/dev/null 2>&1 \\\n"
" || { apt-get update -q >/dev/null 2>&1; DEBIAN_FRONTEND=noninteractive apt-get install -y -q age >/dev/null 2>&1; } \\\n"
" || die \"failed to install the 'age' package (the escrow ceremony needs it)\"\n")
decoy("hostinstall/age-commented-out", "hostinstall_gates.py",
replace_in(_HI, _AGE_CMD, "".join("#" + l[1:] + "\n" for l in _AGE_CMD.splitlines())))
decoy("hostinstall/hub-version-const-subpkg", "hostinstall_gates.py",
plant_file(os.path.join(ROOT, "hub", "internal", "web", "zzr426decoy", "v.go"),
u'package zzr426decoy\n\nconst hostInstallVersion = "1.32.0"\n'))
# the genuine article beside the third: a comment that merely NAMES the identifier must pass (configs.go
# carries exactly such a note), so the scope widening is not bought with false convictions.
decoy("hostinstall/comment-names-identifier", "hostinstall_gates.py",
plant_file(os.path.join(ROOT, "hub", "internal", "web", "zzr426decoy", "v.go"),
u'package zzr426decoy\n\n// hostInstallVersion was deleted on purpose (R-94).\n'),
expect="accept")
# ── due-checks (R-426, 2026-10-06) ──────────────────────────────────────────────────────────────
#
# The 2026-09-01 sweep's attempt was a no-op and was withdrawn. These are the shapes a session really
# produces in the DUE-CHECKS block of OPEN-ITEMS.md:
# * the label without the row: a dated check whose R-number appears in the register only in PROSE
# ("merged into …") — its row is gone, so the check is orphaned and must convict;
# * the boundary: a check due TODAY is due (the docstring's `<=` rule), and one due TOMORROW is not —
# both directions, with "today" pinned through the gate's own FELHOM_GATE_TODAY seam;
# * the block's marker named in prose elsewhere in the register: two markers are two sources of
# truth, and the gate must refuse to guess (exit 2) rather than read one of them.
# "Today" is pinned to 2026-01-01 — BEFORE every real dated check — so the real rows stay pending and only
# the planted row decides the verdict. The id of an EXISTING row is read from the register at run time, so the boundary decoys cannot turn
# into orphan decoys when a row closes.
_DC_END = u"<!-- DUE-CHECKS-END -->"
_dc_live_id = _open_reg_first_id
def _dc_plant(row, today=None, prose=u""):
inner = replace_in(_OPEN_REG, _DC_END, row + u"\n" + _DC_END)
def _plant():
undo = inner()
if prose:
io.open(_OPEN_REG, "a", encoding="utf-8").write(prose)
old = os.environ.get("FELHOM_GATE_TODAY")
if today:
os.environ["FELHOM_GATE_TODAY"] = today
def _clean():
undo()
if old is None:
os.environ.pop("FELHOM_GATE_TODAY", None)
else:
os.environ["FELHOM_GATE_TODAY"] = old
return _clean
return _plant
decoy("due-checks/orphan-named-in-prose", "due_checks_gate.py",
_dc_plant(u"| R-99426 | 2099-01-01 | a far-future check whose row was merged away |",
prose=u"\nNote: R-99426 was merged into another row on 2026-10-06.\n"))
decoy("due-checks/due-today", "due_checks_gate.py",
_dc_plant(u"| %s | 2026-01-01 | the boundary decoy |" % _dc_live_id(), today="2026-01-01"))
decoy("due-checks/due-tomorrow", "due_checks_gate.py",
_dc_plant(u"| %s | 2026-01-02 | the boundary genuine article |" % _dc_live_id(), today="2026-01-01"),
expect="accept")
decoy("due-checks/marker-named-in-prose", "due_checks_gate.py",
_dc_plant(u"", prose=u"\nThe dated checks sit between the DUE-CHECKS-BEGIN and DUE-CHECKS-END markers.\n"))
# ── script-tests (R-885) ─────────────────────────────────────────────────────────────────────────
#
# Its decoys are whole fake repos, so they live in their own file and are RUN from here (guide-quote's shape).
ran += 1
_st = subprocess.run([sys.executable, os.path.join("scripts", "test_script_tests_gate.py")],
cwd=ROOT, capture_output=True, text=True)
if _st.returncode != 0:
fails.append("script-tests: its decoy suite FAILED — a decoy did not convict\n%s"
% (_st.stdout + _st.stderr)[-800:])
else:
_n = _st.stdout.strip().splitlines()[-1] if _st.stdout.strip() else "?"
print(" ok %-20s %s" % ("script-tests", _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")
# ── wire-contract (R-555) ────────────────────────────────────────────────────────────────────────
#
# The real receiver (the hub tree) with ONE received tag taken out of its token set and put back only
# through a file that names it in prose. The gate runs end to end against the real emitters, so the
# decoy needs the controller and agent clones beside this one (CI fetches both; a missing clone is
# INCONCLUSIVE and fails this suite, never a pass). `repo_size_bytes` is `offsite.repo_size_bytes`,
# which the hub really decodes, so the genuine case passes for the right reason.
_WC = r"""
import os, sys, tempfile
sys.path.insert(0, "scripts")
import wire_contract_gate as g
T, mode = "repo_size_bytes", sys.argv[1]
tmp = tempfile.mkdtemp(prefix="r555-")
body = {"comment": "package prose\n// %s is described here, in prose only.\n/* and %s again */\n" % (T, T),
"genuine": "package prose\nvar u = \"http://x\" // not a comment start inside a string\n"
"type X struct { A int `json:\"%s\"` }\n" % T}[mode]
open(os.path.join(tmp, "prose.go"), "w").write(body)
orig, hub = g.receiver_tokens, os.path.abspath(g.REPOS["hub"])
g.receiver_tokens = lambda p: ((orig(p) - {T}) | orig(tmp)) if os.path.abspath(p) == hub else orig(p)
rc, conv = g.run(quiet=True)
print("rc=%d convicted_T=%s" % (rc, any(T in str(c) for c in conv)))
sys.exit(0 if rc == 0 else (10 if any(T in str(c) for c in conv) else 11))
"""
for _mode, _want in (("comment", 10), ("genuine", 0)):
ran += 1
_p = subprocess.run([sys.executable, "-c", _WC, _mode], cwd=ROOT, capture_output=True, text=True)
if _p.returncode != _want:
fails.append("wire-contract/%s: rc=%d, want %d (10 = the comment-only tag convicted, 0 = passed)\n%s"
% (_mode, _p.returncode, _want, (_p.stdout + _p.stderr)[-500:]))
else:
print(" ok %-20s %s" % ("wire-contract/" + _mode,
"decoy rejected" if _want else "genuine accepted"))
# ── wire-contract mirror check (R-315) ───────────────────────────────────────────────────────────
#
# The measured R-315 mutation: the AGENT renames its mirror's `superseded_at` tag. The name check passed
# it, because `superseded_at` also occurs in the agent as a local-API map key. The rename is applied to
# the parsed mirror body only (no copy of the agent tree), so the token set still holds the old name —
# exactly the shape that fooled the gate. The genuine article (no rename) must pass.
_WCM = r"""
import os, sys
sys.path.insert(0, "scripts")
import wire_contract_gate as g
mode = sys.argv[1]
orig, agent = g.build_index, os.path.abspath(g.REPOS["agent"])
def patched(root):
by_dir, by_name = orig(root)
if mode == "renamed" and os.path.abspath(root) == agent:
k = ("internal/hub", "RetainedEscrowPackage")
assert 'json:"superseded_at"' in by_dir[k], "mutation target missing"
by_dir[k] = by_dir[k].replace('json:"superseded_at"', 'json:"superseded_at_RENAMED"')
by_name["RetainedEscrowPackage"] = [(d, by_dir[k] if d == k[0] else b)
for d, b in by_name["RetainedEscrowPackage"]]
return by_dir, by_name
g.build_index = patched
rc, conv = g.run(quiet=True)
hit = any(d == "packages.superseded_at" for _, _, m in conv for _, d in m)
print("rc=%d convicted=%s" % (rc, hit))
sys.exit(0 if rc == 0 else (10 if hit else 11))
"""
for _mode, _want in (("renamed", 10), ("genuine", 0)):
ran += 1
_p = subprocess.run([sys.executable, "-c", _WCM, _mode], cwd=ROOT, capture_output=True, text=True)
if _p.returncode != _want:
fails.append("wire-contract-mirror/%s: rc=%d, want %d (10 = the renamed mirror tag convicted, 0 = passed)\n%s"
% (_mode, _p.returncode, _want, (_p.stdout + _p.stderr)[-500:]))
else:
print(" ok %-20s %s" % ("wire-mirror/" + _mode,
"decoy rejected" if _want else "genuine accepted"))
# ── wire-contract subtree mirror (R-444) ─────────────────────────────────────────────────────────
#
# `guest_disk_trim` is checked field by field BOTH ways against hub sysfacts.DiskTrim. Two decoys on the agent's parsed
# GuestDiskTrim body (no tree copy): `ok` renamed to `succeeded` — the name check passes it, since `succeeded` occurs in the hub — and
# `last_ok_at` dropped, which only the reverse direction can see. The genuine article must pass.
_WCS = r"""
import os, sys
sys.path.insert(0, "scripts")
import wire_contract_gate as g
mode = sys.argv[1]
orig, agent = g.build_index, os.path.abspath(g.REPOS["agent"])
def patched(root):
by_dir, by_name = orig(root)
if mode != "genuine" and os.path.abspath(root) == agent:
k = ("internal/hub", "GuestDiskTrim")
b = by_dir[k]
if mode == "renamed":
assert 'json:"ok"' in b, "mutation target missing"
b = b.replace('json:"ok"', 'json:"succeeded"')
else:
assert 'json:"last_ok_at' in b, "mutation target missing"
b = "\n".join(l for l in b.split("\n") if 'json:"last_ok_at' not in l)
by_dir[k] = b
by_name["GuestDiskTrim"] = [(d, b if d == k[0] else x) for d, x in by_name["GuestDiskTrim"]]
return by_dir, by_name
g.build_index = patched
rc, conv = g.run(quiet=True)
want = {"renamed": "guest_disk_trim.guests.succeeded", "dropped": "guest_disk_trim.guests.last_ok_at"}.get(mode)
hit = any(d == want for _, _, m in conv for _, d in m)
print("rc=%d convicted=%s" % (rc, hit))
sys.exit(0 if rc == 0 else (10 if hit else 11))
"""
for _mode, _want in (("renamed", 10), ("dropped", 10), ("genuine", 0)):
ran += 1
_p = subprocess.run([sys.executable, "-c", _WCS, _mode], cwd=ROOT, capture_output=True, text=True)
if _p.returncode != _want:
fails.append("wire-trim/%s: rc=%d, want %d (10 = the mutated trim field convicted, 0 = passed)\n%s"
% (_mode, _p.returncode, _want, (_p.stdout + _p.stderr)[-500:]))
else:
print(" ok %-20s %s" % ("wire-trim/" + _mode, "decoy rejected" if _want else "genuine accepted"))
# ── stands (R-819) ───────────────────────────────────────────────────────────────────────────────
#
# check_stands.py was red and in no runner. Its decoys are stand files written as a session would write
# them, checked with the real registers. The genuine case is the one R-819 fixed: a citation of a row
# that CLOSED (R-273, the never-tagged agent) is a valid source, not a dangling one.
_STANDS = os.path.join(ROOT, "documentation", "architecture", "zz-r819-decoy-stands.yaml")
_stand = lambda status, ref: (u"claims:\n - id: decoy.stand\n title: \"decoy\"\n status: %s\n"
u" sources:\n - register: %s\n" % (status, ref))
decoy("stands/dangling-id", "check_stands.py", plant_file(_STANDS, _stand("built", "R-99999")), args=(_STANDS,))
decoy("stands/walked-no-walk", "check_stands.py", plant_file(_STANDS, _stand("walked", "R-273")), args=(_STANDS,))
decoy("stands/closed-row-ok", "check_stands.py", plant_file(_STANDS, _stand("built", "R-273")), args=(_STANDS,),
expect="pass")
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)