TEST-26: a missing value in a check is a named FAIL, not a traceback
checks / unit (pull_request) Successful in 8m44s

Checks and prints that indexed an empty list, exhausted next(), or formatted
None killed the rest of their file (FCST-06). Read them through the new
named_absence.py (nth/first/field/given/present) and conjoin a presence term.

- every site in the brief, plus the family the new scan and a sweep found
- test_named_absence.py: scans every test_*.py for unguarded filtered-list
  indexing, next() without a default and numeric formats on maybe-None
  names; proven to bite; prints what it cannot see, counted
- test_price.py 16a: the `_T17_CAPS is _T17_CAPS` tautology now grades the
  two assemble() calls' arguments
- test_control.py: coverage table re-measured on runs graded to the end

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
This commit is contained in:
glenn schrooyen
2026-09-25 00:06:58 +02:00
co-authored by Claude Opus 5.5
parent a45ed06c05
commit 770988c902
19 changed files with 853 additions and 286 deletions
+37 -70
View File
@@ -17,6 +17,7 @@ Read MUTATION.md before you report a survivor.
import sys
from named_absence import nth
from app.control import (LOOP_TICK_S, PLANT_SETTLE_DOWN_S, PLANT_SETTLE_S,
SETTLE_CYCLES, SLEW_RAIL_FRAC, Tuning, compute,
slew_limit_w, unasked_power,
@@ -59,68 +60,34 @@ def check(name, cond):
# stopped testing anything is precisely the failure this ticket exists to fix.
# A comment cannot go stale-green, because it never claims to be running.
#
# Measured at 389d9ec. Numbers are the lead's independent reproduction.
# ⚠️ THE SAFETY-13 ROWS WERE RE-MEASURED IN ROUND 2 AND THREE OF THEM WERE
# WRONG AS FIRST WRITTEN. Every figure below marked (S13) was taken by deleting
# the mechanism, purging __pycache__, running this file in the CI image and
# counting `FAIL` lines, on THIS tree; the release/1.0 comparison figures were
# taken the same way on a pristine c8dcb38 worktree.
# Measured at TEST-26 on a purged tree in the CI image, each run graded to the
# end of the file (98/99 where the `if _bad:` check is skipped). Before TEST-26
# the rows marked * killed the file with an IndexError or ZeroDivisionError in
# a check, so their earlier figures were short; those checks now fail by name.
#
# - `output freeze` was written as 11. The mutation form its own note
# describes - the whole `target = min(...)/max(...)` line removed, `reason`
# left alone - gives 6 on this tree AND 6 on the first SAFETY-13 commit,
# against 2 on release/1.0. 11 is not reproducible by that form, or by
# removing the line and its `reason` (4), or by swapping min/max (12). The
# row is 6.
# - `saturation detector, saturated_now = False` was left at 11 while the
# SAFETY-13 section added checks that grade it. It is 15.
# - the footnote's `frozen = False` figure was left at 10 for the same
# reason. It is 13.
#
# The rows below `output freeze` are this ticket's own mechanisms: removing the
# EXIT restores release/1.0's unbounded freeze; making `unasked_power()` return
# True gives an exit to the limit case as well, which is the half of the
# decision that has to stay unbounded; deriving SETTLE_CYCLES from
# PLANT_SETTLE_S (18 s, the UP ramp) instead of PLANT_SETTLE_DOWN_S (23 s) is
# the round-1 defect itself; and pinning `_SAT_COUNT_CAP` back at release/1.0's
# literal 10 caps the counter below the bound and deletes the exit silently.
#
# ⚠️ ONLY THE ROWS THIS BRANCH MOVES WERE RE-MEASURED. Everything not marked
# (S13) still carries 389d9ec's figures and was not re-run here.
#
# ⚠️ THE SLEW ROWS WERE RE-MEASURED AT SAFETY-39 AND ONE OF THEM MOVED: the
# slew limit was 4 and is now 5, because the SAFETY-39 section grades it too.
# The new row is the RAIL DEFAULT specifically - `slew_limit_w()` reduced to
# `return abs(float(tuning.slew_w))`, which leaves the limit intact and only
# removes the 0-means-the-rail resolution. Three checks notice, which is over
# the invariant's floor of two. Both figures were reproduced by deleting the
# mechanism, running this file and counting, on a tree with __pycache__ purged.
#
# mechanism in compute() checks that fail when deleted
# ------------------------------------------ -----------------------------
# integrator freeze (AC 3) 2
# integrator clamp (AC 1) 6
# integrator bound follows max_w 4
# output clamp 3
# slew limit 5
# slew follows the rail (SAFETY-39), `slew_w` read raw 3
# output freeze (S13) 6
# the output freeze's EXIT (S13), freeze made unconditional again 4
# `unasked_power()` discriminates, replaced by `return True` (S13) 6
# SETTLE_CYCLES from PLANT_SETTLE_S, not _DOWN_S (S13) 9
# `_SAT_COUNT_CAP` pinned back at release/1.0's 10 (S13) 6
# deadband 5
# quantisation 2
# saturation detector, `saturated_now = False` (S13) 15
# saturation duration (AC 2), fires instantly 2
# sat counter reset on a good cycle 6
# target_grid_w bias 3
# i_w=None seeding from prev_w 6
#
# The detector figure is for the `saturated_now = False` form specifically;
# disabling it further down as `frozen = False` is a weaker mutation and gives
# 13 on this tree (10 on release/1.0). Reproduce the same form or the number
# will not match.
# mechanism in compute(), and the exact form deleted checks that fail
# --------------------------------------------------------- ----------------
# integrator freeze: `if not frozen or i_w == 0.0` -> `if True` 6
# integrator clamp: `bounded = i_w` 6
# integrator bound follows max_w: default `limit = 5000.0` 3
# output clamp: `target = want` 3
# slew limit: `slewed = target` 17
# slew follows the rail (SAFETY-39): `slew_limit_w()` returns
# `abs(float(tuning.slew_w))` * 9
# output freeze: the `target = min(...)/max(...)` line removed 7
# output freeze: `if frozen:` -> `if False:` * 14
# the output freeze's EXIT (S13): its `if` -> `if False` * 11
# `unasked_power()` returns True (S13) 6
# SETTLE_CYCLES from PLANT_SETTLE_S, not _DOWN_S (S13) 9
# `_SAT_COUNT_CAP = 10`, release/1.0's literal (S13) * 11
# deadband: its `if` -> `if False` 7
# quantisation: `target = target` 4
# saturation detector: `saturated_now = False` * 27
# saturation detector: `frozen = False` * 25
# saturation duration fires instantly: `sat_count >= 1` 5
# sat counter not reset on a good cycle: `else sat_count` 5
# target_grid_w bias: `error = grid_w` 5
# i_w=None seeding from prev_w: `i_w = 0.0` 11
#
# ⚠️ IT HAS FOUND A DEAD MECHANISM TWICE, BOTH THE SAME WAY: a clamp standing in
# for the mechanism under test. Deleting the integrator freeze once failed
@@ -142,10 +109,9 @@ def check(name, cond):
# mutation run always has one, because the previous run wrote it. -B is still
# worth passing, for the NEXT edit rather than this one.
#
# The error is one-directional - stale bytecode can only under-report - so
# every figure above is a lower bound at worst, and the two zeros ever recorded
# were both confirmed by fixing them and watching the count rise, which a
# caching artefact cannot do.
# Two things under-report a figure here, both in the same direction: stale
# bytecode, and a check that raises and stops the file before the rest are
# graded (MUTATION.md, FCST-06). The second is what the * rows above had.
# ---------------------------------------------------------------------------
print("control law")
@@ -520,7 +486,7 @@ for _ in range(20):
rows.append((prev, d.target_w, d.i_w, d.reason))
prev, i_w, sat = d.target_w, d.i_w, d.sat_count
pinned = [r for r in rows if r[3] == "saturated-freeze"]
check(f"non-zero prev_w, another writer: pinned at {pinned[0][1]:.0f} W...",
check(f"non-zero prev_w, another writer: pinned at {nth(pinned, 0)[1]:.0f} W...",
pinned and all(r[1] == -800.0 for r in pinned))
# ⚠️ SAFETY-48 MOVED THIS NUMBER FROM -2390 W TO -1200 W, DELIBERATELY, AND
# THE PROPERTY IT IS HERE FOR IS UNCHANGED. The release lets the command walk
@@ -550,9 +516,9 @@ for c in range(24):
cmds.append(d.target_w)
prev, i_w, sat = d.target_w, d.i_w, d.sat_count
check("the card's case: the block does hold at first, at 0 W",
cmds[6] == 0.0 and cmds[7] == 0.0)
nth(cmds, 6) == 0.0 and nth(cmds, 7) == 0.0)
started = [i for i, c in enumerate(cmds) if c != 0.0]
check(f"the card's case: the charge starts at cycle {started[0]}"
check(f"the card's case: the charge starts at cycle {nth(started, 0)}"
f" instead of never", bool(started))
# ⚠️ AND IT STOPS AT THE FROZEN INTEGRATOR, NOT AT THE RAIL. This is AC 4: the
# integrator moved while the output was blocked, and the release must not turn
@@ -991,7 +957,8 @@ check("the shipped Tuning default is 0, i.e. the rail, so the law carries no "
# serial, per goodwe/NOTES.md §1.1b. It is here because the ARITHMETIC is what
# is being graded, and the arithmetic does not care.
_rails = [100.0, 500.0, 1500.0, 2000.0, 5000.0, 9600.0, 10000.0]
_ticks = {r: r / slew_limit_w(Tuning(max_w=r)) for r in _rails}
# A zero limit grades as nan ticks (a named FAIL), not a ZeroDivisionError.
_ticks = {r: r / (slew_limit_w(Tuning(max_w=r)) or float("nan")) for r in _rails}
print(" ticks to cross the rail at the default, by rail: "
+ ", ".join(f"{r:.0f}W={t:.1f}" for r, t in _ticks.items()))
check("⭐ AT EVERY RAIL THE DEFAULT TAKES THE SAME NUMBER OF TICKS TO CROSS IT - "
@@ -1010,7 +977,7 @@ check("...and that is the same as saying the crossing takes the plant's own "
# refuses on, and it is asserted HERE too so that the law and the gate cannot
# drift apart - the rule can only ever refuse what this ratio says is too fast.
_broken = Tuning(max_w=5000.0, slew_w=1000.0)
_broken_s = 5000.0 / slew_limit_w(_broken) * LOOP_TICK_S
_broken_s = 5000.0 / (slew_limit_w(_broken) or float("nan")) * LOOP_TICK_S
print(f" the 2026-09-05 configuration (max_w 5000, slew_w 1000) crosses its "
f"rail in {_broken_s:.0f} s against a plant measured at {PLANT_SETTLE_S:.0f} s")
check("⭐ RESTORING slew_w 1000 AT max_w 5000 IS FASTER THAN THE PLANT, and by "