SAFETY-44: decide per key, because one shared flag let an unrelated latch veto the repair
checks / unit (pull_request) Successful in 6m58s
checks / unit (pull_request) Successful in 6m58s
⚠️ 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
This commit is contained in:
co-authored by
Claude Opus 5
parent
5e382f52f6
commit
d1b7e3f113
@@ -563,14 +563,43 @@ def diff(before: Snapshot, after: Snapshot) -> list:
|
||||
#
|
||||
# ⚠️ THE TEST IS NOT "the name appears somewhere". It is: the SAME bare
|
||||
# key, at the SAME value, under a key in `machine_read` - the set built
|
||||
# by driving a real start, not by pattern-matching a namespace. A latch
|
||||
# that genuinely went missing has no such key and is still a finding,
|
||||
# which is what M3 (delete the flat rollback view) measures and what
|
||||
# keeps this from being a softening of the check.
|
||||
# by driving a real start, not by pattern-matching a namespace.
|
||||
#
|
||||
# ⚠️ THIS USED TO CITE "M3 (delete the flat rollback view)" AS WHAT
|
||||
# BOUNDS IT, AND THAT CITATION WAS WRONG IN BOTH HALVES. M3 drops
|
||||
# `sign_reason` from `LEGACY_UNIT_KEYS` and expects `LATCH KEY NOT UNDER
|
||||
# A ROSTER NAME`; the delete-the-rollback-view mutant is M9, and every
|
||||
# key M9 vanishes is FLAT, so it can never satisfy the `unit/` gate
|
||||
# here. Neither mutation exercises this branch. The sentence "what keeps
|
||||
# this from being a softening of the check" was not supported by the
|
||||
# thing it named - and a citation is the worst possible carrier for that,
|
||||
# because it reads as having been checked. The branch is now bounded by
|
||||
# M12, written for it, in test_rehearse.py.
|
||||
moved_to = sorted(
|
||||
k for k in after.machine_read
|
||||
if k.rsplit("/", 1)[-1] == bare and after.data.get(k) == value)
|
||||
if key.startswith("unit/") and moved_to:
|
||||
# ⚠️ AND THE VANISHED KEY'S OWN UNIT MUST BE **OFF THE ROSTER**, WHICH
|
||||
# THE FIRST VERSION OF THIS RULE DID NOT REQUIRE AND WHICH IS WHAT MADE
|
||||
# IT A SOFTENING. 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. Measured: a build that DROPS
|
||||
# `unit/shed/sign_*` while `unit/garage/sign_*` survives, both latched
|
||||
# by one site event so the reasons match too, reported
|
||||
#
|
||||
# none in LEG SAME-STEP: nothing vanished, nothing stranded ...
|
||||
# NO FINDINGS in the rehearsed transitions
|
||||
#
|
||||
# exit 0, on a roster unit's SAFETY-02 latch that had been deleted.
|
||||
#
|
||||
# The legitimate case is a RENAME: the key left `inverter-a`, which is
|
||||
# not on this roster, and arrived under `garage`, which is. A key whose
|
||||
# own 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.
|
||||
vanished_unit = key.split("/")[1] if key.startswith("unit/") else None
|
||||
if vanished_unit is not None and vanished_unit not in after.units \
|
||||
and moved_to:
|
||||
after.vanished_but_migrated.append(
|
||||
f"{key} = {value!r} -> {moved_to[0]}")
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user