68df84bcaa
A version literal in a CLAUDE.md's effective text must convict; the same inside an HTML comment must pass. Seen failing with the version regex disabled. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
594 lines
34 KiB
Python
594 lines
34 KiB
Python
#!/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)",
|
||
"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"),
|
||
"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"),
|
||
"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'<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. (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))
|
||
|
||
# ── 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"))
|
||
|
||
# ── 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"))
|
||
|
||
# ── 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)
|