`self.i_w` has been a separate quantity from the command since SAFETY-04 and has left the process nowhere at all - no entity, no MQTT attribute, no log line. The investigation behind SAFETY-48 could only RECONSTRUCT it from the grid trace plus a replay; the reconstruction reproduced about a third of the engagements it was checked against and was discarded, so the card it was gathered for had to be argued from a measurement nobody could stand behind. It could not have been made to work either: `i_w` depends on every cycle before it, including the ones where the arbiter overrode the loop and `main.py` re-seeded `self.i_w = self.target`, and that re-seed is invisible in the grid trace. THREE SITE ROWS, APPENDED AFTER release/1.0's EIGHT. `loop_i` is `Decision.i_w` - the accumulator after the cycle, before the output clamp, the slew limit and quantisation. `loop_lead` is `decision.i_w - prev_w` against the command `compute()` was HANDED, which is not a subtraction of the other two published rows: `Setpoint` is the arbiter's output and is this cycle's, so on any cycle the arbiter overrode the loop the two differ - and that is exactly the cycle on which the integrator was re-seeded. `loop_reason` is `compute()`'s own word; the arbiter's `explain()` prints the winning claim's NAME and has never carried the claim's reason, so `slew-limited` appears in neither the `Why` row nor `Controller status`. Measured on the same cycle: arbiter `'loop -> -5000 W'`, loop `'i-clamped'`. NOTHING IN THE CONTROL PATH MOVED. `app/control.py` is not in this diff. `compute()`, its returned `Decision` and every constant it reads are identical to release/1.0, and all nine of release/1.0's retained discovery messages are byte-identical and still FIRST in the announcement. Evidence: 30/30 suites green in the CI image. Nine mutations, ONE survivor, reported as one: `decision.i_w - self.target` for the lead is +2 B and green because `cycle()` has not reassigned `self.target` at that line - the name is kept for the coupling, not for a behaviour it buys, and M2b (+8 B) is the mutation that does change the value and is red. AND TWO OF THIS CARD'S OWN CHECKS COULD NOT REACH THE FAILURE THEY NAMED. "the published integrator reaches its bound" was GREEN under the card's named mutation (publish `prev_w`, -3 B), because the command reaches the bound too - twelve cycles later. It asserts the LEAD TIME now and is red on 5. "a stopped controller publishes not-running" was GREEN under deleting that branch's assignments (-45 B, -67 B), because the fixture STARTED stopped and `__init__` writes the same values; it stops the controller part-way through a run that has already wound the integrator to -5000 W. Both are MUTATION.md habit 4, found by the battery rather than by review. No CHANGELOG section: `test_version_gate.py` 11b refuses a newest `## ` heading that is not config.yaml's `version:`, and opening one would mean taking a packaging decision this card does not get to take. The operator account is in DOCS.md and the release note belongs to the next PKG ticket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
60 lines
3.6 KiB
Plaintext
60 lines
3.6 KiB
Plaintext
# WHAT THIS BUILD IS SUPPOSED TO ANNOUNCE, recorded so that a change to the
|
|
# composition has something to disagree with. THREE COLUMNS:
|
|
#
|
|
# <entity_id> <unique_id> <device identifier>
|
|
#
|
|
# ⚠️ ALL THREE, AND THE SECOND AND THIRD ARE THE HALF THE FIRST DRAFT MISSED.
|
|
# A reference that carries only entity_ids cannot see the two changes that
|
|
# orphan an install without changing a single composed id:
|
|
#
|
|
# `BASE` renamed (`goodwe_ctl` -> anything) every unique_id changes
|
|
# `DEVICE["identifiers"]` changed HA sees a NEW device
|
|
#
|
|
# Home Assistant's MQTT integration keys its entity registry on `unique_id` and
|
|
# its device registry on the device identifier. Change either and HA creates a
|
|
# NEW entity beside the old one - and because discovery is published with
|
|
# `retain=True` and nothing publishes a removal payload, the OLD config stays on
|
|
# the broker and the dead entity stays in the registry, still named by every
|
|
# automation that referenced it, never updating again. The composed entity_id is
|
|
# identical on both, so an entity_id-only comparison reports a clean transition.
|
|
# That is the same shape as the habit-4 this file's own card already caught
|
|
# once: a reference that does not carry the thing that moved.
|
|
#
|
|
# ⚠️ AND `object_id` IS DELIBERATELY NOT A COLUMN. `app/mqtt.py` lines 36-42 and
|
|
# 61-76 record that HA 2026.8 does NOT honour `object_id` in a discovery
|
|
# payload - the id is composed from device name plus entity name - so renaming
|
|
# one is a true no-op and a column for it would make this file refuse a change
|
|
# that costs nothing. The check is graded on what HA actually keys on.
|
|
#
|
|
# PROVENANCE, stated exactly:
|
|
# - recorded 2026-09-08 from `release/1.0` at `meter_source: firmware`,
|
|
# roster of one, by reading `mqtt.discovery_payloads(units=[])`. It is what
|
|
# the code announces.
|
|
# - `sensor.goodwe_p1_sample_age` is additionally recorded in `app/mqtt.py` as
|
|
# VERIFIED AGAINST THE LIVE SYSTEM on 2026-08-26, and the ESP32's ingest
|
|
# guard subscribes to that exact string.
|
|
# - ⚠️ IT IS NOT A DUMP OF THE LIVE INSTALL'S ENTITY REGISTRY. That is what
|
|
# `--live-entities` is for. This file is about THE BUILD.
|
|
#
|
|
# Editing this file is how you say "the announcement changed on purpose". Do not
|
|
# edit it to make a check pass.
|
|
#
|
|
# ⚠️ TEL-10 ADDED THE LAST THREE ROWS, AND THEY ARE ADDITIONS RATHER THAN A
|
|
# RE-COMPOSITION. Nothing above them moved: same entity_ids, same unique_ids,
|
|
# same device. They are recorded here - the BUILD's reference - and deliberately
|
|
# NOT in `live-entities-recorded.txt`, because the install does not have them
|
|
# yet and an id the build announces that the install lacks is a NEW entity, not
|
|
# an orphan. `test_mqtt.py` part 1 grades the same two claims on the payload
|
|
# bytes.
|
|
sensor.goodwe_setpoint goodwe_ctl_setpoint goodwe_rs485_controller
|
|
sensor.goodwe_grid_power goodwe_ctl_grid goodwe_rs485_controller
|
|
sensor.goodwe_battery_power goodwe_ctl_battery goodwe_rs485_controller
|
|
sensor.goodwe_battery_soc goodwe_ctl_soc goodwe_rs485_controller
|
|
sensor.goodwe_circulating_power goodwe_ctl_circulation goodwe_rs485_controller
|
|
sensor.goodwe_maintenance_phase goodwe_ctl_phase goodwe_rs485_controller
|
|
sensor.goodwe_controller_status goodwe_ctl_status goodwe_rs485_controller
|
|
sensor.goodwe_p1_sample_age goodwe_ctl_p1_age goodwe_rs485_controller
|
|
sensor.goodwe_loop_integrator goodwe_ctl_loop_i goodwe_rs485_controller
|
|
sensor.goodwe_loop_integrator_lead goodwe_ctl_loop_lead goodwe_rs485_controller
|
|
sensor.goodwe_loop_reason goodwe_ctl_loop_reason goodwe_rs485_controller
|