diff --git a/scripts/decoy_coverage_gate.py b/scripts/decoy_coverage_gate.py index 65860343..7fcfd3e5 100644 --- a/scripts/decoy_coverage_gate.py +++ b/scripts/decoy_coverage_gate.py @@ -36,9 +36,6 @@ EXEMPT = { ("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", "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-controller ("felhom-controller", "docker-v"): "COVERED IN THE SWEEP, not yet in a suite: an unallowlisted `-v` host path in a new Go file " diff --git a/scripts/test_gate_decoys.py b/scripts/test_gate_decoys.py index be243bb5..c069de55 100644 --- a/scripts/test_gate_decoys.py +++ b/scripts/test_gate_decoys.py @@ -74,6 +74,8 @@ COVERS = { "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)"), "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 " @@ -437,6 +439,61 @@ decoy("hostinstall/comment-names-identifier", "hostinstall_gates.py", 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"" + + +def _dc_live_id(): + for line in io.open(_OPEN_REG, encoding="utf-8"): + m = re.match(r"^\| \*\*(R-\d+[a-z]?)\*\* \|", line) + if m: + return m.group(1) + + +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).