OPT-11 P1 review: a mode never beats the peak floor; SAFETY-03 kept at the rail
checks / unit (pull_request) Successful in 11m4s
infra-pin / fresh (pull_request) Successful in 2s

- arbiter: a mode now gives way only as far as a site limit above P_MODE
  needs (_unit_modes at N>1, _site_mode at N=1). Before, the modes were
  released only when the peak floor bound S, so a trade won above the floor
  kept them and placed less than the floor (an Off battery at N=1 went to 0 W
  under a 2000 W floor), and a capped grid charge released an Off battery.
  While released, held batteries weigh 0 and carry only the shortfall.
- control: a side of the loop bound that reaches the rail is left to the
  rail, so SAFETY-03's i_clamped still fires with every battery on.
- opt11_identity: add maintenance scenarios and a 6000-case arbiter digest
  (peak floor, trades, maintenance, refusals); a5dddad digests pinned.
- tests: test_arbiter part 11, test_control (g), test_modes (0/1 refused,
  loop bound counts live units only). DOCS and DECISIONS say the new rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBtqPrG1mDLXzTEBnhEkkV
This commit is contained in:
glenn schrooyen
2026-09-28 19:41:36 +02:00
co-authored by Claude Opus 5.5
parent d69fd8fbfd
commit ffc9a4d721
10 changed files with 365 additions and 40 deletions
+27 -2
View File
@@ -98,6 +98,12 @@ A5DDDAD = {
"n2": "a60003b1930fa3cba21f3220670d9172b9ff438347d77ba9bc3383fca1ceb15a",
"n1_off": "fc3bca170c46920f08d86baee62ea883954a89ea6222d6bbf7d6652877226262",
"n2_off": "a60003b1930fa3cba21f3220670d9172b9ff438347d77ba9bc3383fca1ceb15a",
# Added in review (PR #149), printed on a5dddad the same way: both
# batteries due for maintenance, and the arbiter alone on 6000 seeded
# claim lists with the peak floor, trades, maintenance and refusals.
"n1_maint": "5a21d43ec7cbf95a224bbb9bca13a8e62a00161101b586ae9fa68ab565992977",
"n2_maint": "48a1ad96121592e207a4664fb2da9f5208a49b88e5802a358873a5d30cb8c18e",
"resolve": "43a874216711936e14b9185bcc8d26c58fe4f3cfc3c1c8eaf89bffd95d6e81ef",
}
_claims, _undo_c = spy_claims()
_bounds, _undo_b = spy_bounds()
@@ -112,7 +118,8 @@ for k in A5DDDAD:
check("⭐ at N=1 and N=2, with the option absent and with it false, every "
"command, reason and allocation record over 540 cycles each is "
"byte-identical to a5dddad (house steps, a solar surplus, a battery "
"at its cut-out level, and at N=2 a refusal with probes)",
"at its cut-out level, at N=2 a refusal with probes, both batteries "
"due for maintenance), and so is the arbiter on 6000 claim lists",
_now == A5DDDAD)
check("...and no cycle in those runs filed a `mode` claim or handed "
"`compute()` a bound",
@@ -194,6 +201,20 @@ finally:
_undo_b()
check("...because main.py hands compute() the loop bound, B's 0 W included: "
"[-2500, 2500]", _bounds and set(_bounds) == {(-2500.0, 2500.0)})
# Review (PR #149): a unit the allocator drops (stale link) must not count in
# the loop bound either, or the loop asks for watts nobody live can place.
_rows, _c = rig.run(2, extra=ON, dur=10.0, load=flat, soc=lambda u, t: 50.0,
cut=lambda u, t: False)
_both = _c.loop_bound([app_main.Claim.limit(
"supervised", app_main.P_SAFETY, -2500.0, 2500.0,
actuator=_c.actuator_of("supervised", i)) for i in _c.inverters])
_c.live_units = lambda: (A,)
_one_live = _c.loop_bound([app_main.Claim.limit(
"supervised", app_main.P_SAFETY, -2500.0, 2500.0,
actuator=_c.actuator_of("supervised", i)) for i in _c.inverters])
check("the loop bound counts only live units: both live [-5000, 5000], B's "
"link stale [-2500, 2500]",
_both == (-5000.0, 5000.0) and _one_live == (-2500.0, 2500.0))
for _n in (1, 2):
_st = store_with(**{A: (False, packmode.SOLAR_FIRST),
@@ -310,10 +331,14 @@ check("pack_use stores the switch, and both are written to the action log",
_bad = [asyncio.run(_c.handle_action("pack_use", unit="inverter-z",
value=True)),
asyncio.run(_c.handle_action("pack_use", unit=A, value="yes")),
# 0 == False in Python: a stored 0 would read back as ON.
asyncio.run(_c.handle_action("pack_use", unit=A, value=0)),
asyncio.run(_c.handle_action("pack_use", unit=A, value=1)),
asyncio.run(_c.handle_action("pack_mode", unit=A,
value=packmode.CHEAP_HOURS)),
asyncio.run(_c.handle_action("pack_mode", unit=A, value=None))]
check("an unknown battery, a switch that is not true or false, and a mode "
check("an unknown battery, a switch that is not true or false (0 and 1 "
"included), and a mode "
"this release does not offer (Cheap hours arrives in P3) are refused, "
"with a plain reason, and store nothing",
all(isinstance(x, dict) and set(x) == {"error"} for x in _bad)