Merge origin/release/1.0 into SAFETY-20
checks / unit (pull_request) Successful in 3m36s

# Conflicts:
#	goodwe_controller/CHANGELOG.md
This commit is contained in:
glenn schrooyen
2026-09-01 02:43:08 +02:00
22 changed files with 754 additions and 10 deletions
+55
View File
@@ -68,6 +68,61 @@ process; the unwiring mutation fails three checks there. The peak-guard call
site's wiring is still ungraded — it sits behind `p1_enabled` and needs a fed
averager to have any observable consequence — and that gap is named in the file
rather than implied.
**A mutation run could grade code that was not there, and the fix is a purge
rather than a flag (TEST-15).** FCST-03's developer reverted `self.provisional`
to `here.provisional`, re-ran, and reported a survivor; disassembly showed the
source read `here` while the running code loaded `self`. CPython validates a
`.pyc` on (source size, source mtime truncated to whole seconds), and a
size-preserving edit reverted inside one second moves neither number, so the
stale bytecode was reused. That does not make past results wrong - it made them
unverified, and the distinction is the whole card.
Reproduced rather than reasoned about. `test_bytecode.py` builds a real package
whose two return values are the same length, imports it in a real subprocess,
rewrites it inside the same second and reads back which value ran. Part 1 shows
the interpreter running `SELF` off a file that says `HERE`.
**And it shows the two obvious mitigations do not work.** `python3 -B` and
`PYTHONDONTWRITEBYTECODE=1` are write-side only: neither stops an EXISTING stale
`.pyc` being read, and a mutation run always has one because the previous run
wrote it. The advice already in `test_control.py`'s coverage audit led with `-B`
and is corrected here - the purge is the half that works, and `-B` earns its
place by protecting the NEXT edit rather than this one. No `sleep` anywhere:
one-second mtime resolution is the mechanism, and waiting it out would slow every
run down while still only covering the edit somebody remembered to slow.
`goodwe_controller/tools/mutation_run.sh` is the harness the house adopts: it
purges, prints a sha256/byte fingerprint of every `app/*.py` it is about to
grade - so a mutation report can say WHICH source produced it - runs each suite
under `python3 -B`, refuses a tree with no `test_*.py` (CI-01's rule), and fails
if a cache appears during the run. It takes the tree as an argument so the infra
repo's replay harnesses can call it.
`MUTATION.md` states the guarantee where a mutation gets written, and every one
of the 16 suites now points at it from its docstring - a list `test_bytecode.py`
derives from the tree, so a new suite joining without the pointer is red.
CI was already immune and is described that way rather than as a hole being
closed: `checks.yml` mounts the checkout `-v "$WS:/w:ro"`, and a read-only tree
cannot take a `__pycache__` at all - measured, and it fails silently, exit
status 0 - while each job `rm -rf`s its workspace and clones afresh.
`PYTHONDONTWRITEBYTECODE=1` is added as belt and braces, because the `:ro` is one
character wide. The shipped add-on was never affected: `Dockerfile` has set
`PYTHONDONTWRITEBYTECODE=1` since it was written.
**The survey of this week's mutation claims is in `MUTATION.md`, scoped by the
property rather than by re-running everything.** The defect is one-directional -
stale bytecode turns a red into a green survivor and never the reverse - so every
"all red, no survivors" result in the window is not at risk, and the exposure is
the recorded SURVIVORS from source-edit runs made before the purge discipline
landed on 2026-08-31. Two of them were re-run here under the harness and both
still stand (TARIFF-01's `record_block()` ignoring its `policy` argument;
SAFETY-03's `abs()` on the clamp limit). The rest are labelled still-unverified
rather than quietly assumed or noisily re-run.
Out of scope and recorded: the infra repo's `sim/` replay harnesses import the
add-on and write bytecode into both trees. Anyone mutating a module graded by a
replay must purge both by hand until that repo adopts the harness.
**`release/1.0` went red at midnight on a commit nobody had touched (TEST-17).**
`Controller.checks()` read the capacity-tariff peak with `self.peak.peak()` - no