fix: review findings on the subentry migration - #1515
Merged
Conversation
The migration's device move looked devices up by a bare string where the registry keys on (domain, identifier) tuples, so it never found one and the whole block was dead. Every upgrading instance took the deprecated implicit path instead -- a warning per slot device today, a hard failure on HA 2027.8. Its test built the fixture device with the same malformed identifier, so bug and test agreed with each other and disagreed with production. The test now registers the device through the DeviceInfo the entities carry, and exercises the migration on its own: a full setup adds the entities, which drag the device across by themselves, so the end state is the same either way and could never have told the difference. Also: the four allocation refusals had no sentence in the subentry flow, so a full or sleeping lock showed a bare translation key; editing a user who somehow holds no number raised out of the dialog; the shipped card was the only thing keeping the pre-6.0 flat add_user shim alive; and BLUEPRINTS.md and AGENTS.md still promised a removed event kept firing. Each gap gets the guard that would have caught it. Entire-Checkpoint: 75d1ad41c0e9
This was referenced Aug 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Breaking change
None. Every change here fixes something already on
v6.Proposed change
Two independent review passes over #1514 agreed on the same critical, plus three smaller findings. Each fix comes with the guard that would have caught it.
1. The migration's device move never found a device
__init__.pylooked slot devices up asidentifiers={build_slot_device_identifier(...)}— a bare string. The device registry keys on(domain, identifier)tuples, which is howentity.pyand every other call site creates them. The lookup returnedNonefor every device, so the whole block was dead and every upgrading instance took the deprecated implicit path that block exists to avoid: oneassigns an existing device to a different config subentrywarning per slot device on every start, and a hard failure on HA Core 2027.8.Its test could not have caught it. The test built its fixture device with the same malformed identifier the buggy code searched for, so the two agreed with each other and disagreed with production. It now registers the device through the same
DeviceInfothe entities carry.Correcting the identifier alone was still not enough: with the fixture fixed, the test still passed against the broken lookup, because a full setup adds the user's entities and those drag the device across by themselves. The end state is identical whether the migration moved the device or not. The test now exercises
async_migrate_entryon its own, which is the only way to observe the difference.2. Four allocation refusals had no sentence in the subentry flow
config_subentries.user.errorwas missingtoo_many_users,numbers_needed_exceed_capacity,search_limit_reachedandoccupancy_unknown. Adding a user is now the only way to add a user, so a full lock or a sleeping one — the likeliest refusal in practice — showed the bare translation key.Guard: a test that reads every
SlotAllocationErrorkey out ofallocation.pyand asserts each flow that allocates has a sentence for it. Read from the source rather than listed, so it cannot go stale.Also pruned
options.errorto the one key the locks-only options flow can still produce.3. Editing a user who holds no number raised out of the dialog
subentry.data[CONF_SLOT]in the reconfigure step. Not a state the integration writes — hand-edited storage or a half-finished write leaves one — but it meant the one record a user might need to repair was the one record the UI could not touch. It now issues a number, or refuses cleanly if the lock cannot be read.4. The shipped card was keeping a back-compat shim alive
add-user-card.tscalledadd_userwith the flat pre-6.0 shape, which works only through_flat_user_to_list. Removing that shim would have broken the Add User button, and no frontend test would have said so — they mockcallService, so nothing on that side ever meets the schema. The card now sends the declaredusers: [...]shape, with a contract test asserting it.5. Docs promised a removed event still fires
BLUEPRINTS.mdandAGENTS.mdboth saidlock_code_manager_lock_state_changed"keeps firing" / is "retained for backward compatibility". It was removed in #1508.BLUEPRINTS.md's claim sat inside the breaking-change note explaining where to recover the fields the entity dropped — pointing users at an event that never fires.Also
Corrected a comment in the update listener that stated an invariant the options flow can violate: an options submission that changes nothing returns before the settle, leaving
optionsstanding. Harmless whileoptionsonly ever holdsdata's own contents, which is now noted at the site so nobody adds a field that breaks it.Type of change
Additional information
prekclean.