TEST-16: pin the two one-sided arbitrage rungs, and un-mask them from each other
checks / unit (pull_request) Successful in 3m51s
checks / unit (pull_request) Successful in 3m51s
SAFETY-26's AC4 gave the same tolerance mutation to every price and quantity
boundary in arbitrage.py and static_tariff.py. Everything in arbitrage.py went
red - `price < 0`, `soc <= floor_pct`, `soc >= ceiling_pct` are each red under
+/-5. Two survived, both in static_tariff.py, and this card closes them.
D `spread == 0.0` (:912) weakened to `spread <= 0.001` SURVIVED at +2 B.
F `len(firm) < 2` (:901) weakened to `len(firm) < 1` SURVIVED at +0 B - the
delta MUTATION.md names as the risky one.
THE DEFECT IS NOT EITHER ROW, IT IS THE INTERACTION. D and F MASK EACH OTHER. A
single firm hour also has a spread of 0.0, so with F weakened the flat rung
refuses the stimulus anyway; with D weakened the small-but-real spread is still
refused, only for a lie. Each rung's failure is absorbed by the other, so BOTH
can be weakened together and a suite grading `kind == "refused"` reports
nothing. That is structurally invisible to single-mutation testing, which is how
this pair came through four rounds of review on TARIFF-03 and FCST-03 - and it
is the arrangement in which the next edit to either rung opens a real hole with
nothing to report it.
D is not cosmetic either. With the tolerance in place a series priced 0.20000 /
0.20050 - a REAL 0.0005 EUR/kWh spread - flips ALLOWED to REFUSED at
min_spread_eur_kwh=0.0, a value arbitrage()'s own docstring documents ("pass 0.0
to mean any positive spread, explicitly") and its validator at :861 accepts on
purpose. At a shipped threshold the outcome does not move, but the operator
reads the false sentence "the tariff is flat - every one of the 2 firm hours
costs 0.2000 EUR/kWh" about a tariff that is not flat.
WHAT WAS ADDED. test_static_tariff.py section 9, and the shape of it is the
argument:
* THE REASON IS GRADED, NOT THE VERDICT. The masked case refuses CORRECTLY
while reporting the wrong sentence, so a check asserting only "refused"
passes on a broken tree. A `rung9()` helper names which refusal answered,
from the sentence the operator would read; an unrecognised refusal reports
itself as UNATTRIBUTED rather than quietly matching nothing.
* EACH RUNG IS GIVEN A STIMULUS THE OTHER CANNOT ABSORB, or the pinning is
circular. The flat rung gets a THREE-firm-hour flat series, which no
`len(firm) < N` a person would write can reach. The firm-pair rung gets a
ONE-FIRM-HOUR series, which the flat rung CAN reach - so its attribution is
the whole check, and that is why it is written as a rung name rather than a
verdict.
* AC1 is a sweep of small-but-real spreads (0.0005 down to 0.000001) all
ALLOWED at min_spread_eur_kwh=0.0, with the exactly flat series on the other
side still refused BY THE FLAT RUNG - without that mirror the sweep would
also pass for a rung deleted outright. 1e-6 is the floor because the shipped
line rounds the spread to 6 decimals; that quantisation is graded in the
open as its own row rather than left unstated.
* AC3 is a five-row table graded on rung attribution, which denies the masking
from both sides at once.
* The firm-pair count is also pinned from ABOVE (`< 3`) and BELOW (`< 0`),
which the criteria do not ask for but which is what stops this section being
one-sided in exactly the way it was written to fix.
EVIDENCE. Eight rounds through TEST-15's tools/mutation_run.sh, used UNCHANGED:
recursive __pycache__ purge before each run, python3 -B, the tree re-copied FROM
DISK between rounds, the mutated line grepped out of the file before the run,
byte delta printed, test_static_tariff.py and test_arbitrage.py graded each
time. Baseline green.
baseline, shipped +0 B PASS
D1 `spread == 0.0` -> `<= 0.001` +2 B RED, 3 checks
D2 `spread == 0.0` -> `<= 0.000001` +5 B RED, AC1 alone
D3 `spread == 0.0` -> `< 0.0` (rung gone) +0 B RED (section 2 as well)
F1 `len(firm) < 2` -> `< 1` +0 B RED, 3 checks
F2 `len(firm) < 2` -> `< 3` +0 B RED, 6 checks
F3 `len(firm) < 2` -> `< 0` (rung gone) +0 B RED (ValueError)
D1+F1 BOTH AT ONCE - THE TICKET +2 B RED, 5 checks
Four of the seven are +0 B, the delta MUTATION.md names as the one stale
bytecode can hide. All are REDS, the direction that defect cannot fabricate -
but they were run under the purge anyway, because "it went red so the cache
cannot have lied" is a conclusion and not a procedure.
NO SHIPPED BEHAVIOUR CHANGED. app/ is untouched; both rungs were correct all
along, and the card is explicit that a rung found actually wrong is a stop-and-
report rather than an edit. Also updated: MUTATION.md gains a short section on
masking as a SECOND way a survivor lies, with the two rules that fall out of it
(grade the reason, and mutate suspected pairs together); and test_arbitrage.py's
SAFETY-26 AC4 note is marked closed rather than left reading as an open finding.
UNSURE ABOUT. The 5e-7 row asserts the shipped `round(spread, 6)` quantisation.
That is a real property of the line and worth stating, but it does pin a
rounding the ticket never named - if a reviewer would rather that stayed
unpinned, it is one check to drop and nothing else depends on it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e8fb596b99
commit
20f9f4969d
@@ -2,6 +2,33 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
**The two `arbitrage()` rungs SAFETY-26's AC4 sweep found pinned from ONE SIDE
|
||||
ONLY are now pinned from both - and, the actual finding, are pinned so they stop
|
||||
MASKING EACH OTHER (TEST-16).** `spread == 0.0` weakened to `<= 0.001` survived
|
||||
the whole suite at +2 B, and `len(firm) < 2` weakened to `< 1` survived at
|
||||
+0 B - the delta MUTATION.md names as the risky one. Neither was individually
|
||||
live, and that was the problem: a single firm hour *also* has a spread of zero,
|
||||
so each rung's failure was absorbed by the other and both could be weakened
|
||||
together with nothing reporting it. That is invisible to single-mutation
|
||||
testing, which is how the pair survived four rounds of review on TARIFF-03 and
|
||||
FCST-03. `test_static_tariff.py` gains section 9, which grades WHICH RUNG
|
||||
answered each stimulus rather than only whether it refused - the masked case
|
||||
refuses correctly while reporting the false sentence "the tariff is flat" about
|
||||
a series that is not flat, so a check asserting only "refused" passes on a
|
||||
broken tree. The flat rung is swept with small-but-REAL spreads down to
|
||||
0.000001 EUR/kWh at `min_spread_eur_kwh=0.0` (a value `arbitrage()`'s docstring
|
||||
documents and its validator accepts on purpose), the firm-pair rung is probed
|
||||
with a ONE-FIRM-HOUR series and graded on §8.3's sentence coming back rather
|
||||
than the flat rung's, and a five-row table denies the masking on both sides.
|
||||
Verified through TEST-15's `tools/mutation_run.sh` unchanged - recursive
|
||||
`__pycache__` purge, `python3 -B`, tree re-copied from disk between rounds,
|
||||
mutated line grepped off disk, byte delta printed: 7 mutations plus a green
|
||||
baseline, 4 of them at +0 B, ALL RED, including both rungs weakened AT ONCE
|
||||
(5 checks red). **No shipped behaviour changed - both rungs were correct all
|
||||
along**; `app/` is untouched. `MUTATION.md` gains a section on masking as a
|
||||
second way a survivor lies, and `test_arbitrage.py`'s SAFETY-26 AC4 note is
|
||||
marked closed rather than left reading as an open finding.
|
||||
|
||||
**`max_w` and the `LIVE_MAX_W` literal beside it were unpinned in test_freeze.py
|
||||
and test_tariff.py, the same gap TEST-06 closed one key over for
|
||||
`meter_freeze_s` (TEST-10).** Measured before the fix: `max_w: 2000 -> 4500`
|
||||
|
||||
Reference in New Issue
Block a user