SAFETY-42 round 2: pin the two thresholds at a config that is not the shipped one
checks / unit (pull_request) Successful in 6m7s

THE BLOCKER. At the `delinquent()` call site, `self.tuning.saturation_w` ->
`250.0` (-19 B) was a survivor of all 23 suites. It is not an equivalent
mutant: two copies of inverter A, B wedged, `saturation_w: 1800` (inside its
own schema int(100,2000)) and a load of 1600 W put B's shortfall strictly
between the literal and the configured value - head names nobody, the mutant
names B. At the shipped 500 both name it, which is exactly why every fixture
in part 4 was blind: their gaps are all above both thresholds.

It matters beyond a mutation score. `delivery.py`'s MIN_W_IS_SATURATION_W note
and the `delivery_hold_s` block in config.yaml both promise an operator, in
shipped prose, that there is no fourth copy of a threshold to keep in step and
no way for the two to drift - raise `saturation_w` and this check moves with
you. A literal at the call site removes exactly that promise, silently, and
the promise is why the design was chosen over a `delivery_w` key.

ASKING THE SAME QUESTION OF EVERY OTHER NUMBER THIS CHECK READS FOUND A SECOND
SURVIVOR OF ALL 23 SUITES: `bms_floor=inv.bms_soc_floor` -> `10.0` (-13 B),
invisible because config.yaml's own example states 10 % and every fixture in
this file used it. Two arms bracket it: a pack at 15 % against its own 25 %
cut-out is cold and must stay unnamed (the literal names an empty pack), and a
pack at 8 % against its own 5 % cut-out is wedged and must be named (the
literal explains the fault away). One arm on each side leaves no constant at
all.

`delivery_hold_s` and `is_live()`'s window were ALREADY pinned - the fixtures
had to move them to run - and `LINK_STALE_S` has no option behind it, so
"pinned across a changed config" is not available for it and is not claimed.
DECISIONS section 9 enumerates all of it with the deltas and which check kills
which.

Verified in the CI image, tree mounted :ro, `python3 -B`, `__pycache__` purged
and counted before every run (TEST-15):
  R5 saturation_w -> 250.0                    -19 B  killed by part 4b(a)
  R6 bms_soc_floor -> 10.0                    -13 B  killed by part 4b(b), both arms
  R7 arm(self.delivery_cfg) -> DeliveryConfig()  -1 B  killed (already)
  R8 is_live(now) -> is_live(now, 3600.0)      +8 B  killed (already)
  R9 pack cut-out -> conjoined site floor    +105 B  killed twice
  head: 23/23 test files PASS, no bytecode written during the run.

No shipped behaviour changes: the only edits to app/ are comments.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
This commit is contained in:
Claude
2026-09-06 19:25:45 +02:00
committed by glenn schrooyen
parent c7eb8c6b97
commit 090a8ecc35
6 changed files with 366 additions and 6 deletions
+10
View File
@@ -345,6 +345,16 @@ DELIVERY_OPTION_DEFAULTS = {
# that a bigger gap is ordinary tracking, and this check moves with them by
# construction rather than by somebody remembering.
#
# ⚠️ AND THAT SENTENCE IS A CHECKED CLAIM, NOT A DESCRIPTION OF AN INTENTION.
# It is only true while the call site passes the CONFIGURED value; a literal
# there that happens to equal 500 today keeps every fixture green and quietly
# removes the whole guarantee - `250.0` in place of `self.tuning.saturation_w`
# was a survivor of all 23 suites, because every fixture's gap was far above
# both numbers. test_delivery.py part 4b is what makes it checkable: it runs
# the same wedged unit at the shipped `saturation_w` and at a raised one inside
# the shipped schema, with a shortfall that falls BETWEEN them, and the two
# arms must answer differently. No constant passes both.
#
# ⚠️ IT IS ALSO WHY THIS CANNOT DOUBLE-REPORT THE SATURATION FREEZE'S OWN
# EVENT. `compute()` needs `saturation_cycles` (3) CONSECUTIVE cycles over
# that same gap before it freezes anything, so every cycle this module counts
+38
View File
@@ -1803,7 +1803,36 @@ class Controller:
# cut-out and a wedged unit produce IDENTICAL traces at the unit and at
# the meter, so nothing computed from one unit alone can tell them
# apart. At a roster of one the reduction is empty for every input.
#
# ⚠️ EVERY NUMBER BELOW IS THE CONFIGURED ONE, NEVER A LITERAL THAT
# HAPPENS TO EQUAL IT TODAY, AND THAT IS A SHIPPED PROMISE RATHER THAN
# A STYLE RULE. `delivery.MIN_W_IS_SATURATION_W` and config.yaml's
# `delivery_hold_s` block both tell an operator, in prose, that there is
# no fourth copy of a threshold to keep in step and no way for the two
# to drift - an operator who raises `saturation_w` has told the loop a
# bigger gap is ordinary tracking, and this check moves with them BY
# CONSTRUCTION. A literal here breaks exactly that promise, silently.
#
# Both of these were once literals that no check in this repository
# could see, because every fixture sat at the shipped value:
#
# self.tuning.saturation_w -> 250.0 (-19 B) - a survivor of all
# 23 suites, because part 4's
# gaps are far above BOTH
# bms_floor=inv.bms_soc_floor -> 10.0 (-13 B) - a survivor of all
# 23 suites, because every
# fixture's cut-out WAS 10 %
#
# test_delivery.py part 4b is what stands between this call and both of
# them: it grades this reduction at a `saturation_w` and at two
# `bms_soc_floor`s that are NOT the shipped ones, and its arms are
# answered opposite ways at two configured values, so no constant can
# pass. Change an argument here to a number and read that part before
# you decide the red is wrong.
for inv in self.inverters:
# ⚠️ `self.delivery_cfg`, NOT `DeliveryConfig()`. Same promise: the
# window is `delivery_hold_s`, and part 4b's neighbours already
# grade this one at a hold that is not the shipped 185 s.
inv.delivery_wd.arm(self.delivery_cfg)
_delinquent = delinquent(
[UnitDelivery(
@@ -1815,8 +1844,17 @@ class Controller:
# "the allocator has already dropped this unit and said so",
# and a different window here would be this check answering
# a question about a roster the allocator did not build.
# It is the ONE number here with no option behind it, so
# "graded at a changed config" is not available for it;
# what part 6 grades instead is that WIDENING the window
# changes the answer.
live=inv.is_live(now)[0],
parked=inv.sign_wd.tripped,
# ⚠️ THIS UNIT'S OWN CUT-OUT. Not the shipped 10 %, and not
# the site's `peak_soc_floor` either - that one bounds a
# money-saving discharge only, and grading a pack against it
# explains a wedged unit away in a band an ordinary install
# has ten points of.
soc=inv.soc, bms_floor=inv.bms_soc_floor))
for inv in self.inverters],
self.tuning.saturation_w)