Skip to content

fix(coordinator): respect authoritative provider event labels - #765

Open
tykeal wants to merge 1 commit into
FutureTense:mainfrom
tykeal:fix/699-keypad-event-inference
Open

fix(coordinator): respect authoritative provider event labels#765
tykeal wants to merge 1 commit into
FutureTense:mainfrom
tykeal:fix/699-keypad-event-inference

Conversation

@tykeal

@tykeal tykeal commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes the remaining Bug 3 from #699 by making provider event/action inference explicitly provider-aware. Bugs 1, 2, 4, and 5 from #699 were already fixed by prior work, so this completes the issue.

Proposed change

Zigbee2MQTT delivers its keypad action and the pre-operation lock_state in the same MQTT payload. That means the provider callback can report Unlocked via Keypad while Home Assistant still exposes locked, causing the coordinator's state-first inference to route the event to _lock_locked.

This PR adds a narrow BaseLockProvider.lock_event_label_is_authoritative hook. Direct operation-event providers (Zigbee2MQTT, ZHA, and Akuvox) opt in so unambiguous labels such as Unlocked via Keypad win over stale entity state. Z-Wave JS keeps the default False, preserving the existing protection where its fallback path may derive labels from stale alarm/access-control sensors and a genuinely changed entity state should remain authoritative.

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New feature (which adds functionality)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Validation:

  • /home/tykeal/repos/personal/homeassistant/keymaster/.tox/py314/bin/pytest
  • tox --workdir /home/tykeal/repos/personal/homeassistant/keymaster/.tox -e lint with the ruff commands temporarily adjusted locally to use explicit custom_components/ tests/ paths
  • Regression proof: with only the production changes reverted, tests/test_coordinator_events.py::test_handle_provider_lock_event_zigbee2mqtt_label_overrides_changed_state fails because kmlock.lock_state becomes locked instead of remaining unlocked.

@github-actions github-actions Bot added the bugfix Fixes a bug label Sep 3, 2026
@tykeal
tykeal requested a lite review from Copilot September 3, 2026 22:54
@codecov-commenter

codecov-commenter commented Sep 3, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 77.77778% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.94%. Comparing base (cdb4922) to head (5c1d833).
⚠️ Report is 305 commits behind head on main.

Files with missing lines Patch % Lines
custom_components/keymaster/providers/_base.py 66.66% 1 Missing ⚠️
custom_components/keymaster/providers/akuvox.py 66.66% 1 Missing ⚠️
custom_components/keymaster/providers/zha.py 66.66% 1 Missing ⚠️
...stom_components/keymaster/providers/zigbee2mqtt.py 66.66% 1 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@             Coverage Diff             @@
##             main     #765       +/-   ##
===========================================
+ Coverage   84.14%   94.94%   +10.79%     
===========================================
  Files          10       35       +25     
  Lines         801     5597     +4796     
===========================================
+ Hits          674     5314     +4640     
- Misses        127      283      +156     
Flag Coverage Δ
python 94.94% <77.77%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

This comment was marked as outdated.

Let direct event providers declare when their callback labels describe the intended lock operation more reliably than the current entity state. This lets Zigbee2MQTT keypad unlock payloads route to the unlock path even when the same MQTT payload still exposes the pre-operation lock_state, while keeping state-first inference for Z-Wave JS fallback labels that may come from stale alarm/access-control sensors.

Assisted-by: GitHub Copilot CLI 1.0.82 (Claude Opus 5, model claude-opus-5)
Signed-off-by: Andrew Grimberg <tykeal@bardicgrove.org>
@tykeal
tykeal force-pushed the fix/699-keypad-event-inference branch from e0d09ff to 5c1d833 Compare September 3, 2026 22:58
@tykeal
tykeal requested a lite review from Copilot September 3, 2026 23:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The change is narrow, provider-scoped via an explicit hook, and is supported by targeted coordinator event-handling tests.

Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@secondof9 secondof9 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Review Summary

Note

Review Status: 🟢 APPROVED
Change Type: 🐛 Bug Fix
Review Effort: 🟢 Low
Core Impact: Completes Bug 3 from #699 by adding a narrow, provider-scoped lock_event_label_is_authoritative hook so operation-event-first providers (Zigbee2MQTT, ZHA, Akuvox) can let an unambiguous label like Unlocked via Keypad win over stale pre-operation entity state.


🚦 CI & Pipeline Health Summary

Check / Workflow Name Status Impact on Review
Pytest (3.14) ✅ PASSED All 50 tests pass, incl. the two new regression tests (verified locally on CPython 3.14.3).
coverage ✅ PASSED Project coverage well within threshold.
HACS Validation ✅ PASSED Manifest/manifest hygiene clean.
Hassfest Validation ✅ PASSED Integration manifest compliant.
Prek ✅ PASSED Pre-commit/pre-merge gate clean.
HACS Validation (2nd job) ✅ PASSED
Autolabel PR ⏩ SKIPPED Builtin autolabel, non-blocking.

Note

CI Pipeline Clear: All GitHub Actions workflows completed successfully. Ruff (ruff check custom_components/ tests/) also passes cleanly in the checked-out workspace.

The only non-CI comment is a Codecov coverage bot note reporting 4 uncovered lines. That is informational only and not a failing check — see the "Coverage" note below.


🔍 Architectural Walkthrough

Coordinator inference logic (custom_components/keymaster/coordinator.py)

  • _handle_provider_lock_event now computes provider_label_is_authoritative from the new provider hook and checks it before the state_changed branch. The reordered decision tree is correct: authoritative unlock/lock (excluding jam) labels take precedence, then the existing state-first fallback for Z-Wave JS and others remains intact.
  • The "jam" not in label_lower guard correctly excludes Lock Jammed from being treated as a lock (the existing test_handle_provider_lock_event_jam_label_falls_back_to_state still covers this).
  • No new I/O, no blocking calls, no async-boundary violations — this is pure inference over already-held state. Fully HA-compliant.

Provider hook (custom_components/keymaster/providers/_base.py)

  • Adds lock_event_label_is_authoritative returning False by default, with a clear docstring explaining the semantics. Defaulting to False preserves the prior behavior for every provider that does not opt in — a safe, additive change.
  • Zigbee2MQTT, ZHA, and Akuvox override it to True, each with a short justification comment. Z-Wave JS intentionally stays False, matching the PR's stated rationale about stale alarm/access-control sensor fallbacks.

Tests (tests/test_coordinator_events.py)

  • Two new tests directly exercise the modified branch:
    • test_handle_provider_lock_event_zigbee2mqtt_label_overrides_changed_state — Z2M label Unlocked via Keypad wins over a LOCKED entity state.
    • test_handle_provider_lock_event_authoritative_lock_label_overrides_changed_state — authoritative Locked via Keypad wins over an UNLOCKED entity state.
  • Both were run locally on Python 3.14 and pass, and they also serve as the regression proof the PR body describes (reverting the production change makes state_changed true and routes to new_state, which these asserts reject).

Tip

On the Codecov coverage comment: the first Copilot review (on commit e0d09ff) flagged the new inference branch as lacking test coverage. That concern is now fully addressed by the two new tests, and the second Copilot review on HEAD already flipped to "Approval recommended." Coverage is not blocking CI here. No action required.


✅ Conclusion

Narrow, well-scoped, additive, and thoroughly tested. Completes #699 cleanly. Approving.

@secondof9

secondof9 commented Sep 4, 2026

Copy link
Copy Markdown

Thanks for the heads-up on that gap — it's now covered. The new test test_handle_provider_lock_event_authoritative_lock_label_overrides_changed_state sets lock_event_label_is_authoritative=True, kmlock.lock_state=LOCKED, entity state UNLOCKED, label Locked via Keypad, and asserts the emitted state is LOCKED (and kmlock.lock_state == LOCKED). It was run locally on Python 3.14 and passes, so the lock-label path the reviewer flagged now has full patch-level regression protection. Overall this is a clean, narrow, additive fix — approving.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Fixes a bug

Projects

None yet

4 participants