fix: settle staged options on every exit, and stop double-counting a repaired user - #1516
Conversation
…repaired user Two follow-ups to #1515. An options submission that changes nothing returned before the settle, so it sat in `options` for good -- read in preference to `data` for as long as the entry existed, then cleared by the next write that touched the entry, taking anything that lived only there. Settling is now one helper called from both exits. The unnumbered-user repair asked allocation for room for one user more than exists: the person being edited is already in the entry's users, so they were counted twice. On a lock with exactly enough room the repair dialog refused, naming a user count that does not exist -- the very failure the path was added to prevent. A repair that also renamed issued the new name a second number and burned the first. Both come from the same place, so both now read from one `others` dict, which is what the name check already used. Entire-Checkpoint: 0c8adf394d8d
One delete_user call is several entry writes -- a subentry removal per departing user -- and each wakes the update listener. The listener took the whole hand-off set on its first pass, so from the second user onward the credential the caller explicitly asked to leave programmed was wiped off every lock instead. Consumed per pair now. Also bind the reclaimed slot device to its user's subentry, so the pre-2026.8 upgrade path stops taking the deprecated implicit move, and drop the ZHA operation-source names the event removal left behind. Entire-Checkpoint: 7121ca62e8d5
|
Pushed two more, from a second review pass over the full A batch hand-off only honoured the first name. The reclaim path created its slot device with no subentry, so the pre-2026.8 upgrade path took the deprecated implicit move — on the exact upgrade v6 is aimed at. Also dropped Two findings from that pass are not in this PR, and both are worth a decision rather than a rushed fix. Deleting a user while the entry is unloaded leaves the PIN on the lock. HA's I tried the obvious remedy — release the slots whose device outlived their configuration, which What is left is a design call: run
|
Breaking change
None. Both fixes are to code already on
v6.Proposed change
1. A no-op options submission stranded itself in
optionsdatais where an entry's configuration lives;optionsis a staging area, because an options flow cannot writedataitself. The update listener is what keeps that true — it foldsoptionsintodataand clears it.That settle sat at the bottom of the listener, behind an early return for "nothing changed". So opening the options flow and saving without changing anything left the submission staged permanently, across restarts.
Inert today: what the options flow writes is a strict subset of
data, andEntryConfig.from_entryreads those keys options-first, so both sides always agreed. It stops being inert the moment an options-flow field exists that isn't also indata— which is what an options flow is normally for. Such a field would read correctly (options-first) and then be silently erased by the next write that touched the entry, since every other write doesdata=..., options={}.Settling is now one
_async_settle_optionshelper called from both exits, rather than a block at the bottom that one path skips.I did not move the settle to the top of the listener, which was the obvious-looking fix: the re-entrant pass would diff against a cache the outer pass had already refreshed, and at setup — which passes
EntryConfig.empty()as the old config — it would skip entity creation entirely.2. The unnumbered-user repair counted that user twice
async_step_reconfigureasked allocation forlen(config.users) + 1. The+ 1was carried over from the add flow, where it is right because the newcomer is not inconfig.usersyet. On the edit path they are —EntryConfig.from_entryputs every user subentry intouserswhether or not it carries a slot; onlyassignmentskips the unnumbered one.Two consequences, both reproduced:
Aliceon slot 1 plus an unnumberedNomad, lock with 2 slots:{"base": "too_many_users"}, telling the user "3 users will not fit" when there are two. Exactly the failure this path was added to prevent — a dialog that can only fail — reintroduced at the capacity boundary.reconcilewas handed a dict still containing the old name alongside the new one, so the same person appeared twice under two identities and the new name was numbered around the old one, leaving slot 1 permanently unused.Both now read from one
othersdict — everybody except the person being edited — which is what the name-uniqueness check two lines above already used.The existing tests passed either way: the one test on this path has a single user on an empty lock, where
2and1allocate identically, and nothing renamed during a repair. Both scenarios are now covered, and both fail against the old arithmetic.Type of change
Additional information
prekclean.