Repository navigation
Releases: joyfulhouse/brilliant-mqtt
Release list
v0.11.4 — recovery settlement, update connect retry, repair code policy
This is a Home Assistant integration release only. The bundled on-panel agent
stays at 0.11.3 with release ordinal 5, so panels already on 0.11.3 need no
update and their Update entities stay current. Installing it does not touch
panels or their retained rollback baselines.
Fixed
- Repair no longer upgrades a panel's code or bypasses a retained rollback
baseline. Automatic repair, the Repair button, thebrilliant_mqtt.repair
action (including against the fleet entity), post-OTA refresh, and watchdog
enablement used to replace installed bridge or watchdog code with a newer
loaded release without capturing a baseline. That included a panel rolled
back to its predecessor during a canary rehearsal. These paths now install
code only where none is installed, and otherwise keep the installed release's
own code and unit while still restoring configuration and services. Upgrades
go only through the canaried Update entity orbrilliant_mqtt.redeploy, so
loading a new integration release no longer rolls it out to panels that go
offline. While a panel holds a retained baseline in any state, these paths
install no code at all and reportbaseline_retained_finalize_firstwhere code
would be needed. Enabling a watchdog whose installed code differs from the
bundle reportsrelease_update_required. In the stopped pre-ordinal bridge
recovery, turn the watchdogs back on only after finalizing the baseline.
(#220) - Agent updates from the Update entity or
redeployretry transient SSH connect
failures before any panel change. Retried failures are host or network
unreachable, refused or reset connections, connect timeouts, temporary DNS
failures and connections lost during connect. A failure is retried only if SSH
authentication never began, so the password is never sent twice. An update
makes up to 5 attempts with capped, jittered exponential backoff. The 2-minute
retry budget starts once the first attempt holds the fleet SSH lock, and later
lock waits are cut off at that deadline. Progress stays at the pre-connect
stage while it waits, and each retry logs its attempt number and classified
reason. Authentication, host-key, key-exchange and admission failures fail
immediately without retry. When every attempt fails, the update reports the
attempt count. The fleet SSH lock is released during backoff, and the panel
stays reserved against a concurrent update or repair. An unload or reload
during the wait stops the update without connecting or raising a Repair issue.
Resuming after a connection loss during or after remote mutation remains open
in #219. - Panel connect-failure logs record the errno and its symbolic name instead of
the OS error text, which for a failed TCP connect includes the panel address. - An interrupted Add Brilliant panel operation that never created a panel entry
can be settled again (#214). If its recorded address or password no longer
works, start Add Brilliant panel with the panel's current address and root
password. After the pinned SSH identity matches, an explicit confirmation rolls
the panel back to its recorded prior state. The existing verified-restored
finalization then follows. Recovery uses the candidate recorded in the
journal, not the currently bundled release. Fresh credentials are never
written to retained metadata, and failed or unverified recovery is never
finalized or erased. - Recovery failures before restore intent now record a typed
last_error
(recovery_connection_failed,panel_authentication_failed,
panel_identity_mismatch,recovery_artifact_invalid) shown in diagnostics
and the repair, instead of an untypedrecovery_failedwith
last_error: null. - Other panels' updates and canary actions blocked by an unrecovered journal
reportprovisioning_recovery_required, with guidance, instead of
transaction_in_progress. Operations remain serialized.
Validation
- Root gate (Python 3.10): 2,819 passed, 1 skipped. Integration gate (Python 3.14): 2,131 passed, 6 skipped. Ruff, formatting and strict mypy clean; all eight hosted checks passed.
- Each fix had an independent review, and review findings were fixed before merge.
Known limitations
v0.11.3 — recovery hardening, watchdog lease revocation, MQTT durability and safe provisioning diagnostics
The on-panel agent and Home Assistant integration both move to 0.11.3. The
bundled bridge and watchdog payload carries release ordinal 5; the separately
versioned voice payload remains at 0.1.1. Qualify this release on a canary panel
before updating the remaining panels.
Fixed
- The bus watchdog remembers an independently observed live writer releasing its
phase-marker lease, so stale failed-attempt evidence stays revoked after that
writer exits. Failed continuing bus-attempt writes retain their lease. This
reduces the read-only-filesystem residual in #143; entirely unsampled recovery
and watchdog-restart ambiguity still require a separate delivery contract. - Watchdog reboot guards reject unsafe state files and preserve their rolling
reboot limits across clock corrections. Wi-Fi storage refusals log once per
outage while continuing to retry the storage operation. - Scene events and results wait for their own state to persist before MQTT
delivery. Retained-ledger loading has a deadline, and orphan cleanup preserves
temporary files belonging to other ledgers. - Panel credential repair accepts a changed address and password together while
preserving the stored SSH identity. Updates explain when an earlier rollback
baseline needs explicit finalization, and snapshot probe failures include
safe retry guidance. - Onboarding can explicitly finalize a verified restored baseline that has no
Home Assistant owner, using fresh credentials and the retained SSH identity.
Interrupted cleanup requires fresh authorization on retry and never persists
the submitted password in retained rollback metadata. - Onboarding bounds operation-lock and recovery waits. Payload uploads perform
local filesystem work outside Home Assistant's event loop and settle an
in-flight read before cancellation closes the remote stream. - Legacy watchdogs without a VERSION file can inherit a verified same-panel
pre-0.11.0 bridge version. Admission audit records distinguish admission from
verified deployment; stopped legacy recovery covers both watchdog units. - MQTT connection failures use capped exponential retry delays. Repeated bus
starts fenced by unresolved cleanup eventually exit for a systemd restart;
BUS_FENCED_START_LIMITconfigures the threshold (default three). - Both watchdogs record a persistent UTC shutdown marker on SIGTERM.
- Ignored transition fields no longer prevent safe partial command merging, and
discarded folded commands produce a warning without exposing payloads.
Changed
- BLE mesh peripherals appear as separate Home Assistant devices, with their
primary and auxiliary entities grouped together. Entity unique IDs and MQTT
topics remain stable across this change and mesh leader changes.
Validation
- Agent (Python 3.10): 2,819 passed, 1 skipped. Home Assistant integration (Python 3.14): 2,014 passed, 6 skipped. Ruff, formatting, strict mypy and generated-payload parity pass; all eight hosted checks passed on
e790bdd. - The published
brilliant_mqtt.zipwas verified byte-for-byte against the reviewed integration and payload manifests before installation. - Live canary: installed through HACS and deployed to a pilot panel through the supported single-panel update (normal ordinal 4 → 5 admission, retained predecessor baseline), then soaked for more than 48 hours with matching component digests, unchanged service processes and no automatic restarts, before the remaining panels were updated.
Known limitations
- A dedicated rollback rehearsal and responsiveness measurements for this release are still pending (#164).
- Updates do not yet retry transient panel-connection failures automatically (#219); retry the update once the panel is reachable.
- The read-only-filesystem watchdog residual (#143) and network return-path investigation (#173) remain open.
v0.11.2 — fail-closed watchdog reboot guards, legacy auto-repair canary gate, ledger deadline, lane field merge
The on-panel agent and Home Assistant integration both move to 0.11.2. The
bundled bridge and watchdog payload carries release ordinal 4; the separately
versioned voice payload remains at 0.1.1. Qualify this release on a canary panel
before updating the remaining panels.
Fixed
- A bus panel whose reboot guard state cannot be written now refuses to
self-reboot until an operator fixes the storage, preventing an unrecorded
reboot from being authorized on every boot (#209). - The Wi-Fi watchdog no longer crashes when it cannot record a reboot in its
guard state file. If the state file was already unreadable (for example, a
directory at that path), the watchdog logs the failure and still reboots,
at most once per cooldown. If the state reads fine but cannot be written
(full disk, read-only filesystem, permission denied), the watchdog logs the
failure and refuses to reboot, because an unrecorded reboot would be
authorized again on every boot. A panel in that state will not reboot itself
to recover Wi-Fi until an operator fixes the storage.
(#195) - The manifest uses minimum-version specs for
asyncsshandpaho-mqtt, so
hassfest accepts them and the integration still installs on HA 2026.6.x. - A partial MQTT setter that supersedes a light or switch write still waiting
for the device now merges per field, with the later value winning, rather than
dropping the waiting write's other fields. Writes already issued to the
device, and commands for other loads, are never merged.
(#159) - A stalled retained-ledger write now fails closed after 10 seconds, matching
the MQTT acknowledgement bound. Before that deadline, state for loads the
panel already owns is no longer held up by another load's pending write.
After it, publishes fail closed until the late write settles; the next
publish then rewrites the ledger from memory before anything reaches the
broker, so a late write cannot drop an owned topic or race a later write.
Session shutdown and process exit are delayed by at most the deadline.
(#176) - A ledger failure during a wired light or switch state publish now reports
theretained_ledgerdegraded status on the next session tick instead of
after the next periodic resync.
(#177) - Background auto-repair, the Repair button, and the repair service no longer
auto-admit a pre-0.11.0 panel's code. Only the Update entity,redeploy, and
provisioning, which run the canary and retain a rollback baseline, perform the
first pre-ordinal upgrade (#181). Arelease_overridecannot waive this, and
the blocked message now points at a plain update instead of an override.
Those paths need a baseline, which requires the bridge unit to beactive. If
a pre-0.11.0 panel's bridge is stopped or failed, repair no longer brings it
back (as in 0.11.0; only 0.11.1 did) and update reportsbaseline_correlation_unsupported. Start
brilliant-mqtton the panel and update. Otherwise turn off the panel's
watchdog component switches, runbrilliant_mqtt.uninstall, update, then turn
the watchdogs back on. Uninstall stops and removes the agent, so the bridge
stays offline until the update completes. See "Recovering a stopped pre-ordinal bridge" in
docs/ha-integration.md. - Automatic pre-ordinal admission now logs one INFO line per operation, and a
failure to write its audit record is reported with an operator-safe detail
(#182). - After the scene state file is found corrupt, only the first scene or mode
record the agent observes is treated as history and suppressed. Previously a
second record that arrived while the first one was still being written to
disk was also suppressed, so a real scene execution could go unreported
(#112). - The Wi-Fi watchdog no longer treats a corrupt, unreadable or wrong-type
reboot-guard state file as an empty history. It blocks reboots for one
cooldown period, logs the lost history once at ERROR, then resumes under the
normal cooldown and cap. A missing state file still counts as a fresh install
(#140). The
fail-closed window is held in memory, so a watchdog restart re-arms it, and a
restart loop faster than one cooldown keeps reboots blocked; this is the
deliberate fail-closed direction. - The bus watchdog now treats corrupt, unreadable, wrong-type, non-finite, or
future reboot-guard state as lost history and blocks reboots for one cooldown
before resuming the normal cooldown and rolling cap
(#193).
Known follow-ups
- #173: canary connectivity (ARP/neighbor fault) still blocks the soak; a workaround is in place.
- #208: a refused reboot (unwritable guard state) re-logs every poll during an outage; the ERROR line also claims a reset that did not happen.
- #188, #191, #201: release-identity audit ordering, the snapshot_probe_failed operator detail, and scene-bridge publish-before-persist.
- #197–#200, #202–#205: test hardening and hostile-state-file follow-ups.
Reviewed by gpt-6-astra and claude-opus-5-5 (with claude-fable-5-1 on earlier rounds); implemented by claude-opus-5-5 and gpt-6-luna.
v0.11.1 — auto-admit upgrades from pre-ordinal releases
The on-panel agent and Home Assistant integration both move to 0.11.1. The
bundled bridge and watchdog payload carries release ordinal 3; the separately
versioned voice payload remains at 0.1.1. Qualify this release on a canary panel
before updating the remaining panels.
Fixed
- Panels running a strict pre-0.11.0 release with no ordinal can upgrade to an
ordinal-bearing release without a manual override. Admission is recorded for
audit and retains the existing canary rollback baseline.
Known follow-ups
- #180: panels on 0.10.0 or older whose watchdog has no VERSION file still need a one-time manual override.
- #181: background auto-repair can move a pre-0.11.0 panel to 0.11.x without the canary or a rollback baseline.
- #179, #182: release-identity policy clarification and test/audit gaps.
Reviewed by gpt-6-astra and claude-opus-5-5; implemented by gpt-6-sol.
v0.11.0 — reliable command lanes, partial-command merging, wired feedback freshness, verified canary rollback
The on-panel agent and Home Assistant integration both move to 0.11.0. The
bundled bridge and watchdog payload carries release ordinal 2; the separately
versioned voice payload remains at 0.1.1. Update through the per-panel bridge
Update entity after qualifying the release candidate on a canary panel.
Added
- Panel response diagnostics distinguish command queue/native-write timing,
outcomes, ticket revocations, and reconnect degradation from basic online
status in bounded retained bridge metadata. The operational runbooks cover
Wi-Fi/broadcast response qualification and a diyHue reflection proposal;
the project documentation also gains a Built with Omnigent badge and footer.
(#152,
#157,
#154) - Mesh writes expose pending feedback separately from confirmed physical state;
cancelled publishes invalidate feedback caches.
(#156) - Canary updates retain a verified predecessor baseline through the soak period,
with explicit single-panel rollback/finalization and recovery after interrupted
operations. Release identity admission checks selected code digests and ordinal
before mutation; same-version but different code cannot silently overwrite an
incumbent, and legacy unknown-ordinal installations require a bound override.
(#168;
#165,
#166)
Changed
- Interactive writes take priority over maintenance without breaking target
ordering; newer waiting intents supersede stale ones, while maintenance
retries back off after failures. Ticket recovery preserves the newest write
across a route change and contains benign write cancellation.
(#158,
#163)
Fixed
- Compatible partial MQTT setters now merge before dispatch rather than losing
earlier fields; unsafe commands remain FIFO barriers.
(#170;
#159) - Failed or cancelled per-peripheral command workers no longer strand queued
commands or stall teardown. Bounded recovery, cooldown, and discard logging
make lane failures and active-fold fallback observable without exposing
command contents.
(#171;
#160,
#161) - Wired light and switch feedback no longer lets a delayed pre-command snapshot
overwrite the requested state (the ON -> OFF -> ON flicker). Native
observations are kept separate from a provisional request that expires after
a fixed 20-second window; contradictions that metadata cannot resolve are
published asambiguousrather than suppressed, and the device's own state is
published once the window ends. Mesh behaviour is unchanged. New
wired_write_status,wired_requestedandwired_write_deadlinekeys
appear while a write is pending.
(#174;
#172)
Known follow-ups
v0.10.2 — harden the watchdog relay against marginal-link stalls
The on-panel agent and Home Assistant integration both move to 0.10.2. The
agent's code is unchanged from 0.10.1; the version bump exists so every panel's
per-panel bridge Update entity offers this release and re-runs the deploy
through the hardened path below. The voice payload remains at 0.1.1 and is
unchanged.
This release hardens the HA-side deploy machinery after a live finding during
the 0.10.1 fleet rollout — nothing in the panel agent changed.
Canary: the hardened watchdog deploy (deploy_bus_watchdog streaming one
tarball + the relay's carried restart) was exercised against the pilot panel
(Office) before release — bus watchdog marker 0.10.1 → 0.10.2, watchdog
restarted, bus-phase marker success, LWT online.
Fixed
- Integration: on a panel with a marginal Wi-Fi link (guest-bath, during the
0.10.1 rollout) the bus watchdog's recursive SFTPput_dirstalled past the
120 s operation timeout while the bridge tarball deployed fine. The relay had
already swapped the new watchdog tree and stamped itsVERSIONmarker when it
died at the restart step, so every later converge read "already converged"
and skipped the restart — leaving the previous process running code it had
never loaded (a manualsystemctl restartfixed it live). Three fixes:
deploy_wifi_watchdog/deploy_bus_watchdognow ship ONE gzipped tarball
(built off-loop, streamed in a single SFTP write, extracted into staging,
then swapped) — the same transport the bridge payload already uses, instead
of asyncssh's one-round-trip-per-file recursive put;_relay_watchdog
retries a failed converge once and carries a restart owed by this call's
deploy across the retry even when theVERSIONmarker already reads
converged, and force-restarts once more when both attempts fail while the
marker says the new code is live; andshell.put_dirpre-walks the local
tree in an executor (HA's event-loop watchdog had logged a blockingscandir
for asyncssh's recursive put during the rollout).
v0.10.1
The on-panel agent and Home Assistant integration both move to 0.10.1. The
agent's code is unchanged from 0.10.0; the version bump exists so every panel's
per-panel bridge Update entity offers this release, and installing it is
what brings the bus-health watchdog on each panel up to the same release as
the bridge (see Fixed). The voice payload remains at 0.1.1 and is unchanged.
Canary: the release's panel recipes (deploy_payload + bus-watchdog
inspect/redeploy/restart, exactly what the Update entity now runs) were
executed against the pilot panel (Office, flat legacy layout) before release —
watchdog marker converged None → 0.10.1, bridge and watchdog restarted
cleanly, bus-phase marker success, LWT online.
Fixed
- Integration: the per-panel Update entity install (and the
brilliant_mqtt.redeployservice) shipped the bridge app only, so a fleet
updated that way kept the previous release's bus-health watchdog code —
silently breaking the 0.10.0 "upgrade the bridge and the bus watchdog
together" contract (the old watchdog reads the new bus-phase marker as
unknown and fails safe to no reboot). The Wi-Fi and bus watchdogs now carry a
VERSIONmarker under their/var/brilliant-mqtt/<name>/directory, and
every repair, post-OTA refresh and Update-entity install redeploys and
restarts a selected watchdog whose marker is missing or differs from the
bundled release. The Update entity converges the companion components before
restarting the bridge; a companion failure is logged and never fails the
bridge update.
v0.10.0 — bus-phase watchdog gating, scene delivery guarantees, bounded MQTT backlog
The on-panel agent and Home Assistant integration both move to 0.10.0. Update
every panel through its per-panel bridge Update entity. The voice payload
remains at 0.1.1 and is unchanged.
Upgrade the bridge and the bus-health watchdog together. The bridge now
stamps a bus-phase marker (BUS_PHASE_FILE, default
/run/brilliant-mqtt/bus-phase) that the bus-health watchdog reads before it
may reboot a panel. The marker's on-disk format is a shared, versioned
contract: writer and reader must be upgraded and rolled back as a pair. If a
deploy or rollback leaves only one side updated, delete the marker before
starting either service: rm -f /run/brilliant-mqtt/bus-phase. Unknown or
version-skewed records fail safe to no reboot. See
docs/reference/deployment.md (bus-watchdog phase-file coupling) and
docs/CONFIGURATION.md (Bus-health watchdog → BUS_PHASE_FILE).
Canary: pilot panel (Office) ran this exact payload under systemd before
release — bus + broker connect, discovery/telemetry, bus-phase marker
stamped and read by the watchdog, and LWT/restart recovery all verified.
Fixed
On-panel agent
- Bus session teardown no longer leaks resources or lets stale callbacks
touch the next session. The observer and processor are owned before they
are started and unwound on any startup failure or cancellation (a handshake
failure used to leave the processor's reconnect work alive, and three
retries stacked three live peers); teardown is fenced by a session token
and the shutdown flag, so a callback that outlives shutdown cannot deliver
the rest of its snapshot or continue reconnect fan-out; observer and
processor close under one total deadline, with an unresolved close fencing
the next session's start instead of allocating another peer.
(#104,
#135;
#88,
#130,
#131) - Quiet panels reconnect promptly after the MQTT reader dies. An
unexpected end of the MQTT receive loop is consumed on the next session
tick and rebuilds the session, instead of going unnoticed until the next
publish or the periodic resync on a panel whose state was not changing.
(#100;
#89) - Inbound MQTT backlog is bounded, so a stalled bus write plus an
automation burst can no longer grow memory until the 96 MB service cap
restarts the agent. The transport queue upstream of the per-peripheral
command lanes is capped at 64 messages / 256 KiB of payload, with
latest-wins coalescing for idempotent setters at admission. Exceeding a
bound is an observable overload: the excess is shed and logged and the
session is rebuilt through the normal backoff, never dropped silently.
(#101;
#90) - Scene events and command results survive a disconnect between local
send and broker receipt. The durable outbox publishes at QoS 1 and marks
a record delivered only after the broker's PUBACK; an unacknowledged record
is replayed with the same dedup key / command id, including across a
restart. Broker acknowledgment is a transport guarantee only, not proof
that Home Assistant processed the message.
(#107;
#92) - A second scene or mode command after a delivered result reaches the
bus. Outbox pruning could drop an event still referenced by a delivered
result; the next state snapshot then failed validation, marked the scene
state untrusted, and routed every later command to health only. Pruning
now clears the dependency first and persists once per delivery; a state
file written by the old rule self-heals on load.
(#136;
#124,
#122) - Repeated same-mode activations are detected through polling when the
push is missed. The hot-poll change fingerprint includes the
manual_mode_idtimestamp, so re-activating the current mode with a new
bus timestamp emits its event and confirms a pending command instead of
timing out. Scene blobs, whose value already embeds the execution time,
keep their timestamp-only refreshes suppressed.
(#105;
#93) - Scene and mode commands are confirmed only by executions at or after the
request. Each pending command records a panel-clock baseline; a delayed
or historical execution newer than the watermark but older than the
request still emits its native event but no longer returns
accepted: true. ModeVariable.timestampis panel-clock epoch-ms and a
same-value write does not re-stamp, so a request for the already-active
mode is settled by the successful write RPC rather than waiting for a
stamp that never comes.
(#116,
#141;
#94,
#119)
Operator note: the scene state file gains aconfirm_after_msfield
(no version bump; old files load and are migrated on first save). A state
file written by 0.10.0 and read by an older agent tripsstate_untrusted
(a fail-safe drop, not corruption) until the agent is upgraded again. - The liveness heartbeat is stamped before broker-dependent work. Startup
runs a real bus read and heartbeat before the scene bridge's MQTT
subscriptions, andreconcilebeats before publishing availability, so a
broker that rejects a publish or never SUBACKs cannot starve the heartbeat
and provoke a bus-watchdog reboot of a healthy panel. Scene callbacks are
registered at construction and early executions buffered, so nothing is
lost during the startup window.
(#118,
#139;
#87,
#125)
Bus-health watchdog
- Broker-only outages no longer reboot a healthy panel. A stale
heartbeat alone is no longer enough: the watchdog also requires the
bus-phase marker to attribute the stall to the local bus. Broker refusal,
DNS/TLS/auth failures, and retained-ledger errors never reach the bus, so
they read aspre_busand cannot qualify; a sustained bus handshake/read
failure still reboots under the existing cooldown/cap. A failed stamp
disables only the reboot guard (logged at WARNING) and never takes the
bridge down; a marker naming pid <= 1 is rejected. Attribution is timed
and generation-aware: only an unbroken run of validated bus-attempt
failures within a 300 s continuity bound counts, a successful read resets
it, an attempt is not authoritative until its lease is acquired, and
evidence older than the bridge unit's current systemd start generation is
discarded. The watchdog needssystemctl showon the bridge unit to
succeed; a failed or empty query degrades to no reboot.
(#99,
#115,
#139;
#87,
#111,
#126,
#127,
#133)
Operator note: see the paired-upgrade contract at the top of this
release. Known residual: on a read-only filesystem a recovered-but-
unrecorded bus whose writer then dies can be honored as a stale attempt
for up to ~300 s during restart backoff
(#143). - Every probe, recovery, and reboot child process is bounded (
ip route,ping,systemctl is-active,systemctl reboot): on timeout the
whole process group is killed and reaped, the result reads conservatively,
and the poll loop continues instead of wedging alive-but-stuck.
(#117;
#97)
Wi-Fi watchdog
- A reboot deferred or unconfirmed during an outage is retried. The
reboot rung used to latch as fired the moment it was wanted, so a reboot
blocked by the 1 h cooldown or the rolling cap — or one whosesystemctl reboottimed out or failed after the GPIO/SDIO reset — was never
requested again until connectivity returned or the daemon restarted. The
rung now stays re-armable on every guard-eligible poll, every destructive
attempt is charged to the persisted cooldown/cap (the sole rate limiter),
and a blocked reboot logs its deferral once rather than looping.
Soft-reconnect and service-restart rungs remain one-shot.
(#106,
#138;
#91,
[#128](https://github.com/joyfulhouse/brilliant...
v0.9.3 — Ring live-view bus-stall tolerance
Agent-only release. Fixes #83 (PR #84): bounded hot-poll backoff (30 s) after a first bus read timeout, periodic resync deferred while the backoff is active and retried once after the window; a second consecutive miss still rebuilds the session. Operator note: a genuinely dead bus is now detected in ~40–45 s instead of ~12–20 s. Integration and voice payload (0.1.1) unchanged. See CHANGELOG [0.9.3].
v0.9.2 — MQTT subscription hardening, connect diagnostics, honest recovery reasons
The on-panel agent and Home Assistant integration move to 0.9.2. Update every
panel through its per-panel bridge Update entity. The voice payload remains
at 0.1.1 and is unchanged.
Fixed
- Connect-stage failures now carry actionable diagnostics. All four
integration connect-stage handlers log the panel identity and exception
class;OSErrorincludes its errno and strerror, while AsyncSSH failures
include their class and message. User-facing Repairs summaries and the
allow-listed panel-operation path are unchanged.
(#79; #75) - MQTT subscription hardening prevents avoidable session rebuilds. SUBACK
reason codes are validated, command-topic subscriptions are deduplicated per
session, and periodic resync gets one retry before two consecutive subscribe
failures rebuild the session; initial reconcile remains fail-fast. This
directly targets the multi-minute availability outages observed on weak-Wi-Fi
Guest Bathroom and Office Bathroom panels after SUBACK-timeout session
rebuilds.
(#78; #76) - Post-update and repair recovery reports its true origin and tolerates
reconnect activity. Timeout reasons now name whether an update or repair
failed to recover, and append the API-drift diagnosis only when the journal
contains its signature. A reconnecting panel receives one bounded 90 s
extension after the existing 60 s window; a silent panel still escalates at
60 s. Recovery timers use monotonic generations, cannot be orphaned, snapshot
await-race state consistently, and classify diagnostics against the actual
recovery origin. This changes the post-update/repair health-escalation reason
text and caps the extended recovery window at 150 s.
(#80; #77)