diff --git a/goodwe_controller/CHANGELOG.md b/goodwe_controller/CHANGELOG.md index d018649..102dae9 100644 --- a/goodwe_controller/CHANGELOG.md +++ b/goodwe_controller/CHANGELOG.md @@ -138,6 +138,13 @@ one-inverter runs, and the flapping family is 0.0 s in all 840. `delivery_hold_s` is declared `int(30,900)` while the measured legitimate worst was 35.0 s; it is now 40.0 s, so the band of schema-legal false-tripping values widens from `[30, 35]` to `[30, 40]`. CFG-07 should be re-derived against 40.0 s. +⚠️ **IT MUST CLOSE BEFORE INVERTER B IS COMMISSIONED (~2026-09-12)**, not before +the next deploy: the band is UNREACHABLE at N=1 - `delinquent()` answers `{}` for +every input at a roster of one, so no `delivery_hold_s` can false-trip on the +install running today - and commissioning B is the moment it becomes reachable. +⚠️ **AND CFG-07 HAS NO DECISION DOCUMENT IN THIS TREE** - it exists as a card +reference here and in SAFETY-43's record and nowhere else, so whoever takes it +writes `DECISIONS/CFG-07-*.md` first. **SAFETY-43 CHANGES NO OPTION VALUE, NO SCHEMA BOUND AND NO DEFAULT** - the fraction is a derived constant with a committed harness behind it, deliberately not a knob, for the reason there is no `delivery_w`. The one edit to diff --git a/goodwe_controller/DECISIONS/SAFETY-43-delinquency-relative-to-the-ask.md b/goodwe_controller/DECISIONS/SAFETY-43-delinquency-relative-to-the-ask.md index b185c7a..ea2bb87 100644 --- a/goodwe_controller/DECISIONS/SAFETY-43-delinquency-relative-to-the-ask.md +++ b/goodwe_controller/DECISIONS/SAFETY-43-delinquency-relative-to-the-ask.md @@ -100,12 +100,15 @@ row in it, run for run. cd goodwe/sim && GOODWE_ADDON_REPO=/path/to/goodwe-addon \ python3 -B delivery_window.py --selftest --campaign --fractions -⚠️ **`FRACTIONS` IN THAT FILE HOLDS TEN VALUES AND THE TABLE BELOW HAS TWENTY.** -It was run twice - one pass over `(0.25, 0.50, 0.60, 0.70, 0.75, 0.80, 0.85, -0.90, 0.95, 0.99)`, the shipped list, and one over `(0.05, 0.10, 0.15, 0.20, -0.30, 0.35, 0.40, 0.45, 0.55, 0.65)` with only that tuple changed, to resolve -the low end. **The absolute-arm control row came out identical in both** -(30.0 / 0.0 / 35.0 / 0.0), which is what says the two passes are one campaign. +⚠️ **THE TABLE BELOW WAS PRODUCED BY TWO PASSES; `FRACTIONS` NOW HOLDS ALL +TWENTY SO IT TAKES ONE.** The derivation ran the shipped ten +`(0.25 … 0.99)` and then the ten that resolve the low end +`(0.05 … 0.65)` with only that tuple edited between them. **The absolute-arm +control row came out identical in both** (30.0 / 0.0 / 35.0 / 0.0, same argmax), +which is what says the two passes are one campaign. A table a reviewer cannot +reproduce without knowing to edit a line nobody wrote down is PROC-01's shape in +miniature, so the committed `FRACTIONS` was widened to all twenty in review: the +command above now reproduces every row below. ### 3.1 The curve @@ -223,22 +226,58 @@ A card that adds a way to raise an alarm has to show what it did **not** add. `test_packshare.py` part 8b runs the same fixture, the same weights and the same 125 W share with the one fact that makes the shortfall legitimate changed: -| fixture | named | -|---|---| -| B wedged at a healthy 15 % (the fault) | **200 / 200** | -| B at its own 10 % cut-out, otherwise identical trace | **0 / 200** | -| B delivering **half** its share | **0 / 200** | +| fixture | B's share | named | why it is silent | +|---|---|---|---| +| B wedged at a healthy 15 % (the fault) | 77–154 W | **200 / 200** | — it is the fault | +| B at its own 10 % cut-out | **0 W, all 200 cycles** | **0 / 200** | it is never **asked** | +| B delivering **half** its share | 120 W | **0 / 200** | short by 50 %, inside both arms | -The first and second have **identical power traces** — 0 W against a positive -share, 100 % of the ask missing, the relative arm's exact shape — and what -separates them is `account()`, the pack's own `bms_soc_floor`. SAFETY-43 did not -go near that gate, and the third row is ordinary tracking on a small share, which -neither arm reports. +### ⚠️ The cut-out row does NOT grade `account()`, and this record said it did -`test_delivery.py` part 1 carries the same controls on the predicate itself, -including that the relative bound is **strict** (a gap of exactly `frac · share` -is not over it) and that the proper-subset rule still refuses a site-wide cause -(both units delivering none of a 125 W share names nobody). +**Corrected in review.** The first draft of this section claimed the wedged and +cut-out arms had *"identical power traces — 0 W against a positive share, 100 % +of the ask missing"* and that *"what separates them is `account()`"*. **Every +part of that was false**, and the check that carried it was **vacuous**: + +* at its own cut-out the pack's usable energy **above that cut-out is zero**, so + `packshare.weights()` answers **0.0** for it, `_fill()`'s preferred pass gives + it nothing, the healthy sibling covers the load alone so the last-resort pass + never runs, and its share is **0 W on all 200 cycles**; +* a unit with a 0 W share is not in `asked`, so `len(asked) < 2` returns `{}` + **before either arm is evaluated** and `account()` is **never consulted**; +* the traces are not identical — the cut-out arm's share is **0 W on every + cycle** (asserted in part 8b and printed on its line) while the wedged arm's + is **125 W** at the last cycle (asserted in the GREEN check) and takes four + distinct values 77/125/133/154 W across the 200, measured by re-running the + same loop with per-cycle recording during review — and "100 % of the ask" is + 0/0. + +Measured, by deleting the gate the claim rested on: + + cut-out arm, account() live named 0/200, distinct B shares: [0] + cut-out arm, account() DELETED named 0/200, distinct B shares: [0] + wedged arm, account() live named 200/200, shares [77,125,133,154] + +⚠️ **The fact that replaces it is better, not weaker.** At its own cut-out +**OPT-09 stops asking the pack at all**, so SAFETY-43's relative arm is never +reached — a **second, independent protection sitting upstream of `account()`**: +the allocator declines to ask before the detector has to decline to alarm. Part +8b now asserts the 0 W share and prints it, and runs the arm a second time with +`account()` deleted to show which mechanism is doing the work. + +⚠️ **It was invisible for one reason worth keeping**: that line was the only one +in part 8b that did not print its shares. `inverter-b+0` would have been on +screen from the first run. Every line in that part prints them now, and the +derated control's vacuity guard now asserts **its own** unit is asked, rather +than re-asserting a different run's 200/200 — which is exactly why the old guard +could not see this. + +**The account gate is graded where it is real**, on a positive 125 W share, in +`test_delivery.py` part 1 (*"THE ACCOUNT GATE IS UNTOUCHED BY IT"* — the same +0 of 125 W with the shortfall explained names nobody). That part also carries +that the relative bound is **strict** (a gap of exactly `frac · share` is not +over it) and that the proper-subset rule still refuses a site-wide cause (both +units delivering none of a 125 W share names nobody). ## 5. N=1 is byte-identical, and it is asserted rather than argued @@ -262,18 +301,36 @@ is commissioned (~2026-09-12), not before the next deploy. ⚠️ **RUN, NOT REASONED ABOUT.** Each mutation was applied to a tree **copied fresh** from this branch and mounted `:ro` inside the checks image, so no `.pyc` could go stale — MUTATION.md's defect, and all three of these are **reds**, the -direction that defect cannot fabricate. Recorded in full in `test_packshare.py` -part 11 as M12–M14. +direction that defect cannot fabricate. Recorded in `test_packshare.py` part 11 +as M12–M14. -| mutation | caught by | -|---|---| -| **M12** drop the relative arm, keep `gap > min_w` — *the card itself reverted* | `test_delivery.py` part 1 (1 named check) and `test_packshare.py` 8b (4): the GREEN check goes back to 0/200, the equal arm back to 199, the 6000 W arm back to 195/199. `test_alloc.py` and `test_control.py` stay green — nothing outside this check moves. | -| **M13** `DELIVERY_SHARE_FRACTION` 0.85 → **1.0** — *too loose* | 5 named checks, including part 8b's PRECONDITION that the fraction is strictly below 1.0. At 1.0 the card is a no-op: gap/share of exactly 1.0 is not *over* 1.0. | -| **M14** `DELIVERY_SHARE_FRACTION` 0.85 → **0.05** — *too sensitive* | `test_delivery.py` parts 1 and **4b** (4 named checks) and part 8b's DERATED positive control: a unit delivering half its share starts being named, and 4b's `saturation_w` pin goes vacuous. | +| mutation | named checks | where | +|---|---:|---| +| **M12** drop the relative arm, keep `gap > min_w` — *the card itself reverted* | **7** | `test_delivery.py` part 1 (3: the 0-of-125 W naming, the proportion in the sentence, the strict relative bound) + `test_packshare.py` 8b (4: GREEN back to 0/200, the equal arm's 200 back to 199, the 6000 W arm back to 195/199, the derated control's guard). `test_alloc.py` / `test_control.py` stay green — nothing outside this check moves. | +| **M13** `DELIVERY_SHARE_FRACTION` → **1.0** — *too loose* | **7** | part 1 (2) + 8b (5), including the PRECONDITION that the fraction is strictly below 1.0. At 1.0 the card is a no-op: gap/share of exactly 1.0 is not *over* 1.0. | +| **M14** `DELIVERY_SHARE_FRACTION` → **0.05** — *too sensitive* | **6** | part 1 (2: the strict bound, and *"IT TOOK NOTHING AWAY"* stops being true) + part **4b** (3: the `saturation_w` pin goes **vacuous**, the relative arm naming B at every threshold) + 8b's derated control, where a unit delivering half its share starts being named. | M13 and M14 are the pair that matters: the constant cannot be moved in **either** direction without a named check moving with it. +### ⚠️ Two of these counts were wrong, and the reason is worth keeping + +**Corrected in review.** + +* **M12 and M13 read 1 and 1 in `test_delivery.py`, not 3 and 2.** Part 1 printed + the alarm sentence with a bare subscript, `delinquent(_tiny, 500)["b"]`. On any + tree where the relative arm is broken or absent that raises `KeyError` and + **aborts the file at part 1**, leaving parts 2–8 ungraded — so the recorded + coverage of a mutation run read *smaller* than the truth. The direction is safe + (an abort is a red, and MUTATION.md's defect can only fabricate a green), but a + figure that does not reproduce is a figure that does not reproduce. It is a + `.get` now, with the reason written beside it, and the counts above are from + re-runs with the guard in place. +* **M14 was recorded as 4 named checks in `test_delivery.py` and it is 5** — part + 1's pre-existing *"a shortfall AT the threshold is not one"* also goes red. That + one under-claimed, so it never inflated the evidence; it is still a recorded + number that did not reproduce. + ## 6. ⚠️ [[CFG-07]] is adjacent and is NOT absorbed here CFG-07 is that `delivery_hold_s` is declared `int(30,900)` in `config.yaml`'s @@ -290,6 +347,21 @@ with its own compatibility argument (an existing config carrying `35` would star being refused), and folding it into a safety fix would hide it. **CFG-07 should be re-derived against 40.0 s, not 35.0 s.** +⚠️ **WHEN IT HAS TO CLOSE: BEFORE INVERTER B IS COMMISSIONED (~2026-09-12).** +Not before the next deploy. The band is **unreachable at N=1** — `delinquent()` +returns `{}` for every input at a roster of one, so no value of +`delivery_hold_s` can false-trip on the install that is running today — and the +live install is N=1 until B exists. Commissioning B is the moment the band +becomes reachable, and that is the deadline this card hands it. + +⚠️ **AND CFG-07 HAS NO DECISION DOCUMENT IN THIS TREE.** It exists as a +`[[CFG-07]]` mention in `CHANGELOG.md` and in this section, and nowhere else — +so there is no file for the 40.0 s to be recorded in, and a reader who goes +looking will find a card reference with no record behind it. Whoever takes +CFG-07 writes `DECISIONS/CFG-07-*.md` first; the number it has to answer to is +**40.0 s**, measured here, and the harness that produces it is +`admin/goodwe:sim/delivery_window.py --campaign --fractions`. + ## 7. Provenance of every number in this document | number | where it came from | @@ -300,7 +372,10 @@ be re-derived against 40.0 s, not 35.0 s.** | 1000 W / 6500 W suppression windows | part 8b's arithmetic, asserted there | | 185 s, 5.29× | SAFETY-42 §3.3 / TEST-21, unchanged by this card | | 4.62× | 185 / 40.0, printed by the campaign | -| 0/200 on both positive controls | part 8b, run | +| 0/200 on both positive controls, and B's 0 W share at the cut-out | part 8b, run and asserted | +| 0 W cut-out share / 125 W wedged share | part 8b, asserted and printed | +| 77/125/133/154 W (the wedged arm's four distinct shares) | the same loop re-run in review with per-cycle recording — NOT part 8b's printed line, which shows the last cycle only | +| M12/M13/M14 named-check counts (7 / 7 / 6) | re-run in review on fresh `:ro` trees, after the `KeyError` abort in part 1 was guarded | | 84 single-unit rosters | part 8b, run | ⚠️ Every two-inverter figure above is **two copies of inverter A**. diff --git a/goodwe_controller/app/delivery.py b/goodwe_controller/app/delivery.py index 7810dbd..f355913 100644 --- a/goodwe_controller/app/delivery.py +++ b/goodwe_controller/app/delivery.py @@ -478,11 +478,12 @@ MIN_W_IS_SATURATION_W = "control.Tuning.saturation_w" # stagger lands B's number up to four cycles late and a unit whose new command # has not arrived IS delivering a small fraction of what is standing on its # wire. What is zero at every fraction is false ALARMS: the longest legitimate -# episode anywhere in the 20-point sweep is 90.0 s (at 0.05, twelve times more -# sensitive than shipped) against a `delivery_hold_s` of 185 s, so NOTHING in -# 2640 no-fault runs comes within 95 s of notifying an operator at any fraction -# measured. At 0.85 the hold is 4.62x the worst legitimate episode where the -# absolute arm alone gave 5.29x. That is the price, in full. +# episode anywhere in the 20-point sweep is 90.0 s - at 0.05, the most sensitive +# fraction measured and far below anything this card would ship - against a +# `delivery_hold_s` of 185 s, so NOTHING in 2640 no-fault runs comes within 95 s +# of notifying an operator at any fraction measured. At 0.85 the hold is 4.62x +# the worst legitimate episode where the absolute arm alone gave 5.29x. That is +# the price, in full. # # ⚠️ THE DIRECTION IS THE OPPOSITE OF `min_w`'s AND IT IS EASY TO GET BACKWARDS. # A SMALLER fraction names MORE units - it is the more sensitive setting and the diff --git a/goodwe_controller/test_delivery.py b/goodwe_controller/test_delivery.py index 6dd1980..f7cf11e 100644 --- a/goodwe_controller/test_delivery.py +++ b/goodwe_controller/test_delivery.py @@ -172,12 +172,19 @@ check("A UNIT DELIVERING 0 OF 125 W IS NAMED, AND UNDER THE OLD RULE IT WAS " "because 100 % of its ask is not arriving", delinquent(_tiny, 500, _ABS) == {} and list(delinquent(_tiny, 500)) == ["b"]) -print(f" {delinquent(_tiny, 500)['b']}") +# ⚠️ `.get`, NOT `["b"]`, AND THAT IS NOT STYLE. A bare subscript here raises +# KeyError on any tree where the relative arm is broken or absent, which ABORTS +# THE FILE at part 1 and leaves parts 2-8 UNGRADED. The direction is safe - an +# abort is a red, and MUTATION.md's defect can only fabricate a green - but the +# recorded coverage of a mutation run then reads larger than it was, which is +# the shape M11's entry in test_packshare.py part 11 already discloses. Found +# in review of this card, on M12 and M13. +_tiny_said = delinquent(_tiny, 500).get("b", "(not named - see the check above)") +print(f" {_tiny_said}") check("...and the sentence carries the PROPORTION as well as the watts, because " "'125 W is not arriving' reads like a rounding error and '125 W (100 % of " "it)' is the fault", - "125 W of its share (100 % of it) is not arriving" - in delinquent(_tiny, 500)["b"]) + "125 W of its share (100 % of it) is not arriving" in _tiny_said) check("THE ACCOUNT GATE IS UNTOUCHED BY IT - the same 0 of 125 W with the " "shortfall EXPLAINED names nobody. A relative criterion that could talk " diff --git a/goodwe_controller/test_packshare.py b/goodwe_controller/test_packshare.py index a7a9062..d25f565 100644 --- a/goodwe_controller/test_packshare.py +++ b/goodwe_controller/test_packshare.py @@ -662,7 +662,7 @@ ABSOLUTE_ONLY = None # `share_frac=None` - SAFETY-42's rule def wedged_loop(load_w, weighted, cycles=200, share_frac=DELIVERY_SHARE_FRACTION, - b_delivers=0.0, b_soc=WEDGED_B_SOC): + b_delivers=0.0, b_soc=WEDGED_B_SOC, no_account=False): """The closed loop with unit B WEDGED at a healthy SoC. Real everything. Real `compute()`, real `plan()`, real `packshare.weights()`, real @@ -679,6 +679,12 @@ def wedged_loop(load_w, weighted, cycles=200, share_frac=DELIVERY_SHARE_FRACTION cycles under SAFETY-42's rule, which is how the red and the green below come out of ONE run over ONE tree instead of out of two commits. + `no_account=True` DELETES the account gate - every unit is handed + `accounted=None` - and it exists to answer "would this arm still be silent + if `account()` did nothing at all?". A control that answers the same way + with the gate removed is not grading the gate, and the cut-out arm below is + exactly that case. See it there; it is the point of that block. + Returns `(cycles on which delinquent() named somebody, last cycle)`. """ packs = [PE(A, WEDGED_A_SOC, A_KWH), PE(Bee, b_soc, B_KWH)] @@ -699,6 +705,7 @@ def wedged_loop(load_w, weighted, cycles=200, share_frac=DELIVERY_SHARE_FRACTION grid = load_w - sum(power) who = delinquent( [UnitDelivery(p.name, r.shares[p.name], m, + None if no_account else account(live=True, parked=False, soc=p.soc, bms_floor=p.bms_floor)) for p, m in zip(packs, power)], MIN_W, share_frac) @@ -825,18 +832,56 @@ print(" THE POSITIVE CONTROL: what the relative arm must STILL NOT say") # HA". So: the same fixture, the same weights, the same tiny 125 W share, with # the ONE fact that makes the shortfall legitimate changed. +# ⚠️ AND THE FIRST DRAFT OF THIS BLOCK WAS VACUOUS, WHICH IS WHY IT NOW PRINTS +# ITS SHARES. It ran the cut-out arm, saw 0/200, and concluded "`account()` +# explains it" and "identical trace to the wedged arm". BOTH WERE FALSE. At its +# own cut-out the pack's usable energy ABOVE that cut-out is zero, so +# `weights()` gives it 0.0, `_fill()`'s preferred pass gives it nothing, the +# healthy sibling covers the load alone so the LAST-RESORT pass never runs, and +# its share is 0 W on ALL 200 CYCLES. A unit with a 0 W share is not in `asked`, +# so `len(asked) < 2` returns {} BEFORE EITHER ARM IS EVALUATED and `account()` +# is never consulted - measured below by deleting the gate and getting the same +# answer. The wedged arm's share is the 125 W printed on its own line above; +# this one's is 0, printed and asserted below. Nothing was identical and nothing +# reached the comparison. +# +# ⚠️ THE FACT THAT REPLACES IT IS BETTER, NOT WEAKER: at its own cut-out OPT-09 +# STOPS ASKING THE PACK AT ALL, so SAFETY-43's relative arm is never reached. +# That is a SECOND, INDEPENDENT protection sitting UPSTREAM of `account()` - the +# allocator declines to ask before the detector has to decline to alarm. The +# account gate itself is graded where it is REAL, on a positive 125 W share, in +# `test_delivery.py` part 1 ("THE ACCOUNT GATE IS UNTOUCHED BY IT"). +# +# ⚠️ AND IT WAS INVISIBLE FOR ONE REASON: this was the only line in part 8b that +# did not print its shares. `inverter-b+0` would have been on screen from the +# first run. Every line in this part prints them now. _sat_named, _sat_last = wedged_loop(1500.0, True, b_soc=FLOOR) +_sat_nogate, _sat_nogate_last = wedged_loop(1500.0, True, b_soc=FLOOR, + no_account=True) print(f" load 1500 W, B AT ITS OWN {FLOOR:.0f} % CUT-OUT, OPT-09 weights, " f"frac {DELIVERY_SHARE_FRACTION:.2f}: named on {_sat_named:3d}/200 " - f"cycles, named=" - + (str(sorted(_sat_last[3])) if _sat_last[3] else "(nobody)")) -check("A PACK AT ITS OWN CUT-OUT DELIVERING NOTHING OF A SMALL SHARE IS STILL " - "NOT DELINQUENT. Identical trace to the wedged arm - 0 W against a " - "positive share, 100 % of the ask missing, the relative arm's exact " - "shape - and `account()` explains it, so nobody is named on any of the " - "200 cycles. The account gate is what keeps normal saturation out and " - "SAFETY-43 did not go near it", - _sat_named == 0 and _sat_last[3] == {}) + f"cycles, shares " + + ", ".join(f"{u}{v:+d}" for u, v in sorted(_sat_last[1].items())) + + ", named=" + + (str(sorted(_sat_last[3])) if _sat_last[3] else "(nobody)") + + f"; with account() DELETED: {_sat_nogate:3d}/200") +check("A PACK AT ITS OWN CUT-OUT IS NEVER ASKED, SO THE RELATIVE ARM IS NEVER " + "REACHED. Its usable energy above its own cut-out is zero, `weights()` " + "answers 0.0 for it, the healthy sibling covers the load so `_fill()`'s " + "last-resort pass never runs, and its share is 0 W on every one of the " + "200 cycles - so it is not in `asked` and `len(asked) < 2` returns {} " + "before either arm is compared. ⚠️ THE SHARE IS ASSERTED, not inferred " + "from the silence: a control that cannot say WHY it is quiet is a control " + "that cannot fail", + _sat_named == 0 and _sat_last[3] == {} and _sat_last[1][Bee] == 0) +check("⚠️ AND THAT IS A SECOND, INDEPENDENT PROTECTION - NOT THE ACCOUNT GATE, " + "WHICH THIS ARM DOES NOT TOUCH. Delete `account()` entirely, hand every " + "unit `accounted=None`, and this arm is STILL silent on all 200 cycles, " + "because the protection is UPSTREAM of it: the allocator declines to ask " + "before the detector has to decline to alarm. ⚠️ SO DO NOT READ THIS " + "BLOCK AS EVIDENCE ABOUT `account()` - it is evidence about the ask. The " + "account gate on a POSITIVE share is graded in test_delivery.py part 1", + _sat_nogate == 0 and _sat_nogate_last[3] == {}) _derate_named, _derate_last = wedged_loop(1500.0, True, b_delivers=0.5) print(f" load 1500 W, B delivering HALF its share, OPT-09 weights, frac " @@ -853,11 +898,15 @@ check("...and ORDINARY TRACKING ON A SMALL SHARE IS NOT DELINQUENCY EITHER. " "whole module is written against", _derate_named == 0 and _derate_last[3] == {}) -check("...and the two controls are not vacuous: the SAME loop with the SAME " - "weights and a WEDGED B names it on all 200, so what separates them is " - "the pack's own cut-out and the size of the shortfall, not something " - "about the fixture that stopped the check reaching the comparison", - _wedge[(1500.0, True, DELIVERY_SHARE_FRACTION)][0] == 200) +check("...and the DERATED control is not vacuous, which is asserted on the ONE " + "thing that could make it so: unlike the cut-out arm above, its unit IS " + "asked - a positive share on every cycle - so it really does reach both " + "arms and is turned away by the size of its shortfall. ⚠️ THIS GUARD USED " + "TO RE-ASSERT THE WEDGED ARM's 200/200 INSTEAD, WHICH IS WHY IT DID NOT " + "CATCH THE CUT-OUT ARM BEING VACUOUS: a check on a DIFFERENT run cannot " + "say anything about whether THIS one reached the comparison", + _derate_last[1][Bee] > 0 + and _wedge[(1500.0, True, DELIVERY_SHARE_FRACTION)][0] == 200) print() print(" N=1 IS BYTE-IDENTICAL, AND THAT IS ASSERTED RATHER THAN ARGUED") @@ -1186,24 +1235,27 @@ MUT = [ # --- SAFETY-43's three, and they pin the new arm from BOTH sides -------- ("M12 delinquent(): drop the relative arm, keep `gap > min_w` <- THE " "CARD ITSELF REVERTED", - "test_delivery 1 (one named check) and test_packshare 8b (FOUR): the " - "GREEN check goes back to 0/200 under OPT-09's weights, the equal arm " - "goes back to 199, the 6000 W arm back to 195/199, and the vacuity guard " - "on the positive controls falls with them. test_alloc and test_control " - "stay green, which is the point - nothing outside this check moves"), + "SEVEN named checks: test_delivery 1 (THREE - the 0-of-125 W naming, the " + "proportion in the sentence, and the strict relative bound) and " + "test_packshare 8b (FOUR - the GREEN check goes back to 0/200 under " + "OPT-09's weights, the equal arm's 200 back to 199, the 6000 W arm back " + "to 195/199, and the derated control's vacuity guard falls with them). " + "test_alloc and test_control stay green, which is the point - nothing " + "outside this check moves"), ("M13 DELIVERY_SHARE_FRACTION 0.85 -> 1.0 <- THE TOO-LOOSE DIRECTION", - "test_delivery 1 and test_packshare 8b, five named checks including the " - "PRECONDITION that the fraction is strictly below 1.0. At 1.0 the arm can " - "only fire on a unit IMPORTING while asked to discharge, so the card's " - "own case - 0 of 125 W, gap over share exactly 1.0 - is missed and the " - "whole card is a no-op"), + "SEVEN named checks: test_delivery 1 (TWO) and test_packshare 8b (FIVE), " + "including the PRECONDITION that the fraction is strictly below 1.0. At " + "1.0 the arm can only fire on a unit IMPORTING while asked to discharge, " + "so the card's own case - 0 of 125 W, gap over share exactly 1.0 - is " + "missed and the whole card is a no-op"), ("M14 DELIVERY_SHARE_FRACTION 0.85 -> 0.05 <- THE TOO-SENSITIVE ONE", - "test_delivery 1 and 4b (four named checks) and test_packshare 8b's " - "DERATED positive control: a unit delivering half its share starts being " - "named, and part 4b's `saturation_w` pin goes vacuous because the " - "relative arm names B at every threshold. ⚠️ THIS IS THE ROW THAT MATTERS " - "- the fraction is pinned from BOTH directions, so it is a derived number " - "with checks around it rather than a literal nobody can move"), + "SIX named checks: test_delivery 1 (TWO - the strict bound, and 'IT TOOK " + "NOTHING AWAY' stops being true) and 4b (THREE - the `saturation_w` pin " + "goes VACUOUS, because the relative arm names B at every threshold), plus " + "test_packshare 8b's DERATED positive control, where a unit delivering " + "half its share starts being named. ⚠️ THIS IS THE ROW THAT MATTERS - the " + "fraction is pinned from BOTH directions, so it is a derived number with " + "checks around it rather than a literal nobody can move"), ] for _n, _w in MUT: print(f" caught {_n}") @@ -1216,7 +1268,13 @@ check("every mutation in the table above was RUN and every one of them is " "why the call site is pinned statically as well. ⚠️ M12-M14 ARE " "SAFETY-43's and were run the same way, on trees copied fresh and " "mounted :ro so no `.pyc` could go stale - MUTATION.md's defect. All " - "three are REDS, which is the direction that defect cannot fabricate", + "three are REDS, which is the direction that defect cannot fabricate. " + "⚠️ AND THEIR COUNTS WERE RE-RUN IN REVIEW AND TWO OF THEM MOVED: " + "test_delivery.py part 1 used to subscript `delinquent(...)['b']` for a " + "print, which raised KeyError under M12 and M13 and ABORTED THE FILE at " + "part 1, leaving parts 2-8 ungraded and the recorded coverage smaller " + "than the truth. It is a `.get` now. M14's count was simply wrong: five " + "named checks in test_delivery, not four", len(MUT) == 14) print()