SAFETY-32 round 4: the same family at the SIBLING ARGUMENT, and the family enumerated
checks / unit (pull_request) Successful in 4m53s
checks / unit (pull_request) Successful in 4m53s
Rounds 2 and 3 hardened the FLOOR axis - the max inside binding_soc_floor(), the roster handed to it, Controller.packs, pack_floor_pct()'s conjunction. peakguard.decide() is handed a SECOND reduction over the same roster beside it, soc=self.pack_floor_soc, and peakguard.py's own docstring names the two as a pair. Three rewrites of Controller.pack_floor_soc were green across all 19 files on52380c0and again after the rebase onto3934ad7: min(float(v) for v in vals) -> ... vals[:2] +4 bytes -> ... vals[-2:] +5 bytes -> ... vals[:3] +4 bytes while [:1], [-1:], [1:], [:-1], max() and the mean were all red. The suite's separating power on the SoC axis stopped at exactly N=2 - the state the floor axis was in before round 3. It changes a real answer: three packs at 50/60/11 %, emptiest THIRD, answers 11.0 shipped and 50.0 mutated. The site is then graded for discharge PERMISSION against a pack 39 points fuller than the emptiest and spends a pack its own BMS stops, through sim/inject.py which runs no validator rule. Round 3's machinery transfers directly. New test_peakguard.py part 12i: - six REAL Controller.cycle()s, emptiest pack at every position of a three-pack roster and both positions of a two-pack one, observable read off PeakDecision.soc; cut-outs held flat so the floor axis cannot move the answers - computed slice and non-slice tables, printed, red naming any live survivor - a LENGTH SWEEP over the real property, 78 rosters of 1..12 real inverters, closing head/tail widths 1-11 and asserting width 12 is unreached - a NONE-GUARD sweep over the same 78 - THREE exact structural pins: the vals comprehension iterates the bare self.inverters, and both any() and min() reduce the whole vals - closing every slice width at every roster length, which no bounded fixture can SPIKE-04: pack B is 4 x Dyness DL5.0C, DoD 90 -> floor 10 % [FROM DATASHEET AND SERIAL - inverter B does not exist and nothing about it is measured]. Pack A is DoD 90 -> 10 % and that one IS measured. So production is DEGENERATE on the floor axis and a differing floor exists ONLY in the fixtures - which is the argument for keeping them, not for simplifying them. Recorded in CHANGELOG and DECISIONS so nobody later collapses them to one number. The SoC axis has no such degeneracy. AND THE FAMILY, not the member. Every reduction over self.inverters/self.packs in Controller was found from the AST and mutated to a slice against the full suite. Eighteen were already graded. FOUR were not, and three of them are SAFETY-30's, all any(i.sign_wd.tripped for i in self.inverters), each +4 bytes and green across 19 files - because the only fixture that reached them was a roster of ONE, where self.inverters[:1] IS self.inverters: - cycle()'s stale-input hold: a LATCHED unit must take the roster past it or its failsafe SET stops being re-filed the moment a poll is missed - publish()'s status vocabulary: a site holding an inverter at 0 W published "running" - status_payload()'s banner: the one headline on the page New test_watchdog.py part 8 grades all three with the latch at EACH position plus a no-latch counterfactual. New part 12j pins all 26 roster comprehensions in Controller to iterating the roster WHOLE, so a reduction added over a slice anywhere in the class is red on the line it is written, with no fixture needed. Two rows reported OPEN rather than closed: _grade_store_migration()'s second disjunct (now red on 12j, argued equivalent because a unit tripped at startup implies its namespaced key is set) and cycle()'s undeliverable-unit LOG LINE, where sorted(...)[:1] is still green - it truncates names in one event message; the refusal itself is a different expression and is graded. MUTATION.md gains habits 10 and 11: a fixture of N members cannot separate two reductions that agree on N, and mutate the SIBLINGS of anything you harden. No shipped behaviour changed - tests and documentation only. 19/19 green; all three named survivors and all round-3 mutations verified RED. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
This commit is contained in:
@@ -300,6 +300,38 @@ Cheap habits fall out of it, and all of them are now used in this repository:
|
||||
assert the **shape** of that second report, not only its verdict, so the
|
||||
check cannot silently revert to grading the first path.
|
||||
|
||||
10. **A fixture of N members cannot separate two reductions that agree on N
|
||||
members — so grade the FAMILY, and grade it from the SOURCE.** This is the
|
||||
only lesson SAFETY-32 cost four review rounds to learn, and it generalises
|
||||
past packs to every roster, list and window in this repository. Two packs
|
||||
cannot tell `max` from `min` from first from last. Three cannot tell `max`
|
||||
from `max(x[-2:])`. **The regress does not terminate in a fixture**: `[:1]`
|
||||
needs two members, `[:2]` three, `[:3]` four, and adding one more member
|
||||
moves the hole rather than closing it. Three things follow, and all three
|
||||
are now in `test_peakguard.py` parts 12c2, 12g, 12i and 12j:
|
||||
* the fixture is a **SET** — the extreme at every position of the roster,
|
||||
not one roster with the extreme somewhere;
|
||||
* the family table is **COMPUTED and PRINTED**, so a member no fixture kills
|
||||
shows up as a named survivor and takes the check red, instead of being
|
||||
absent from a list nobody re-derived;
|
||||
* where the claim is an **IDENTITY** ("this roster", "this list"), pin it
|
||||
**exactly on the AST** — `ast.unparse(arg) == "self.packs"` is false of
|
||||
every slice, where `"self.packs" in src` is true of all of them. Where the
|
||||
claim is **SEMANTIC** ("the maximum"), a shape pin would refuse honest
|
||||
refactors, so report the unreached width **with its size** instead.
|
||||
|
||||
11. **When a check of this shape is found, mutate its SIBLINGS before shipping
|
||||
the fix.** SAFETY-32 was rejected three times for the same defect one step
|
||||
over: the reduction, then the roster handed to it, then the reduction handed
|
||||
to the *other argument of the same call*. Enumerating the family from the
|
||||
AST and mutating each member took one afternoon and found **four more
|
||||
survivors in code the card never touched** — three of SAFETY-30's
|
||||
`any(i.sign_wd.tripped for i in self.inverters)` reductions, invisible
|
||||
because the only fixture that reached them was a roster of ONE, where
|
||||
`self.inverters[:1]` **is** `self.inverters`. Ask of every fix: *what is the
|
||||
sibling of the thing I just hardened, and does any fixture separate it from
|
||||
a slice of itself?*
|
||||
|
||||
And the mutation-table consequence: **a variant is not covered because a
|
||||
near-miss of it is.** DEV-02's table listed the *actuator* variant of the
|
||||
per-unit claim defect (`device`/`supervised` filed without `inv`), which the
|
||||
|
||||
Reference in New Issue
Block a user