SAFETY-119 round 3: rolling 15-minute budget for the hidden integrator bound
checks / unit (pull_request) Successful in 42m33s
infra-pin / fresh (pull_request) Successful in 2s

At most 8 hidden bound cycles in any 900 s across all hand-backs, replacing
the per-run budget. Chosen from a sweep (N 3 to 10, 1 to 15 s meters, both
cases); DECISIONS has the table. Tests: never case at 5, 10, 15 s, quarter
edge at 2, 10, 15 s, the rolling budget at its boundaries.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
glenn schrooyen
2026-10-20 17:27:43 +02:00
co-authored by Claude
parent 85542e443b
commit 103db2fcda
3 changed files with 213 additions and 156 deletions
@@ -17,34 +17,51 @@ until the plant's settle has passed (`SHORT_CYCLES` cycles and
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.
**One window per run of hand-backs (round 2).** A run is hand-backs with the
wire away less than a settle (12 cycles and 22 s) between them. It ends when
the loop holds the wire, or the wire is away, a whole settle in a row. Each
hand-back restarts the settle, but a run hides the bound for at most 11 bound
cycles and 22 s in all. Round 1 restarted the window 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). Now a bound that stays warns after at most 11
of its cycles, however often the wire changes hands.
**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.
**Delay.** The first counted bound cycle, from the hand-back (worst case):
**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.
| meter | one hand-back | repeated hand-backs |
|---|---|---|
| 2 s | 22 s | 264 s |
| 5 s (default) | 55 s | 660 s |
| 60 s | 660 s | 7920 s |
**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, 4 loads each):
One hand-back: 11 cycles, never less than 22 s. Repeated: the worst case is
one bound cycle per turn with the wire away 11 cycles between (22 at 1 s),
so 11 turns. It grows with the meter; it stays under a quarter (900 s) up
to a 5 s meter.
Measured: N=2, 5 s meter, cheap-window charge, no usable forecast, house 50 W
over the 5000 W rating: 14 warnings in 2 h (two in each quarter after the first), 0 with
round 1, 28 without any rule. The cap held.
| 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 23 | 7 | 28 |
| 10 s | 7 to 10 | 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.
**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).
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