SAFETY-48: what the lead cap actually does to a frozen integrator, and what the peak figure depends on
checks / unit (pull_request) Successful in 7m55s

Four review findings, none of them in executable code. The control law, the
tests and the instruments' arithmetic are untouched; every number below was
re-measured against the committed 16-day capture before it was written down.

R1 - app/control.py carried a justification the decision record and MUTATION.md
habit 22 had already retracted, and it was false about the code it sits in. It
said an unconditional placement "would also pull the integrator toward a
command the SATURATION freeze is pinning", priced at 118 of 256 250 ticks. The
118 was measured against a different pair; on the pair the sentence names the
two forms are bit-identical - 0 of 256 246 ticks, re-measured. And the narrow
form that shipped DOES move a saturation-frozen integrator: it runs before the
freeze block and is not gated on `frozen`. Of 79 160 frozen ticks the cap
changes `i_w` on 1095, and on 679 of those it moves it further out on the side
it was already on - putting back magnitude the freeze had just let fall, which
the freeze's own rule forbids. Worst case 2424 W, at prev_w -3400 W, grid
+4697 W, actual -4144 W, sat_count 2 -> 3: the freeze leaves the integrator at
-176 W and the cap drags it to -2600 W. It never overrides a same-cycle refusal
(the freeze clipped on 75 512 ticks, the slew bound on none of them), and that
is stated too. It is also where SAFETY-13's settle values went: gating the cap
on `not frozen` puts test_control.py part 5 back at -1190 W exactly, while part
4(b) does not come back - that one moved for the winding reason.

R3 - the record read its own sensitivity table backwards. `fit_plant()` picks
lag 7.5 s on battery-power RMS, which separates it from 2.5 s by 0.3 %, while
the tool's own printed fidelity measure (export against the recorded 29.43 kWh)
prefers 2.5 s by the whole 35 % gap. Swept properly: at every lag <= 5.0 s
release/1.0's worst quarter is ALREADY 3434 W and this card changes it by
exactly zero. The 3739 -> 3434 W headline exists at one plant. New section 3a
carries the table; the export reduction now leads every summary and the peak is
presented as lag-dependent. The form-A rejection is strengthened rather than
weakened: form A is worse on the billed quarter at every lag where release/1.0
produces a peak at all, and at every top-N cut of the quarters.

R2 - DOCS.md stated modelled watts to the owner as measurement. It now leads
with the export reduction, says the figures are simulated, and says plainly
that no capacity-tariff improvement is promised.

R4 - tools/slew_sim.py --slew narrated "the OVER-ABSORPTION does not move, and
at the slowest slews it is slightly WORSE" while printing a monotonically
falling column. That sentence is TRUE on release/1.0 (reproduced:
958/1107/1133/1058/987/1029/1150/1143/1081) and false under this branch's law
(777/734/663/563/553/507/435/358/321), so SAFETY-39 is not retracted: its
record gets a forward reference, and the script now reads SLEW_LEAD_STEPS out
of the law it imported and narrates from the column it just printed. Both
branches of that narration were driven.

R5 - three counts in the L table were decoration and are re-driven in the CI
image on a `cp -a`, purged, `:ro`, one file at a time: L5 is 5+5 with the
function names exchanged and 5+4 with the branch bodies exchanged (two
mutations wearing one name, now listed separately), L6 is 6+3. The separable-
state count is grid-dependent and three sweeps disagree, so all three are
quoted with their grids and the load-bearing claim is WHERE they separate.

R7 - "a large import answered too weakly" is not what the aggregate shows. Form
A cuts import by 5.45 kWh and export by 5.18 kWh, and paired per tick delivers
8.56 kWh less offtake energy on the ticks release/1.0 was importing. The harm
is a tail redistribution inside quarters that already net to offtake. The
conclusion stands; the sentence is narrowed to it.

Verified: the full suite in the CI image, purged tree, `:ro` mount, one file at
a time, exactly the command in .gitea/workflows/checks.yml - 30/30, exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
This commit is contained in:
glenn schrooyen
2026-09-19 02:37:01 +02:00
co-authored by Claude Opus 5
parent 03ef61fa3b
commit a6af332048
6 changed files with 364 additions and 71 deletions
+44 -4
View File
@@ -220,6 +220,7 @@ def slew_table(ps, ct, tick_s, rail):
f"faster than the")
print(f" plant settles. {rail:.0f} W / ({ct.PLANT_SETTLE_S:.0f} s / "
f"{tick_s:.0f} s per tick) = {frac * rail:.0f} W per tick.\n")
overs = []
for slew in (1000, 800, 700, 600, int(frac * rail), 500, 400, 300, 250):
w = family(ps, ct, gain=0.6, slew_w=float(slew), rail_w=rail,
tick_s=tick_s)
@@ -227,13 +228,52 @@ def slew_table(ps, ct, tick_s, rail):
" <- the 2026-09-05 live pair" if slew == 1000 else (
" <- the live interim" if slew == 400 else ""))
row(f"slew_w {slew}{tag}", w)
overs.append(w["over"])
print("\n READ IT THIS WAY. The >= 900 W step - the live symptom, which")
print(" escalated 2 -> 1 -> 13 -> 43 per 30 min that morning - is gone from")
print(" every run below 900 W of slew, and that is what the slew owns.")
print(" The OVER-ABSORPTION does not move, and at the slowest slews it is")
print(" slightly WORSE. The rig cannot pick the derived value out of that")
print(" column, and it is not asked to: the CONSTRAINT picks it, and this")
print(" table is what says what the constraint does and does not buy.")
# ⚠️ THE NARRATION BELOW IS DERIVED FROM THE COLUMN JUST PRINTED, NOT
# WRITTEN ABOUT IT. SAFETY-39 wrote "the over-absorption does not move, and
# at the slowest slews it is slightly WORSE" - true of release/1.0's law,
# which is what it was measured against, and FALSE under SAFETY-48's lead
# cap, where the same script on the same plant prints a monotonically
# FALLING column. A transcript whose prose contradicts its own table is a
# failing check (MUTATION.md habit 8), and a sentence that is right under
# one law and wrong under another has to be computed rather than typed. So:
# which law is imported is read off `control.py` itself, and whether the
# column falls is read off the column.
cap = getattr(ct, "SLEW_LEAD_STEPS", None)
falling = all(b < a for a, b in zip(overs, overs[1:]))
law = ("release/1.0's law - the integrator's lead over the command is "
"UNBOUNDED" if cap is None else
f"SAFETY-48's law - the integrator may lead the command by "
f"{cap} slew step(s)")
print(f"\n THE LAW THIS RAN AGAINST: {law}.")
if cap is None and not falling:
print(" The OVER-ABSORPTION does not move with the slew, and at the")
print(" slowest slews it is slightly WORSE - `slew_w` bounds the")
print(" OUTPUT while the integrator goes on winding, so a slower slew")
print(" leaves it winding for longer. SAFETY-48 bounds that lead and")
print(" INVERTS this column; see DECISIONS/SAFETY-48 §6.")
elif cap is not None and falling:
print(" The OVER-ABSORPTION FALLS MONOTONICALLY with the slew, which")
print(" is the OPPOSITE of what SAFETY-39 measured on release/1.0")
print(" (958/1107/1133/1058/987/1029/1150/1143/1081 W, flat and")
print(" non-monotone). Bounding the integrator's lead is what changed")
print(" it: the slew now owns a share of the over-absorption it did")
print(" not own before. DECISIONS/SAFETY-48 §6 has both tables and an")
print(" independent confirmation on test_control.py's own plant.")
else:
print(" ⚠️ NEITHER NARRATION FITS. The law above and the shape of the")
print(f" column ({'falling' if falling else 'not falling'}) are the")
print(" combination this script has no sentence for - which means the")
print(" measurement has moved and DECISIONS/SAFETY-39 §3 and")
print(" DECISIONS/SAFETY-48 §6 both need re-reading before this table")
print(" is quoted anywhere.")
print("\n EITHER WAY THE RIG DOES NOT PICK THE NUMBER, and it is not asked")
print(" to: the CONSTRAINT picks it, and this table is what says what the")
print(" constraint does and does not buy.")
def gain_table(ps, ct, tick_s, rail):