09 §3 decision 250 (operator 2026-10-15 11:26/11:29): a release needs no golden, CC raises the floor after the three test boxes, the golden is weekly; 247 reversed, 7 stands. golden_currency_gate judges the golden's AGE (>7 days); Sunday golden step; rule files, CLAUDE.md, runbooks, build skill follow; R-1045 closed (floor 0.313.0); R-1051 floor route, R-1052 new-box path, R-1053 rc tags never moved
gates / gates (push) Successful in 6m37s

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-15 11:41:11 +02:00
parent 5791fa83e3
commit e92501f5a1
19 changed files with 220 additions and 42 deletions
+12
View File
@@ -1,3 +1,15 @@
## 2026-10-15 — golden_currency_gate: the golden is judged by its AGE (`09` §3 decision 250)
- A controller release needs no golden (operator ruling 2026-10-15 11:26). The gate no longer convicts because the newest
controller CHANGELOG heading is ahead of the newest bake; it convicts when a release is ahead AND the newest bake is MORE
THAN 7 days old (the date in its evidence directory). Ahead and at most 7 days old: an advisory, exit 0. Unchanged: an
unrecorded golden (R-385) at any age; a malformed waiver is exit 2; a valid waiver turns the age conviction into an
advisory; documents-only pushes stay advisory (R-404). The gate stays offline, so it cannot see the floor or the vouch —
„release ahead" stands in for „floor ahead".
- `test_golden_currency_gate.py` CASE 18–21: 3 days → 0, 8 days → 1, exactly 7 → 0, an unrecorded golden however fresh → 1.
Red-proved both ways (limit 0: CASE 18/20 fail; limit 30: CASE 19 fails).
- `repo_gates.py`: the advisory block quotes the golden's age and names the weekly bake.
## 2026-10-14 (night) — https_only_check.py: the box-walk check for „https only” (R-1033)
- New `scripts/https_only_check.py`. Reads each box's public names from the box (traefik labels + dynamic files, the
+39 -5
View File
@@ -1,5 +1,19 @@
# -*- coding: utf-8 -*-
"""Golden-currency gate (R-242) — a controller release is not DELIVERED until a golden carries it.
"""Golden-currency gate (R-242) — the golden must not fall more than a WEEK behind the releases.
── THE AGE RULE (2026-10-15, operator, `09` §3 decision 250) — read this first ───────────────────
A controller release needs NO golden: the floor carries it to every box and a new box installs the vouched golden
and updates itself to the floor. The golden is baked WEEKLY (Sunday night, runbooks/nightly-rotation.md). So this gate
no longer convicts because the newest CHANGELOG heading is ahead of the newest bake; it convicts when the release is
ahead AND the newest bake is MORE THAN 7 DAYS old (by the date in its evidence directory). Ahead and at most 7 days old
is an advisory. Everything below still holds: an unrecorded golden (R-385) is convicted at any age; a malformed waiver
is exit 2; a valid waiver turns the age conviction into an advisory. The gate stays offline (`--fast`): it cannot read
the floor or the vouch from the hub, so „release ahead" stands in for „floor ahead" (the floor follows a release within
hours, after the three test boxes). Pinned by test_golden_currency_gate.py CASE 18-21.
(The text below is the gate's history; its „not DELIVERED until a golden carries it" premise is the old rule.)
Run from the repo root: python3 scripts/golden_currency_gate.py
Exit 0 clean · 1 convicted (a released controller has no golden) · 2 inconclusive.
@@ -151,6 +165,9 @@ REGISTER_PATH = os.environ.get("GOLDEN_GATE_REGISTER",
os.path.join(ROOT, "documentation", "backlog", "OPEN-ITEMS.md"))
# The HARD LIMIT on a waiver's life. Enforced here and nowhere else — see the module docstring.
WAIVER_MAX_DAYS = 14
# The age rule (decision 250): a golden at most this many days old may lag the newest release.
GOLDEN_MAX_AGE_DAYS = 7
NEWEST_BAKE_DATE = None # set by newest_baked(): the date in the newest bake's evidence directory
# `## v0.206.0 — …` on the FIRST such line: the CHANGELOG is newest-first by convention.
RELEASED_RE = re.compile(r"^##\s+v(\d+)\.(\d+)\.(\d+)\b")
@@ -258,6 +275,8 @@ def newest_baked():
same = [lbl for k, lbl in found if k[0] == key[0]]
if len(same) > 1:
print(" %d bakes of %s on file; the NEWEST is read: %s" % (len(same), ".".join(map(str, key[0])), label))
global NEWEST_BAKE_DATE
NEWEST_BAKE_DATE = datetime.date.fromisoformat(key[1])
return key[0], label
@@ -399,6 +418,19 @@ def main():
if released > baked:
lag = sorted(v for v in every_released if v > baked)
today = datetime.datetime.now(datetime.timezone.utc).date()
age = (today - NEWEST_BAKE_DATE).days if NEWEST_BAKE_DATE else None
print(" golden age : %s day(s) (baked %s; the limit is %d — `09` §3 decision 250)"
% (age, NEWEST_BAKE_DATE, GOLDEN_MAX_AGE_DAYS))
if age is not None and age <= GOLDEN_MAX_AGE_DAYS:
print("")
print("GOLDEN CURRENCY GATE ADVISORY — the golden (%s) is behind the newest release (v%s, %d release(s): %s) "
"but only %d day(s) old. Releases ride the floor; the golden is weekly (decision 250). A new box "
"installs %s and updates itself to the floor."
% (vstr(baked), vstr(released), len(lag), ", ".join(vstr(v) for v in lag), age, vstr(baked)))
print("golden currency gate OK — the golden is at most %d days old (NOTE: the BAKE, not the vouch)"
% GOLDEN_MAX_AGE_DAYS)
return
if wstate == "valid":
print("")
print("GOLDEN CURRENCY GATE ADVISORY — WAIVED, NOT CLEAN: controller v%s is released and "
@@ -415,16 +447,18 @@ def main():
"a valid waiver covers it (NOTE: this checks the BAKE, not the vouch)")
return
print("")
print("GOLDEN CURRENCY GATE FAILED: controller v%s is released and NO golden carries it "
"(newest bake is %s; %d release(s) behind)." % (vstr(released), vstr(baked), len(lag)))
print("GOLDEN CURRENCY GATE FAILED: controller v%s is released and the newest golden (%s) is %s day(s) old "
"— more than %d (decision 250: the golden is baked weekly; %d release(s) behind)."
% (vstr(released), vstr(baked), age, GOLDEN_MAX_AGE_DAYS, len(lag)))
if wstate == "expired":
print("The waiver at documentation/tests/golden-waiver.yml EXPIRED on %s (%s). It ran "
"out, as a dated waiver is meant to: bake a golden, or renew it with a new dated "
"commit (at most %d days)." % (winfo["expires"], winfo["row"], WAIVER_MAX_DAYS))
print("A machine installed right now would receive v%s — the release is written, tested and "
"pushed, and NOT delivered." % vstr(baked))
print("Fix: bake a golden per documentation/runbooks/RUNBOOK-manual-build.md §4.1, then vouch "
"it (a THREE-field change: golden_version + agent_version + min_agent).")
print("Fix: bake the weekly golden at the floor's version per documentation/runbooks/RUNBOOK-manual-build.md "
"§4.1 (runbooks/nightly-rotation.md, Sunday) and put „vouch golden X\" on the operator's morning list — "
"the vouch is the operator's (a THREE-field change: golden_version + agent_version + min_agent).")
print("If the cadence ruling covers this release, issue a DATED waiver at "
"documentation/tests/golden-waiver.yml (RUNBOOK-manual-build.md §4.2) — never a bypass.")
sys.exit(1)
+5 -5
View File
@@ -328,17 +328,17 @@ def _print_advisory_block(advisories):
# "which version owes a golden" is a second thing that can be wrong.
for line in text.splitlines():
t = line.strip()
if t.startswith("newest released controller") or t.startswith("newest golden baked"):
if t.startswith("newest released controller") or t.startswith("newest golden baked") or t.startswith("golden age"):
print("!! %s" % t)
print("!!")
print("!! This push touches DOCUMENTS ONLY, so it can neither create this debt nor clear")
print("!! it — and the push that DOES clear it (a bake record under documentation/tests/)")
print("!! is itself documents-only. Blocking here blocked the cure.")
print("!!")
print("!! WHAT CLEARS IT: bake a golden per documentation/runbooks/RUNBOOK-manual-build.md")
print("!! section 4.1, then vouch it (a THREE-field change: golden_version + agent_version")
print("!! + min_agent). The debt stays visible in STATUS.md and in the controller repo's")
print("!! own golden-notice until then.")
print("!! WHAT CLEARS IT (`09` §3 decision 250): the WEEKLY golden — bake at the floor's version per")
print("!! documentation/runbooks/RUNBOOK-manual-build.md section 4.1 (nightly-rotation.md, Sunday), and the")
print("!! operator vouches it (golden_version + agent_version + min_agent). The gate convicts only a golden")
print("!! more than 7 days old while a release is ahead of it.")
print("!" * 78)
+37 -3
View File
@@ -119,6 +119,7 @@ def main():
fails += waiver_cases()
fails += rebake_cases()
fails += age_cases()
if fails:
print()
@@ -148,7 +149,7 @@ def run_gate_env(env):
return p.returncode, p.stdout + p.stderr
def synthetic_tree(released, baked, waiver_text, register_rows=("R-468",)):
def synthetic_tree(released, baked, waiver_text, register_rows=("R-468",), bake_date="2026-01-01"):
"""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-")
@@ -157,8 +158,8 @@ def synthetic_tree(released, baked, waiver_text, register_rows=("R-468",)):
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",
os.makedirs(os.path.join(ev, "golden-%s-%s" % (baked, bake_date)))
with io.open(os.path.join(ev, "golden-%s-%s" % (baked, bake_date), "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")
@@ -286,4 +287,37 @@ def rebake_cases():
return fails
# ── The age rule (2026-10-15, `09` §3 decision 250) ─────────────────────────────────────────────
#
# A release needs no golden; the golden is weekly. The gate convicts only when the release is AHEAD and the newest
# bake is MORE THAN 7 days old. COMPANION RED-PROOF (2026-10-15): set GOLDEN_MAX_AGE_DAYS to 0 → CASE 18 and CASE 20
# fail (a 3-day and a 7-day golden convicted); set it to 30 → CASE 19 fails (an 8-day golden passes). Restored.
def age_cases():
fails = []
today = datetime.datetime.now(datetime.timezone.utc).date()
ago = lambda n: (today - datetime.timedelta(days=n)).isoformat()
BEHIND = (["9.9.9", "9.9.8", "9.9.7"], "9.9.7")
def check(label, released_baked, bake_date, want_rc, must=(), must_not=()):
env, tmp = synthetic_tree(released_baked[0], released_baked[1], None, bake_date=bake_date)
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)
check("CASE 18 (age: release ahead, golden 3 days old -> 0, advisory)", BEHIND, ago(3), 0,
must=("ADVISORY", "3 day(s) old", "golden currency gate OK"), must_not=("GOLDEN CURRENCY GATE FAILED",))
check("CASE 19 (age: release ahead, golden 8 days old -> 1)", BEHIND, ago(8), 1,
must=("GOLDEN CURRENCY GATE FAILED", "8 day(s) old", "more than 7"))
check("CASE 20 (age: exactly 7 days is the limit and PASSES)", BEHIND, ago(7), 0, must=("ADVISORY",))
check("CASE 21 (age: an UNRECORDED golden is convicted however fresh — R-385)", (["9.9.9", "9.9.7"], "9.9.8"),
ago(0), 1, must=("UNRECORDED",))
return fails
sys.exit(main())