Closes the first of the two gaps the 2026-09-07 verification left open on SAFETY-31. The rig half is a separate PR against admin/goodwe (SAFETY-31-rig) — the scenario cannot live in this repository.
It silently deletes the "Right now garage is discharging while shed charges" clause from the operator notification. circulation.py's own docstring calls that sentence the first thing somebody woken by this alarm needs, and "impossible to reconstruct from the number alone". The commit that shipped this card claimed "18 mutations, all RED"; the DECISIONS ledger M1–M19 has no entry for it.
It is not dead code. Ledger M18 — the whole await check_circulating() call removed — is red on four checks, so the function runs. The three checks on that notification asserted METER CANNOT SEE, CONTROL HAS BEEN STOPPED and isolator, and never an inverter name.
The fix
test_watchdog.py §6a, on a fixture built to tell three failures apart:
the roster is ["garage", "shed", "cellar"] — operator names, none of them UNIT_A. This is test_maintenance.py §11's lesson: on a fixture whose names are the module's own constants, a check cannot tell the roster from the constant.
the site runs twice, mirrored, so the sentence is not symmetric and a pos/neg swap is a different red from a deletion.
a third pack rests at 5 W, so the floor that stops a standby pack being named as a participant in a 6 kW loop is graded too.
Mutations — every one run on a tree with no __pycache__, mounted :ro into the checks image
#
mutation
Δ
result
M20
pos, neg = opposing(...) → ([], [])
−102 B
RED (2 checks) — the survivor
M21
pos and neg exchanged
+0 B
RED (3 checks)
M22
opposing(..., min_w=cfg.min_w / 2.0) → min_w=0.0
−25 B
RED (3 checks)
=== MUTATION M20 (-102 bytes)
test_watchdog.py exit=1
FAIL THE ALARM NAMES THE TWO PACKS AND SAYS WHICH WAY EACH ONE IS PULLING...
FAIL AND WITH THE TWO SWAPPED, THE SENTENCE SWAPS...
2 FAILED
=== MUTATION M21 (0 bytes)
test_watchdog.py exit=1
FAIL THE ALARM NAMES THE TWO PACKS...
FAIL AND WITH THE TWO SWAPPED, THE SENTENCE SWAPS...
FAIL ...and each run carries ONLY its own sentence...
3 FAILED
=== MUTATION M22 (-25 bytes)
test_watchdog.py exit=1
FAIL THE ALARM NAMES THE TWO PACKS...
FAIL AND WITH THE TWO SWAPPED, THE SENTENCE SWAPS...
FAIL ...and the RESTING pack is not named. `cellar` is on the roster...
3 FAILED
Suite with the block in place: 26/26 green.
⚠️ And a disclosure, which is the uncomfortable half
DECISIONS/SAFETY-31-circulating-power.md §3 records that the 924-run campaign behind circulation_w: 700 and circulation_hold_s: 75 used a harness committed in neither repository. Two further sweeps have now been run and no two of the three agree:
sweep
worst peak
worst continuous
trips
924-run campaign — harness uncommitted
1700 W
505.0 s at 100 W; 5.0 s above 700 W
0 / 924
independent reviewer sweep, 2026-09-07
2750 W
15.0 s above 700 W
0 / 48
two_master_circulation, 840 runs — committed
1048 W
10.0 s at 100 W; 5.0 s above 700 W
0 / 840
This is not evidence of fabrication and is not reported as any. The committed sweep measures why the spread is that wide — on one grid, changing only the plant model and the write-stagger set:
plant / stagger
worst legitimate peak
modelled plant (lag 10 s, ramp 800 W/s), staggers up to a whole cycle
1048 W
INSTANT (lag 0, unbounded ramp), staggers up to a whole cycle
2250 W
INSTANT, staggers strictly under a whole cycle
0 W
A factor of 2.1×, and a factor of infinity, between three defensible descriptions of the same rig. The plant and the stagger are terms of the number. Three sweeps can disagree that much without anybody having invented anything — and nobody can settle it while the instrument is missing.
What survives: the window argument. All three sweeps trip zero times, across 924 + 48 + 840 non-faulty runs, and all three put the worst continuous episode above 700 W at one to three control cycles against a fifteen-cycle window.
What does not survive as stated:505.0 s at 100 W, which is what the "knee at 400 W" argument for circulation_w: 700 rests on. The committed grid measures 10.0 s there. The curve still falls with the threshold (126/840 runs cross 100 W against 1/840 at 700 W), so the knee is real in direction; its depth is established by nothing anybody can run. §3 now says so, and the overclaiming comment at test_watchdog.py's 1700 W fixture ("the worst single transient the rig can produce") is replaced with what the fixture can support. The number in the check is unchanged.
Nothing about the guard is compromised. 0 trips in 3 independent grids.
What would settle it: the original harness, committed.
Not done here
sim/inject.py was never executed in this session (prohibited), and no hardware, container or rig lock was touched. The add-on suite ran in goodwe-addon-checks:1 against a read-only mount.
Closes the first of the two gaps the 2026-09-07 verification left open on SAFETY-31. **The rig half is a separate PR against `admin/goodwe` (`SAFETY-31-rig`)** — the scenario cannot live in this repository.
## The gap
A mutation **survived the whole 26-file suite**:
```
app/main.py:3119 pos, neg = opposing(...) -> ([], []) (-102 B) SURVIVED 26/26
```
It silently deletes the *"Right now garage is discharging while shed charges"* clause from the operator notification. `circulation.py`'s own docstring calls that sentence the first thing somebody woken by this alarm needs, and *"impossible to reconstruct from the number alone"*. The commit that shipped this card claimed *"18 mutations, all RED"*; the DECISIONS ledger M1–M19 has no entry for it.
**It is not dead code.** Ledger M18 — the whole `await check_circulating()` call removed — is red on four checks, so the function runs. The three checks on that notification asserted `METER CANNOT SEE`, `CONTROL HAS BEEN STOPPED` and `isolator`, and **never an inverter name**.
## The fix
`test_watchdog.py` §6a, on a fixture built to tell three failures apart:
* the roster is **`["garage", "shed", "cellar"]`** — operator names, none of them `UNIT_A`. This is `test_maintenance.py` §11's lesson: on a fixture whose names are the module's own constants, a check cannot tell the roster from the constant.
* the site runs **twice, mirrored**, so the sentence is not symmetric and a `pos`/`neg` swap is a *different* red from a deletion.
* a **third pack rests at 5 W**, so the floor that stops a standby pack being named as a participant in a 6 kW loop is graded too.
## Mutations — every one run on a tree with no `__pycache__`, mounted `:ro` into the checks image
| # | mutation | Δ | result |
|---|---|---:|---|
| **M20** | `pos, neg = opposing(...)` → `([], [])` | −102 B | **RED** (2 checks) — *the survivor* |
| M21 | `pos` and `neg` exchanged | **+0 B** | **RED** (3 checks) |
| M22 | `opposing(..., min_w=cfg.min_w / 2.0)` → `min_w=0.0` | −25 B | **RED** (3 checks) |
```
=== MUTATION M20 (-102 bytes)
test_watchdog.py exit=1
FAIL THE ALARM NAMES THE TWO PACKS AND SAYS WHICH WAY EACH ONE IS PULLING...
FAIL AND WITH THE TWO SWAPPED, THE SENTENCE SWAPS...
2 FAILED
=== MUTATION M21 (0 bytes)
test_watchdog.py exit=1
FAIL THE ALARM NAMES THE TWO PACKS...
FAIL AND WITH THE TWO SWAPPED, THE SENTENCE SWAPS...
FAIL ...and each run carries ONLY its own sentence...
3 FAILED
=== MUTATION M22 (-25 bytes)
test_watchdog.py exit=1
FAIL THE ALARM NAMES THE TWO PACKS...
FAIL AND WITH THE TWO SWAPPED, THE SENTENCE SWAPS...
FAIL ...and the RESTING pack is not named. `cellar` is on the roster...
3 FAILED
```
Suite with the block in place: **26/26 green.**
## ⚠️ And a disclosure, which is the uncomfortable half
`DECISIONS/SAFETY-31-circulating-power.md` §3 records that the 924-run campaign behind `circulation_w: 700` and `circulation_hold_s: 75` used a harness **committed in neither repository**. Two further sweeps have now been run and no two of the three agree:
| sweep | worst peak | worst continuous | trips |
|---|---:|---:|---:|
| 924-run campaign — harness uncommitted | 1700 W | 505.0 s at 100 W; 5.0 s above 700 W | 0 / 924 |
| independent reviewer sweep, 2026-09-07 | **2750 W** | **15.0 s above 700 W** | 0 / 48 |
| `two_master_circulation`, 840 runs — **committed** | 1048 W | 10.0 s at 100 W; 5.0 s above 700 W | **0 / 840** |
**This is not evidence of fabrication and is not reported as any.** The committed sweep measures *why* the spread is that wide — on one grid, changing only the plant model and the write-stagger set:
| plant / stagger | worst legitimate peak |
|---|---:|
| modelled plant (lag 10 s, ramp 800 W/s), staggers up to a whole cycle | 1048 W |
| `INSTANT` (lag 0, unbounded ramp), staggers up to a whole cycle | 2250 W |
| `INSTANT`, staggers strictly under a whole cycle | **0 W** |
A factor of 2.1×, and a factor of infinity, between three defensible descriptions of the same rig. **The plant and the stagger are terms of the number.** Three sweeps can disagree that much without anybody having invented anything — and nobody can settle it while the instrument is missing.
**What survives:** the window argument. All three sweeps trip **zero** times, across 924 + 48 + 840 non-faulty runs, and all three put the worst continuous episode above 700 W at one to three control cycles against a fifteen-cycle window.
**What does not survive as stated:** `505.0 s at 100 W`, which is what the *"knee at 400 W"* argument for `circulation_w: 700` rests on. The committed grid measures **10.0 s** there. The curve still falls with the threshold (126/840 runs cross 100 W against 1/840 at 700 W), so the knee is real in direction; its **depth is established by nothing anybody can run**. §3 now says so, and the overclaiming comment at `test_watchdog.py`'s 1700 W fixture (*"the worst single transient the rig can produce"*) is replaced with what the fixture can support. The number in the check is unchanged.
**Nothing about the guard is compromised.** 0 trips in 3 independent grids.
**What would settle it:** the original harness, committed.
## Not done here
`sim/inject.py` was never executed in this session (prohibited), and no hardware, container or rig lock was touched. The add-on suite ran in `goodwe-addon-checks:1` against a read-only mount.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
`pos, neg = opposing(...)` -> `([], [])` in main.py's `check_circulating()`
(-102 B) deletes the "Right now X is discharging while Y charges" clause from
the operator notification, and SURVIVED ALL 26 FILES. The commit that shipped
this card claimed "18 mutations, all RED"; the ledger has no entry for the
attribution, and this is that entry.
It is not dead code. Ledger M18 - the whole `await check_circulating()` call
removed - is red on four checks, so the function runs. The three checks on the
notification asserted METER CANNOT SEE, CONTROL HAS BEEN STOPPED and `isolator`,
and never an inverter name. `circulation.py`'s own docstring calls that sentence
the first thing somebody woken by this alarm needs and "impossible to
reconstruct from the number alone".
test_watchdog.py section 6a closes it on a fixture that can tell three failures
apart:
* the roster is ["garage", "shed", "cellar"] - operator names, none of them
`UNIT_A`, for test_maintenance.py section 11's reason: on a fixture whose
names are the module's own constants a check cannot tell the roster from
the constant;
* the site is run TWICE, MIRRORED, so the sentence is not symmetric and a
pos/neg swap is a different red from a deletion;
* a third pack rests at 5 W, so the floor that stops a pack in standby being
named as a participant in a 6 kW loop is graded too.
Mutations, on a tree with no __pycache__ mounted :ro into the checks image:
M20 pos, neg = opposing(...) -> ([], []) -102 B RED (2 checks)
M21 pos and neg exchanged +0 B RED (3 checks)
M22 opposing(..., min_w=cfg.min_w/2.0) -> 0.0 -25 B RED (3 checks)
26/26 files green with the block in place.
AND A DISCLOSURE, WHICH IS THE UNCOMFORTABLE HALF. DECISIONS section 3 records
that the 924-run campaign behind `circulation_w: 700` and `circulation_hold_s:
75` used a harness that is not committed in either repository. Two further
sweeps have now been run and no two of the three agree: peaks of 1700 W, 2750 W
and 1048 W, and worst-continuous figures above 700 W of 5.0 s, 15.0 s and 5.0 s.
That is NOT evidence of fabrication and is not reported as any. The committed
sweep measures why the spread is that wide: on one grid, changing only the
plant model and the write-stagger set, the worst legitimate peak is 1048 W, or
2250 W, or 0 W. The plant and the stagger are TERMS of the number. What the
spread does establish is that the claim cannot be adjudicated while the
instrument is missing.
The window argument survives and is now committed - all three sweeps trip zero
times, on 924 + 48 + 840 non-faulty runs. The threshold CURVE does not survive
as stated: `505.0 s at 100 W`, which is what the "knee at 400 W" argument rests
on, is 10.0 s on the committed grid. The curve still falls with the threshold,
so the knee is real in direction; its depth is established by nothing anybody
can run, and this commit says so rather than repeating it. The overclaiming
comment at test_watchdog.py's 1700 W fixture ("the worst single transient the
rig can produce") is replaced with what the fixture can support.
The committed, re-runnable half is admin/goodwe's `two_master_circulation`,
opened as a separate PR against that repository.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
Review reproduced every figure in both PRs except one, and rejected on it.
⚠️ "0 of 90 GAINS x SPLITS combinations produce opposing shares" - the SUBSTANCE
is right and review re-measured it, but the DENOMINATOR is invented. sitesim.py
declares GAINS with 7 entries and SPLITS with 6: the grid is 42. 90 is not even
divisible by 7, so no reading of "GAINS x SPLITS" reaches it. It is almost
certainly TEST-20`s RUN count (84 + 6), which is a different quantity that
happened to be nearby.
⚠️ AND THE WORST COPY OF IT WAS INSIDE THE `PRECONDITION FAILED` TEXT, so the
next engineer to see this scenario go red would have been handed an
unreproducible measurement as their diagnosis - the exact defect class this
campaign exists to correct, in the failure path where it does the most harm.
Corrected in all seven places. At the failure-text site the denominator is now
`len(GAINS) * len(SPLITS)`, computed rather than typed, so it cannot drift from
the grid again - the same fix test_translations.py already took.
Also N3: a missing blank line was going to render the S3 and M19 paragraphs as
one in DECISIONS.
Re-run after the change: `inject.py two_master_circulation` PASS in 35 s, norig,
no rig lock taken. Add-on suite 26 files, all pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
admin
merged commit a0d981731c into release/1.02026-09-08 04:27:17 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes the first of the two gaps the 2026-09-07 verification left open on SAFETY-31. The rig half is a separate PR against
admin/goodwe(SAFETY-31-rig) — the scenario cannot live in this repository.The gap
A mutation survived the whole 26-file suite:
It silently deletes the "Right now garage is discharging while shed charges" clause from the operator notification.
circulation.py's own docstring calls that sentence the first thing somebody woken by this alarm needs, and "impossible to reconstruct from the number alone". The commit that shipped this card claimed "18 mutations, all RED"; the DECISIONS ledger M1–M19 has no entry for it.It is not dead code. Ledger M18 — the whole
await check_circulating()call removed — is red on four checks, so the function runs. The three checks on that notification assertedMETER CANNOT SEE,CONTROL HAS BEEN STOPPEDandisolator, and never an inverter name.The fix
test_watchdog.py§6a, on a fixture built to tell three failures apart:["garage", "shed", "cellar"]— operator names, none of themUNIT_A. This istest_maintenance.py§11's lesson: on a fixture whose names are the module's own constants, a check cannot tell the roster from the constant.pos/negswap is a different red from a deletion.Mutations — every one run on a tree with no
__pycache__, mounted:rointo the checks imagepos, neg = opposing(...)→([], [])posandnegexchangedopposing(..., min_w=cfg.min_w / 2.0)→min_w=0.0Suite with the block in place: 26/26 green.
⚠️ And a disclosure, which is the uncomfortable half
DECISIONS/SAFETY-31-circulating-power.md§3 records that the 924-run campaign behindcirculation_w: 700andcirculation_hold_s: 75used a harness committed in neither repository. Two further sweeps have now been run and no two of the three agree:two_master_circulation, 840 runs — committedThis is not evidence of fabrication and is not reported as any. The committed sweep measures why the spread is that wide — on one grid, changing only the plant model and the write-stagger set:
INSTANT(lag 0, unbounded ramp), staggers up to a whole cycleINSTANT, staggers strictly under a whole cycleA factor of 2.1×, and a factor of infinity, between three defensible descriptions of the same rig. The plant and the stagger are terms of the number. Three sweeps can disagree that much without anybody having invented anything — and nobody can settle it while the instrument is missing.
What survives: the window argument. All three sweeps trip zero times, across 924 + 48 + 840 non-faulty runs, and all three put the worst continuous episode above 700 W at one to three control cycles against a fifteen-cycle window.
What does not survive as stated:
505.0 s at 100 W, which is what the "knee at 400 W" argument forcirculation_w: 700rests on. The committed grid measures 10.0 s there. The curve still falls with the threshold (126/840 runs cross 100 W against 1/840 at 700 W), so the knee is real in direction; its depth is established by nothing anybody can run. §3 now says so, and the overclaiming comment attest_watchdog.py's 1700 W fixture ("the worst single transient the rig can produce") is replaced with what the fixture can support. The number in the check is unchanged.Nothing about the guard is compromised. 0 trips in 3 independent grids.
What would settle it: the original harness, committed.
Not done here
sim/inject.pywas never executed in this session (prohibited), and no hardware, container or rig lock was touched. The add-on suite ran ingoodwe-addon-checks:1against a read-only mount.🤖 Generated with Claude Code
https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
`pos, neg = opposing(...)` -> `([], [])` in main.py's `check_circulating()` (-102 B) deletes the "Right now X is discharging while Y charges" clause from the operator notification, and SURVIVED ALL 26 FILES. The commit that shipped this card claimed "18 mutations, all RED"; the ledger has no entry for the attribution, and this is that entry. It is not dead code. Ledger M18 - the whole `await check_circulating()` call removed - is red on four checks, so the function runs. The three checks on the notification asserted METER CANNOT SEE, CONTROL HAS BEEN STOPPED and `isolator`, and never an inverter name. `circulation.py`'s own docstring calls that sentence the first thing somebody woken by this alarm needs and "impossible to reconstruct from the number alone". test_watchdog.py section 6a closes it on a fixture that can tell three failures apart: * the roster is ["garage", "shed", "cellar"] - operator names, none of them `UNIT_A`, for test_maintenance.py section 11's reason: on a fixture whose names are the module's own constants a check cannot tell the roster from the constant; * the site is run TWICE, MIRRORED, so the sentence is not symmetric and a pos/neg swap is a different red from a deletion; * a third pack rests at 5 W, so the floor that stops a pack in standby being named as a participant in a 6 kW loop is graded too. Mutations, on a tree with no __pycache__ mounted :ro into the checks image: M20 pos, neg = opposing(...) -> ([], []) -102 B RED (2 checks) M21 pos and neg exchanged +0 B RED (3 checks) M22 opposing(..., min_w=cfg.min_w/2.0) -> 0.0 -25 B RED (3 checks) 26/26 files green with the block in place. AND A DISCLOSURE, WHICH IS THE UNCOMFORTABLE HALF. DECISIONS section 3 records that the 924-run campaign behind `circulation_w: 700` and `circulation_hold_s: 75` used a harness that is not committed in either repository. Two further sweeps have now been run and no two of the three agree: peaks of 1700 W, 2750 W and 1048 W, and worst-continuous figures above 700 W of 5.0 s, 15.0 s and 5.0 s. That is NOT evidence of fabrication and is not reported as any. The committed sweep measures why the spread is that wide: on one grid, changing only the plant model and the write-stagger set, the worst legitimate peak is 1048 W, or 2250 W, or 0 W. The plant and the stagger are TERMS of the number. What the spread does establish is that the claim cannot be adjudicated while the instrument is missing. The window argument survives and is now committed - all three sweeps trip zero times, on 924 + 48 + 840 non-faulty runs. The threshold CURVE does not survive as stated: `505.0 s at 100 W`, which is what the "knee at 400 W" argument rests on, is 10.0 s on the committed grid. The curve still falls with the threshold, so the knee is real in direction; its depth is established by nothing anybody can run, and this commit says so rather than repeating it. The overclaiming comment at test_watchdog.py's 1700 W fixture ("the worst single transient the rig can produce") is replaced with what the fixture can support. The committed, re-runnable half is admin/goodwe's `two_master_circulation`, opened as a separate PR against that repository. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU