SAFETY-25: pin the claim-source set, so the tripwire is one
checks / unit (pull_request) Successful in 3m53s
checks / unit (pull_request) Successful in 3m53s
The durability claim this PR shipped was FALSE, and the reviewer of #31 disproved it by building the fix. Part 10 said, in four committed places, that it "goes RED the day a loop-side floor is built, so the exposure cannot change size under a green suite". The reviewer implemented the memo's own recommended Option B in main.py - a `Claim.limit("reserve", P_LOOP, hi=0.0)` filed under a `loop_soc_floor` - and the ENTIRE SUITE STAYED GREEN. Part 10 green. Mutation 13 went on printing "NOTHING PREVENTS IT, ON PURPOSE" and the same 21 % -> 10.0 %, on a tree where something DID prevent it. Pushed to an absurd always-binding 95 % reserve it was caught only by SAFETY-24's pre-existing part 7 wiring checks; part 10 was still green. THE CAUSE IS STRUCTURAL, not a missing assertion. `replay_loop()` hand-builds main.py's claim list as a literal `[supervised, loop, peakguard.claim(...)]`, so it is blind to any claim main.py ADDS - which is exactly where AC 2 would be built. That is this repository's named failure mode: a check that observed something other than what it claimed to observe, shipped as a guarantee inside the document that recommends the change. THE FIX IS 10f: THE CLAIM-SOURCE SET, PINNED, two ways because the two fail differently. * STATICALLY, over `Controller.cycle()`'s own AST. Every `claims.append(...)` in it must be one the scan can read - an unreadable append FAILS rather than counting as nothing, which is how a scan like this goes quietly vacuous - and the sources it can file must be exactly {device, supervised, failsafe, safety, peak, arbitrage, maintenance, loop}. A ninth takes part 10 red on the line it is written, in every configuration, whether or not any fixture here reaches the state that files it. * DYNAMICALLY, through REAL `Controller.cycle()`s with main.py's OWN `resolve` spied - not a list this file built and hoped matched - swept over the WHOLE 0..100 % SoC range in the two states 10a-10c replay. Idle: exactly {device, supervised, loop}. Floor-limited: exactly {device, supervised, loop, peak}. A claim conditioned on a low pack, which is the shape a loop-side floor has, cannot hide from a sweep that visits 0 %. And the fold is checked rather than assumed: main.py's two P_SAFETY rating limits are both symmetric LIMITs, so replay_loop() modelling them as one `supervised` claim at the tighter bound is exact. MEASURED AGAINST THE COUNTEREXAMPLE. Reproduced first on the unfixed tree (green, as reported). On this tree the reviewer's Option B at its recommended default takes part 10 RED on 3 checks, and the 95 % variant on 6. Nine mutations were run in all, each against a freshly copied tree with no `__pycache__` present, mounted `:ro`; all nine red, no survivors, no `+0 B` delta. MUTATION.md records why a second round was needed: the first round's mutations were all aimed at DISARMING the guard, while the claim part 10 actually made was about the guard being FIXED. The four false sentences are replaced with what is now true, and each says WHICH half catches WHICH shape - 10f for an arbiter claim (Options B and C), 10a-10c for a clamp inside control.compute() or the guard (Option D), which the replay drives directly. AC 2 IS STILL OPEN AND NOTHING HERE BUILDS IT. The loop-side floor was a test mutant, reverted before commit. The memo still says "Status: OPEN. This is a memo, not a decision." Also, non-blocking, from the same review: the NOT READY banner had inherited the new scope sentence and become a ~550-character paragraph that the row three lines below repeated verbatim. A checks() row may now carry an optional short `banner` form; the floor-limited row does, and `status_payload()` prefers it. THE ROW IS UNCHANGED - shortening that would have been weakening AC 3 - and rows without the key are unaffected. Graded, and mutated: dropping the short form goes red. Merged origin/release/1.0 first (CI-02), keeping both tickets' CHANGELOG entries. Full suite green: 17/17 in goodwe-addon-ci (test_ci_gate.py needs `git`, which CI-02 added to .gitea/ci/Dockerfile but the local image lacks; run with it installed and it passes). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3cb9d3b7d9
commit
e498dd4c1c
@@ -229,9 +229,27 @@ around.
|
||||
reading that has never been recalibrated is picking a number against an
|
||||
instrument we have not checked.
|
||||
3. **Whichever way:** the tripwire in `test_peakguard.py` part 10 stays until the
|
||||
question is answered. It **records the exposure**, so the day someone builds a
|
||||
loop-side floor it goes red and has to be updated deliberately, rather than
|
||||
the exposure quietly changing under a green suite.
|
||||
question is answered, so the day someone builds a loop-side floor it goes red
|
||||
and has to be updated deliberately, rather than the exposure quietly changing
|
||||
under a green suite. It has two halves and they cover the two places the floor
|
||||
can land:
|
||||
|
||||
- **10f pins the claim-source set.** Every `claims.append(...)` in
|
||||
`Controller.cycle()` is read off its AST and must be one of eight named
|
||||
sources, and real cycles — spied at main.py's own `resolve`, swept over the
|
||||
whole 0–100 % SoC range in both the idle and the floor-limited state — must
|
||||
file exactly those. **Option B is a ninth source**, and so is **Option C**'s
|
||||
`set` claim, so filing either takes part 10 red on the line it is written.
|
||||
**Option A** files no claim at all — it is the honesty half already
|
||||
shipped, and part 10 is the record it leaves behind.
|
||||
- **10a–10c replay the shipped code**, so **Option D** — a clamp inside
|
||||
`control.compute()` — moves the replayed numbers instead.
|
||||
|
||||
⚠️ **10f is load-bearing and was added late.** PR #31 shipped this section
|
||||
claiming the tripwire without it, and the reviewer disproved the claim by
|
||||
building Option B in `main.py`: the entire suite, part 10 included, stayed
|
||||
green, and mutation 13 went on printing *"nothing prevents it, on purpose"* on
|
||||
a tree where something did. Do not remove 10f while this question is open.
|
||||
|
||||
## 7. What the replay is and is not
|
||||
|
||||
|
||||
Reference in New Issue
Block a user