R-404: the golden NOTICE, in the repo where the debt is created - NOT A RELEASE
gates / gates (push) Successful in 12s
gates / gates (push) Successful in 12s
No version heading on purpose. No Go code, no image, no version bump; giving this one would create the exact golden debt the change is about. Until today this repo - where a release actually happens - had NO golden-currency check at all, while felhom.eu ran one on every push including documents-only ones that can neither create the debt nor clear it. The person who could act heard nothing; the person who could not act was blocked, thirteen --no-verify uses' worth. golden_notice.py is ADVISORY IN EVERY CASE, and that is the only correct behaviour rather than timidity: at the moment a release is committed the golden legitimately does not exist yet, so blocking there would refuse the commit that STARTS the process - and blocking later is the mistake being undone. NO SECOND IMPLEMENTATION: it IMPORTS felhom.eu/scripts/golden_currency_gate.py and calls that gate's own released_versions()/newest_baked(), so it is the same comparison read in the other direction. Cross-repo shape copied from instructions_gate.py; never a copy of the script, because a copy recreates the drift these gates exist to detect. An absent sibling clone is INCONCLUSIVE and silent about currency - it never guesses. controller_gates.py GAINED A FIFTH `blocking` FIELD. It could not express a reporting-only gate at all before: every registered gate's non-zero exit failed the run, so the only way to add a notice was to give it the power to refuse a push. The capability was added rather than the notice compromised (R-420). False for exactly one gate, and test_golden_notice.py asserts it stays one. Tests N1-N4 with a positive control that every other gate is still blocking. RED-PROOF RUN: making the debt branch return 1 fails N1 - in production that would refuse the commit that starts a release.
This commit is contained in:
@@ -52,28 +52,44 @@ SHARED_INSTRUCTIONS = os.path.join(
|
||||
SHARED_OBSERVATIONS = os.path.join(
|
||||
os.path.dirname(REPO), "felhom.eu", "scripts", "observations_gate.py")
|
||||
|
||||
# (label, absolute script path, args, fast)
|
||||
# R-404 — the golden NOTICE. Lives here, beside the runner, because it is about THIS repo's
|
||||
# releases; it imports the felhom.eu gate rather than copying its comparison.
|
||||
GOLDEN_NOTICE = os.path.join(SCRIPTS, "golden_notice.py")
|
||||
|
||||
# (label, absolute script path, args, fast, blocking)
|
||||
#
|
||||
# `blocking` — R-404, 2026-09-01. FALSE means this gate REPORTS and never changes the runner's exit
|
||||
# code. Before this the runner could not express such a gate at all: every registered gate's
|
||||
# non-zero exit failed the run, so the only way to add a notice was to give it the power to refuse
|
||||
# a push. That was the wrong trade for the golden notice, whose whole point is that it fires at the
|
||||
# moment a release is committed — when the golden legitimately does not exist yet and refusing
|
||||
# would be absurd. The capability was added rather than the notice compromised.
|
||||
#
|
||||
# It is FALSE for exactly one gate. Everything else blocks, as it always has.
|
||||
GATES = [
|
||||
("template-id", os.path.join(SCRIPTS, "template_id_gate.py"), [], True),
|
||||
("emoji", os.path.join(SCRIPTS, "emoji_gate.py"), [], True),
|
||||
("native-confirm", os.path.join(SCRIPTS, "native_confirm_gate.py"), [], True),
|
||||
("offbox-rename", os.path.join(SCRIPTS, "offbox_rename_gate.py"), [], True),
|
||||
("app-row-dedup", os.path.join(SCRIPTS, "app_row_dedup_gate.py"), [], True),
|
||||
("mojibake", os.path.join(SCRIPTS, "mojibake_gate.py"), [], True),
|
||||
("docker-v", os.path.join(SCRIPTS, "docker_run_volume_path_gate.py"), [], True),
|
||||
("secret-markup", os.path.join(SCRIPTS, "secret_in_markup_gate.py"), [], True),
|
||||
("retrieval-promise", os.path.join(SCRIPTS, "retrieval_promise_gate.py"), [], True),
|
||||
("template-id", os.path.join(SCRIPTS, "template_id_gate.py"), [], True, True),
|
||||
("emoji", os.path.join(SCRIPTS, "emoji_gate.py"), [], True, True),
|
||||
("native-confirm", os.path.join(SCRIPTS, "native_confirm_gate.py"), [], True, True),
|
||||
("offbox-rename", os.path.join(SCRIPTS, "offbox_rename_gate.py"), [], True, True),
|
||||
("app-row-dedup", os.path.join(SCRIPTS, "app_row_dedup_gate.py"), [], True, True),
|
||||
("mojibake", os.path.join(SCRIPTS, "mojibake_gate.py"), [], True, True),
|
||||
("docker-v", os.path.join(SCRIPTS, "docker_run_volume_path_gate.py"), [], True, True),
|
||||
("secret-markup", os.path.join(SCRIPTS, "secret_in_markup_gate.py"), [], True, True),
|
||||
("retrieval-promise", os.path.join(SCRIPTS, "retrieval_promise_gate.py"), [], True, True),
|
||||
# R-400 — every debug-page control resolves to a handler, and every handler is reachable.
|
||||
# Registered AFTER the seven dead controls were implemented or deleted: a registered-but-failing
|
||||
# gate refuses every push, so the order matters here exactly as it did for instructions_gate.
|
||||
("debug-routes", os.path.join(SCRIPTS, "debug_route_gate.py"), [], True),
|
||||
("reuse-refs", SHARED_REUSE, [REPO], True),
|
||||
("instructions", SHARED_INSTRUCTIONS, [REPO], True),
|
||||
("debug-routes", os.path.join(SCRIPTS, "debug_route_gate.py"), [], True, True),
|
||||
("reuse-refs", SHARED_REUSE, [REPO], True, True),
|
||||
("instructions", SHARED_INSTRUCTIONS, [REPO], True, True),
|
||||
# R-389 — a REPORT.md observation with no register row behind it. Fast: stdlib file reads.
|
||||
("observations", SHARED_OBSERVATIONS, [REPO], True),
|
||||
("observations", SHARED_OBSERVATIONS, [REPO], True, True),
|
||||
# R-404 — ADVISORY. Reports the golden debt where it is created; never refuses.
|
||||
("golden-notice", GOLDEN_NOTICE, [REPO], True, False),
|
||||
]
|
||||
|
||||
VERDICT = {0: "OK", 1: "FAILED", 2: "INCONCLUSIVE"}
|
||||
ADVISORY = "ADVISORY" # R-404: a non-blocking gate — it reports, it never refuses
|
||||
|
||||
|
||||
def hooks_armed_note(root):
|
||||
@@ -124,21 +140,26 @@ def main(argv):
|
||||
print(" --fast SKIPPED (deliberate periodic runs, never in a hook): %s" % ", ".join(skipped))
|
||||
hooks_armed_note(REPO)
|
||||
|
||||
results = [(label, run_gate(label, path, args)) for label, path, args, _f in selected]
|
||||
results = [(label, run_gate(label, path, args), blocking)
|
||||
for label, path, args, _f, blocking in selected]
|
||||
|
||||
print("\n" + "=" * 78)
|
||||
print("== summary")
|
||||
print("=" * 78)
|
||||
worst = 0
|
||||
for label, rc in results:
|
||||
for label, rc, blocking in results:
|
||||
if not blocking:
|
||||
# A non-blocking gate's exit code is INFORMATION, never a verdict on the push.
|
||||
print(" %-18s %-13s (exit %d, advisory)" % (label, ADVISORY, rc))
|
||||
continue
|
||||
print(" %-18s %-13s (exit %d)" % (label, VERDICT.get(rc, "ERROR"), rc))
|
||||
if rc != 0:
|
||||
worst = 1 if rc == 1 or worst == 1 else 2
|
||||
if worst == 0:
|
||||
print("\nall controller gates OK")
|
||||
return 0
|
||||
convicted = [l for l, rc in results if rc == 1]
|
||||
undecided = [l for l, rc in results if rc not in (0, 1)]
|
||||
convicted = [l for l, rc, b in results if rc == 1 and b]
|
||||
undecided = [l for l, rc, b in results if rc not in (0, 1) and b]
|
||||
if convicted:
|
||||
print("\nCONVICTED: %s" % ", ".join(convicted))
|
||||
if undecided:
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""golden_notice.py — tell the repo that CREATES the golden debt, at the moment it creates it.
|
||||
|
||||
Usage: python3 scripts/golden_notice.py <repo-root>
|
||||
Exit ALWAYS 0 when it can answer, 2 when it cannot. **NEVER 1. It cannot refuse a push.**
|
||||
|
||||
WHY THIS EXISTS (R-404, 2026-09-01).
|
||||
|
||||
The golden-currency check was pointed at the wrong repository. `felhom.eu` — which holds the bake
|
||||
evidence, the register and the architecture — ran it on every push, including pushes that touch only
|
||||
documents and therefore can neither create the debt nor clear it. `felhom-controller` — where a
|
||||
release actually happens — **never checked at all.** So the person who could act heard nothing and
|
||||
the person who could not act was blocked, and `--no-verify` was reached for thirteen times.
|
||||
|
||||
This is the other half of that correction: the notice belongs where the debt is born.
|
||||
|
||||
⚠ IT IS ADVISORY IN EVERY CASE, AND THAT IS NOT TIMIDITY — IT IS THE ONLY CORRECT BEHAVIOUR.
|
||||
At the moment a release is committed the golden legitimately does NOT exist yet: you cannot bake a
|
||||
golden for a version you have not pushed. Blocking here would refuse the very commit that starts the
|
||||
process. And blocking LATER is the mistake this whole change is undoing. So it prints, and the
|
||||
runner's exit code is untouched. `controller_gates.py` gained a `blocking` field to express that;
|
||||
before this, that runner could not describe a gate that reports without refusing.
|
||||
|
||||
⚠ IT NEVER GUESSES. With no `felhom.eu` sibling clone it says INCONCLUSIVE and stays silent about
|
||||
currency — an absent input is "I do not know", never "fine".
|
||||
|
||||
NO SECOND IMPLEMENTATION. It IMPORTS `felhom.eu/scripts/golden_currency_gate.py` and calls that
|
||||
gate's own `released_versions()` and `newest_baked()`, so the comparison here is the SAME
|
||||
comparison, read in the other direction. A private copy of "which version owes a golden" is a second
|
||||
thing that can be wrong, and the two would drift. This follows `instructions_gate.py`'s cross-repo
|
||||
pattern: the shared script lives in ONE repo and is invoked across the workspace, never copied.
|
||||
"""
|
||||
import importlib.util
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
CTRL = os.path.dirname(HERE)
|
||||
REPO_DEFAULT = os.path.dirname(CTRL)
|
||||
|
||||
|
||||
def load_gate(repo_root):
|
||||
"""Import the sibling felhom.eu gate. Returns (module, tried_path) or (None, tried_path)."""
|
||||
path = os.path.join(os.path.dirname(repo_root), "felhom.eu", "scripts",
|
||||
"golden_currency_gate.py")
|
||||
if not os.path.isfile(path):
|
||||
return None, path
|
||||
try:
|
||||
spec = importlib.util.spec_from_file_location("golden_currency_gate", path)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod, path
|
||||
except Exception as e:
|
||||
sys.stdout.write("golden-notice: the sibling gate could not be imported: %s\n" % e)
|
||||
return None, path
|
||||
|
||||
|
||||
def main(argv):
|
||||
repo_root = os.path.abspath(argv[1]) if len(argv) > 1 else REPO_DEFAULT
|
||||
gate, tried = load_gate(repo_root)
|
||||
if gate is None:
|
||||
print("golden-notice: INCONCLUSIVE — no felhom.eu sibling clone.")
|
||||
print(" tried: %s" % tried)
|
||||
print(" Nothing is claimed about golden currency. An absent input is 'I do not know',")
|
||||
print(" never 'fine'. This is still NOT a refusal — it never blocks a push.")
|
||||
return 2
|
||||
|
||||
try:
|
||||
released = gate.released_versions()
|
||||
baked = gate.newest_baked()
|
||||
except Exception as e:
|
||||
print("golden-notice: INCONCLUSIVE — the sibling gate raised %s" % e)
|
||||
return 2
|
||||
|
||||
# `released_versions()` returns newest-first; `newest_baked()` returns the newest bake.
|
||||
newest_rel = released[0] if released else None
|
||||
newest_bake = baked[0] if isinstance(baked, tuple) else baked
|
||||
if newest_rel is None:
|
||||
print("golden-notice: INCONCLUSIVE — could not read a released version from CHANGELOG.md.")
|
||||
return 2
|
||||
|
||||
rel_s = gate.vstr(newest_rel)
|
||||
bake_s = gate.vstr(newest_bake) if newest_bake else "none"
|
||||
|
||||
if newest_bake and tuple(newest_bake) >= tuple(newest_rel):
|
||||
print("golden-notice: OK — v%s is released and a golden carries it (newest bake %s)."
|
||||
% (rel_s, bake_s))
|
||||
return 0
|
||||
|
||||
# The debt exists. Say so plainly, and say what clears it.
|
||||
print("=" * 78)
|
||||
print("NOTICE — v%s OWES A GOLDEN. (this NEVER blocks; see the docstring)" % rel_s)
|
||||
print("=" * 78)
|
||||
print(" newest released controller : %s (this repo's CHANGELOG.md)" % rel_s)
|
||||
print(" newest golden baked : %s (felhom.eu documentation/tests/)" % bake_s)
|
||||
print("")
|
||||
print(" A machine installed right now would receive %s, not %s." % (bake_s, rel_s))
|
||||
print("")
|
||||
print(" This is a REMINDER AT THE ONE MOMENT IT IS USEFUL — you are in the repo where the")
|
||||
print(" release happens. It does not block, and must not: at the moment a release is")
|
||||
print(" committed the golden cannot exist yet.")
|
||||
print("")
|
||||
print(" WHAT CLEARS IT: bake a golden (felhom.eu 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 bake record lands in felhom.eu documentation/tests/golden-<ver>-<date>/.")
|
||||
print("")
|
||||
print(" If this release deliberately needs no golden, record a waiver row in")
|
||||
print(" felhom.eu documentation/backlog/OPEN-ITEMS.md — never a habit of bypassing.")
|
||||
print("=" * 78)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""test_golden_notice.py — the notice REPORTS and never REFUSES (R-404).
|
||||
|
||||
N1 IS THE LOAD-BEARING CASE. The notice's value depends entirely on it being harmless: it fires at
|
||||
the moment a release is committed, when the golden legitimately cannot exist yet. A notice that
|
||||
blocked there would refuse the very commit that starts the process, and would be disabled within a
|
||||
day. Its red-proof is written out below and was run.
|
||||
|
||||
Run from `controller/`: python3 scripts/test_golden_notice.py
|
||||
Exit 0 all pass · 1 a case failed.
|
||||
"""
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
CTRL = os.path.dirname(HERE)
|
||||
REPO = os.path.dirname(CTRL)
|
||||
NOTICE = os.path.join(HERE, "golden_notice.py")
|
||||
SHA = "9287f7cef5f13166276e8406005e3f28004004510c5184f1c1c7377f7aafad2e"
|
||||
|
||||
|
||||
def run(repo_root):
|
||||
p = subprocess.run([sys.executable, NOTICE, repo_root], capture_output=True, text=True)
|
||||
return p.returncode, p.stdout + p.stderr
|
||||
|
||||
|
||||
def fake_workspace(tmp, released, baked):
|
||||
"""A miniature workspace: <tmp>/felhom-controller + <tmp>/felhom.eu, only what the gate reads."""
|
||||
ctrl = os.path.join(tmp, "felhom-controller")
|
||||
eu = os.path.join(tmp, "felhom.eu")
|
||||
os.makedirs(ctrl)
|
||||
os.makedirs(os.path.join(eu, "scripts"))
|
||||
tests = os.path.join(eu, "documentation", "tests")
|
||||
os.makedirs(tests)
|
||||
with io.open(os.path.join(ctrl, "CHANGELOG.md"), "w", encoding="utf-8") as fh:
|
||||
fh.write(u"# changelog\n\n## v%s — a release\n\nstuff\n" % released)
|
||||
# the real gate, copied in so the notice imports a genuine one
|
||||
shutil.copy(os.path.join(os.path.dirname(REPO), "felhom.eu", "scripts",
|
||||
"golden_currency_gate.py"),
|
||||
os.path.join(eu, "scripts", "golden_currency_gate.py"))
|
||||
if baked:
|
||||
d = os.path.join(tests, "golden-%s-2026-01-01" % baked)
|
||||
os.makedirs(d)
|
||||
with io.open(os.path.join(d, "bake.log"), "w", encoding="utf-8") as fh:
|
||||
fh.write(u"[golden] upload OK\nGOLDEN_VERSION=%s\nGOLDEN_SHA256=%s\n" % (baked, SHA))
|
||||
return ctrl
|
||||
|
||||
|
||||
def main():
|
||||
fails = []
|
||||
|
||||
# --- N1: a version with no golden -> the notice PRINTS, exit code UNCHANGED (0) -----------
|
||||
# RED-PROOF (run 2026-09-01, recorded in REPORT.md): changing the debt branch's `return 0` to
|
||||
# `return 1` makes this fail — and in production would refuse the commit that starts a release.
|
||||
tmp = tempfile.mkdtemp(prefix="gnotice-")
|
||||
try:
|
||||
ctrl = fake_workspace(tmp, "0.240.0", "0.230.0")
|
||||
rc, out = run(ctrl)
|
||||
if rc != 0:
|
||||
fails.append("N1: a debt must NOT change the exit code — the notice fires when the "
|
||||
"golden cannot exist yet, so blocking there refuses the commit that "
|
||||
"starts the release. Got exit %d" % rc)
|
||||
elif "0.240.0" not in out or "OWES A GOLDEN" not in out:
|
||||
fails.append("N1: the notice must NAME the version that owes a golden; got:\n%s" % out)
|
||||
elif "0.230.0" not in out:
|
||||
fails.append("N1: the notice must also say which golden IS current; got:\n%s" % out)
|
||||
else:
|
||||
print("N1 ok: debt named (0.240.0 owes; newest bake 0.230.0), exit 0 - never blocks")
|
||||
finally:
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
# --- N2: sibling clone absent -> INCONCLUSIVE, silent about currency, still non-blocking ---
|
||||
tmp = tempfile.mkdtemp(prefix="gnotice-")
|
||||
try:
|
||||
lonely = os.path.join(tmp, "felhom-controller")
|
||||
os.makedirs(lonely)
|
||||
rc, out = run(lonely)
|
||||
if rc != 2:
|
||||
fails.append("N2: an absent sibling must be INCONCLUSIVE (exit 2), never a pass and "
|
||||
"never a conviction; got %d" % rc)
|
||||
elif "OWES A GOLDEN" in out or "OK —" in out:
|
||||
fails.append("N2: with no sibling it must stay SILENT about currency; got:\n%s" % out)
|
||||
elif "INCONCLUSIVE" not in out:
|
||||
fails.append("N2: it must say INCONCLUSIVE out loud; got:\n%s" % out)
|
||||
else:
|
||||
print("N2 ok: absent sibling -> INCONCLUSIVE, silent about currency, exit 2")
|
||||
# and exit 2 must still not fail the runner, because the gate is registered non-blocking
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"cg", os.path.join(HERE, "controller_gates.py"))
|
||||
cg = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(cg)
|
||||
row = [g for g in cg.GATES if g[0] == "golden-notice"]
|
||||
if not row:
|
||||
fails.append("N2: golden-notice is not registered in controller_gates.py at all")
|
||||
elif row[0][4] is not False:
|
||||
fails.append("N2: golden-notice must be registered NON-BLOCKING (5th field False); "
|
||||
"got %r" % (row[0][4],))
|
||||
else:
|
||||
print("N2 ok: registered non-blocking, so exit 2 cannot fail the runner")
|
||||
# POSITIVE CONTROL: every OTHER gate must still be blocking, or this proves nothing.
|
||||
nonblocking = [g[0] for g in cg.GATES if g[4] is False]
|
||||
if nonblocking != ["golden-notice"]:
|
||||
fails.append("N2 CONTROL: exactly ONE gate may be non-blocking; got %r" % nonblocking)
|
||||
else:
|
||||
print("N2 ok (control): golden-notice is the ONLY non-blocking gate")
|
||||
finally:
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
# --- N3: currency fine -> nothing beyond the ordinary line --------------------------------
|
||||
tmp = tempfile.mkdtemp(prefix="gnotice-")
|
||||
try:
|
||||
ctrl = fake_workspace(tmp, "0.230.0", "0.230.0")
|
||||
rc, out = run(ctrl)
|
||||
if rc != 0:
|
||||
fails.append("N3: a current golden must exit 0; got %d" % rc)
|
||||
elif "OWES A GOLDEN" in out:
|
||||
fails.append("N3: it must not cry wolf when the golden is current; got:\n%s" % out)
|
||||
elif len([l for l in out.splitlines() if l.strip()]) != 1:
|
||||
fails.append("N3: a healthy check must be ONE line — a gate that prints a paragraph "
|
||||
"every run is one people stop reading; got:\n%s" % out)
|
||||
else:
|
||||
print("N3 ok: current golden -> one quiet line, exit 0")
|
||||
# NEGATIVE CONTROL: a string that cannot be there.
|
||||
if "ZZZ-NOT-IN-THE-OUTPUT" in out:
|
||||
fails.append("N3: negative control matched — the search is not discriminating")
|
||||
finally:
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
# --- N4: a golden AHEAD of the record is not reported as a debt ----------------------------
|
||||
tmp = tempfile.mkdtemp(prefix="gnotice-")
|
||||
try:
|
||||
ctrl = fake_workspace(tmp, "0.230.0", "0.240.0")
|
||||
rc, out = run(ctrl)
|
||||
if "OWES A GOLDEN" in out:
|
||||
fails.append("N4: a golden AHEAD of the newest release is not a missing golden; that "
|
||||
"is R-385's direction and belongs to the felhom.eu gate, not here")
|
||||
else:
|
||||
print("N4 ok: a golden ahead of the record is not reported here as a debt")
|
||||
finally:
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
if fails:
|
||||
print()
|
||||
for f in fails:
|
||||
print("FAIL: %s" % f)
|
||||
return 1
|
||||
print("\ngolden-notice tests OK — it reports, it never refuses")
|
||||
return 0
|
||||
|
||||
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user