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.
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
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>
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.02026-10-10 01:38:30 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
A hold that loses its SoC reading never ended, so
last_completedwas 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
Review of MAINT-15 at head
35b2a69(base release/1.0 =db9e231). Verdict: approve and merge.Round-1 points: all fixed.
old_hold_branchmatches thedb9e231HOLD branch line for line (same elapsed,>=, the COMPLETE text, IDLE/False/None and HOLD/True/0.0). It does not comparecharge_onlyorfailed, buttest_maintenanceand the whole-cycle trace cover those.Rule.
_enter(HOLDhas 4 call sites and every one follows a charge that counted as done. OnlyMaintenance._enterwritesphase. 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 fromphase_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:1with--network none: 71 pass, 0 fail. CI run 1238 is a real run on35b2a69(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):
phase: holdand nophase_started(hand-edited only) never ends HOLD, with or without a reading, on release/1.0 too._elapsed_minreturns 0. This is a candidate for a follow-up card, not for this PR.test_maint15.pyhas 43.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.