MAINT-15: the balancing hold ends on time with no SoC reading #184

Merged
admin merged 2 commits from MAINT-15 into release/1.0 2026-10-10 01:38:30 +02:00
Owner

A hold that loses its SoC reading never ended, so last_completed was never written. HOLD's time check now runs without a reading (Maintenance._hold()), with the same COMPLETE line. The 15 min HOLD line now says the hold still ends at its normal time.

Rules, cost and limits: DECISIONS/MAINT-15-hold-ends-without-soc.md. Tests: test_maint15.py (12 mutations, all killed). With a reading, behaviour is identical to release/1.0 (800 differential traces).

Not merged: rig test (TEST-23) comes after review.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC

A hold that loses its SoC reading never ended, so `last_completed` was never written. HOLD's time check now runs without a reading (`Maintenance._hold()`), with the same COMPLETE line. The 15 min HOLD line now says the hold still ends at its normal time. Rules, cost and limits: `DECISIONS/MAINT-15-hold-ends-without-soc.md`. Tests: `test_maint15.py` (12 mutations, all killed). With a reading, behaviour is identical to release/1.0 (800 differential traces). Not merged: rig test (TEST-23) comes after review. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
admin added 1 commit 2026-10-09 23:56:31 +02:00
MAINT-15: the balancing hold ends on time with no SoC reading
checks / unit (pull_request) Successful in 35m22s
infra-pin / fresh (pull_request) Successful in 1s
cd1bf2a694
HOLD's time check moves into Maintenance._hold(), used by step() and
_no_soc(). A hold that loses its SoC now ends at hold_min, writes
last_completed and logs the same COMPLETE line. The 15 min HOLD line says
the hold still ends at its normal time. Behaviour with a reading is
unchanged (differential trace against db9e231).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
admin added 1 commit 2026-10-10 00:42:04 +02:00
MAINT-15 round 2: in-repo differential check, full-hold with-reading check
checks / unit (pull_request) Successful in 35m8s
infra-pin / fresh (pull_request) Successful in 1s
35b2a699de
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

Review of MAINT-15 at head 35b2a69 (base release/1.0 = db9e231). Verdict: approve and merge.

Round-1 points: all fixed.

  • Section 7's frozen old_hold_branch matches the db9e231 HOLD branch line for line (same elapsed, >=, the COMPLETE text, IDLE/False/None and HOLD/True/0.0). It does not compare charge_only or failed, but test_maintenance and the whole-cycle trace cover those.
  • The with-reading HOLD check now runs past hold_min and checks the end tick, last_completed and the exact event list.
  • The unused imports are gone.

Rule. _enter(HOLD has 4 call sites and every one follows a charge that counted as done. Only Maintenance._enter writes phase. The N>=2 store migration copies only pack A's own flat history. A restart, or an old state file in HOLD, ends at hold_min counted from phase_started. Checked by test and by my own probe.

Unchanged with a reading. I reran the 800-trace differential against my own git archive db9e231, plus seeds 5000-5099: identical. I also ran a separate trace with SoC missing on about 25% of DRAIN/CHARGE ticks, about 2% process restarts, and the whole state file compared on every tick: 400 traces, identical. A one-minute-late HOLD mutant is caught by the trace, so the trace is sensitive.

Tests. Full suite in goodwe-addon-checks:1 with --network none: 71 pass, 0 fail. CI run 1238 is a real run on 35b2a69 (71 PASS, 35 min).

Mutations. 10 of my own (R1-R10), all killed. Among them: note skipped in HOLD, still owning the setpoint after the end, last_completed set to the hold start, last_completed written to the flat key at N=2, the note repeated every tick, the note said at once, no-SoC charge skipping HOLD, no-SoC DRAIN at discharge_w, COMPLETE minutes with a decimal, and with-reading HOLD one minute late.

Findings (none blocking):

  • Low, not new: a state file with phase: hold and no phase_started (hand-edited only) never ends HOLD, with or without a reading, on release/1.0 too. _elapsed_min returns 0. This is a candidate for a follow-up card, not for this PR.
  • Info: the dev report says 34 checks. After round 2, test_maint15.py has 43.
  • Info, good news: at N=2 on release/1.0, a pack stuck in HOLD without SoC also held the site slot forever, so the other pack could never start. With this PR the slot frees at hold_min (probe: B starts at the next start hour).

Open point (COMPLETE line without a reading): keep it as it is. The line is true. A gap of 15 min or more is already logged once by the 15-min line. Changing it would alter a pinned shipped string for little gain. Leave the minutes-to-hours wording for the UX-08 sweep.

Review of MAINT-15 at head 35b2a69 (base release/1.0 = db9e231). Verdict: approve and merge. **Round-1 points: all fixed.** - Section 7's frozen `old_hold_branch` matches the db9e231 HOLD branch line for line (same elapsed, `>=`, the COMPLETE text, IDLE/False/None and HOLD/True/0.0). It does not compare `charge_only` or `failed`, but `test_maintenance` and the whole-cycle trace cover those. - The with-reading HOLD check now runs past hold_min and checks the end tick, last_completed and the exact event list. - The unused imports are gone. **Rule.** `_enter(HOLD` has 4 call sites and every one follows a charge that counted as done. Only `Maintenance._enter` writes `phase`. The N>=2 store migration copies only pack A's own flat history. A restart, or an old state file in HOLD, ends at hold_min counted from `phase_started`. Checked by test and by my own probe. **Unchanged with a reading.** I reran the 800-trace differential against my own `git archive db9e231`, plus seeds 5000-5099: identical. I also ran a separate trace with SoC missing on about 25% of DRAIN/CHARGE ticks, about 2% process restarts, and the whole state file compared on every tick: 400 traces, identical. A one-minute-late HOLD mutant is caught by the trace, so the trace is sensitive. **Tests.** Full suite in `goodwe-addon-checks:1` with `--network none`: 71 pass, 0 fail. CI run 1238 is a real run on 35b2a69 (71 PASS, 35 min). **Mutations.** 10 of my own (R1-R10), all killed. Among them: note skipped in HOLD, still owning the setpoint after the end, last_completed set to the hold start, last_completed written to the flat key at N=2, the note repeated every tick, the note said at once, no-SoC charge skipping HOLD, no-SoC DRAIN at discharge_w, COMPLETE minutes with a decimal, and with-reading HOLD one minute late. **Findings (none blocking):** - Low, not new: a state file with `phase: hold` and no `phase_started` (hand-edited only) never ends HOLD, with or without a reading, on release/1.0 too. `_elapsed_min` returns 0. This is a candidate for a follow-up card, not for this PR. - Info: the dev report says 34 checks. After round 2, `test_maint15.py` has 43. - Info, good news: at N=2 on release/1.0, a pack stuck in HOLD without SoC also held the site slot forever, so the other pack could never start. With this PR the slot frees at hold_min (probe: B starts at the next start hour). **Open point (COMPLETE line without a reading):** keep it as it is. The line is true. A gap of 15 min or more is already logged once by the 15-min line. Changing it would alter a pinned shipped string for little gain. Leave the minutes-to-hours wording for the UX-08 sweep.
admin merged commit 580c200527 into release/1.0 2026-10-10 01:38:30 +02: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#184