# SAFETY-84 - a maintenance charge at two inverters **Status: DECIDED 2026-09-30.** Follows SAFETY-78's "Not changed" list. Tests: `test_safety84.py`; identity: `tools/safety84_identity.py`. **The defect.** In a cheap window the charging pack files its own `hi = -charge` (P_MAINT_SHAPE). At N>=2 the loop's site target sat above it (a house to cover, or its 400 W slew step from 0 W), so the other pack had to give while this one charged. The allocator refuses opposite directions (SAFETY-31), so the site sat at 0 W whatever the house (`tools/safety84_identity.py` on 9d5b7d1: 436 of 480 cycles refused). N=1 was fine. **The fix.** - While a pack's own limits make it charge at N>=2, `main.py` files a site limit `charge`, `hi` = what that pack's own `hi` places (`allocator.hi_w()`, whole W as the allocator rounds). The site target is never above it, so the other pack only adds power in the same direction. Opposite splits stay forbidden. Mirror of SAFETY-78's `drain` floor. - Peak guard first (owner order, SAFETY-83). The charge is sized to the quarter's headroom under the aim (`charge_headroom_w()`, cap or banked peak minus `peak_margin_w`), so it charges less instead of stopping: the meter sits on the aim and the guard does not need to step in. Where the house alone is over the aim the guard engages, the shaping is suspended as before (`shaping_suspended()`) and no `charge` is filed, so the guard's `lo` and this `hi` never meet in one cycle (no `lo > hi`, no park at 0 W). Without the headroom sizing the aim still held (the suspension did it), but a battery gave power back on 285 of 540 cycles at a 3000 W house (test_safety84.py part 2). - A site stop (a safety SET above P_MAINT_SHAPE) sizes the charge to 0 W: the unit `hi` is a hard limit, so a charge under a 0 W stop needs the other pack to give. It was refused, with an ARBITER line, on most cycles of the stop (test_safety84.py part 4). The stop still parked the site at 0 W. - A pack that cannot take the charge (a refusal, a full pack, a stale link) files no ceiling; the other pack follows the loop. - Not filed at N=1: byte-identical (`tools/safety84_identity.py`, which hashes every claim, in and out of a cheap window and under a banked peak). **Not changed.** - At N=1 the charge is not sized to the headroom: the guard still suspends it and the battery gives until the quarter is safe. The same sizing would fit there; that is an owner question, not this fix. - Outside a cheap window (`hi = 0`, solar only) nothing is filed, at any N. - A HOLD (SAFETY-86) and the "Battery is not keeping up" alarm (SAFETY-85) are untouched. In the identity runs that alarm's log line comes as often at N=2 as at N=1 (2 in 480 cycles).