MAINT-20 part 2 r2: app/main.py comments, lines 501-1000 (_refusal_probe .. _refusal_update)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
glenn schrooyen
2026-11-08 18:06:00 +01:00
co-authored by Claude
parent a5fec377ad
commit f114a19dcc
+16 -27
View File
@@ -499,9 +499,7 @@ class Controller:
# SAFETY-02: one watchdog per actuator, each restored from ITS OWN
# namespaced store keys (a trip on B latches B, A keeps commanding).
# Restored, because a latch held only in memory is cleared by the
# restart an operator staring at a FAILSAFE banner tries first. Graded
# on every start: a latch that failed to move namespace, or was taken
# by an older add-on during a rollback, is a disarmed guard, so a
# restart an operator tries first. Graded on every start: a
# disagreement with the flat value re-arms by hand and shouts (the
# fail-safe direction is ON). DECISIONS/MAIN-orchestration.md.
self._grade_store_migration(store)
@@ -639,27 +637,20 @@ class Controller:
# FCST-04: the 8.3 carry-forward store (published prices of the last
# few Brussels days) so unprinted hours can be filled from yesterday
# and FLAGGED. An instrument's backing file, not a strategy. Carrying
# widens what the optimiser SEES, never what it may act on (carried
# hours are provisional). The windowing DOES move energy: hours
# ALREADY ELAPSED leave the spread, so a past cheap night no longer
# sets the day's cheapest hour; this caveat must always travel with the
# guarantee (`resolve_tariff()`, DOCS.md, CHANGELOG). A broken or
# absent store answers `no_data`; `PriceHistory._load()` drops
# unreadable records (the constructor and `.words` are unguarded).
# and FLAGGED. An instrument, not a strategy: carrying widens what the
# optimiser SEES, never what it may act on. ⚠️ The windowing DOES move
# energy: hours ALREADY ELAPSED leave the spread, so a past cheap night
# no longer sets the day's cheapest hour; that caveat must always
# travel with the guarantee (`resolve_tariff()`, DOCS.md, CHANGELOG).
# DECISIONS/FCST-04-the-day-that-is-carried.md.
self.price_history = PriceHistory(price_history_path)
_LOG.info("Price history: %s", self.price_history.words)
# SAFETY-07: the guard is stateless (peakguard.py); the only state here
# is what an EDGE needs, so the log and notification fire on
# transitions. The ceiling has no config of its own (`peak_cap_w`).
# SAFETY-24's floor does: exactly two keys, defaults from
# `peakguard.PEAK_OPTION_DEFAULTS` (no literals here, as a literal
# repeated the 2000-vs-2500 `maintenance_charge_w` divergence of
# validate.py rule 6; closed by `maintenance.MAINT_OPTION_DEFAULTS`).
# The rule: no numeric fallback in main.py that config.yaml has not
# shipped. DECISIONS/SAFETY-07-peak-guard.md.
# is what an EDGE needs. The ceiling has no config of its own
# (`peak_cap_w`); SAFETY-24's floor has exactly two keys, defaults from
# `peakguard.PEAK_OPTION_DEFAULTS`: no numeric fallback in main.py that
# config.yaml has not shipped. DECISIONS/SAFETY-07-peak-guard.md.
self.peak_guard = None
# SAFETY-79: the guard aims this far under `peak_cap_w`; the bill and
# every report keep the real cap (`self.peak.cap_w`).
@@ -788,6 +779,7 @@ class Controller:
self._settle = {}
# Set by amain() once the transport is built, so the status page can
# show what only the transport knows (homewizard_local's unchanged_s).
# None when P1 is off or the transport has no such counter.
# (DECISIONS/MAIN-orchestration-archive.md)
self.p1_source = None
@@ -863,14 +855,11 @@ class Controller:
# on it and DEV-02 reads `shares` off it.
self.allocation = None
# SAFETY-28: the tick the record above belongs to, incremented ONCE PER
# RESOLUTION, not per loop iteration. A new resolution makes the
# previous record stale at once (no replay into a later tick). A cycle
# with NO resolution does not bump it: the "inputs missing, not yet
# stale" hold returns before the allocator so one missed poll does not
# zero the inverter, and the heartbeat re-affirms `self.target` while
# it does; bumping would make that heartbeat unauthorised and drop the
# site to 0 W. The record is SUPERSEDED, never expired.
# DECISIONS/MAIN-orchestration.md.
# RESOLUTION, not per loop iteration. A cycle with NO resolution (the
# "inputs missing, not yet stale" hold) does not bump it: the heartbeat
# re-affirms `self.target` meanwhile, and bumping would make that
# heartbeat unauthorised and drop the site to 0 W. The record is
# SUPERSEDED, never expired. DECISIONS/MAIN-orchestration.md.
self.alloc_tick = 0
# What the last cycle's allocator dropped, so the log line is
# edge-triggered. Starts empty.