MAINT-05 fix round 2: the refusal has to survive the NEXT start, and both marker guards have to be visible
checks / unit (pull_request) Successful in 5m14s

REJECTED because the fix round's own load-bearing sentence was FALSE, and the
whole suite was blind to that. `_verify_moves()` produced the red the reviewer
reproduced - that half stood - but DECISIONS §2 claimed "`ok` gates the marker,
so a misdirected migration is not stamped and the next start runs it again ...
a re-run is recoverable". THE REFUSAL LASTED EXACTLY ONE START.

MEASURED, Controller level, ONE persisted store, roster ["garage","shed"], flat
last_completed 2026-08-07, call site still handing `inverter-a` on both starts:

    construction 1: ok=False misdirected=4 moved=4 kept=0   marker=<absent>
    construction 2: ok=True  misdirected=0 moved=0 kept=4   marker=1
    history parked under unit/inverter-a/...; marker stamped after ONE restart.

WHY. `_verify_moves()` graded only `rep.moved`. On start 2 the same wrong call
site finds the four keys already present, so rule 1 routes every one of them to
`rep.kept`, `rep.moved` is EMPTY, the roster loop iterates over nothing,
`misdirected` is empty and `ok` is True. Start 3 then takes the
`from_version >= MAINT_SCHEMA_VERSION` early return FOREVER - including after
the call site is fixed. Loud-and-recoverable became silent-and-permanent after
one restart: the exact state store.py's header says must never happen, reached
by the fix written to prevent it.

WHY THE CHECK MISSED IT - the recurring defect, this time INSIDE the remedy.
Part 9b's recovery check was `migrate_maint_keys(_m5, ROSTER[0], ROSTER)` - the
CORRECTED name. It measures recovery along the one path where the operator has
ALREADY fixed the bug. Nothing in the 19 files called `migrate_maint_keys()`
twice with the SAME WRONG `unit`. The check was true and passed for the wrong
reason.

WHAT CHANGED

1. app/store.py `_verify_moves()` grades `set(rep.moved) | set(rep.kept)`.
   Sound by construction: every entry in both lists is a `unit_key(unit,
   legacy)` built from the SAME `unit` argument, so grading `kept` grades the
   same namespace decision `moved` does. A correct call site's `kept` entries
   sit under a roster name and pass; a wrong one cannot launder its namespace by
   having already written it. `migrate_unit_keys()` passes `roster=None` and
   returns before the loop, so it is untouched.

2. test_maintenance.py part 9c - THE CHECK THAT WAS MISSING. Two starts, SAME
   wrong unit, ONE store: the marker is still withheld on the second, the
   refusal names the same roster, the flat history is still there to recover
   from, and a third start with the CORRECTED name still gets a run (which a
   stamped marker would have made impossible). Its PRECONDITION asserts the
   SHAPE of the second report - `moved` empty, `kept` full - so it cannot
   quietly revert to grading 9b's path. Counterfactual beside it: the same two
   calls with the right name are accepted, then skipped as `already at schema`.

3. test_maintenance.py part 9d - THE OTHER GUARD ON THE SAME LINE, and a
   survivor nothing in the repository could see. `if rep.ok:` is
   `not lost and not misdirected`; every check that reached the marker did so on
   a store whose `kept` was empty, so

       if rep.ok:        ->      if rep.ok or rep.kept:        (+12 B)

   stamped the marker on a migration that LOST A MONTH OF HISTORY - defeating
   the older, settled `lost` guard as well as the new one - and ALL 19 SUITES
   WERE GREEN on it (measured at 65cddf3, purged, :ro). 9d's fixture is the one
   shape that separates the guards: two keys already namespaced (`kept`
   non-empty, the mutant's `or` fires) and two whose write the store drops
   (`lost` non-empty), `misdirected` empty throughout.

4. FOUND HERE AND CLOSED: THE SAME MUTATION ONE FUNCTION UP. `migrate_unit_keys()`
   - SAFETY-02's LATCH migration - gates its own marker the same way, and every
   check that reached it ran on a store with an empty `kept`. `if rep.ok or
   rep.kept:` at store.py:307 SURVIVED ALL 19 FILES. It stamps `schema_version`
   on a migration that DROPPED THE LATCH: the guard is off, the file still reads
   `sign_tripped: true`, and the store now says the migration finished, so the
   next start skips the one thing that would re-arm it. test_inverter.py part 10
   now drops the `sign_tripped` write BESIDE an already-namespaced
   `sign_reason`, with the accepted-baseline counterfactual. No source change -
   the shipped gate was right; nothing could see it. Mutation table 34 -> 35.

5. DECISIONS/MAINT-05 §2 CORRECTED. The paragraph that stated the false
   guarantee now states what was measured, why grading `moved` alone could not
   hold, and why the union is sound. §7 records the marker-gate hole found in
   `migrate_unit_keys()` and that it was closed. MUTATION.md gains habit 9 -
   "exercise a refusal a SECOND time with the SAME wrong input, not the
   corrected one" - and two survey rows.

MUTATION LEDGER - 11 mutations, ALL RED on the final tree, every run on a fresh
copy with the bytecode cache purged (entry count printed before and after) and
the tree mounted :ro so none could be written. Two were survivors BEFORE this
commit and both are recorded as such. Full transcript on the PR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
This commit is contained in:
2026-09-04 19:40:51 +02:00
co-authored by Claude Opus 5
parent 65cddf35f1
commit 905b6fd2b3
6 changed files with 416 additions and 14 deletions
+33
View File
@@ -62,6 +62,39 @@ two power-moving phases on one grid connection pulling against each other.
Every two-pack fixture that could see this now names its first pack
`garage`; with them all called `inverter-a`, `self.inverters[0].name` and
`UNIT_A` were the same string and the swap left all 19 suites green.
* **AND THE REFUSAL NOW SURVIVES THE NEXT START** (second fix round). The fix
above graded only the keys it had just *written*, so it refused exactly once:
measured at Controller level over one persisted store, roster
`["garage", "shed"]`, with the call site still wrong on both starts —
construction 1 `ok=False misdirected=4 moved=4 kept=0 marker=<absent>`,
construction 2 `ok=True misdirected=0 moved=0 kept=4` and the marker
**stamped**. On the second start rule 1 finds the wrong keys already there and
routes all four to `kept`, so `moved` is empty and the roster loop grades
nothing — and start 3 skips the migration forever, *including after the call
site is fixed*. Loud-and-recoverable became silent-and-permanent after one
restart. `_verify_moves()` now grades `set(moved) | set(kept)`; both lists are
built from the same `unit` argument, so grading `kept` grades the same
namespace decision. `test_maintenance.py` part **9c** drives two starts with
the same wrong unit over one store — the shape nothing in the suite had, since
9b measured recovery along the path where the operator has *already* fixed the
call site.
* **AND THE MARKER GATE IS PINNED ON BOTH GUARDS.** `if rep.ok:` is
`not lost and not misdirected`, and the mutation `if rep.ok or rep.kept:`
stamped the marker on a migration that **lost a month of history** with all 19
suites green. Part **9d** is the fixture that separates the two guards: two
keys already namespaced (`kept` non-empty) and two whose write the store
drops (`lost` non-empty), `misdirected` empty throughout.
* ⚠️ **AND THE SAME MUTATION ONE FUNCTION UP WAS A SURVIVOR, FOUND HERE AND
CLOSED.** `migrate_unit_keys()` — SAFETY-02's **latch** migration — gates its
own marker on `rep.ok` too, and every check that reached that gate ran on a
store where nothing was namespaced yet, so `rep.kept` was empty in all of
them. `if rep.ok or rep.kept:` there stamps `schema_version` on a migration
that **dropped the latch**: the guard is off, the file still says
`sign_tripped: true`, and the next start skips the migration that would have
re-armed it. Measured a survivor of all 19 files; now red on
`test_inverter.py` part 10, which drops the `sign_tripped` write beside an
already-namespaced `sign_reason`. No source change — the shipped gate was
right; nothing could see it.
* ⚠️ **FOUND, RECORDED, NOT FIXED (SECOND ONE).** `migrate_unit_keys()` —
SAFETY-02's latch migration — has the identical hole and it costs a **guard**
rather than a history. It runs before `build_roster()` (each `Inverter`
@@ -120,13 +120,76 @@ reported as `misdirected` — a **different** failure from `lost`:
not information, whatever its value. So anything in it fails `ok` on its own.
And **`ok` gates the marker**, so a misdirected migration is not stamped and the
next start runs it again. That is the decision the fix round was asked to
record, and the argument for it is asymmetric: a re-run is recoverable — rule 1
makes it a no-op where keys did land, and rule 2 left the flat keys in the file
to recover *from* — while a silent skip is not recoverable at all, because the
store now claims the migration happened. `test_maintenance.py` part 9b measures
the recovery rather than asserting it: the same store, re-run with the roster's
own first name, comes back with its month of history.
next start runs it again. The argument for it is asymmetric: a re-run is
recoverable — rule 1 makes it a no-op where keys did land, and rule 2 left the
flat keys in the file to recover *from* — while a silent skip is not recoverable
at all, because the store now claims the migration happened.
### ⚠️ THE PARAGRAPH ABOVE USED TO END HERE, AND AS IT STOOD IT WAS FALSE
**The refusal lasted exactly one start. The second start stamped the marker.**
That is a correction to what the first fix round wrote down, not a new
qualification of it, and it is recorded here because a decision document that
states a guarantee the code does not provide is worse than one that says
nothing.
`_verify_moves()` graded only `rep.moved`. Measured at Controller level over
**one persisted store**, roster `["garage", "shed"]`, flat `last_completed`
2026-08-07, with the call site still handing `inverter-a` on **both** starts:
```
construction 1: ok=False misdirected=4 moved=4 kept=0 marker=<absent> (withheld)
construction 2: ok=True misdirected=0 moved=0 kept=4 marker=1 (STAMPED)
history parked under unit/inverter-a/…; marker stamped after ONE restart.
```
On start 2 the same wrong call site finds the four keys it wrote already
present, so **rule 1** — never overwrite a namespaced value — routes every one
of them to `rep.kept`. `rep.moved` is empty, the roster-grading loop iterates
over nothing, `misdirected` is empty, `ok` is True, the marker is stamped, and
start 3 takes the `from_version >= MAINT_SCHEMA_VERSION` early return **forever
— including after the call site is fixed**. Loud-and-recoverable became
silent-and-permanent after one restart: exactly the state `store.py`'s header
says must never happen, reached by the fix written to prevent it.
**The remedy is one line**, and it is sound by construction rather than by
argument: grade `set(rep.moved) | set(rep.kept)`. Every entry in *both* lists is
a `unit_key(unit, legacy)` built from the **same** `unit` argument, so grading
`kept` grades the same namespace decision `moved` does. A correct call site's
`kept` entries are under a roster name and pass; a wrong one cannot launder its
namespace by having already written it. `migrate_unit_keys()` passes
`roster=None` and returns before the loop, so it is unaffected.
**Why the check missed it, which is the part worth keeping.** Part 9b's recovery
check was `migrate_maint_keys(_m5, ROSTER[0], ROSTER)` — the **corrected** name.
It measures recovery along the one path where the operator has *already fixed
the bug*. The real failure is a call site that is still wrong on the next start,
and nothing in the 19 files called `migrate_maint_keys()` twice with the **same
wrong** `unit`. The check was true, passed, and could not reach the failure its
sentence named — MUTATION.md's third defect, this time sitting inside the fix
written to close it. Part **9c** now drives two starts with the same wrong unit
over one store, asserts the marker is still withheld on the second, and asserts
the *shape* of the second report (`moved` empty, `kept` full) so it cannot
quietly revert to 9b's path.
### The marker gate is `rep.ok` and nothing weaker — both guards, each pinned
`if rep.ok:` is `not lost and not misdirected`, and until this round only the
`misdirected` half was reachable by a check that also had a non-empty `kept`.
The mutation
```
if rep.ok: -> if rep.ok or rep.kept:
```
stamps the marker on a migration that **lost a month of history** — defeating
the older, settled `lost` guard as well as the new one — and **all 19 suites
were green on it**. Part **9d** is the fixture that separates them: two keys
already namespaced (`kept` non-empty, so the mutant's `or` fires) and two keys
whose write the store drops (`lost` non-empty, so `ok` is False), with
`misdirected` empty throughout so the section cannot pass on the other guard by
accident. Measured differential on it: shipped → marker absent; mutant → marker
`1`, both reporting `ok=False lost=2 kept=2`.
**Why the fixtures had to change, and this is the part that got the card
rejected.** Nothing in the repository could see whether any of the above was
@@ -297,3 +360,29 @@ It is pinned as a running check (`test_maintenance.py` part 13) rather than left
as a comment. ⚠️ **Its assertions state the WRONG behaviour on purpose** — read
them as the specification of a defect; the card that fixes it should expect them
to go red and should replace them.
### And its MARKER GATE had the round-3 hole too — found here, closed here
Separate from the namespace hole above, and **fixed** rather than recorded,
because closing it cost one test fixture and no source change.
`migrate_unit_keys()` gates `schema_version` on `rep.ok` exactly as
`migrate_maint_keys()` gates its own marker, and every check that reached that
gate ran on a store where **nothing** was namespaced yet — so `rep.kept` was
empty in all of them and
```
if rep.ok: -> if rep.ok or rep.kept:
```
**survived all 19 files**. What it does is stamp the schema marker on a
migration that DROPPED THE LATCH: the guard is off, the state file still reads
`sign_tripped: true`, and because the store now says the migration finished, the
next start skips the one thing that would have re-armed it. DEV-02's own worst
case, filed under *done*.
`test_inverter.py` part 10 now drops the `sign_tripped` write **beside an
already-namespaced `sign_reason`** — `lost` non-empty *and* `kept` non-empty,
which is the one shape none of the existing fixtures had — and asserts the
marker is withheld, with the accepted-baseline counterfactual beside it (habit
7). The shipped gate was correct throughout; what changed is that a check can
now see it.
+35 -4
View File
@@ -135,13 +135,14 @@ manufactures them from a **fixture**: the check runs, the assertion is
evaluated, the sentence above it is true — and the numbers it compares are
arranged so that the failure it describes could not have changed the answer.
It is not hypothetical and it is not rare. Seven instances in two weeks; four
It is not hypothetical and it is not rare. EIGHT instances in two weeks; four
were found by review, two - CFG-04's fixture pair - by an author's own mutation
battery, which is the cheaper place, and one by review again, in the one
battery, which is the cheaper place, one by review again, in the one
place nobody had thought to point the rule at: not a check in the suite, but a
COMMITTED EVIDENCE ARTIFACT four documents cite.
COMMITTED EVIDENCE ARTIFACT four documents cite - and the eighth INSIDE THE FIX
WRITTEN TO CLOSE THE SEVENTH.
⚠️ **AND IT HAS NOW APPEARED IN FIVE CONSECUTIVE CARDS.** The seventh instance,
⚠️ **AND IT HAS NOW APPEARED IN SIX CONSECUTIVE CARDS.** The seventh instance,
MAINT-05's, is the one that says why: the defect is not carelessness about
fixtures, it is that **a check and the thing it checks were built from the same
constant**. Ask of every difference-claim not "is this assertion true" but
@@ -196,6 +197,22 @@ same place twice, the check is decoration.
one — grade against something the thing under test did not supply, here the
ROSTER the machines read.
⚠️ **AND THE FIX ITSELF CARRIED THE DEFECT, WHICH IS THE SIXTH CARD IN A ROW
AND THE FIRST TIME IT HAS APPEARED *INSIDE* A REMEDY.** The fix round's
recovery check was `migrate_maint_keys(_m5, ROSTER[0], ROSTER)` — the
**corrected** name. It measures recovery along the one path where the operator
has *already fixed the bug*, and nothing in the 19 files called
`migrate_maint_keys()` twice with the **same wrong** `unit`. So the refusal
the card was rejected twice to obtain lasted **exactly one start**: measured
at Controller level over one persisted store, roster `["garage", "shed"]` —
construction 1 `ok=False misdirected=4 moved=4 kept=0 marker=<absent>`,
construction 2 `ok=True misdirected=0 moved=0 kept=4 marker=1`. On start 2
rule 1 routes the wrongly-written keys to `rep.kept`, `rep.moved` is empty,
the roster loop iterates over nothing, and the marker is stamped on a
migration that reached no readable key — after which the early return skips it
forever, *including after the call site is fixed*. The check was true and
passed for the wrong reason. Habit **9** below is what falls out of it.
* **CFG-04's `tools/supervisor_shape_probe.py` §B** — and this one is the rule
reaching outside the suite. The probe is the committed evidence that
`config.yaml`'s `inverters: []` default is load-bearing, and four documents
@@ -270,6 +287,18 @@ Cheap habits fall out of it, and all of them are now used in this repository:
red. `inverters: []` was deleted from `config.yaml` and the probe re-run
against `ghcr.io/home-assistant/amd64-hassio-supervisor:2026.08.0` for
exactly that reason.
9. **Exercise a refusal a SECOND time with the SAME wrong input — not the
corrected one.** A guard that refuses once and then admits the same bad
caller is not a guard, it is a delay, and the second call is a *different
code path* whenever the first call left state behind. MAINT-05's roster
grading iterated the keys it had just **written**; on the second start the
same wrong caller found them already present, they moved to the
`already namespaced` list, and the loop graded an empty set. Re-running with
the **fixed** argument — which is what the check did — measures the operator's
recovery, never the persistence of the refusal. Ask of every refusal: *what
does this do when the caller has not been fixed and simply runs again?* And
assert the **shape** of that second report, not only its verdict, so the
check cannot silently revert to grading the first path.
And the mutation-table consequence: **a variant is not covered because a
near-miss of it is.** DEV-02's table listed the *actuator* variant of the
@@ -326,6 +355,8 @@ commit and PR bodies.
| **TARIFF-01** reviewer survivor: `record_block()` ignoring its `policy` argument (0dbd485) | **re-verified — still a survivor** | re-run here through `tools/mutation_run.sh` against the 15 suites that were on the record, all green with `billed_w(block, policy)` mutated to `billed_w(block, POLICY_NET)`. The recorded diagnosis holds: every block reaching the store is single-phase, where NET, SUM and MAX collapse. **Now CLOSED by TEST-13** (`test_tariff.py` section 7), which round-trips section 7's own unbalanced `(3000, 500, -200) W` block THROUGH the store under each policy - three distinct banked figures, so a hardcoded policy takes at least two of them red. Third instance of "a stimulus evaluated where the quantity it grades is degenerate", after WITNESS-02's span normalisation and TARIFF-01's own DST window. |
| **SAFETY-25** 8 mutations against `test_peakguard.py` part 10, then 9 more after review, all red | **not at risk** | each run against a tree copied fresh with `__pycache__` excluded and mounted `:ro`, so no `.pyc` existed to go stale - the same structural immunity recorded for CI above. Byte deltas recorded on the PR; none is `+0 B`. All are **reds**, which is the direction this defect cannot fabricate. ⚠️ The second round exists because the first round's mutations were all aimed at DISARMING the guard, and the claim part 10 actually made was about the guard being FIXED - the reviewer built the fix and the suite stayed green. Bytecode was never the issue there; an unaimed battery was. |
| **SAFETY-03** reviewer survivor: `abs()` dropped from `clamp_to_rating`'s limit (6aabb83) | **re-verified — still a survivor** | re-run the same way, all 15 green. Also as recorded: unreachable under the `int(100, 5000)` schema. |
| **MAINT-05 round 3**, `migrate_maint_keys()` marker gate `if rep.ok:` → `if rep.ok or rep.kept:` (`+12 B`) | **was a survivor — now CLOSED** | measured against head `65cddf3` under the purge, tree mounted `:ro`, `19/19 PASS`. It stamps the schema marker on a migration that LOST state, defeating the older `lost` guard as well as the new `misdirected` one, because every check that reached the marker did so on a store whose `kept` list was empty. Closed by `test_maintenance.py` **9d**; the mutation is now red on three named checks. |
| **MAINT-05 round 3**, `migrate_unit_keys()` marker gate `if rep.ok:` → `if rep.ok or rep.kept:` (`+12 B`) | **found here — SAFETY-02's half, now CLOSED** | the SAME mutation one function up, on the LATCH migration rather than the history one. Measured a survivor against this branch under the purge, `:ro`, `19/19 PASS`, then closed by `test_inverter.py` part 10's new fixture — a store with `unit/inverter-a/sign_reason` already present (so `kept` is non-empty) whose `sign_tripped: true` write is dropped. Shipped: marker withheld. Mutant: marker stamped on a **disarmed latch**, and the next start skips the migration that would have re-armed it. |
| **TARIFF-01**'s other three survivors, **SAFETY-03**'s `describe()` survivor, **TARIFF-02**'s M3/M5/M7, **SAFETY-18**'s three | **still unverified** | recorded before the purge discipline, with no byte delta on the record, so a `+0 B` same-second revert cannot be excluded. All are survivors, which is the direction this defect fabricates. TARIFF-02's and SAFETY-18's were **closed by later commits whose claims are reds**, so what is unverified is the *diagnosis*, not the current state of the code. |
**Nothing here says a past result is wrong.** The two survivors re-run above
+47 -2
View File
@@ -373,6 +373,16 @@ def _verify_moves(store, rep: MigrationReport, roster=None) -> None:
next start runs the migration again. A re-run is recoverable, by rule 1; a
silent skip is the state store.py's header says must never happen.
⚠️ AND THE GRADED SET IS `moved` UNION `kept`, WHICH IS WHAT MAKES THAT
PARAGRAPH TRUE ON THE SECOND START AS WELL AS THE FIRST. Graded over `moved`
alone the refusal lasted exactly one start: the wrong call site is still
wrong next time, finds the keys it wrote already present, and rule 1 routes
every one of them to `kept` - so `moved` is empty, this loop sees nothing,
`ok` comes back True and the marker is stamped on a migration that has
reached no readable key. The measurement is in the comment on the loop
itself. `test_maintenance.py` section 9c calls this twice with the SAME
wrong `unit` over one store, which is the shape nothing in the suite had.
`roster=None` means "not graded", and the one caller that passes it is
`migrate_unit_keys()`, which runs BEFORE `build_roster()` for the reason its
own docstring gives - the `Inverter` objects restore their latch in their
@@ -396,7 +406,32 @@ def _verify_moves(store, rep: MigrationReport, roster=None) -> None:
names = [str(n) for n in roster]
# `unit_key()` builds `unit/<unit>/<key>`, so the unit is field 1. Split
# through the same spelling the writer used rather than re-deriving it.
for target in sorted(rep.moved):
#
# ⚠️ `moved` UNION `kept`, AND THE UNION IS THE WHOLE OF THE SECOND FIX
# ROUND. Grading only `moved` made the refusal last EXACTLY ONE START.
# Measured at Controller level over one persisted store, roster
# ["garage", "shed"], flat `last_completed` 2026-08-07, with the call site
# still handing `inverter-a` on both starts:
#
# construction 1: ok=False misdirected=4 moved=4 kept=0 marker=<absent>
# construction 2: ok=True misdirected=0 moved=0 kept=4 marker=1
#
# On start 2 the four keys the WRONG start 1 wrote are already present, so
# rule 1 routes every one of them to `rep.kept`, `rep.moved` is EMPTY, this
# loop iterates over nothing, `misdirected` is empty and `ok` is True - so
# the caller stamps the marker and start 3 takes the `from_version >=
# SCHEMA` early return FOREVER, including after the call site is fixed.
# Loud-and-recoverable became silent-and-permanent after one restart: the
# exact state this module's header says must never happen, reached by the
# fix that was written to prevent it.
#
# The union is sound by construction rather than by hope: every entry in
# both `rep.moved` and `rep.kept` is a `unit_key(unit, legacy)` built from
# the SAME `unit` argument, so grading `kept` grades the same namespace
# decision `moved` does. A correct call site puts its `kept` entries under a
# roster name and they pass; a wrong one cannot launder its namespace by
# having already written it.
for target in sorted(set(rep.moved) | set(rep.kept)):
parts = target.split("/")
unit = parts[1] if len(parts) > 2 else ""
if unit in names:
@@ -443,7 +478,9 @@ def migrate_maint_keys(store, unit: str, roster) -> MigrationReport:
marker. Handing the roster in means the read-back is graded against the
keys the MACHINES read, and a `unit` that is not among these names comes
back `misdirected`: not `ok`, marker NOT written, migration runs again next
start. See `_verify_moves()` for the install this was measured on.
start - and again the start after that, because `_verify_moves()` grades
`kept` as well as `moved`. See `_verify_moves()` for the install this was
measured on, and for what grading `moved` alone cost on start 2.
THE THREE RULES ARE `migrate_unit_keys()`'s, SHARED IN CODE rather than
restated: `_move_flat_keys()` is rule 1 (never overwrite a namespaced value)
@@ -481,6 +518,14 @@ def migrate_maint_keys(store, unit: str, roster) -> MigrationReport:
_move_flat_keys(store, unit, MAINT_UNIT_KEYS, rep)
_verify_moves(store, rep, roster=names)
# ⚠️ `rep.ok` AND NOTHING ELSE - NOT `ok or kept`, NOT `not lost`, NOT
# `not misdirected`. BOTH guards gate this line and each one is pinned by a
# check that the other cannot satisfy: section 9c refuses a store whose
# failure is `misdirected` with `lost` empty, and section 9d refuses one
# whose failure is `lost` with `misdirected` empty AND `kept` non-empty -
# the mutation `if rep.ok or rep.kept:` stamps the marker on a migration
# that lost a month of history, and until section 9d existed all 19 suites
# were green on it.
if rep.ok:
store.set(MAINT_SCHEMA_KEY, MAINT_SCHEMA_VERSION)
else:
+63 -1
View File
@@ -572,6 +572,58 @@ check("...and a store that only fails to persist a DEFAULT is not reported as "
"dismiss before the real one arrives",
migrate_unit_keys(LosesTheLatch(), UNIT_A).ok is True)
# ⚠️ AND THE MARKER GATE, ON A STORE WHERE SOMETHING WAS ALREADY NAMESPACED.
# FOUND BY MAINT-05's ROUND-3 BATTERY, AND IT WAS A SURVIVOR OF ALL 19 FILES.
# `migrate_unit_keys()` gates `SCHEMA_KEY` on `rep.ok`, and every check above
# reaches that gate on a store where NOTHING was already under `unit/...` - so
# `rep.kept` is empty in all of them, and the mutation
#
# if rep.ok: -> if rep.ok or rep.kept:
#
# stamped the marker on a migration that DROPPED THE LATCH, with the whole suite
# green. That is a disarmed safety guard plus a store that says the migration
# finished, so the next start skips the one thing that would have re-armed it -
# DEV-02's own worst case with the paper trail filed under "done".
#
# The fixture is the one shape that separates them: `sign_reason` ALREADY
# namespaced (so `kept` is non-empty and the mutant's `or` fires) and the
# `sign_tripped: true` write dropped on the floor (so `lost` is non-empty and
# `ok` is False). `migrate_unit_keys()` is passed no roster, so `misdirected`
# cannot be what refuses this - it is the `lost` guard or nothing.
_kept_reason = "SIGN_DISAGREEMENT: already namespaced by a later version"
_broken_kept = LosesTheLatch({
"sign_tripped": True,
"sign_reason": "SIGN_DISAGREEMENT: measured",
unit_key(UNIT_A, "sign_reason"): _kept_reason})
_rep_kept = migrate_unit_keys(_broken_kept, UNIT_A)
print(f" a dropped latch write beside an already-namespaced reason: "
f"{_rep_kept.explain()}")
print(f" lost={len(_rep_kept.lost)} kept={len(_rep_kept.kept)} "
f"ok={_rep_kept.ok}; marker "
f"{_broken_kept.data.get(SCHEMA_KEY, '<<absent>>')!r}")
check("PRECONDITION: this fixture reaches the marker gate through the `lost` "
"guard with a NON-EMPTY `kept`, which is the combination none of the "
"checks above has. One key dropped, one key left alone, `ok` False. If a "
"later edit empties `kept` here the mutation goes invisible again, so the "
"shape is asserted rather than assumed",
_rep_kept.ran and len(_rep_kept.lost) == 1 and len(_rep_kept.kept) == 1
and not _rep_kept.ok
and _broken_kept.data[unit_key(UNIT_A, "sign_reason")] == _kept_reason)
check("⚠️ AND THE STORE IS NOT MARKED MIGRATED. The key that was ALREADY there "
"does not buy a marker for the latch that was not: stamping here would "
"tell the next start that the migration which dropped `sign_tripped` had "
"finished, so it would never run again and the guard would stay off on an "
"install whose file still reads `sign_tripped: true`",
SCHEMA_KEY not in _broken_kept.data)
check("...and the counterfactual, one property different: the SAME store shape "
"on a store that does not drop writes IS marked migrated. So the refusal "
"above is about the latch that was lost, not about `kept` being non-empty",
migrate_unit_keys(
MemStore({"sign_tripped": True,
"sign_reason": "SIGN_DISAGREEMENT: measured",
unit_key(UNIT_A, "sign_reason"): _kept_reason}),
UNIT_A).ok is True)
_broken2 = LosesTheLatch({"sign_tripped": True,
"sign_reason": "SIGN_DISAGREEMENT: measured"})
_c2d, _ = built(store=_broken2)
@@ -2149,6 +2201,16 @@ MUTATIONS = [
("validate.py allowing two roster entries with the SAME name",
"part 10: 'two entries with the SAME name are refused' - one Store "
"namespace for two units is DEV-02's defect rebuilt in YAML"),
("`migrate_unit_keys()`'s marker gate `if rep.ok:` -> `if rep.ok or "
"rep.kept:` - the schema marker stamped on a migration that DROPPED THE "
"LATCH, because one key happened to be namespaced already",
"part 10: 'AND THE STORE IS NOT MARKED MIGRATED'. ⚠️ THIS ONE SURVIVED "
"ALL 19 FILES and was found by MAINT-05's round-3 battery, not by this "
"card: every migration check here reached the gate on a store where "
"NOTHING was namespaced yet, so `rep.kept` was empty in all of them and "
"the `or` never fired. Stamping there disarms the latch AND tells the "
"next start the migration finished, so it never re-arms - DEV-02's own "
"worst case filed under 'done'"),
]
# ⚠️ FOUR OF THESE SURVIVED THE FIRST TIME AND THE CHECKS WERE CHANGED, NOT THE
# TABLE - two in DEV-02, two more in CFG-04, and the CFG-04 pair are recorded in
@@ -2167,7 +2229,7 @@ for what, caught in MUTATIONS:
check(f"all {len(MUTATIONS)} defeats of this card are caught by a named check "
f"above. A guard whose checks survive its own defeat is not a guard, and "
f"this repository has three separate records of exactly that",
len(MUTATIONS) == 34)
len(MUTATIONS) == 35)
print()
+142
View File
@@ -806,6 +806,148 @@ check("...and a roster of ONE still returns before any of this, so the "
is False)
# --------------------------------------------------------------------------- #
print()
print("9c. THE REFUSAL HAS TO SURVIVE THE NEXT START: the same wrong call "
"site, twice, over ONE store")
# ⚠️ WHY 9b WAS NOT ENOUGH, AND IT IS THIS FILE'S OWN RECURRING DEFECT ONE
# LEVEL UP. 9b's recovery check re-runs the migration with `ROSTER[0]` - the
# CORRECTED name - so it measures recovery along the one path where the
# operator has ALREADY fixed the call site. The real failure is a call site that
# is STILL WRONG on the next start, and until this section nothing in the 19
# files called `migrate_maint_keys()` twice with the SAME wrong `unit`. 9b was
# true and passed for the wrong reason.
#
# WHAT THAT HID. Measured at Controller level over one persisted store, roster
# ["garage", "shed"], flat `last_completed` 2026-08-07, call site handing
# `inverter-a` on both starts:
#
# construction 1: ok=False misdirected=4 moved=4 kept=0 marker=<absent>
# construction 2: ok=True misdirected=0 moved=0 kept=4 marker=1
#
# `_verify_moves()` graded only `rep.moved`. On start 2 the wrong call site
# finds the keys it wrote already present, rule 1 routes all four to `rep.kept`,
# `rep.moved` is EMPTY, the roster loop iterates over nothing, `misdirected` is
# empty and `ok` is True - so the marker is stamped and start 3 takes the
# `from_version >= MAINT_SCHEMA_VERSION` early return FOREVER, including after
# the call site is fixed. Loud-and-recoverable became silent-and-permanent after
# ONE restart. The fix grades `set(rep.moved) | set(rep.kept)`.
_m7 = flat_store()
_rep7a = migrate_maint_keys(_m7, UNIT_A, ROSTER)
_rep7b = migrate_maint_keys(_m7, UNIT_A, ROSTER)
print(f" start 1: ok={_rep7a.ok} misdirected={len(_rep7a.misdirected)} "
f"moved={len(_rep7a.moved)} kept={len(_rep7a.kept)} marker="
f"{_m7.data.get(MAINT_SCHEMA_KEY, '<<absent>>')!r}")
print(f" start 2: ok={_rep7b.ok} misdirected={len(_rep7b.misdirected)} "
f"moved={len(_rep7b.moved)} kept={len(_rep7b.kept)} marker="
f"{_m7.data.get(MAINT_SCHEMA_KEY, '<<absent>>')!r}")
check("PRECONDITION, SO THIS SECTION CANNOT PASS FOR THE WRONG REASON: start 2 "
"goes down the KEPT path, which is the path no other check in this file "
"reaches. Not one key is written a second time - all four are already "
"there from start 1, so `moved` is EMPTY and `kept` holds every one of "
"them. If a later edit makes start 2 re-WRITE them, this goes red rather "
"than the claim below quietly reverting to 9b's",
_rep7b.ran and _rep7b.moved == {}
and sorted(_rep7b.kept) == sorted(unit_key(UNIT_A, k)
for k in MAINT_UNIT_KEYS))
check("⚠️ THE MARKER IS STILL WITHHELD ON THE SECOND START. This is the whole "
"section: a refusal that lasts exactly one start is not a refusal, it is "
"a delay - the next start stamps the marker on a migration that has "
"reached no readable key, and every start after that skips the migration "
"entirely",
MAINT_SCHEMA_KEY not in _m7.data)
check("...and it is refused for the SAME reason as the first time, graded "
"against the same roster: four keys under a namespace nothing on this "
"site reads. `ok` False, `misdirected` full, `lost` empty - the caller "
"was wrong, the store was not",
not _rep7b.ok and not _rep7b.verified and not _rep7b.lost
and len(_rep7b.misdirected) == len(MAINT_UNIT_KEYS)
and repr(ROSTER) in _rep7b.explain())
check("...and the history is still THERE to recover: rule 2 left every flat key "
"in the file across both starts, and the roster's first pack still reads "
"nothing - which is what makes this loud rather than lost",
all(_m7.data[k] is not None for k in MAINT_UNIT_KEYS)
and _m7.data["last_completed"] == "2026-08-07T12:00:00+00:00"
and unit_key(ROSTER[0], "last_completed") not in _m7.data)
_rep7c = migrate_maint_keys(_m7, ROSTER[0], ROSTER)
print(f" start 3, call site FIXED: {_rep7c.explain()}")
check("⚠️ AND BECAUSE THE MARKER WAS NEVER STAMPED, THE FIXED CALL SITE STILL "
"GETS A RUN. A stamped marker would have made this an early return "
"forever - the operator fixes the bug and the migration never happens "
"again. Here the third start migrates the month into `garage` and stamps",
_rep7c.ran and _rep7c.ok
and _m7.data.get(unit_key(ROSTER[0], "last_completed"))
== "2026-08-07T12:00:00+00:00"
and _m7.data[MAINT_SCHEMA_KEY] == MAINT_SCHEMA_VERSION)
# The counterfactual, so this is not a check satisfied by a validator that
# refuses every second call: the same two calls with the RIGHT name.
_m7ok = flat_store()
_rep7ok_a = migrate_maint_keys(_m7ok, ROSTER[0], ROSTER)
_rep7ok_b = migrate_maint_keys(_m7ok, ROSTER[0], ROSTER)
check("...and the counterfactual: the SAME two calls with the roster's own "
"first name are accepted - stamped on the first, and the second is the "
"`already at schema` early return, not a refusal. So 9c is about the "
"NAME surviving into the second start, not about second calls",
_rep7ok_a.ok and _m7ok.data[MAINT_SCHEMA_KEY] == MAINT_SCHEMA_VERSION
and _rep7ok_b.ran is False and "already at schema" in _rep7ok_b.explain())
# --------------------------------------------------------------------------- #
print()
print("9d. THE OTHER GUARD ON THE SAME LINE: a migration that LOST STATE is "
"not stamped either, even when some keys were already in place")
# ⚠️ WHAT THIS PINS, AND IT IS A SURVIVOR THE SECTIONS ABOVE CANNOT SEE.
# `migrate_maint_keys()` gates the marker on `rep.ok`, which is BOTH guards:
# `not lost and not misdirected`. Every check in this file that reaches the
# marker reaches it through `misdirected` (9b, 9c) or through a store where
# NOTHING was already namespaced (part 9's `LosesTheHistory`, whose `kept` is
# empty). So the mutation
#
# if rep.ok: -> if rep.ok or rep.kept:
#
# stamped the marker on a migration that lost a month of history - defeating the
# OLDER, settled `lost` guard as well as the new `misdirected` one - and all 19
# suites stayed green on it. Measured differential on this exact fixture:
# shipped -> marker absent; mutant -> marker 1, both reporting ok=False lost=2.
#
# The fixture is the one shape that separates them: TWO keys already namespaced
# (so `kept` is non-empty and the mutant's `or` fires) and TWO keys whose write
# the store drops (so `lost` is non-empty and `ok` is False). `misdirected` is
# empty throughout - the unit handed in IS the roster's first name - so this
# section cannot pass on the `misdirected` guard by accident.
_m8 = LosesTheHistory(dict(flat_store(**{
unit_key(ROSTER[0], "phase"): CHARGE,
unit_key(ROSTER[0], "phase_started"): "2026-09-03T22:00:00+00:00"}).data))
_rep8 = migrate_maint_keys(_m8, ROSTER[0], ROSTER)
print(f" two keys already namespaced, two dropped on write: {_rep8.explain()}")
print(f" ok={_rep8.ok} lost={len(_rep8.lost)} kept={len(_rep8.kept)} "
f"misdirected={len(_rep8.misdirected)}; marker "
f"{_m8.data.get(MAINT_SCHEMA_KEY, '<<absent>>')!r}")
check("PRECONDITION: this fixture reaches the marker through the `lost` guard "
"and ONLY that one - two keys in `kept`, two in `lost`, `misdirected` "
"empty. If it ever stops having a non-empty `kept` the mutation below "
"goes invisible again, so the shape is asserted rather than assumed",
_rep8.ran and len(_rep8.kept) == 2 and len(_rep8.lost) == 2
and not _rep8.misdirected and not _rep8.ok)
check("⚠️ THE MARKER IS NOT STAMPED ON A MIGRATION THAT LOST A MONTH OF "
"HISTORY, and the two keys that DID land do not buy it one. `rep.ok` is "
"the gate and nothing weaker: stamping here would tell the next start a "
"migration finished that dropped half of what it was moving",
MAINT_SCHEMA_KEY not in _m8.data)
check("...and the counterfactual, one property different: the SAME fixture on a "
"store that does not drop writes IS stamped. So the refusal above is "
"about the state that was lost, not about `kept` being non-empty",
migrate_maint_keys(
MemStore(dict(flat_store(**{
unit_key(ROSTER[0], "phase"): CHARGE,
unit_key(ROSTER[0], "phase_started"):
"2026-09-03T22:00:00+00:00"}).data)),
ROSTER[0], ROSTER).ok is True)
# --------------------------------------------------------------------------- #
print()