R-459 CLOSED (MariaDB converts itself, proven by harness + live), golden 0.236.0 (R-467), the golden waiver (R-468)

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
This commit is contained in:
2026-09-13 10:14:37 +02:00
parent 4b2e5608c2
commit ae59c31a84
62 changed files with 2607 additions and 220 deletions
+28
View File
@@ -1,3 +1,31 @@
## the golden waiver — goldens on a cadence, not per release (2026-09-13, R-468 / R-242) — NOT A RELEASE
**No product code, no version bump, no image.** A scripts change is not a release.
`golden_currency_gate.py` now reads a dated waiver, `documentation/tests/golden-waiver.yml` (`issued`,
`expires`, `reason`, `register_row`). Operator ruling 2026-09-13: **bake on a cadence — weekly, and
always before any drill or fresh install — not per release.** The gate had tripped on every release
by design and the only honest ways past it were a bake or a declared `--no-verify`; August measured
25 goldens in 26 days and thirteen bypasses.
- **Valid waiver + golden BEHIND the record** → a loud ADVISORY naming the waiver, its expiry and how
many releases the golden lags; exit 0. A waived conviction stays visible on every push.
- **Expired waiver** → exit 1 exactly as before, and the message says it EXPIRED on `<date>`.
- **Unrecorded golden (R-385)** → exit 1 regardless; the waiver is named and declared NOT to cover
it. **The asymmetry is the design:** behind is a cadence choice, unrecorded is the fleet running
something nobody wrote down.
- **Malformed waiver** — more than 14 days, absent or unparseable date, empty reason, a row that does
not exist in `OPEN-ITEMS.md` — → exit 2 INCONCLUSIVE, never 0, never silently ignored. The 14-day
cap lives in the gate (`WAIVER_MAX_DAYS`), not in prose. The register is read for ONE fact (does
the row exist), never for meaning (R-421).
- `GOLDEN_GATE_*` environment variables are a **test seam**: they move where the gate reads, never
what it decides. `test_golden_currency_gate.py` gains cases 5–15 (Scenarios E/F/G/H, each with its
wrong direction; the R-421 decoy — a file saying only `expires` — returns 2). **Red-proof:** the
old gate on the real tree with a valid waiver planted → exit 1 (it cannot read one); the new gate on
synthetic "behind" trees → 0 with the advisory present.
- The docstring's *"honest fix is a recorded waiver, never a habit of bypassing"* is now a mechanism.
**R-242's OTHER half — nothing gates the VOUCH — is unchanged and still open.**
## the decoy sweep — can this gate be fooled by a label? (2026-09-01, R-421) — NOT A RELEASE
**No product code, no version bump, no image, no golden.** A scripts change is not a release.
+184 -7
View File
@@ -37,7 +37,8 @@ That limit is forced, not chosen, and the reasoning is recorded so nobody re-der
and is therefore the step this repo can see; the vouch is an operator act against the hub and needs a
different mechanism. That gap is real and is recorded as R-242's remaining half, NOT papered over
here. In practice the two are minutes apart in the same session, and the recurrence this gate is
built for was a missing BAKE.
built for was a missing BAKE. **That vouch half is STILL open after 2026-09-13** — the waiver below
covers a different thing, and this sentence is kept so the two are not confused.
WHY VERSION AND NOT BEHAVIOUR. It compares version numbers, so a controller release that changed
nothing a customer can see also trips it. That is accepted deliberately: deciding "customer-visible"
@@ -45,7 +46,8 @@ mechanically is not possible, judging it by hand is what already failed twice, a
false trip is one bake — which is the operation the project wants to be routine anyway. **A gate that
cries wolf is one people learn to bypass, and `--no-verify` exists**, so the tolerance is stated
rather than assumed: if this ever fires on a release nobody wants a golden for, the honest fix is a
recorded waiver in the register, never a habit of bypassing.
recorded waiver in the register, never a habit of bypassing. **That waiver is now BUILT — see
"THE WAIVER" below (2026-09-13).**
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
@@ -72,7 +74,47 @@ it is absent. An unrecorded golden is convicted (exit 1) exactly like a stale on
**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.
── THE WAIVER — goldens on a CADENCE, not per release (added 2026-09-13, operator ruling) ────────
What happened between 2026-08-07 and 2026-09-01: **25 goldens in 26 days**, almost one per release,
because this gate trips on every release by design (see WHY VERSION AND NOT BEHAVIOUR) and the only
honest ways past it were a bake or a `--no-verify`. Thirteen bypasses were counted by 2026-09-01
(R-404/R-417). The operator ruled on 2026-09-13: **bake on a cadence — weekly, and always before any
drill or fresh install — not per release.** Every release still raises the FLOOR, so the fleet keeps
getting each release in ~20 s; only the golden, which protects a fresh install and nothing else,
moves to a cadence.
The mechanism is a small tracked file, `documentation/tests/golden-waiver.yml`:
issued: 2026-09-13
expires: 2026-09-27 # at most 14 days after issued, or the gate REFUSES the waiver
reason: pre-customer development; goldens on a weekly cadence (operator ruling 2026-09-13)
register_row: R-468 # must exist as a `**R-468**` row in OPEN-ITEMS.md
While the waiver is VALID, the "behind" conviction becomes a **loud ADVISORY** (exit 0) that names
the waiver, its expiry and how many releases the golden lags. When it runs out, the gate is red
again until someone bakes or renews. **A dated waiver cannot be forgotten — it just expires.** That
is what makes it different from R-242's original rule, which recurred the day after it was written:
renewal is a new commit with a diff, a deliberate act someone can see.
THE ASYMMETRY, stated because it is the whole design: **the waiver covers a golden that is BEHIND the
record. It never covers a golden that is UNRECORDED (R-385).** The first is a cadence choice; the
second is the fleet running something nobody wrote down, and no schedule makes that acceptable.
WHAT REFUSES THE WAIVER (exit 2, INCONCLUSIVE — never 0, never silently ignored): an expiry more
than 14 days after issue; an absent or unparseable date; a missing or empty reason; a register row
that is absent or whose `**R-n**` row does not exist. The 14-day cap is enforced HERE, not in the
runbook, because a cap in prose is the thing that failed. A file at the right path containing only
the word `expires` is the R-421 decoy and is refused like any other malformed waiver.
The register is read for exactly ONE fact — does the named row exist — never for meaning. Reading
prose for meaning is the R-421 class.
WHAT IT DOES NOT COVER: the vouch (still R-242's open half); an unrecorded golden (R-385); anything
after the first external install — the waiver's own row says it is a pre-customer arrangement.
"""
import datetime
import io
import os
import re
@@ -81,8 +123,21 @@ import sys
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# The controller clone sits beside this one. The same sibling assumption reuse_refs_check.py and
# instructions_gate.py already make — an absent sibling is INCONCLUSIVE, never a silent pass.
CONTROLLER_CHANGELOG = os.path.join(os.path.dirname(ROOT), "felhom-controller", "CHANGELOG.md")
EVIDENCE_DIR = os.path.join(ROOT, "documentation", "tests")
#
# The GOLDEN_GATE_* environment variables are a TEST SEAM (2026-09-13): they move WHERE the gate
# reads, never WHAT it decides, so test_golden_currency_gate.py can build a "behind" or an
# "unrecorded" tree without depending on the real controller's release history. Unset in every real
# run — the hook and CI export nothing.
CONTROLLER_CHANGELOG = os.environ.get(
"GOLDEN_GATE_CHANGELOG",
os.path.join(os.path.dirname(ROOT), "felhom-controller", "CHANGELOG.md"))
EVIDENCE_DIR = os.environ.get("GOLDEN_GATE_EVIDENCE_DIR", os.path.join(ROOT, "documentation", "tests"))
WAIVER_PATH = os.environ.get("GOLDEN_GATE_WAIVER",
os.path.join(ROOT, "documentation", "tests", "golden-waiver.yml"))
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
# `## 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")
@@ -182,6 +237,82 @@ def newest_baked():
return found[-1]
WAIVER_KEY_RE = re.compile(r"^\s*([a-z_]+)\s*:\s*(.*?)\s*$")
ROW_RE = re.compile(r"^R-\d+$")
def read_waiver(path=None, register=None, today=None):
"""(state, info) — state is 'absent', 'valid', 'expired' or 'malformed'.
The file is four `key: value` lines; comments (`# …`) and blank lines are ignored. It is parsed by
hand so the gate stays stdlib-only and `--fast`. Every way the file can be wrong returns
'malformed' with the reason in info["why"] — the caller turns that into exit 2, never into 0 and
never into a silent 'absent'. A decoy — the word `expires` with no date — lands here too.
"""
path = path or WAIVER_PATH
register = register or REGISTER_PATH
today = today or datetime.datetime.now(datetime.timezone.utc).date()
if not os.path.isfile(path):
return "absent", {"path": path}
try:
text = io.open(path, encoding="utf-8").read()
except OSError as e:
return "malformed", {"path": path, "why": "cannot be read (%s)" % e}
fields = {}
for line in text.splitlines():
line = line.split("#", 1)[0]
m = WAIVER_KEY_RE.match(line)
if m and m.group(2):
fields[m.group(1)] = m.group(2).strip().strip("'\"")
info = {"path": path, "fields": fields}
def date_of(key):
v = fields.get(key, "")
try:
return datetime.date.fromisoformat(v)
except ValueError:
return None
issued, expires = date_of("issued"), date_of("expires")
if issued is None:
info["why"] = "`issued:` is absent or not a YYYY-MM-DD date (got %r)" % fields.get("issued", "")
return "malformed", info
if expires is None:
info["why"] = "`expires:` is absent or not a YYYY-MM-DD date (got %r)" % fields.get("expires", "")
return "malformed", info
if expires <= issued:
info["why"] = "`expires:` (%s) is not after `issued:` (%s)" % (expires, issued)
return "malformed", info
span = (expires - issued).days
if span > WAIVER_MAX_DAYS:
info["why"] = ("`expires:` is %d days after `issued:` — the hard limit is %d. A waiver that "
"tries to be permanent is refused; renew it with a new dated commit instead."
% (span, WAIVER_MAX_DAYS))
return "malformed", info
if not fields.get("reason"):
info["why"] = "`reason:` is absent or empty"
return "malformed", info
row = fields.get("register_row", "")
if not ROW_RE.match(row):
info["why"] = "`register_row:` is absent or not of the form R-<n> (got %r)" % row
return "malformed", info
# The register is read for ONE fact — does the row exist — never for meaning (R-421).
try:
reg = io.open(register, encoding="utf-8").read()
except OSError as e:
info["why"] = "register %s cannot be read (%s)" % (register, e)
return "malformed", info
if ("**%s**" % row) not in reg:
info["why"] = "`register_row: %s` names a row that does not exist in %s" % (
row, os.path.relpath(register, ROOT) if register.startswith(ROOT) else register)
return "malformed", info
info.update({"issued": issued, "expires": expires, "row": row, "reason": fields["reason"],
"days_left": (expires - today).days})
if today >= expires:
return "expired", info
return "valid", info
def vstr(v):
return ".".join(str(p) for p in v)
@@ -201,6 +332,23 @@ def main():
print(" newest released controller : %s (%s)" % (vstr(released), rel_note))
print(" newest golden baked : %s (documentation/tests/%s)" % (vstr(baked), bake_note))
# The waiver is read BEFORE any verdict, because a malformed one is a result of its own (exit 2)
# whatever the golden's state — a file that says `expires` and means nothing must not lie there
# looking like cover.
wstate, winfo = read_waiver()
if wstate == "malformed":
print("")
print("GOLDEN CURRENCY GATE INCONCLUSIVE: the waiver at %s is MALFORMED — %s"
% (os.path.relpath(winfo["path"], ROOT), winfo["why"]))
print("A malformed waiver is neither cover nor absence. Fix it (four lines: issued, expires "
"<= %d days later, reason, register_row) or delete it." % WAIVER_MAX_DAYS)
sys.exit(2)
if wstate == "valid":
print(" waiver : VALID until %s (%d day(s) left) — %s, reason: %s"
% (winfo["expires"], winfo["days_left"], winfo["row"], winfo["reason"]))
elif wstate == "expired":
print(" waiver : EXPIRED on %s (%s)" % (winfo["expires"], winfo["row"]))
# 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.
@@ -208,6 +356,12 @@ def main():
print("")
print("GOLDEN CURRENCY GATE FAILED: golden %s is baked but UNRECORDED — the controller "
"CHANGELOG has no '## v%s' heading." % (vstr(baked), vstr(baked)))
if wstate == "valid":
# THE ASYMMETRY. A waiver is a cadence choice about a golden that is BEHIND; it says
# nothing about a golden nobody wrote down, and must not be read as if it did.
print("A waiver exists (%s, until %s) and DOES NOT COVER THIS: it covers a golden that is "
"behind the record, never one that is unrecorded (R-385)."
% (winfo["row"], winfo["expires"]))
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 "
@@ -220,17 +374,40 @@ def main():
sys.exit(1)
if released > baked:
lag = sorted(v for v in every_released if v > baked)
if wstate == "valid":
print("")
print("GOLDEN CURRENCY GATE ADVISORY — WAIVED, NOT CLEAN: controller v%s is released and "
"NO golden carries it (newest bake is %s; %d release(s) behind: %s)."
% (vstr(released), vstr(baked), len(lag), ", ".join(vstr(v) for v in lag)))
print("A machine installed right now would receive v%s and reach v%s by self-update."
% (vstr(baked), vstr(released)))
print("Waived by %s until %s (%d day(s) left): %s"
% (winfo["row"], winfo["expires"], winfo["days_left"], winfo["reason"]))
print("This is the operator's 2026-09-13 cadence ruling, not a pass: bake weekly and "
"before ANY drill or fresh install (RUNBOOK-manual-build.md §4.2). When the waiver "
"expires this gate is red again.")
print("golden currency gate OK (WAIVED) — the newest released controller has NO golden; "
"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)." % (vstr(released), vstr(baked)))
"(newest bake is %s; %d release(s) behind)." % (vstr(released), vstr(baked), 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("If this release deliberately needs no golden, record a waiver in "
"documentation/backlog/OPEN-ITEMS.md — never a bypass.")
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)
if wstate == "expired":
print(" NOTE: the waiver expired on %s and covers nothing today (the golden is current). "
"Renew or delete it so it does not read as cover." % winfo["expires"])
print("golden currency gate OK — the newest released controller has a golden "
"(NOTE: this checks the BAKE, not the vouch — see the module docstring)")
+126 -1
View File
@@ -18,6 +18,12 @@ 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
@@ -111,13 +117,132 @@ def main():
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)")
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())