Skip to content

fix(scene): timed roller stop composes from current state, not the activation snapshot#456

Merged
fdebrus merged 1 commit into
mainfrom
claude/platform-audit
Jun 9, 2026
Merged

fix(scene): timed roller stop composes from current state, not the activation snapshot#456
fdebrus merged 1 commit into
mainfrom
claude/platform-audit

Conversation

@fdebrus

@fdebrus fdebrus commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Summary

Platform audit — the cover.py treatment extended to light, switch, scene, button, binary_sensor and the shared entity base (full line-by-line read, hunting the same bug classes: task self-cancellation, stale-state replay, channel=None payloads, AsyncMock-masked paths, optimistic races).

✅ Clean (no changes needed)

  • light.py / switch.py — optimistic patterns are correct: _previous_brightness read before mutation, error paths revert to coordinator state, the A/B latch mutates after the await (so a failed bus write never lies in the UI).
  • button.py — long discovery actions properly backgrounded with a discovery_running guard.
  • binary_sensor.py — event-driven pulse with the reset timer cancelled on removal.
  • entity.py — diffing/availability base is sound.

🟠 One real defect, fixed: stale-snapshot replay in the scene timed stop

_delayed_roller_stop fires 30–60 s after activation and replayed the full module snapshot captured at activation, with only the scene's roller channels forced to STOPPED. Any channel of that module redirected during travel — another shutter adjusted by hand, a wall button, another scene — was silently overwritten with the stale state and restarted. The token guard only protects against re-activation of the same scene entity.

Fix: compose the stop frame from the module's current coordinator state at timeout, and only force STOPPED on channels still doing what this activation sent — a channel someone has since stopped or reversed is no longer ours to touch (the write is skipped entirely when nothing commanded is still running).

Tests

_delayed_roller_stop previously had zero coverage. +4 regressions: bystander channel preserved, redirected channel left alone, stale-token abort, missing-current fallback.

439 passed, ruff clean, mypy 100 (= baseline, no regression).

https://claude.ai/code/session_01LH7yGDY8ttvZUVMVjfSNXj


Generated by Claude Code

…tivation snapshot

Platform audit follow-up (cover.py audit extended to light / switch /
scene / button / binary_sensor / entity). Verdict: light, switch,
button, binary_sensor and the shared entity base are sound — optimistic
patterns correct (pre-mutation reads, post-await latch mutation, error
reverts), tasks bounded and cancelled on removal, action buttons
backgrounded with a discovery guard. One real defect found and fixed:

scene.py _delayed_roller_stop fired 30-60 s after activation and
replayed the FULL module snapshot captured at activation with only the
scene's roller channels forced to STOPPED. Any channel of that module
redirected during travel — another shutter adjusted by hand, a wall
button, another scene — was silently overwritten with the stale state
and restarted. The token guard only protects against re-activation of
the same scene entity, not against everything else on the bus.

Fix: compose the stop frame from the module's CURRENT coordinator
state at timeout, and only force STOPPED on channels still doing what
THIS activation sent — a channel someone has since stopped or reversed
is not ours to touch any more (skip the write entirely when nothing
commanded is still running).

Tests: +4 (bystander channel preserved, redirected channel left alone,
stale-token abort, missing-current fallback) — _delayed_roller_stop
previously had zero coverage. 439 passed, ruff clean, mypy 100
(= baseline, no regression).

https://claude.ai/code/session_01LH7yGDY8ttvZUVMVjfSNXj
@fdebrus fdebrus merged commit 8a6a1d3 into main Jun 9, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants