- REFUSED_SPAN_S: the 12 cycles of evidence must also span (SETTLE_CYCLES-1) * LOOP_TICK_S = 22 s. At a 1 s meter, lagged healthy readings refused both units 10-16 times an hour after load steps (site held 0 W). 2 s and slower ticks are unchanged. - REFUSES_ABOVE_CUT_OUT only for a refusal to give power: a full battery that stops taking charge is always above its cut-out level. - test_delivery: its wedged fixture (0.2 s cycles) no longer reaches a refusal; the release/1.0 checks are restored and a span check added. - test_refusal_units: lagged-reading and full-battery plants, two new checks, U14/U15 in the mutation table. Stale "> 500 W" wording fixed. DOCS/DECISIONS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.7 KiB
SAFETY-73 - a battery that refuses, with two or more inverters
Decided 2026-09-27 by the owner, on the phase A plan (option R). All four new questions answered yes. SAFETY-52's four decisions carry over.
The defect
SAFETY-52 held a refusing battery at 0 W at one inverter only. At N>1
(main.py passed refused=None) nothing closed a unit that gives ~0 W while
its state of charge reads above its configured cut-out, so the allocator kept
handing it its weighted half. Measured on the real Controller, two units,
house 3000 W, A cut out:
| register | law | B gives | grid | A's gate trips / h |
|---|---|---|---|---|
| flat -4 W | old | 533 W | +2471 W | 46 (each stops the whole site) |
| flat -4 W | new | 2000 W (its rating) | +1004 W | 1 |
| jitter | old | 1910 W | +1094 W | 0 |
| jitter | new | 2000 W | +1004 W | 0 |
House 1200 W, which B alone carries: old +671 W (flat) and +284 W (jitter) standing import, new -16 W. Both cut out: old keeps asking two empty batteries (+1910 W each on the jitter shape), new holds the site at 0 W.
What was decided
- A refusing unit is closed in that direction by its own P_SAFETY unit
claim, source
refused. It engages when the unit gives underREFUSED_BELOW_W(100 W) forSETTLE_CYCLES(12) cycles, spanning at leastREFUSED_SPAN_S(22 s), in one direction while asked more thanPROBE_W(200 W), or when its stale-reading gate trips on a held reading under 100 W. The allocator then hands its share to the others within their own limits (owner decision 1). It is a hard unit limit that always intersects, so OPT-11's P_MODE layer fits inside it. - It binds every strategy's ask on that unit (decision 2): peak guard, arbitrage and maintenance are placed around it like the loop.
- A refusal to give power above the configured cut-out logs INFO plus one WARNING ending
in
REFUSES_ABOVE_CUT_OUT(decision 3). SAFETY-42/43 no longer report such a unit: its share is 0 W, andaccount()treats it as parked. - All units refusing the same way pass that sign to
compute(), so the site holds 0 W with SAFETY-52's law (decision 4). Probes go one unit at a time. - Probe: every 15 min for 34 s the claim becomes a pin at +-200 W and the others carry the rest. The pin stands only while the resolved site target asks at least 200 W its way; otherwise it would make a sibling take the opposite direction, so it falls back to the closed limit for that cycle.
- Exits: a reading of 100 W or more in the held direction (probe or not); 100 W or more taken the other way for 12 cycles (charge at the floor; one cycle of the loop overshooting through zero is not enough); stop or start. On every exit the site target and integrator restart from the watts on the wire, or the reopened unit is handed a parked remainder at once.
Why 200 W and not saturation_w. A unit's share is often small: a 400 W
night load split two ways asks 200 W of each. With 500 W the empty battery
kept a phantom share that SAFETY-43's relative arm then reported as not
delivering, the ERROR decision 3 replaces. Shares of 200 W or less that give
under 100 W are "too small to call" in account(): the sibling covers the
meter and nothing is asked of the empty battery that matters.
Why one direction throughout. On TEST-19's flapping family a command that swaps sign each cycle reads ~0 through the plant's lag: 42 engagements in 21 no-fault runs without the rule, 0 with it.
N=1 is unchanged. Every SAFETY-73 path is gated on len(inverters) > 1.
test_refusal_units.py part 6 runs SAFETY-52's whole suite with every
SAFETY-73 method made to raise.
Evidence
test_refusal_units.py: cases a, b and c on both register shapes against the old behaviour, probes and their spacing, one-at-a-time probing, the recovery reseed, each exit, the small-ask and small-site-target cases, the WARNING, the status row and shadow mode. Mutations U1-U13 in its docstring.- No false engagements. TEST-19's no-fault campaign (admin/goodwe
sim/delivery_window.pyfamilies, 2640 runs at N=2 and 2640 at N=1, every unit executing its share) graded with the engage clause: 0 engagements, the longest qualifying streak 5 cycles against 12. The grader isgoodwe-session/scratch_s73/s73_nofault.py. - Recovery. Case a, A recovering into a house that fell below the parked target: worst grid +0 W with the reseed. Phase A measured -1059 W without the reseed and the site hold.
Review changes
- The evidence must span 22 s, not only 12 cycles. At a 1 s meter the 12
cycles were 11 s, shorter than a healthy reading's lag (dead time plus a
10-17 s publish). The reviewer's harness (real
Controller, both units with 6 s dead time and a 13-17 s publish, load steps 300/3000 W every 2 min) then refused both healthy units 10-16 times an hour, and each time the site held 0 W: +50-85 Wh import an hour and a +4.6 kW grid peak against +3.7 kW without the refusal. At a 2 s tick there were none.REFUSED_SPAN_Sis(SETTLE_CYCLES - 1) * LOOP_TICK_S, so 2 s and slower ticks are unchanged. - The cut-out WARNING is for a refusal to give power only. A full battery that stops taking charge is always above its cut-out level, and the WARNING told the installer to check a setting that has nothing to do with it.
Not covered
- A unit that gives a steady part of its ask (100 W or more) is a limit, as at N=1. SAFETY-42/43 still report it.
pack_permissionsstays SoC-based, so the peak guard may size a shave with a refusing battery counted. The shortfall shows asunmet_w.- A wedged unit (HW-01) reading ~0 W is now shown as a refusal, not reported as not delivering. Its share still moves, which is the gain SAFETY-42 measured.