Files
goodwe-addon/goodwe_controller
glenn schrooyenandClaude Opus 5 d1b7e3f113
checks / unit (pull_request) Successful in 6m58s
SAFETY-44: decide per key, because one shared flag let an unrelated latch veto the repair
⚠️ REVIEW DEFEATED THIS CARD'S OWN FIX WITH ONE ADDED KEY, and the project's own
instrument agreed on the unmodified tree. The guard classified each key and then
wrote ONE site-wide `namespaced_latched` for BOTH "on the roster" and
"off-roster but not UNIT_A", so a single latched key belonging to a unit the
operator had removed from `inverters:` vetoed the repair of the misdirected
`inverter-a` latch beside it. Measured, roster ['garage','shed'], store stamped
so the migration fix cannot reach it:

  sign_tripped + unit/inverter-a/sign_tripped     -> [('garage', True),  ...]
  ...AND unit/old-shed/sign_tripped               -> ⚠️ [('garage', False), ...]

A site DRIVING on a state file that reads `sign_tripped: true` - the sentence in
this card's own first paragraph - and `tools/rehearse_roster.py` printed
`LATCH CAME UP DISARMED` on it, exit 1, against shipped code. It matters more
than a normal hole because this guard is, as main.py says, the ONLY repair path
for an install already stamped by 0.9.0.

⚠️ AND IT IS INSTANCE #17 OF HABIT 4, IN THE EXTENSION MUTATION.md ADDED TWO
DAYS AGO. The two fixtures written for this card each hold ONE arrangement -
`_staged_store` has only `inverter-a`, `_other_store` only `inverter-b` - so the
flag was never written twice with opposite intents in a single store. A per-key
check whose keys have never disagreed WITHIN ONE STORE is a check over whichever
key came last. Three variables now, one per case, and a fixture holding BOTH.
Four cases driven: stranded-only repairs, +1 unrelated repairs, +2 repairs,
off-roster-only does NOT (which is DEV-02's control, still green).

⭐ AND THE MOVED-NOT-LOST RULE HID A GENUINE LATCH LOSS. The match was bare key
plus value over the whole of `machine_read`, with no identity - and
`DEFAULTS["sign_tripped"] is False`, so the ONLY non-default value a latch can
hold is `True`. Any surviving latched roster unit therefore suppressed any other
unit's vanished latch. Review measured a build that drops `unit/shed/sign_*`
while `unit/garage/sign_*` survives, both latched by one site event so the
reasons matched too:

    none in LEG SAME-STEP: nothing vanished, nothing stranded ...
    NO FINDINGS in the rehearsed transitions            exit 0

A roster unit's persisted SAFETY-02 latch deleted, transition reported clean.
The legitimate case is a RENAME - the key left `inverter-a`, which is not on
this roster, and arrived under `garage`, which is - so the vanished key's own
unit must be OFF the roster. A key whose unit is still on the roster has not
been renamed, it has been lost, and no amount of the same value elsewhere makes
that untrue.

⚠️ AND THE BOUND I CITED FOR IT DID NOT EXIST. Both the comment and the last
commit message said "what M3 (delete the flat rollback view) measures". M3 is
not that mutant - it drops `sign_reason` from `LEGACY_UNIT_KEYS` - and the one I
meant, M9, vanishes only FLAT keys, so neither can satisfy the `unit/` gate the
rule sits behind. NEITHER EXERCISED THE BRANCH; it had no negative control at
all, in a card whose subject is checks that cannot reach the failure they name.
A citation is the worst carrier for that, because it reads as having been
checked. M12 is written for it and driven: a roster unit that stops restoring
its latch is exit 1, and the suppression does not swallow it.

`misdirected` IS UNREACHABLE FROM THE CALL SITE, and the claim that it was a
benefit is withdrawn rather than restated. `unit` is `roster_names(opts)[0]` and
`build_roster()` names its first entry from the same list, so `unit` is a member
of `roster` by construction; review measured it - dropping the `roster` argument
inside `migrate_unit_keys()` survives 28/28. The argument stays for the reason
`_verify_moves()` gives for its own `roster=None` branch, it is what a NEW
caller gets - and a guard nothing exercises is a guard nobody knows works, so it
is now exercised at the function's own level: a wrong `unit` comes back
misdirected, the marker is not stamped, and the same call with a roster name
stamps it. Dead `if _roster_names else UNIT_A` removed; `roster_names()` never
returns [].

THE OPERATOR MESSAGE IS ASSERTED. Review deleted it whole (`if stranded:` ->
`if False:`) and 28/28 stayed green - `stranded` had no other consumer, so the
list existed only to print a line nothing checked, and that line is the entire
operator-facing half of the repair. Now: exactly one SAFETY-44 line at ERROR,
naming the stranded key and the roster it was measured against.

A wrong line citation removed: the call-site comment said "line 315's comment"
and "two hundred lines below", and both were wrong (315 is the CFG-03 defaults
note; the comment meant is ~54 lines down). Cite the thing, not its coordinates.

⚠️ AND WHAT 5b/5c ACTUALLY PIN IS RECORDED, because review is owed the
distinction: `mutant_tree()` asserts its target exists, so EDITING the guarded
line makes the file red at the ANCHOR before any behaviour is measured - which
looks identical from the exit code and proves much less. It happened twice while
writing this commit. The behavioural coverage lives in test_maintenance.py
section 13 and test_inverter.py, which build stores rather than strings.

Fixture strings no longer say "measured" - they are synthetic, and the repo's
other fixture carries an explicit SYNTHETIC header.

28/28 files pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
2026-09-08 17:08:41 +02:00
..