UX-01's three unmet acceptance criteria, closed out. It shipped in 0.7.0 and is
LIVE, so all three describe the running install. No behaviour, no schema and no
default moves here: config.yaml, translations/ and every module under app/ are
byte-identical to 6e7b233.
⚠️ AC 3 - "the visible count drops to ~15" - IS WITHDRAWN AS UNSATISFIABLE, and
the arithmetic is the reason rather than the effort. options: and schema: both
declare all 71 keys, so the set HA's toggle can hide is EMPTY - it hides a key
only when it is ?-optional AND absent from options:, and nothing is absent. But
the deeper fact is that no manifest could have met it: Supervisor's own
UiOptions marks 59 of the 71 REQUIRED, and the frontend's own filter - schema.filter(e => e.name in opts || e.required), transcribed out of the
bundle in the release image - shows a required field unconditionally. So 59 is
the floor with options: EMPTIED ALTOGETHER. The target was ~4x below anything
reachable.
⚠️ AND THE 12 HIDES THAT ARE EXPRESSIBLE WOULD BE BOUGHT WITH THE UPGRADE PATH.
A key absent from options: has no default for the Supervisor to deep-merge into
an existing box's stored blob. That is exactly the delivery_hold_s mechanism
this release rests on: a REQUIRED key a 0.7.0 blob has never stored resolves to
its options: default of 185 instead of becoming exit 78. Hiding a key is
precisely the edit that removes that. It would also promote the 27 unpinned
in-code literal fallbacks the new check ledgers into the ACTUAL source of the
defaults on a live install - the CFG-03 defect the same card's AC 4 exists to
police, made load-bearing by its AC 3.
⚠️ THE COUNT WAS PRINTED AND NEVER GRADED, WHICH IS HOW THIS PASSED REVIEW.
supervisor_shape_probe.py section F has printed "the Configuration tab renders
71 fields" since the day it was written and exited 0 through UX-01's whole
review while displaying the number that contradicted the criterion it was
evidence for. Three assertions replace the bare print, every number computed
from the manifest and from Supervisor's own required flag rather than typed,
including an ISOLATED COUNTERFACTUAL that empties options: and nothing else.
MEASURED RED: delete one key (price_feed_url) from options: and two of them
fail - Hideable: ['price_feed_url'], 70 of 71 fields render - exit 1.
--- AC 4: the AST check, built --------------------------------------------
AC 4's first clause is VACUOUS over an empty set (see above) and its second -
"the table changed goes red" - already works six times over. The third, the one
with the content, was never written: the only literal scan in the repository is
test_validate.py's regex over eight maintenance_* keys, which is a grep and
predates the card.
test_defaults_pin.py walks ast.Call nodes over app/*.py. It:
DISCOVERS the six *_OPTION_DEFAULTS mirrors from the tree rather than
naming them. Every existing pin names its own table by hand, so a SEVENTH
table added tomorrow is pinned by nobody and nothing says so;
grades all 30 mirrored keys against config.yaml, and requires that no
module carries a literal fallback for any of them;
FREEZES the CFG-03 backlog at exactly today's 27 (module, key) pairs, by
pair and not by count - a count goes green again the moment one site is
deleted and another added. Line numbers are deliberately out of the key: a
ledger that reddens on an unrelated reformat is one people re-bless without
reading. It can only shrink;
grades every remaining literal AGAINST config.yaml, which is the half that
catches CFG-04's actual failure - two copies of one number, one moves.
⭐ ONE PREVIOUSLY UNREPORTED DIVERGENCE, and it is what that last check is for:
main.py:290 answers a missing target_grid_w with 0 where config.yaml ships -10.
It cannot reach an operator - the deep-merge guarantees the key is present - and
the only paths that hand over a partial dict are sim/inject.py and this repo's
fixtures. Recorded as the single permitted divergence with its reason rather
than corrected; changing it alters the control loop's target and belongs to a
card that grades the loop. A second divergence goes red.
⭐ THE RED DIRECTION WAS RUN, TWICE OVER. Part 5 feeds the scanner a synthetic
module containing a deliberate violation of each rule and REQUIRES it to be
caught, in-process, through the same function the graded parts call - so a
scanner that goes blind stops working there too - and the site total is graded
against a floor. And a literal was planted in the REAL tree:
main.py:290 float(opts.get("maintenance_charge_w", 2000))
-> FAIL no literal fallback survives for any of the 30 keys a mirror
already answers for exit 1
main.py:290 opts.get("p1_host", "x"), opts.get("slew_w", 999)
-> FAIL no NEW module/key pair has started carrying a literal fallback
[('main.py', 'p1_host')]
FAIL every remaining literal still AGREES with config.yaml
main.py slew_w: code 999 vs config.yaml 0 exit 1
Both plants removed.
--- AC 5: CFG-04 is closed, and not by this card's predecessor -------------
fef6ef2 touched config.yaml, translations/, test_translations.py and the probe -
no defaults code at all - and on 0.7.0 the max_w 2000 -> 2500 triple mutation
survives 21 files, 0 RED. CONFIRMED CLOSED on release/1.0 by re-running the same
triple mutation on a throwaway copy of 6e7b233: 26 files, RED at
test_selfconfig.py, exit 1 -
1 FAILED: ⚠️ CLEARING max_w ON THE LIVE SHAPE IS REFUSED - rule 10 catches
the rail collapsing to the 2000 default under an explicit 400 W slew
git log -L 310,325:test_selfconfig.py attributes that check to bcee235,
"UX-02: pin both halves of the max_w fix, because neither was graded". The AC is
met BY UX-02 and ships with the pending 0.9.0 deploy. Nothing was re-done.
--- the 8 per-entry translations: they stay --------------------------------
Measured negative and not a defect in them: UiOptions gives inverters type
schema WITH multiple, the frontend sends that case to ha-selector-object, which
labels fields fields[name].label || name, and the converter sets neither label
nor translation_key. The Supervisor tab CANNOT be made to render them. They stay
because UX-02's ingress panel is the surface that can, because they cost
nothing, and because deleting them removes the only thing that would catch a
per-entry key added without a translation - which is how SAFETY-32's REQUIRED
bms_soc_floor would have joined the roster unlabelled. A written decision, not a
change; translations/ is untouched.
tools/supervisor_selfapi_probe.py stays red on its hardcoded len(_sub) == 7.
That is TOOL-04, already filed, and it is not re-filed or fixed here.
positive control test_defaults_pin.py's mirror floor moved 6 -> 7 inside the
harness container: "### RED: test_defaults_pin.py", exit 1
suite 27 files, ALL GREEN, exit 0 (goodwe-addon-checks:1, the
checkout mounted read-only)
shape probe all cases passed, exit 0, on
ghcr.io/home-assistant/amd64-hassio-supervisor:2026.08.0
Nothing here touched hardware, the live Home Assistant at 192.168.2.158, or
sim/. Both probe runs were throwaway containers over a read-only mount.
DECISIONS/UX-04-the-configuration-tab-and-the-defaults-pin.md carries all four
decisions, the frontend transcription, and the two counterfactual transcripts.
UX-01's three unmet acceptance criteria, closed out. It shipped in 0.7.0 and is
LIVE, so all three describe the running install. No behaviour, no schema and no
default moves here: config.yaml, translations/ and every module under app/ are
byte-identical to 6e7b233.
⚠️ AC 3 - "the visible count drops to ~15" - IS WITHDRAWN AS UNSATISFIABLE, and
the arithmetic is the reason rather than the effort. options: and schema: both
declare all 71 keys, so the set HA's toggle can hide is EMPTY - it hides a key
only when it is ?-optional AND absent from options:, and nothing is absent. But
the deeper fact is that no manifest could have met it: Supervisor's own
UiOptions marks 59 of the 71 REQUIRED, and the frontend's own filter -
`schema.filter(e => e.name in opts || e.required)`, transcribed out of the
bundle in the release image - shows a required field unconditionally. So 59 is
the floor with options: EMPTIED ALTOGETHER. The target was ~4x below anything
reachable.
⚠️ AND THE 12 HIDES THAT ARE EXPRESSIBLE WOULD BE BOUGHT WITH THE UPGRADE PATH.
A key absent from options: has no default for the Supervisor to deep-merge into
an existing box's stored blob. That is exactly the delivery_hold_s mechanism
this release rests on: a REQUIRED key a 0.7.0 blob has never stored resolves to
its options: default of 185 instead of becoming exit 78. Hiding a key is
precisely the edit that removes that. It would also promote the 27 unpinned
in-code literal fallbacks the new check ledgers into the ACTUAL source of the
defaults on a live install - the CFG-03 defect the same card's AC 4 exists to
police, made load-bearing by its AC 3.
⚠️ THE COUNT WAS PRINTED AND NEVER GRADED, WHICH IS HOW THIS PASSED REVIEW.
supervisor_shape_probe.py section F has printed "the Configuration tab renders
71 fields" since the day it was written and exited 0 through UX-01's whole
review while displaying the number that contradicted the criterion it was
evidence for. Three assertions replace the bare print, every number computed
from the manifest and from Supervisor's own `required` flag rather than typed,
including an ISOLATED COUNTERFACTUAL that empties options: and nothing else.
MEASURED RED: delete one key (price_feed_url) from options: and two of them
fail - `Hideable: ['price_feed_url']`, `70 of 71 fields render` - exit 1.
--- AC 4: the AST check, built --------------------------------------------
AC 4's first clause is VACUOUS over an empty set (see above) and its second -
"the table changed goes red" - already works six times over. The third, the one
with the content, was never written: the only literal scan in the repository is
test_validate.py's regex over eight maintenance_* keys, which is a grep and
predates the card.
test_defaults_pin.py walks ast.Call nodes over app/*.py. It:
- DISCOVERS the six *_OPTION_DEFAULTS mirrors from the tree rather than
naming them. Every existing pin names its own table by hand, so a SEVENTH
table added tomorrow is pinned by nobody and nothing says so;
- grades all 30 mirrored keys against config.yaml, and requires that no
module carries a literal fallback for any of them;
- FREEZES the CFG-03 backlog at exactly today's 27 (module, key) pairs, by
pair and not by count - a count goes green again the moment one site is
deleted and another added. Line numbers are deliberately out of the key: a
ledger that reddens on an unrelated reformat is one people re-bless without
reading. It can only shrink;
- grades every remaining literal AGAINST config.yaml, which is the half that
catches CFG-04's actual failure - two copies of one number, one moves.
⭐ ONE PREVIOUSLY UNREPORTED DIVERGENCE, and it is what that last check is for:
main.py:290 answers a missing target_grid_w with 0 where config.yaml ships -10.
It cannot reach an operator - the deep-merge guarantees the key is present - and
the only paths that hand over a partial dict are sim/inject.py and this repo's
fixtures. Recorded as the single permitted divergence with its reason rather
than corrected; changing it alters the control loop's target and belongs to a
card that grades the loop. A second divergence goes red.
⭐ THE RED DIRECTION WAS RUN, TWICE OVER. Part 5 feeds the scanner a synthetic
module containing a deliberate violation of each rule and REQUIRES it to be
caught, in-process, through the same function the graded parts call - so a
scanner that goes blind stops working there too - and the site total is graded
against a floor. And a literal was planted in the REAL tree:
main.py:290 float(opts.get("maintenance_charge_w", 2000))
-> FAIL no literal fallback survives for any of the 30 keys a mirror
already answers for exit 1
main.py:290 opts.get("p1_host", "x"), opts.get("slew_w", 999)
-> FAIL no NEW module/key pair has started carrying a literal fallback
[('main.py', 'p1_host')]
FAIL every remaining literal still AGREES with config.yaml
main.py slew_w: code 999 vs config.yaml 0 exit 1
Both plants removed.
--- AC 5: CFG-04 is closed, and not by this card's predecessor -------------
fef6ef2 touched config.yaml, translations/, test_translations.py and the probe -
no defaults code at all - and on 0.7.0 the max_w 2000 -> 2500 triple mutation
survives 21 files, 0 RED. CONFIRMED CLOSED on release/1.0 by re-running the same
triple mutation on a throwaway copy of 6e7b233: 26 files, RED at
test_selfconfig.py, exit 1 -
1 FAILED: ⚠️ CLEARING max_w ON THE LIVE SHAPE IS REFUSED - rule 10 catches
the rail collapsing to the 2000 default under an explicit 400 W slew
git log -L 310,325:test_selfconfig.py attributes that check to bcee235,
"UX-02: pin both halves of the max_w fix, because neither was graded". The AC is
met BY UX-02 and ships with the pending 0.9.0 deploy. Nothing was re-done.
--- the 8 per-entry translations: they stay --------------------------------
Measured negative and not a defect in them: UiOptions gives inverters type
schema WITH multiple, the frontend sends that case to ha-selector-object, which
labels fields `fields[name].label || name`, and the converter sets neither label
nor translation_key. The Supervisor tab CANNOT be made to render them. They stay
because UX-02's ingress panel is the surface that can, because they cost
nothing, and because deleting them removes the only thing that would catch a
per-entry key added without a translation - which is how SAFETY-32's REQUIRED
bms_soc_floor would have joined the roster unlabelled. A written decision, not a
change; translations/ is untouched.
tools/supervisor_selfapi_probe.py stays red on its hardcoded len(_sub) == 7.
That is TOOL-04, already filed, and it is not re-filed or fixed here.
--- evidence ---------------------------------------------------------------
positive control test_defaults_pin.py's mirror floor moved 6 -> 7 inside the
harness container: "### RED: test_defaults_pin.py", exit 1
suite 27 files, ALL GREEN, exit 0 (goodwe-addon-checks:1, the
checkout mounted read-only)
shape probe all cases passed, exit 0, on
ghcr.io/home-assistant/amd64-hassio-supervisor:2026.08.0
Nothing here touched hardware, the live Home Assistant at 192.168.2.158, or
sim/. Both probe runs were throwaway containers over a read-only mount.
DECISIONS/UX-04-the-configuration-tab-and-the-defaults-pin.md carries all four
decisions, the frontend transcription, and the two counterfactual transcripts.
---
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
UX-01's three unmet acceptance criteria, closed out. It shipped in 0.7.0 and is
LIVE, so all three describe the running install. No behaviour, no schema and no
default moves here: config.yaml, translations/ and every module under app/ are
byte-identical to 6e7b233.
⚠️ AC 3 - "the visible count drops to ~15" - IS WITHDRAWN AS UNSATISFIABLE, and
the arithmetic is the reason rather than the effort. options: and schema: both
declare all 71 keys, so the set HA's toggle can hide is EMPTY - it hides a key
only when it is ?-optional AND absent from options:, and nothing is absent. But
the deeper fact is that no manifest could have met it: Supervisor's own
UiOptions marks 59 of the 71 REQUIRED, and the frontend's own filter -
`schema.filter(e => e.name in opts || e.required)`, transcribed out of the
bundle in the release image - shows a required field unconditionally. So 59 is
the floor with options: EMPTIED ALTOGETHER. The target was ~4x below anything
reachable.
⚠️ AND THE 12 HIDES THAT ARE EXPRESSIBLE WOULD BE BOUGHT WITH THE UPGRADE PATH.
A key absent from options: has no default for the Supervisor to deep-merge into
an existing box's stored blob. That is exactly the delivery_hold_s mechanism
this release rests on: a REQUIRED key a 0.7.0 blob has never stored resolves to
its options: default of 185 instead of becoming exit 78. Hiding a key is
precisely the edit that removes that. It would also promote the 27 unpinned
in-code literal fallbacks the new check ledgers into the ACTUAL source of the
defaults on a live install - the CFG-03 defect the same card's AC 4 exists to
police, made load-bearing by its AC 3.
⚠️ THE COUNT WAS PRINTED AND NEVER GRADED, WHICH IS HOW THIS PASSED REVIEW.
supervisor_shape_probe.py section F has printed "the Configuration tab renders
71 fields" since the day it was written and exited 0 through UX-01's whole
review while displaying the number that contradicted the criterion it was
evidence for. Three assertions replace the bare print, every number computed
from the manifest and from Supervisor's own `required` flag rather than typed,
including an ISOLATED COUNTERFACTUAL that empties options: and nothing else.
MEASURED RED: delete one key (price_feed_url) from options: and two of them
fail - `Hideable: ['price_feed_url']`, `70 of 71 fields render` - exit 1.
--- AC 4: the AST check, built --------------------------------------------
AC 4's first clause is VACUOUS over an empty set (see above) and its second -
"the table changed goes red" - already works six times over. The third, the one
with the content, was never written: the only literal scan in the repository is
test_validate.py's regex over eight maintenance_* keys, which is a grep and
predates the card.
test_defaults_pin.py walks ast.Call nodes over app/*.py. It:
- DISCOVERS the six *_OPTION_DEFAULTS mirrors from the tree rather than
naming them. Every existing pin names its own table by hand, so a SEVENTH
table added tomorrow is pinned by nobody and nothing says so;
- grades all 30 mirrored keys against config.yaml, and requires that no
module carries a literal fallback for any of them;
- FREEZES the CFG-03 backlog at exactly today's 27 (module, key) pairs, by
pair and not by count - a count goes green again the moment one site is
deleted and another added. Line numbers are deliberately out of the key: a
ledger that reddens on an unrelated reformat is one people re-bless without
reading. It can only shrink;
- grades every remaining literal AGAINST config.yaml, which is the half that
catches CFG-04's actual failure - two copies of one number, one moves.
⭐ ONE PREVIOUSLY UNREPORTED DIVERGENCE, and it is what that last check is for:
main.py:290 answers a missing target_grid_w with 0 where config.yaml ships -10.
It cannot reach an operator - the deep-merge guarantees the key is present - and
the only paths that hand over a partial dict are sim/inject.py and this repo's
fixtures. Recorded as the single permitted divergence with its reason rather
than corrected; changing it alters the control loop's target and belongs to a
card that grades the loop. A second divergence goes red.
⭐ THE RED DIRECTION WAS RUN, TWICE OVER. Part 5 feeds the scanner a synthetic
module containing a deliberate violation of each rule and REQUIRES it to be
caught, in-process, through the same function the graded parts call - so a
scanner that goes blind stops working there too - and the site total is graded
against a floor. And a literal was planted in the REAL tree:
main.py:290 float(opts.get("maintenance_charge_w", 2000))
-> FAIL no literal fallback survives for any of the 30 keys a mirror
already answers for exit 1
main.py:290 opts.get("p1_host", "x"), opts.get("slew_w", 999)
-> FAIL no NEW module/key pair has started carrying a literal fallback
[('main.py', 'p1_host')]
FAIL every remaining literal still AGREES with config.yaml
main.py slew_w: code 999 vs config.yaml 0 exit 1
Both plants removed.
--- AC 5: CFG-04 is closed, and not by this card's predecessor -------------
fef6ef2 touched config.yaml, translations/, test_translations.py and the probe -
no defaults code at all - and on 0.7.0 the max_w 2000 -> 2500 triple mutation
survives 21 files, 0 RED. CONFIRMED CLOSED on release/1.0 by re-running the same
triple mutation on a throwaway copy of 6e7b233: 26 files, RED at
test_selfconfig.py, exit 1 -
1 FAILED: ⚠️ CLEARING max_w ON THE LIVE SHAPE IS REFUSED - rule 10 catches
the rail collapsing to the 2000 default under an explicit 400 W slew
git log -L 310,325:test_selfconfig.py attributes that check to bcee235,
"UX-02: pin both halves of the max_w fix, because neither was graded". The AC is
met BY UX-02 and ships with the pending 0.9.0 deploy. Nothing was re-done.
--- the 8 per-entry translations: they stay --------------------------------
Measured negative and not a defect in them: UiOptions gives inverters type
schema WITH multiple, the frontend sends that case to ha-selector-object, which
labels fields `fields[name].label || name`, and the converter sets neither label
nor translation_key. The Supervisor tab CANNOT be made to render them. They stay
because UX-02's ingress panel is the surface that can, because they cost
nothing, and because deleting them removes the only thing that would catch a
per-entry key added without a translation - which is how SAFETY-32's REQUIRED
bms_soc_floor would have joined the roster unlabelled. A written decision, not a
change; translations/ is untouched.
tools/supervisor_selfapi_probe.py stays red on its hardcoded len(_sub) == 7.
That is TOOL-04, already filed, and it is not re-filed or fixed here.
--- evidence ---------------------------------------------------------------
positive control test_defaults_pin.py's mirror floor moved 6 -> 7 inside the
harness container: "### RED: test_defaults_pin.py", exit 1
suite 27 files, ALL GREEN, exit 0 (goodwe-addon-checks:1, the
checkout mounted read-only)
shape probe all cases passed, exit 0, on
ghcr.io/home-assistant/amd64-hassio-supervisor:2026.08.0
Nothing here touched hardware, the live Home Assistant at 192.168.2.158, or
sim/. Both probe runs were throwaway containers over a read-only mount.
DECISIONS/UX-04-the-configuration-tab-and-the-defaults-pin.md carries all four
decisions, the frontend transcription, and the two counterfactual transcripts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
Review approved and found four things worth fixing. Three are cosmetic; the
fourth is the same defect this card is about, one level up.
⚠️ THE FLOOR WAS PRINTED AS 59 AND ASSERTED AS `> 15`. So a manifest change
dropping the required count to 20 would have passed while the sentence beside it
still said 59 - a number displayed but not graded, which is exactly what let
UX-01's AC 3 survive review in the first place, and exactly the shape
`test_translations.py` was already fixed for.
Now DERIVED and graded in two separate assertions, because they are two
different claims:
1. `_EXPECT_REQ = len(schema) - len(_OPTIONAL)`, where optional is Supervisor's
own rule - a `?` suffix, plus `inverters` because `UiOptions` marks a list
optional - asserted EQUAL to Supervisor's required count. The printed
number is computed, so it cannot go stale.
2. ⚠️ AND THE WITHDRAWAL'S PREMISE, SEPARATELY. The equality alone is not
enough and I checked why: both sides read the same `?` convention, so
marking a key optional moves BOTH and it stays green - measured, by making
`slew_w` optional and watching it pass. That grades Supervisor agreeing
with us, NOT the thing AC 3 was withdrawn for. The second assertion grades
the premise: the floor is still far above the ~15 AC 3 asked for, and if it
ever is not, the withdrawal needs re-arguing rather than re-asserting.
Driven, both directions, against the real 2026.08.0 image:
clean PASS "floor is DERIVED: 71 schema keys minus
12 optional ones is 59, and
Supervisor's own required flag agrees"
premise threshold 15 -> 40 FAIL "the floor of 59 is still far above
the ~40 AC 3 asked for"
Also: a dangling reference to `DECISIONS/UX-04-hiding-and-the-deepmerge.md`,
which was never the filename shipped, and `DOCS.md` still saying section F
"prints" what it now grades.
Not changed, and review agreed both are disclosed rather than defects: the
pair-keyed ledger cannot see a SECOND site for a pair it already holds (the
site count can grow while the pair set cannot), and the probe's assertions run
by hand rather than in CI because they need the Supervisor image - the same
status as before this card, not a regression.
Suite: 27 files, all pass. Probe: all cases passed on
ghcr.io/home-assistant/amd64-hassio-supervisor:2026.08.0, --network none.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
admin
merged commit bb469163a6 into release/1.02026-09-07 19:03:36 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
UX-01's three unmet acceptance criteria, closed out. It shipped in 0.7.0 and is
LIVE, so all three describe the running install. No behaviour, no schema and no
default moves here: config.yaml, translations/ and every module under app/ are
byte-identical to
6e7b233.⚠️ AC 3 - "the visible count drops to ~15" - IS WITHDRAWN AS UNSATISFIABLE, and
the arithmetic is the reason rather than the effort. options: and schema: both
declare all 71 keys, so the set HA's toggle can hide is EMPTY - it hides a key
only when it is ?-optional AND absent from options:, and nothing is absent. But
the deeper fact is that no manifest could have met it: Supervisor's own
UiOptions marks 59 of the 71 REQUIRED, and the frontend's own filter -
schema.filter(e => e.name in opts || e.required), transcribed out of thebundle in the release image - shows a required field unconditionally. So 59 is
the floor with options: EMPTIED ALTOGETHER. The target was ~4x below anything
reachable.
⚠️ AND THE 12 HIDES THAT ARE EXPRESSIBLE WOULD BE BOUGHT WITH THE UPGRADE PATH.
A key absent from options: has no default for the Supervisor to deep-merge into
an existing box's stored blob. That is exactly the delivery_hold_s mechanism
this release rests on: a REQUIRED key a 0.7.0 blob has never stored resolves to
its options: default of 185 instead of becoming exit 78. Hiding a key is
precisely the edit that removes that. It would also promote the 27 unpinned
in-code literal fallbacks the new check ledgers into the ACTUAL source of the
defaults on a live install - the CFG-03 defect the same card's AC 4 exists to
police, made load-bearing by its AC 3.
⚠️ THE COUNT WAS PRINTED AND NEVER GRADED, WHICH IS HOW THIS PASSED REVIEW.
supervisor_shape_probe.py section F has printed "the Configuration tab renders
71 fields" since the day it was written and exited 0 through UX-01's whole
review while displaying the number that contradicted the criterion it was
evidence for. Three assertions replace the bare print, every number computed
from the manifest and from Supervisor's own
requiredflag rather than typed,including an ISOLATED COUNTERFACTUAL that empties options: and nothing else.
MEASURED RED: delete one key (price_feed_url) from options: and two of them
fail -
Hideable: ['price_feed_url'],70 of 71 fields render- exit 1.--- AC 4: the AST check, built --------------------------------------------
AC 4's first clause is VACUOUS over an empty set (see above) and its second -
"the table changed goes red" - already works six times over. The third, the one
with the content, was never written: the only literal scan in the repository is
test_validate.py's regex over eight maintenance_* keys, which is a grep and
predates the card.
test_defaults_pin.py walks ast.Call nodes over app/*.py. It:
naming them. Every existing pin names its own table by hand, so a SEVENTH
table added tomorrow is pinned by nobody and nothing says so;
module carries a literal fallback for any of them;
pair and not by count - a count goes green again the moment one site is
deleted and another added. Line numbers are deliberately out of the key: a
ledger that reddens on an unrelated reformat is one people re-bless without
reading. It can only shrink;
catches CFG-04's actual failure - two copies of one number, one moves.
⭐ ONE PREVIOUSLY UNREPORTED DIVERGENCE, and it is what that last check is for:
main.py:290 answers a missing target_grid_w with 0 where config.yaml ships -10.
It cannot reach an operator - the deep-merge guarantees the key is present - and
the only paths that hand over a partial dict are sim/inject.py and this repo's
fixtures. Recorded as the single permitted divergence with its reason rather
than corrected; changing it alters the control loop's target and belongs to a
card that grades the loop. A second divergence goes red.
⭐ THE RED DIRECTION WAS RUN, TWICE OVER. Part 5 feeds the scanner a synthetic
module containing a deliberate violation of each rule and REQUIRES it to be
caught, in-process, through the same function the graded parts call - so a
scanner that goes blind stops working there too - and the site total is graded
against a floor. And a literal was planted in the REAL tree:
Both plants removed.
--- AC 5: CFG-04 is closed, and not by this card's predecessor -------------
fef6ef2touched config.yaml, translations/, test_translations.py and the probe -no defaults code at all - and on 0.7.0 the max_w 2000 -> 2500 triple mutation
survives 21 files, 0 RED. CONFIRMED CLOSED on release/1.0 by re-running the same
triple mutation on a throwaway copy of
6e7b233: 26 files, RED attest_selfconfig.py, exit 1 -
1 FAILED: ⚠️ CLEARING max_w ON THE LIVE SHAPE IS REFUSED - rule 10 catches
the rail collapsing to the 2000 default under an explicit 400 W slew
git log -L 310,325:test_selfconfig.py attributes that check to
bcee235,"UX-02: pin both halves of the max_w fix, because neither was graded". The AC is
met BY UX-02 and ships with the pending 0.9.0 deploy. Nothing was re-done.
--- the 8 per-entry translations: they stay --------------------------------
Measured negative and not a defect in them: UiOptions gives inverters type
schema WITH multiple, the frontend sends that case to ha-selector-object, which
labels fields
fields[name].label || name, and the converter sets neither labelnor translation_key. The Supervisor tab CANNOT be made to render them. They stay
because UX-02's ingress panel is the surface that can, because they cost
nothing, and because deleting them removes the only thing that would catch a
per-entry key added without a translation - which is how SAFETY-32's REQUIRED
bms_soc_floor would have joined the roster unlabelled. A written decision, not a
change; translations/ is untouched.
tools/supervisor_selfapi_probe.py stays red on its hardcoded len(_sub) == 7.
That is TOOL-04, already filed, and it is not re-filed or fixed here.
--- evidence ---------------------------------------------------------------
positive control test_defaults_pin.py's mirror floor moved 6 -> 7 inside the
harness container: "### RED: test_defaults_pin.py", exit 1
suite 27 files, ALL GREEN, exit 0 (goodwe-addon-checks:1, the
checkout mounted read-only)
shape probe all cases passed, exit 0, on
ghcr.io/home-assistant/amd64-hassio-supervisor:2026.08.0
Nothing here touched hardware, the live Home Assistant at 192.168.2.158, or
sim/. Both probe runs were throwaway containers over a read-only mount.
DECISIONS/UX-04-the-configuration-tab-and-the-defaults-pin.md carries all four
decisions, the frontend transcription, and the two counterfactual transcripts.
🤖 Generated with Claude Code
https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU