ae59c31a84
Operator rulings 2026-09-13, both shipped the same day: - MariaDB finishes its own conversion (catalog eec1228/bd32830/3525e35). Harness E3/E3b `proven` with engine_state_after "already upgraded to 12.3.3-MariaDB [exit=1]", the skip line gone, C3 still `failed`; landed on demo-hp through the real 15-min cycle, nothing recreated, one deliberate restart logged "MariaDB upgrade not required" with the app serving. Evidence: documentation/audits/r459-close-2026-09-13/. The engine-major rule + gate keep every engine inside its major until Slice 4 (R-448) — removal tracked as R-469. - Goldens on a cadence, not per release. golden_currency_gate.py reads a dated waiver (documentation/tests/golden-waiver.yml, <= 14 days, row-bound): valid + BEHIND -> loud advisory, exit 0; expired -> red again naming the date; UNRECORDED (R-385) never covered; malformed -> 2, never 0. Tests cases 5-15 incl. the R-421 decoy; red-proof old-vs-new on the real behind tree. R-242's vouch half stays open. Cadence in RUNBOOK-manual-build.md §4.2 + the checklist. - Golden 0.236.0 baked, round-tripped, vouched, floor raised 0.232.0 -> 0.236.0 (documentation/tests/golden-0.236.0-2026-09-13/) — the last per-release bake; the waiver was issued AFTER it landed. No --no-verify anywhere in this session. Rows: R-459 CLOSED, R-467 CLOSED, R-242 narrowed; R-468/R-469/R-470/R-471 opened. 09 §3 gains decisions 5 and 6; STATUS items 11 and 12 closed; CONTEXT records the cadence ruling. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
249 lines
12 KiB
Python
249 lines
12 KiB
Python
#!/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.
|
|
|
|
2026-09-13: the WAIVER (Scenarios E-H of the task that built it) is tested below in `waiver_cases`,
|
|
against a synthetic tree through the gate's GOLDEN_GATE_* seam — a valid waiver passes with the
|
|
advisory PRESENT; an expired one convicts and NAMES the date; a valid one does NOT save an
|
|
unrecorded golden (R-385); a 15-day, absent-expiry, unparseable, bad-row and empty-reason waiver
|
|
each return 2; and the R-421 decoy (a file saying only `expires`) returns 2.
|
|
"""
|
|
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")
|
|
|
|
fails += waiver_cases()
|
|
|
|
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), "
|
|
"and the waiver is judged on its dates, its row and its direction (2026-09-13)")
|
|
return 0
|
|
|
|
|
|
# ── THE WAIVER (2026-09-13) — Scenarios E, F, G, H, each with its red-proof ─────────────────────
|
|
#
|
|
# These run the gate against a SYNTHETIC tree through the GOLDEN_GATE_* seam (a fake controller
|
|
# CHANGELOG, a fake evidence dir with a real-shaped bake log, a fake register, a waiver file), so the
|
|
# verdicts do not depend on what the real controller happens to have released this week. The seam
|
|
# moves where the gate reads, never what it decides.
|
|
|
|
import datetime
|
|
import tempfile
|
|
|
|
|
|
def run_gate_env(env):
|
|
e = dict(os.environ)
|
|
e.update(env)
|
|
p = subprocess.run([sys.executable, GATE], capture_output=True, text=True, env=e)
|
|
return p.returncode, p.stdout + p.stderr
|
|
|
|
|
|
def synthetic_tree(released, baked, waiver_text, register_rows=("R-468",)):
|
|
"""Build a tree where the CHANGELOG lists `released` (newest first) and one golden `baked` has a
|
|
real-shaped bake log. Returns (env, tmpdir)."""
|
|
tmp = tempfile.mkdtemp(prefix="golden-waiver-")
|
|
ch = os.path.join(tmp, "CHANGELOG.md")
|
|
with io.open(ch, "w", encoding="utf-8") as fh:
|
|
for v in released:
|
|
fh.write("## v%s — synthetic (2026-01-01)\n\nbody\n\n" % v)
|
|
ev = os.path.join(tmp, "tests")
|
|
os.makedirs(os.path.join(ev, "golden-%s-2026-01-01" % baked))
|
|
with io.open(os.path.join(ev, "golden-%s-2026-01-01" % baked, "bake.log"), "w",
|
|
encoding="utf-8") as fh:
|
|
fh.write("[golden] upload OK (HTTP 201)\nGOLDEN_VERSION=%s\nGOLDEN_SHA256=%s\n" % (baked, REAL_SHA))
|
|
reg = os.path.join(tmp, "OPEN-ITEMS.md")
|
|
with io.open(reg, "w", encoding="utf-8") as fh:
|
|
for r in register_rows:
|
|
fh.write("| **%s** | synthetic row | READY | CC |\n" % r)
|
|
wv = os.path.join(tmp, "golden-waiver.yml")
|
|
if waiver_text is not None:
|
|
with io.open(wv, "w", encoding="utf-8") as fh:
|
|
fh.write(waiver_text)
|
|
env = {"GOLDEN_GATE_CHANGELOG": ch, "GOLDEN_GATE_EVIDENCE_DIR": ev,
|
|
"GOLDEN_GATE_WAIVER": wv, "GOLDEN_GATE_REGISTER": reg}
|
|
return env, tmp
|
|
|
|
|
|
def waiver(issued, expires, reason="pre-customer development; weekly cadence", row="R-468"):
|
|
lines = ["# synthetic waiver"]
|
|
if issued is not None:
|
|
lines.append("issued: %s" % issued)
|
|
if expires is not None:
|
|
lines.append("expires: %s" % expires)
|
|
if reason is not None:
|
|
lines.append("reason: %s" % reason)
|
|
if row is not None:
|
|
lines.append("register_row: %s" % row)
|
|
return "\n".join(lines) + "\n"
|
|
|
|
|
|
def waiver_cases():
|
|
fails = []
|
|
today = datetime.date.today()
|
|
d = lambda n: (today + datetime.timedelta(days=n)).isoformat()
|
|
BEHIND = (["9.9.9", "9.9.8", "9.9.7"], "9.9.7") # released 9.9.9, golden 9.9.7: two behind
|
|
UNRECORDED = (["9.9.9", "9.9.7"], "9.9.8") # golden 9.9.8 has no heading (R-385)
|
|
|
|
def check(label, released_baked, wtext, want_rc, must=(), must_not=(), rows=("R-468",)):
|
|
env, tmp = synthetic_tree(released_baked[0], released_baked[1], wtext, rows)
|
|
try:
|
|
rc, out = run_gate_env(env)
|
|
finally:
|
|
shutil.rmtree(tmp, ignore_errors=True)
|
|
bad = [m for m in must if m not in out] + ["NOT expected: " + m for m in must_not if m in out]
|
|
if rc != want_rc or bad:
|
|
fails.append("%s: rc=%d (wanted %d) %s\n%s" % (label, rc, want_rc, bad, out[-900:]))
|
|
else:
|
|
print("%s ok" % label)
|
|
|
|
# BASELINE for the synthetic tree — behind, no waiver: convicted exactly as before this change.
|
|
check("CASE 5 (baseline, behind, no waiver -> 1)", BEHIND, None, 1,
|
|
must=("GOLDEN CURRENCY GATE FAILED", "2 release(s) behind"))
|
|
# E — a valid waiver turns the conviction into a LOUD advisory, exit 0.
|
|
check("CASE 6 (E: valid waiver, behind -> ADVISORY, 0)", BEHIND, waiver(d(0), d(13)), 0,
|
|
must=("ADVISORY", "WAIVED", "R-468", d(13), "2 release(s) behind", "OK (WAIVED)"),
|
|
must_not=("GOLDEN CURRENCY GATE FAILED",))
|
|
# F — the same waiver, expired: red again, and the reader learns it RAN OUT.
|
|
check("CASE 7 (F: expired waiver, behind -> 1, names the date)", BEHIND,
|
|
waiver(d(-10), d(0)), 1, must=("GOLDEN CURRENCY GATE FAILED", "EXPIRED on %s" % d(0)))
|
|
check("CASE 7b (F: waiver expired yesterday -> 1)", BEHIND, waiver(d(-10), d(-1)), 1,
|
|
must=("EXPIRED on %s" % d(-1),))
|
|
# G — a valid waiver never saves an UNRECORDED golden (R-385).
|
|
check("CASE 8 (G: valid waiver, UNRECORDED golden -> 1)", UNRECORDED, waiver(d(0), d(13)), 1,
|
|
must=("UNRECORDED", "DOES NOT COVER THIS", "R-385"))
|
|
# H — a waiver that tries to be permanent, or is malformed, is INCONCLUSIVE — never 0.
|
|
check("CASE 9 (H: 15-day waiver -> 2)", BEHIND, waiver(d(0), d(15)), 2,
|
|
must=("MALFORMED", "hard limit is 14"))
|
|
check("CASE 9b (H: exactly 14 days is the limit and PASSES)", BEHIND, waiver(d(0), d(14)), 0,
|
|
must=("ADVISORY",))
|
|
check("CASE 10 (H: absent expiry -> 2)", BEHIND, waiver(d(0), None), 2,
|
|
must=("MALFORMED", "expires"))
|
|
check("CASE 11 (H: unparseable expiry -> 2)", BEHIND, waiver(d(0), "next week"), 2,
|
|
must=("MALFORMED", "expires"))
|
|
check("CASE 12 (H: register row does not exist -> 2)", BEHIND, waiver(d(0), d(13), row="R-999999"),
|
|
2, must=("MALFORMED", "R-999999", "does not exist"))
|
|
check("CASE 12b (H: empty reason -> 2)", BEHIND, waiver(d(0), d(13), reason=""), 2,
|
|
must=("MALFORMED", "reason"))
|
|
# THE DECOY (R-421): a file at the right path saying only `expires` must not pass.
|
|
check("CASE 13 (DECOY: a file containing only the word `expires` -> 2)", BEHIND, "expires\n", 2,
|
|
must=("MALFORMED",), must_not=("ADVISORY", "OK (WAIVED)"))
|
|
# A malformed waiver is refused EVEN WHEN the golden is current — it must not lie there as cover.
|
|
check("CASE 14 (malformed waiver, golden CURRENT -> still 2)", (["9.9.9"], "9.9.9"),
|
|
waiver(d(0), d(30)), 2, must=("MALFORMED",))
|
|
# An expired waiver with a current golden: OK, but the expiry is NAMED.
|
|
check("CASE 15 (expired waiver, golden current -> 0 with a note)", (["9.9.9"], "9.9.9"),
|
|
waiver(d(-10), d(-1)), 0, must=("expired on %s" % d(-1), "golden currency gate OK"))
|
|
return fails
|
|
|
|
|
|
sys.exit(main())
|