R-385: make an UNRECORDED golden fail the currency gate; file R-386; own the alarm ladder
gates / gates (push) Successful in 17s

The gate failed only on `released > baked`, so it could catch a forgotten bake
and nothing else. A golden AHEAD of the record passed silently - and that is
how controller 0.221.1 was built, baked AND vouched while the newest CHANGELOG
heading still read v0.221.0, with every gate green. Reproduced on the real
history: newest released 0.221.0 / newest golden baked 0.221.1 -> exit 0.

The gate now asks whether the version being shipped is WRITTEN DOWN: the baked
version must have its own `## vX.Y.Z` heading anywhere in the CHANGELOG.
Membership rather than `baked > released` deliberately - a comparison against
the newest heading alone goes green the moment any later entry is written,
leaving the unrecorded version permanently unrecorded. INCONCLUSIVE (exit 2)
preserved; every refusal names a reason and a route.

Red-proofed both directions: old gate/old record exit 0, new gate/old record
exit 1, new gate/fixed record exit 0, absent clone exit 2, post-bake exit 0.

08-alarm-ladder.md is new, and its absence was itself the finding: no document
owned "when does a broken app raise an alarm?". The rules lived as comments in
four packages, each locally correct, with the ordering between them legible only
by reading one function top to bottom - which is how R-384 survived review.

R-383 and R-384 closed into CLOSED-ITEMS with their rules kept. R-385 filed
closed. R-386 filed OPEN: a single-container app stopped out of band raises no
alarm, and a comment claims the opposite - measured live, 9 scans, 0 events,
against a positive control from the same box 17 minutes earlier. Not fixed here.

Golden 0.222.0 baked and published; vouching is the operator's act.
This commit is contained in:
2026-08-23 07:59:52 +02:00
parent 1eb64bec51
commit 55274d5ef3
39 changed files with 4986 additions and 65 deletions
+60 -6
View File
@@ -50,6 +50,28 @@ recorded waiver in the register, never a habit of bypassing.
FAIL-CLOSED, BUT HONEST ABOUT NOT KNOWING. An absent controller clone, or a CHANGELOG whose top
header cannot be parsed, exits **2 (INCONCLUSIVE)** — never 0. The runner reports 2 distinctly for
exactly this reason: an undetermined result is not a pass, and it is not a conviction either.
── THE SECOND BLINDNESS, R-385 (added 2026-08-23) ────────────────────────────────────────────
Until this change the gate asked ONE question — *is the golden BEHIND the record?* — and so it could
only ever catch a forgotten bake. It said nothing when the golden was **AHEAD** of the record, and
that is not a harmless direction: a golden ahead of every CHANGELOG heading was built from something
**never written down**.
That is not a hypothetical. Controller **0.221.1** was built, baked AND vouched on 2026-08-23 while
the newest heading in the controller CHANGELOG still read v0.221.0 — the fix had been written inside
the v0.221.0 entry instead of getting its own. Every gate was green throughout, including this one,
measured: `newest released 0.221.0 / newest golden baked 0.221.1 → OK`. The fleet ran a version the
record did not name.
So the test is no longer "behind?" but "**is the version we are shipping WRITTEN DOWN?**". The gate
now looks for the baked version's own `## vX.Y.Z` heading anywhere in the CHANGELOG — not merely at
the top, because an entry may legitimately be overtaken by later ones; what may never happen is that
it is absent. An unrecorded golden is convicted (exit 1) exactly like a stale one.
**Why membership and not `baked > released`.** A comparison against the newest heading alone would go
green again the moment ANY later entry was written, leaving 0.221.1 permanently unrecorded and the
gate permanently silent about it. Membership cannot be satisfied by an unrelated later release.
"""
import os
import re
@@ -67,16 +89,30 @@ RELEASED_RE = re.compile(r"^##\s+v(\d+)\.(\d+)\.(\d+)\b")
EVIDENCE_RE = re.compile(r"^golden-(\d+)\.(\d+)\.(\d+)-\d{4}-\d{2}-\d{2}$")
def newest_released():
"""(tuple, str) of the newest controller release, or (None, reason)."""
def released_versions():
"""(newest_tuple, note, set_of_all_tuples) of the controller releases, or (None, reason, set()).
R-385: the whole set is returned, not only the newest. The newest answers "is the golden behind?";
membership answers "is the version we are shipping written down at all?" — and only the second
question could have caught 0.221.1, whose heading was missing while a NEWER heading existed.
"""
if not os.path.isfile(CONTROLLER_CHANGELOG):
return None, "controller clone not found at %s" % CONTROLLER_CHANGELOG
return None, "controller clone not found at %s" % CONTROLLER_CHANGELOG, set()
newest = None
note = ""
every = set()
with open(CONTROLLER_CHANGELOG, encoding="utf-8") as fh:
for line in fh:
m = RELEASED_RE.match(line)
if m:
return tuple(int(g) for g in m.groups()), line.strip()[:90]
return None, "no '## vX.Y.Z' header found in %s" % CONTROLLER_CHANGELOG
v = tuple(int(g) for g in m.groups())
every.add(v)
if newest is None:
# newest-first by convention: the FIRST heading is the newest release.
newest, note = v, line.strip()[:90]
if newest is None:
return None, "no '## vX.Y.Z' header found in %s" % CONTROLLER_CHANGELOG, set()
return newest, note, every
def newest_baked():
@@ -99,7 +135,7 @@ def vstr(v):
def main():
released, rel_note = newest_released()
released, rel_note, every_released = released_versions()
if released is None:
print("GOLDEN CURRENCY GATE INCONCLUSIVE: %s" % rel_note)
sys.exit(2)
@@ -113,6 +149,24 @@ def main():
print(" newest released controller : %s (%s)" % (vstr(released), rel_note))
print(" newest golden baked : %s (documentation/tests/%s)" % (vstr(baked), bake_note))
# R-385 — UNRECORDED, checked before "behind". A golden whose version has no heading of its own
# was built from something never written down, and that is a different (worse) fault than a
# forgotten bake: there is nothing to read to find out what the fleet is running.
if baked not in every_released:
print("")
print("GOLDEN CURRENCY GATE FAILED: golden %s is baked but UNRECORDED — the controller "
"CHANGELOG has no '## v%s' heading." % (vstr(baked), vstr(baked)))
print("The newest heading is %s. A golden ahead of the record was built from a version "
"nobody wrote down, so no one can read what the fleet is running." % vstr(released))
print("Fix: give v%s its own '## v%s — <what changed>' heading in "
"felhom-controller/CHANGELOG.md, above the entries it supersedes. If its fix is "
"currently described inside another version's entry, MOVE that text — do not "
"duplicate it, and do not delete the reasoning." % (vstr(baked), vstr(baked)))
print("If this bake was a throwaway that must never be delivered, delete its "
"documentation/tests/golden-<VER>-<DATE>/ directory — never leave it to read as "
"shipped.")
sys.exit(1)
if released > baked:
print("")
print("GOLDEN CURRENCY GATE FAILED: controller v%s is released and NO golden carries it "