SAFETY-137: the shutdown 0 W must be the last write every unit hears #240

Merged
admin merged 1 commits from SAFETY-137 into release/1.0 2026-11-08 04:20:05 +01:00
Owner

SAFETY-137: the shutdown and Stop 0 W must be the last write every unit hears.

The 0 W of shutdown and of the Stop button now waits, bounded (2 s), for a write already on its way to the same unit, for every driver (GoodWe included), and then goes out. Once shutdown has begun, cycle, heartbeat and safety-stop writes are dropped at the one place a write is created. One shared wait (_await_inflight) serves the 0 W and SMA-10's _release_last. Worst case for a hung write is 2 s + 4 s = 6 s (HAND_BACK_WORST_S).

  • app/main.py: SETTLE_BUDGET_S, _await_inflight, _write_unit(last=), write_setpoint(last=), command_site_zero(last=), command_site_release.
  • New test_safety137_last_write.py; test_sma10_two_idle_states.py call-site finder now accepts await self.name(...).
  • DECISIONS note and one CHANGELOG line. No user-facing text change. Out of scope as briefed: heartbeat, safety-stop paths, SMA-4, OPT-40.

Not merged. Report: reports/SAFETY-137-dev-report.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC

SAFETY-137: the shutdown and Stop 0 W must be the last write every unit hears. The 0 W of shutdown and of the Stop button now waits, bounded (2 s), for a write already on its way to the same unit, for every driver (GoodWe included), and then goes out. Once shutdown has begun, cycle, heartbeat and safety-stop writes are dropped at the one place a write is created. One shared wait (`_await_inflight`) serves the 0 W and SMA-10's `_release_last`. Worst case for a hung write is 2 s + 4 s = 6 s (`HAND_BACK_WORST_S`). - `app/main.py`: `SETTLE_BUDGET_S`, `_await_inflight`, `_write_unit(last=)`, `write_setpoint(last=)`, `command_site_zero(last=)`, `command_site_release`. - New `test_safety137_last_write.py`; `test_sma10_two_idle_states.py` call-site finder now accepts `await self.name(...)`. - DECISIONS note and one CHANGELOG line. No user-facing text change. Out of scope as briefed: heartbeat, safety-stop paths, SMA-4, OPT-40. Not merged. Report: reports/SAFETY-137-dev-report.md. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
admin added 1 commit 2026-11-08 03:03:01 +01:00
SAFETY-137: the shutdown and Stop 0 W is the last write every unit hears
checks / unit (pull_request) Successful in 22m59s
infra-pin / fresh (pull_request) Successful in 1s
8953be0362
The 0 W of shutdown and of the Stop button now waits (bounded, 2 s) for a
write already on its way to the same unit, for every driver, GoodWe included.
Once shutdown has begun, cycle, heartbeat and safety-stop writes are dropped
right before the write is created. One shared wait (_await_inflight) serves
the 0 W and SMA-10's _release_last.

Co-Authored-By: Claude <noreply@anthropic.com>
Author
Owner

Review (SAFETY-137, head 8953be0): approved, merging.

  • Race closed: with a 0.3 s, 0.6 s or hung cycle write on its way, at shutdown and at Stop, N=1 and N=2, GoodWe and a fake that does not hand back, the last write per unit is 0 W on head (base ends on the cycle value). With one unit hung, every unit still gets 0 W, at 2.0 s.
  • Worst case, with every write hung: 6.005 s on head (4.0 s on base), which is the 6 s bound plus loop overhead.
  • The gate does not drop the final 0 W. It drops a plain safety-stop 0 W only after shutdown has begun, and the shutdown's own 0 W follows it. Task death, an in-flight write that raises, and the refusal path still end on 0 W.
  • Mutants b, d (the dev's) and my own e (in-flight tracking only for hand-back drivers) are all caught: 8, 8 and 36 checks red.
  • GoodWe identity: 7 replay tools byte-identical, announce 0/0/0. Cycle time is the same as base within noise.
  • Full suite on head: 101 files, 0 failed. CI 1569 and 1570 are green.

Follow-ups (not blocking):

  1. A cycle write that takes 2 to 4 s still lands after the 0 W (measured at 2.5 s and 3.5 s). The CHANGELOG line ("can no longer") says more than that. The ESPHome stall timer still covers this case.
  2. Stop (not shutdown): a cycle that has decided but not yet started its writes can send its share right after the Stop 0 W. The next cycle corrects it. Base does the same.
Review (SAFETY-137, head 8953be0): approved, merging. - Race closed: with a 0.3 s, 0.6 s or hung cycle write on its way, at shutdown and at Stop, N=1 and N=2, GoodWe and a fake that does not hand back, the last write per unit is 0 W on head (base ends on the cycle value). With one unit hung, every unit still gets 0 W, at 2.0 s. - Worst case, with every write hung: 6.005 s on head (4.0 s on base), which is the 6 s bound plus loop overhead. - The gate does not drop the final 0 W. It drops a plain safety-stop 0 W only after shutdown has begun, and the shutdown's own 0 W follows it. Task death, an in-flight write that raises, and the refusal path still end on 0 W. - Mutants b, d (the dev's) and my own e (in-flight tracking only for hand-back drivers) are all caught: 8, 8 and 36 checks red. - GoodWe identity: 7 replay tools byte-identical, announce 0/0/0. Cycle time is the same as base within noise. - Full suite on head: 101 files, 0 failed. CI 1569 and 1570 are green. Follow-ups (not blocking): 1. A cycle write that takes 2 to 4 s still lands after the 0 W (measured at 2.5 s and 3.5 s). The CHANGELOG line ("can no longer") says more than that. The ESPHome stall timer still covers this case. 2. Stop (not shutdown): a cycle that has decided but not yet started its writes can send its share right after the Stop 0 W. The next cycle corrects it. Base does the same.
admin merged commit b3cfcf2df4 into release/1.0 2026-11-08 04:20:05 +01:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: admin/goodwe-addon#240