Commit Graph
5 Commits
Author SHA1 Message Date
adminandClaude Opus 5.5 6a95447248 UX-08 2d: plain log lines, the rest of the status page, and DOCS
infra-pin / fresh (pull_request) Successful in 1s
checks / unit (pull_request) Successful in 8m55s
Every WARNING and ERROR line is one plain line of at most 25 words, with
no card ids, capitals or semicolons; the reasoning of the ten longest is
on a DEBUG line. The ux08 allowlist is empty and its cap is 0 (43 -> 0).

The status page: the month-peak and peak-guard rows, the stale-reading
row, the top table (Why, Tariff, Arbitrage, Shadow mode, labels), the
Stopped banner and the line under the buttons, which now shows the plain
hint of each event instead of the raw action log. The Supervisor question
at start logs one line with a real reason.

Refusals are counted as shown (labels expanded, long cases driven), seven
were shortened, the card and parse errors lost their internals, a blank
value reads "empty", and the panel no longer files inverters.max_w under
the flat max_w. Option names lose "(JSON)", the NL texts are checked too.

DOCS gets a glossary (control board, isolator, Developer Tools) and card
ids move from headings to a "For installers" line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p
2026-09-27 08:01:16 +02:00
glenn schrooyenandClaude Opus 5.5 2c516a032c UX-08 2c: short refusals, error texts and option texts
checks / unit (pull_request) Successful in 8m50s
infra-pin / fresh (pull_request) Successful in 2s
Validator refusals name the setting by its label and key, say what is
wrong and what to change, and keep the rejected value and limit. Option
names and descriptions are short in en and nl; citations moved to DOCS
"For installers" and the test_translations citation check now pins the
instruction on the tab and the record in DOCS. No rule, key, schema,
default or exit code changed. Allowlist 167 -> 43 (log lines only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p
2026-09-27 05:55:45 +02:00
adminandClaude Opus 5.5 00e3ee9de8 UX-09: Details link for current HA, "would" in shadow mode, plain terms
infra-pin / fresh (pull_request) Successful in 2s
checks / unit (pull_request) Successful in 8m50s
- Details link: /config/app/<slug>/documentation on HA 2026.2+ (the old
  /hassio/addon path is a 404 from 2026.5), /hassio/addon on 2026.1 and
  older. Slug from GET /addons/self/info, version from GET /info, hostname
  as fallback.
- Shadow mode: the over-rating alarm and row say what would have been sent,
  the peak alarm no longer says the battery is giving all it can.
- "controller" -> "the add-on" / "control". Settings named by their labels;
  four labels changed to match (Peak reserve, Battery level entity, Solar
  production ..., Reversed-sign solar entities).
- Shadow banner and inverter link notes moved to messages.py and checked.
- DOCS Notifications and Status page rewritten for home owners, with a
  "For installers" part each. Allowlist 168 -> 167.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p
2026-09-27 04:10:39 +02:00
glenn schrooyenandClaude Opus 5 e3ccd9f6ea SAFETY-47: what a shadow run can show, and the watts on its status row
checks / unit (pull_request) Successful in 7m48s
Not a leak and not a write-path defect. Shadow mode's safety is intact -
the substitution, the re-seed, the authorisation gate and the clamp are
untouched. What was wrong is what three merged documents SAY a shadow run
can show you, and a number on the status row that can be 6.61x what a live
controller would command.

SHADOW MODE CANNOT SURFACE A CLOSED-LOOP DEFECT, AND THE DOCUMENTS NOW SAY
SO. The battery is held at 0 W and the carried command is re-seeded to it,
so every measured input - the meter, the billed-quarter accumulator behind
it, the battery power, the SoC - describes a plant that did not respond. A
closed-loop defect is one whose stimulus IS that response. This is the same
property that makes shadow mode safe, so it is not a gap to close later:
the closed-loop answer comes from the closed-loop rig or from a live run.
SAFETY-10 §5 already conceded this for the grid-following loop. It was
never the loop leg's limitation; it is the general case.

THE "STRATEGIES ARE UNAFFECTED" EXEMPTION WAS TRUE UNTIL SAFETY-45 MERGED,
ONE COMMIT EARLIER. That card sizes the buy leg as min(asked, headroom)
with headroom = (900/remaining) x (ceiling - projected), and `projected`
reads the open quarter's accumulator - the meter - which is a function of
what the battery did earlier in that quarter. Measured on TEST-23 stage 3
at peak_cap_w 1500 / house 1200 / max_w 2000, one hour at 5 s:

  SAFETY-45's limit cycle  NOT visible. 28 onsets live vs 1; grid
                           -455.5..+3200.0 W vs flat at the 1200 W house;
                           guard engaged 410/720 cycles vs 0/720.
  SAFETY-46's SoC churn    NOT visible. The SoC moves 0.000000000 pp.
  TARIFF-14's wrong hour   VISIBLE, and it is the only one: which hour, at
                           what price, clearing what spread - identical on
                           720 of 720 charging cycles at a binding cap.

The exemption narrows to exactly what survives: WHICH hour a strategy picks
and WHY are unaffected; HOW HARD it trades is not. The peak guard's ceiling
and reasons are worth reading from a shadow run; its ENGAGEMENTS are not.

THE NUMBER IS CAVEATED, NOT RE-COMPUTED, AND THAT IS THE DECISION. The
arithmetic is not wrong - 900/remaining is correct for a meter genuinely
under the ceiling, and in shadow the meter genuinely is. It is the right
answer to a question nobody asked. Both surfaces now introduce the watts as
what would have been commanded ON THIS CYCLE, in every state, and carry
shadow.SIZED_FROM_METER whenever a measured billed quarter sized them.
Rejected, and recorded in the decision doc: re-computing it (the recorded
decision would no longer be the decision the shipped path reached, while the
arbiter, clamp, SAFETY-28 gate, log and MQTT all ran on the real one - a
second controller everywhere except the wire); recording nothing (blanks the
row exactly when arbitrage is trading, and the log's edge key is the watts);
and recording the unsized ask beside it (a second number, no warning, and at
the shipped 3500/2000 both figures read 2000 W).

"UPPER BOUND" AND NOT "SMALLER", AND THE DIFFERENCE WAS MEASURED. Charging
only adds offtake, so the shadow size is >= the live size with EQUALITY at
the start of every quarter. Pairwise over 3600 charging cycles in five
configurations: 0 below, 730 exactly equal. The first draft said "smaller",
which would have been false on every one of those 730.

AND THE PREDICATE IS NOT KEYED ON WHETHER THE TRADE WAS CAPPED. arbitrage's
own sentence says "SIZED BY THE CAPACITY-TARIFF GUARD" only when the
headroom came out below the ask - in a shadow run, the START of the quarter,
where the two runs agree to the float. By the end, 900/remaining has
multiplied the headroom past the ask, the trade is not capped, the sentence
is silent, and the record is at its most wrong. A caveat keyed on it would
be quietest exactly where it is needed loudest.

SAFETY-10 §6's COMMITTED EVIDENCE COULD NEVER HAVE FAILED, FOR TWO
INDEPENDENT REASONS, AND CLOSING EITHER ALONE CLOSES NOTHING. Its fixture is
peak_cap_w 15000 against a 350 W house, so the guard never engages and the
sizing is inert - the harness's own comment says so. AND every identity row
reads the FIRST charging sample, where at elapsed=0 the projection IS the
held level and the two runs agree to the float even at a cap that does bind.
Measured: widening the fixture alone leaves that comparison passing. That
second reason is a new shape for MUTATION.md's habit-4 list - an INDEX, not
a fixture - and it is recorded there as the twelfth instance. §6 is re-taken
at a binding cap across the whole run: 716 of 720 charging cycles differ,
headroom 300.0..54000.0 W in shadow against 299.9..307.8 W live, recorded
-2000.0 W against a live -302.4 W.

THE GRADER IS test_arbitrage.py PART 3d, beside SAFETY-45's own closed-loop
regression and built on the same s45_loop() harness, so the runs differ in
one argument. Three lines stand in for _write_unit()'s substitution and
_shadow_settle()'s record and re-seed, marked as a transcription;
test_clamp.py part 8 remains the check on the shipped placement. The section
carries its own precondition into every check: a mutation that stopped
either run trading made it raise on its first min(), kill the file and
report ZERO reds - FCST-06's shape, driven rather than imagined.

AND THE FIX'S OWN FIRST DRAFT CARRIED THE SAME DEFECT. Deleting the caveat
from the action-log line left all 29 files green, because the sentence was
an f-string inside _shadow_settle() and the only suite that drives it uses a
meter_source:off fixture where no arbitrage decision exists. It is
shadow.log_line() now, for the reason words() is a function.

14 mutations, no survivors; M13 and M14 are +0 B and were confirmed on disk
by sha256 with every run purged. M14's first form was aimed at the wrong
direction and correctly survived - min->max makes the shadow record larger,
which is still an upper bound. All 29 check files green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
2026-09-15 01:26:57 +02:00
Claude 8fa4febfef SAFETY-10: the whole decision path runs and nothing reaches the inverter
checks / unit (pull_request) Successful in 7m58s
Every optimiser defect this project has found was invisible from outside
while it was happening, and each was paid for at the meter first: SAFETY-45's
28 charge onsets an hour against a binding peak_cap_w, TARIFF-14's wrong
quarter-hour for 45 minutes in every 60, SAFETY-46's 598 Wh spent to gain
310 Wh. There was no way to watch what the optimiser WOULD do without letting
it do it.

`shadow_mode` runs strategies, arbiter, clamps and guards, records the
decision with its reason, and commands nothing.

⚠️ IT WRITES. It writes 0 W, on the ordinary heartbeat, through the ordinary
single writer. The inverter holds its last command forever - it has no meter
timeout - so "commands nothing" implemented as "stops writing" would leave
whatever was standing when shadow mode came on standing for as long as it was
left on, under a page saying nothing is being commanded. That is the failure
this repository keeps finding. Refreshing 0 W is also what
firmware/goodwe-master.yaml's 30 s stale-input watchdog requires of us; going
quiet would keep that board tripped for the whole run, which is an alarm being
used as a mode.

⚠️ IT IS NOT A SECOND WRITE PATH. No claim, no transport, no arithmetic of its
own. It is a substitution inside Controller._write_unit() - the single choke
point every write already reaches - in the same form as the three above it,
and it is the LAST of them, so SAFETY-28's gate and SAFETY-03's clamp still
grade the number the decision path produced.

⚠️ THE DEFAULT IS `auto`, NOT `true`, AND THAT IS THE CARD'S REAL CONTENT. The
Supervisor composes options as {**config.yaml defaults, **stored user
options}, so a new key with a new default reaches EVERY EXISTING INSTALL on
the next update - including the one regulating a real battery. A boolean
default-on would have stopped it silently. There is no mechanism by which one
key can default differently for a new install and an existing one, so
"default-on for new" is a value the add-on RESOLVES: `auto` is shadow while
control is STOPPED and live once it is STARTED. A fresh install ships
`auto_start: false` and comes up observing; an install that is controlling is
started and does not change by one watt.

THE FAILSAFES. Sign watchdogs (SAFETY-02/30): INERT - they grade
Inverter.standing_w(), which shadow mode holds at 0 W. Fed the shadow decision
they would disagree with a meter that never saw it and LATCH A TRIP THAT
SURVIVES INTO LIVE OPERATION AND A RESTART. Clamp (SAFETY-03), write gate
(SAFETY-28), meter freeze (SAFETY-18), peak guard: OBSERVE. Circulation
(SAFETY-31) and delivery (SAFETY-42): observe and may legitimately latch,
because their evidence is measured power. The operator stop is the one
deliberate change: enforced at the wire rather than as a P_SAFETY claim, which
would otherwise make every recorded decision read "stopped".

⚠️ RECORDED LIMITATION: _shadow_settle() re-seeds the carried command to what
stands on the wire, which makes the exit safe by construction - and therefore
the loop leg's shadow decision is ONE SLEW STEP, not a trajectory. A shadow
run answers "what would it command now", not "what would it have done next".
The strategies are unaffected; all three defects above are visible from one.

Graded on the writes and on the rendered surface, never on the flag: 22
mutations, no survivors, including one that lets a write escape and one that
goes silent instead of commanding 0 W. Closed loop on TEST-23 stage 3's rig,
same hour twice: live drives the pack to -2000 W and SoC 50.000 -> 62.331 %;
shadow commands 0 W throughout, SoC stays 50.0 exactly, and the arbitrage
decision is identical in action, instant, signed power, reason and arbiter
winner.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
2026-09-14 17:25:43 +02:00