UX-02: pin both halves of the max_w fix, because neither was graded
checks / unit (pull_request) Successful in 6m16s

Review of the fix commit found that BOTH halves reverted with the whole 24-file
suite still green - including the half that turns a crash into a SILENT
ACCEPTANCE, which was my own wrong first attempt at this fix. A fix nothing
grades is a fix that comes back. MUTATION.md habit 4.

⚠️ AND THE OBVIOUS PIN HAD THE SAME DEFECT IT WAS WRITTEN TO CATCH. "problems_
and_ranges does not raise" is ALSO satisfied by the backstop swallowing the
exception and returning [] - the silent acceptance half two exists to prevent.
MEASURED: with both halves reverted, every max_w check reported `ok` and only
the forced-boom pins went red. So half one is now pinned on `build_roster`
ITSELF, where the backstop cannot answer for it. That is the same defect as the
one being fixed - a check whose data cannot reach the failure it names - written
into the check for it, and it is the twelfth recorded instance.

New section 2b of test_selfconfig.py:
  - `_flat_max_w` agrees with `_number()` across ten shapes, so composition
    tolerates exactly what rule 9 tolerates.
  - ⭐ `build_roster(max_w="")` does not raise - half one, standing alone.
  - a cleared box answers with the validator's own verdict; an unparseable one
    answers "max_w 'abc' is not a number".
  - ⭐ clearing max_w on the LIVE shape (5000 W rail, explicit slew_w 400) is
    REFUSED by rule 10 "against a site rail of 2000 W". `unit_caps_indexed`
    reads the flat rail through the same `_number`, so the rail collapses in
    lockstep and rule 10 catches the collapse. This is what makes the fix safe
    rather than merely non-crashing: the box cannot be emptied into a quietly
    halved rail.
  - ⭐ the backstop never returns an empty list, forced by monkeypatching
    build_roster to raise; and the patch is shown undone.

DRIVEN, all four arms through the real harness:
  clean            rc=0  fails=0
  revert half one  rc=1  fails=2     (_flat_max_w -> bare float())
  revert half two  rc=1  fails=2     (backstop returns [] again)
  revert BOTH      rc=1  fails=3
Each half is caught on its own, which is the property the earlier draft lacked.

Suite: 24 files, all pass.

Not addressed here, filed instead: N3 (`/config/` 404s), N4 (undeclared keys on
a hand-crafted POST, which the page cannot produce and Supervisor drops), N5
(saving pins every default as an explicit option), the page reporting "would
start" for a blob Supervisor's own schema refuses, and 2000.0 hardcoded in three
places that agree today with nothing pinning them together.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
This commit is contained in:
glenn schrooyen
2026-09-06 22:38:54 +02:00
co-authored by Claude Opus 5
parent 2bb07ccdde
commit bcee2356e0
+97
View File
@@ -247,6 +247,103 @@ check("...and the caller gets the new token back, so the next save can "
res["fingerprint"] == selfconfig.fingerprint(_good))
# --------------------------------------------------------------------------- #
print()
print("2b. ⚠️ THE TWO HALVES OF THE max_w FIX, EACH PINNED SO IT CANNOT BE UNDONE")
# Review found that BOTH halves reverted with the whole 24-file suite still
# green - including the half that turns a crash into a SILENT ACCEPTANCE, which
# is the more dangerous of the two and was a wrong first attempt at this very
# fix. A fix nothing grades is a fix that comes back. MUTATION.md habit 4.
from app.inverter import _flat_max_w, build_roster # noqa: E402
from app.validate import _number # noqa: E402
# ⚠️ THIS CHECK EXISTS BECAUSE THE OBVIOUS ONE HAS A HOLE. "problems_and_ranges
# does not raise" is ALSO satisfied by the backstop swallowing the exception and
# returning [] - which is the silent-acceptance bug half two exists to prevent.
# Measured: with BOTH halves reverted, every max_w check below reported ok and
# only the forced-boom pins went red. So half one is pinned HERE, on the
# composition itself, where the backstop cannot answer for it.
_raised = None
try:
build_roster(base(max_w=""), None)
except Exception as exc: # noqa: BLE001
_raised = exc
check("⭐ THE COMPOSITION ITSELF TOLERATES A CLEARED max_w - not merely the "
"backstop above it. `validate.py`'s `_number()` reads '' as the default, "
"so rule 9 sees 2000 and is content; a bare `float('')` in the flat path "
"raised instead, inverting inverter.py's own rule that a shape rule 9 "
"tolerates must be composable",
_raised is None)
# HALF ONE: composition tolerates exactly what rule 9 tolerates.
for raw in ("", None, 0, False, True, -1, "2000", " 2000 ", "2e3", 1e18):
o = base(max_w=raw)
expect = _number(o, "max_w", 2000.0)
if expect is not None:
check(f"_flat_max_w agrees with _number() for max_w={raw!r}",
_flat_max_w(o) == expect)
_raised = None
try:
_probs, _ = run(selfconfig.problems_and_ranges(base(max_w=""), None))
except Exception as exc: # noqa: BLE001
_raised, _probs = exc, None
check("⚠️ A CLEARED max_w BOX ANSWERS WITH A VERDICT, NOT AN EXCEPTION. It "
"used to raise ValueError from inside Controller.__init__, which the "
"panel served as HTTP 500 on a non-JSON body and the page rendered as "
"'cannot reach the add-on' - untrue, for the most prominent limit "
"field's most likely transient state",
_raised is None)
check("...and it agrees with the validator, which reads '' as the default",
_probs == list(validate_startup_config(base(max_w=""), None, None)))
_raised = None
try:
_probs, _ = run(selfconfig.problems_and_ranges(base(max_w="abc"), None))
except Exception as exc: # noqa: BLE001
_raised, _probs = exc, None
check("an UNPARSEABLE max_w answers with the validator's own sentence",
_raised is None and any("is not a number" in p for p in (_probs or [])))
# HALF ONE, THE PART THAT MAKES IT SAFE RATHER THAN MERELY NOT-CRASHING:
# clearing the box on the LIVE shape must FAIL CLOSED, not silently halve the
# rail. `unit_caps_indexed` reads the flat rail through the same `_number`, so
# the rail collapses to 2000 in lockstep and rule 10 catches the collapse.
_live = base(max_w=5000, slew_w=400)
_probs, _ = run(selfconfig.problems_and_ranges(dict(_live), None))
check("the live shape (5000 W rail, slew_w 400) starts", _probs == [])
_probs, _ = run(selfconfig.problems_and_ranges(dict(_live, max_w=""), None))
check("⚠️ CLEARING max_w ON THE LIVE SHAPE IS REFUSED - rule 10 catches the "
"rail collapsing to the 2000 default under an explicit 400 W slew, so "
"the box cannot be emptied into a quietly halved rail",
any("site rail of 2000 W" in p for p in _probs))
# HALF TWO: the backstop cannot report "no problems" for a config it could not
# compose. Returning [] there enables Save - a crash became a green.
_orig_roster = selfconfig.build_roster
def _boom(*_a, **_kw):
raise ValueError("forced boom")
selfconfig.build_roster = _boom
try:
_probs, _ = run(selfconfig.problems_and_ranges(base(), None))
finally:
selfconfig.build_roster = _orig_roster
check("⚠️ THE BACKSTOP NEVER RETURNS AN EMPTY LIST. A roster that cannot be "
"composed while the validator names no reason is itself the finding - "
"returning [] would turn a crash into a silent acceptance and enable "
"Save, which is worse than the 500 it replaced",
_probs != [] and len(_probs) == 1)
check("...and it says so in words, naming the exception",
any("could not be composed" in p and "forced boom" in p
for p in _probs))
check("...and the patch was undone, so nothing below inherits it",
selfconfig.build_roster is _orig_roster)
# --------------------------------------------------------------------------- #
print()
print("3. THE OTHER SURFACE WRITES WHILE THIS PAGE IS OPEN")