The rolling budget now holds without a monotonic clock (it never refills). Quarter-edge tests use seeds 1-3 at 2, 5, 10 and 15 s (no hand-picked seeds). Re-pin comments point at part 6. DECISIONS lists the limits the owner accepted with N=8 on 2026-10-20. Co-Authored-By: Claude <noreply@anthropic.com>
5.2 KiB
SAFETY-119 - no "not keeping up" when the loop takes the wire back
Status: for review. Tests: test_safety119.py.
Cause. When another owner (the peak guard, a maintenance charge) has set
the wire and the loop wins again, cycle() re-seeds the integrator from that
owner's command, and compute() adds gain * error from a meter that does
not show that command yet. On the TEST-23 rig (SAFETY-118 I-2) the guard
stepped from -2000 W to +429 W with 5 s left. A cycle later the loop's ask
crossed the +2500 W rail and beat the guard's floor, so SAFETY-85 rule 1
counted it as the loop's own bound and raised goodwe_integrator_bound. The
battery gave what it was told, and the cap held.
Decision. After a hand-back, the loop's integrator bound is not counted
until the plant's settle has passed (SHORT_CYCLES cycles and
SHORT_SPAN_S, the window SAFETY-85 grades a battery on). After that it
counts as before. An over-rating (clamped, a bug) on a cycle the loop
decided always counts. No command changes, and no text was added or changed.
A rolling budget (round 3). The settle above is per hand-back, and
losing the wire restarts it. Across all hand-backs the bound is hidden on at
most 8 cycles in any 15 minutes (ROLLING_BOUND_CYCLES, ROLLING_S). A cycle
over the budget counts. Round 1 restarted the settle at
every hand-back, so a guard and a charge taking turns hid the bound for good
(0 warnings in 2 h where 28 were raised before). Round 2 gave each run of
hand-backs its own budget; at 10 s and 15 s meters each quarter then started
a fresh one and the loop's bound was never counted. A quarter-edge kick is a
few bound cycles in one quarter, a real bound that stays is many: a budget
per 15 minutes tells them apart.
Why 8. Measured over 2 h (goodwe-addon-checks:1), budgets 3 to 10, the
quarter-edge case (house 800 W, cap 2500 W, 1 to 15 s meters) and the case
below. 3, 4, 5 and 6 let the false alarm back (at 2 s, for each). 7 let it
back on 10 of 30 seeds at 2 s. 8: 1 of 30 seeds at 2 s, none at 1, 5, 10 and 15 s.
9 and 10: none, but the case below then misses quarters at 10 s. So 8 is the
largest budget that still warns every quarter at 2, 5 and 10 s, and the
margin is narrow: 7 and 9 each fail one side.
What holds with 8. Single hand-back, the loop at its rail: the first counted bound cycle is the 9th, so 8 cycles after the hand-back (measured, real Controller): 8 s at 1 s, 16 s at 2 s, 40 s at 5 s (default), 80 s at 10 s, 120 s at 15 s, 240 s at 30 s, 480 s at 60 s. Repeated hand-backs (2 x 2500 W, cheap-window charge, no usable forecast, cap 5000 W, house 5050 and 5100 W, 2 h; 20 runs at 2, 5 and 10 s, 8 at 15 s):
| meter | warnings in 2 h | quarters warned (of 7 after the first) | without the fix |
|---|---|---|---|
| 2 s | 51 to 57 | 7 | 54 to 56 |
| 5 s | 19 to 25 | 7 | 28 |
| 10 s | 7 to 11 | 7 | 14 |
| 15 s | 1 to 3 | 1 to 3 | 14 |
| 30 s | 0 | 0 | 7 and 11 |
At 15 s it warns in some quarters, not all. At 30 s it does not warn in this case. For 60 s the rig's cycle is a different regime (the house is not over the cap there), so nothing is claimed. The cap held at 2 s and 5 s; at 10 s and 15 s the meter's lag puts the highest quarter 20 to 55 W over 5000 W, the same with and without the fix. Quarter-edge case, 15 s meter: the highest quarter is 2327 W to 2680 W, the same with and without the fix.
Accepted limits (owner, 2026-10-20, with N=8). The owner accepted N=8 knowing that:
- at a 2 s meter the quarter-edge false alarm still shows on about 1 of 30 seeds (one notification in 2 h);
- at a 15 s meter the case above warns in only 1 to 3 of the 7 quarters (some runs are silent for more than one quarter in a row);
- at a 30 s meter and slower it gives no warning in that case (0 in 2 h, against 7 and 11 without the fix): a real bound is suppressed there;
- the cap is exceeded at 10 s and slower in that case (and up to 2680 W of 2500 W at 15 s on the quarter edge), the same with and without the fix. A budget that follows the cycle length (10 cycles up to 3.5 s, 8 to 11 s, 6 to 16 s, 4 beyond) met both cases on every swept seed at 1 to 20 s (at 30 s: no false alarm, 3 to 5 of 7 quarters) in the dev round-3 sweep; it was not adopted. Without a monotonic clock the budget never refills, so a bound that stays is counted after 8 hidden cycles.
Kept. The loop in charge at its rail warns on the same cycle as before.
The start and a restart after a stop are not hand-backs. The quarter-edge
kick of test_safety119.py part 1 stays hidden (0 warnings in 2 h at
1, 2, 5, 10 and 15 s meters).
Re-pinned. test_safety84 (all six) and test_safety87 (n1_crossing,
n1_above, n1_charge_low): only OUTPUT_CLAMP log rows differ; commands and
claims are the same (test_safety119.py part 6).
Not hidden. A battery that gives less than another owner told it still alarms through SAFETY-85 rule 2. "Always counts" for an over-rating holds on cycles the loop decided; on others SAFETY-85 rule 1 already dropped it.
Not changed. The kick itself is control behaviour: for a few seconds at
a quarter's edge the loop asks up to its rail. In test_safety119.py part 1
that is more discharge than the guard asked at a quarter's end, and a few
seconds of up to 500 W more charge at the next quarter's start.