the golden gate reads a fact not a name (R-410); R-133's collision resolved (R-406, R-416)
gates / gates (push) Failing after 17s
gates / gates (push) Failing after 17s
R-410. golden_currency_gate.py matched EVIDENCE_RE against os.listdir and read nothing inside, so `mkdir documentation/tests/golden-9.9.9-2026-01-01` turned it green with no bake behind it - noticed while the 0.230.0 bake was running, when the evidence directory existed before the bake finished. It now reads the GOLDEN_SHA256= line out of that directory's bake log: a directory name is a label, that line is a fact only a completed publish produces. Still offline, still --fast, one file read. Directories that look right and hold nothing are printed by name rather than silently ignored, so a half-finished bake is visible. test_golden_currency_gate.py ships the red-proof with a POSITIVE CONTROL, without which "it fails on an empty directory" would be satisfied by a gate that fails on everything: CASE 1 empty directory -> rejected and named CASE 2 log with no GOLDEN_SHA256 -> rejected CASE 3 real bake log -> counted, and its sha read <- the control CASE 4 the tree is left byte-identical Red-proofed: reverting the gate to name-matching fails cases 1, 2 and 3. R-406. Citations MEASURED before choosing, which is what the row asked for: hub-uniqueness had 3 references (all inside one audit doc), plaintext-break-glass had 5 (CONTEXT.md, break-glass.md, hub/CHANGELOG.md, the capability map, a spike). The FEWER-cited one moved - hub uniqueness is now R-415 - and all three citations were rewritten to "R-415 (was R-133)" rather than silently swapped. THIS IS THE OPPOSITE OF THE TASK'S LITERAL INSTRUCTION, which said renumber the second row on the stated ground that "the older number has the longer reference trail". Measured, that ground points the other way. The principle was followed and the letter was not, and the row says so rather than leaving an unexplained diff. R-416 filed: the within-register duplicate rule was deliberately NOT added in the same commit that removed its only subject - a guard whose red-proof can only be a planted fixture is not this project's standard. Now that the register is clean it can ship with the next real duplicate as its first subject.
This commit is contained in:
@@ -73,6 +73,7 @@ it is absent. An unrecorded golden is convicted (exit 1) exactly like a stale on
|
||||
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 io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
@@ -115,17 +116,68 @@ def released_versions():
|
||||
return newest, note, every
|
||||
|
||||
|
||||
# GOLDEN_SHA_RE — the line `build-golden.sh` prints when it has actually published a bake.
|
||||
# Reading THIS, rather than the directory's name, is R-410's whole fix.
|
||||
GOLDEN_SHA_RE = re.compile(r"^GOLDEN_SHA256=([0-9a-f]{64})\s*$", re.MULTILINE)
|
||||
|
||||
# The bake log, as the runbook's §4.1 teardown copies it out. Two names are accepted because the
|
||||
# 0.230.0 bake wrote `06-bake.log` alongside its README while 0.229.0 wrote `bake.log`; both are real
|
||||
# bakes and neither should be called a fake.
|
||||
BAKE_LOG_NAMES = ("bake.log", "06-bake.log", "bake-clean.log", "06-bake-clean.log")
|
||||
|
||||
|
||||
def bake_sha_in(dirpath):
|
||||
"""The GOLDEN_SHA256 a bake log in dirpath records, or None with the reason it could not be read.
|
||||
|
||||
R-410. Until 2026-09-01 this gate matched EVIDENCE_RE against `os.listdir` and nothing else, so
|
||||
`mkdir documentation/tests/golden-9.9.9-2026-01-01` turned it green with no bake behind it —
|
||||
noticed while the 0.230.0 bake was running, when the evidence directory was created BEFORE the
|
||||
bake finished and the gate would have passed at that moment.
|
||||
|
||||
A directory NAME is a label; `GOLDEN_SHA256=<64 hex>` is a fact only a completed publish produces.
|
||||
Reading it keeps the gate offline and `--fast`: it is one file read, no network.
|
||||
"""
|
||||
for n in BAKE_LOG_NAMES:
|
||||
p = os.path.join(dirpath, n)
|
||||
if not os.path.isfile(p):
|
||||
continue
|
||||
try:
|
||||
with io.open(p, encoding="utf-8", errors="replace") as fh:
|
||||
m = GOLDEN_SHA_RE.search(fh.read())
|
||||
except OSError as e:
|
||||
return None, "bake log %s could not be read (%s)" % (n, e)
|
||||
if m:
|
||||
return m.group(1), n
|
||||
return None, "bake log %s carries no GOLDEN_SHA256= line" % n
|
||||
return None, "no bake log (looked for %s)" % ", ".join(BAKE_LOG_NAMES)
|
||||
|
||||
|
||||
def newest_baked():
|
||||
"""(tuple, str) of the newest golden bake recorded here, or (None, reason)."""
|
||||
"""(tuple, str) of the newest golden bake recorded here, or (None, reason).
|
||||
|
||||
A directory counts ONLY if it holds a bake log with a GOLDEN_SHA256 line (R-410). Directories that
|
||||
look right and hold nothing are reported by name, so a half-finished bake is visible rather than
|
||||
silently ignored.
|
||||
"""
|
||||
if not os.path.isdir(EVIDENCE_DIR):
|
||||
return None, "bake-evidence directory not found at %s" % EVIDENCE_DIR
|
||||
found = []
|
||||
found, rejected = [], []
|
||||
for name in os.listdir(EVIDENCE_DIR):
|
||||
m = EVIDENCE_RE.match(name)
|
||||
if m:
|
||||
found.append((tuple(int(g) for g in m.groups()), name))
|
||||
if not m:
|
||||
continue
|
||||
sha, why = bake_sha_in(os.path.join(EVIDENCE_DIR, name))
|
||||
if sha is None:
|
||||
rejected.append("%s (%s)" % (name, why))
|
||||
continue
|
||||
found.append((tuple(int(g) for g in m.groups()), "%s [sha %s…]" % (name, sha[:12])))
|
||||
if rejected:
|
||||
print(" NOT counted as bakes — a directory name is not a bake (R-410):")
|
||||
for r in sorted(rejected):
|
||||
print(" %s" % r)
|
||||
if not found:
|
||||
return None, "no golden-<VER>-<DATE>/ evidence directory under %s" % EVIDENCE_DIR
|
||||
return None, ("no golden-<VER>-<DATE>/ directory under %s holds a bake log with a "
|
||||
"GOLDEN_SHA256 line" % EVIDENCE_DIR)
|
||||
found.sort()
|
||||
return found[-1]
|
||||
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""test_golden_currency_gate.py — R-410's red-proof, shipped as a test.
|
||||
|
||||
WHAT IT PINS. Until 2026-09-01 `golden_currency_gate.py` matched `EVIDENCE_RE` against `os.listdir`
|
||||
and read nothing inside the directory, so
|
||||
|
||||
mkdir documentation/tests/golden-9.9.9-2026-01-01
|
||||
|
||||
turned the gate GREEN with no bake behind it. That was noticed while the 0.230.0 bake was running —
|
||||
the evidence directory was created BEFORE the bake finished, and the gate would have passed at that
|
||||
moment. **A directory name is a label; `GOLDEN_SHA256=<64 hex>` is a fact only a completed publish
|
||||
produces.**
|
||||
|
||||
This is the gate's own instrument check: an empty directory must FAIL, a real bake log must PASS, and
|
||||
a log with no sha line must FAIL. Without the last two, "it fails on an empty directory" would be
|
||||
satisfied by a gate that fails on everything.
|
||||
|
||||
Run: python3 scripts/test_golden_currency_gate.py
|
||||
Exit 0 all pass · 1 a case failed.
|
||||
"""
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
GATE = os.path.join(ROOT, "scripts", "golden_currency_gate.py")
|
||||
EVIDENCE_DIR = os.path.join(ROOT, "documentation", "tests")
|
||||
|
||||
REAL_SHA = "9287f7cef5f13166276e8406005e3f28004004510c5184f1c1c7377f7aafad2e"
|
||||
|
||||
|
||||
def run_gate():
|
||||
p = subprocess.run([sys.executable, GATE], capture_output=True, text=True)
|
||||
return p.returncode, p.stdout + p.stderr
|
||||
|
||||
|
||||
def with_dir(name, files):
|
||||
"""Context: create documentation/tests/<name>/ holding `files` (name -> contents), then remove it."""
|
||||
class _C(object):
|
||||
def __enter__(self):
|
||||
self.path = os.path.join(EVIDENCE_DIR, name)
|
||||
if os.path.exists(self.path):
|
||||
raise SystemExit("refusing to overwrite an existing %s" % self.path)
|
||||
os.makedirs(self.path)
|
||||
for fn, body in files.items():
|
||||
with io.open(os.path.join(self.path, fn), "w", encoding="utf-8") as fh:
|
||||
fh.write(body)
|
||||
return self.path
|
||||
|
||||
def __exit__(self, *a):
|
||||
shutil.rmtree(self.path, ignore_errors=True)
|
||||
return False
|
||||
return _C()
|
||||
|
||||
|
||||
def main():
|
||||
if not os.path.isfile(GATE):
|
||||
print("FAIL: the gate script is missing — that is a failure, never a skip")
|
||||
return 1
|
||||
|
||||
fails = []
|
||||
|
||||
# --- BASELINE: what does the gate say with the tree as it is? ------------------------------
|
||||
base_rc, base_out = run_gate()
|
||||
print("baseline: gate exit %d" % base_rc)
|
||||
|
||||
# --- CASE 1: an EMPTY directory with a perfect name must NOT count as a bake ----------------
|
||||
# Version 9.9.9 is far above any real release, so if it counted, the gate would go GREEN.
|
||||
with with_dir("golden-9.9.9-2026-01-01", {}):
|
||||
rc, out = run_gate()
|
||||
counted = "9.9.9" in out and "NOT counted" not in out.split("9.9.9")[0][-200:]
|
||||
if "newest golden baked : 9.9.9" in out:
|
||||
fails.append("CASE 1: an EMPTY directory was counted as a bake — this is R-410 exactly")
|
||||
elif "NOT counted as bakes" not in out:
|
||||
fails.append("CASE 1: the empty directory was neither counted nor reported; a half-finished "
|
||||
"bake must be VISIBLE, not silently ignored")
|
||||
else:
|
||||
print("CASE 1 ok: an empty golden-9.9.9-2026-01-01/ is rejected and named")
|
||||
_ = counted
|
||||
|
||||
# --- CASE 2: a directory whose log has NO sha line must NOT count ---------------------------
|
||||
with with_dir("golden-9.9.8-2026-01-01", {"bake.log": "[golden] starting\n[golden] it all went wrong\n"}):
|
||||
rc, out = run_gate()
|
||||
if "newest golden baked : 9.9.8" in out:
|
||||
fails.append("CASE 2: a bake log with no GOLDEN_SHA256 line was counted as a bake")
|
||||
else:
|
||||
print("CASE 2 ok: a log with no GOLDEN_SHA256 line is rejected")
|
||||
|
||||
# --- CASE 3: a REAL bake log must count, and the gate must read its sha ---------------------
|
||||
# The positive control. Without it, cases 1 and 2 are satisfied by a gate that rejects everything.
|
||||
with with_dir("golden-9.9.7-2026-01-01",
|
||||
{"bake.log": "[golden] upload OK (HTTP 201)\nGOLDEN_VERSION=9.9.7\nGOLDEN_SHA256=%s\n" % REAL_SHA}):
|
||||
rc, out = run_gate()
|
||||
if "newest golden baked : 9.9.7" not in out:
|
||||
fails.append("CASE 3 (POSITIVE CONTROL): a real bake log was NOT counted — the gate now "
|
||||
"rejects everything, which would make cases 1 and 2 meaningless")
|
||||
elif REAL_SHA[:12] not in out:
|
||||
fails.append("CASE 3: the gate counted the bake but did not read its sha")
|
||||
else:
|
||||
print("CASE 3 ok: a real bake log counts, and its sha is read and shown")
|
||||
|
||||
# --- CASE 4: the tree is left exactly as found ----------------------------------------------
|
||||
post_rc, _ = run_gate()
|
||||
if post_rc != base_rc:
|
||||
fails.append("CASE 4: the gate's verdict changed after the test cleaned up (%d -> %d) — a test "
|
||||
"that leaves the tree different is not a test" % (base_rc, post_rc))
|
||||
else:
|
||||
print("CASE 4 ok: the tree is unchanged; the gate's verdict is the same as the baseline")
|
||||
|
||||
if fails:
|
||||
print()
|
||||
for f in fails:
|
||||
print("FAIL: %s" % f)
|
||||
return 1
|
||||
print("\ngolden-currency gate self-test OK — a directory name alone cannot satisfy it (R-410)")
|
||||
return 0
|
||||
|
||||
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user