Decide event transitions under the completion lock

complete/uncomplete return a CompletionResult str subclass carrying
appended (decided with the write under the lock), so duplicate
concurrent calls fire one transition event; day-complete only
evaluates for an instance due today.
This commit is contained in:
2026-09-19 12:03:42 -04:00
parent fe38e5040e
commit 2a4eec658f
4 changed files with 140 additions and 29 deletions
+7 -12
View File
@@ -295,17 +295,15 @@ def _complete_quest(
async def _handler(call: Any) -> None:
database = _require_runtime(hass, find_runtime).database
instance_id = call.data["instance_id"]
# Events fire on an ACTUAL transition only: an already-done
# instance is a no-op below and must not re-announce itself.
state_before = await completion_layer.instance_state(
database, instance_id
)
await completion_layer.complete_instance(
# ``appended`` is decided under the same lock as the write, so
# a concurrent duplicate call (a double tap) can not produce a
# second transition event: the loser appends nothing.
result = await completion_layer.complete_instance(
database,
instance_id,
**_actor_kwargs(call),
)
if state_before != "done":
if result.appended:
await fire_quest_completed(hass, database, instance_id)
return _with_errors(_handler)
@@ -317,15 +315,12 @@ def _uncomplete_quest(
async def _handler(call: Any) -> None:
database = _require_runtime(hass, find_runtime).database
instance_id = call.data["instance_id"]
state_before = await completion_layer.instance_state(
database, instance_id
)
await completion_layer.uncomplete_instance(
result = await completion_layer.uncomplete_instance(
database,
instance_id,
**_actor_kwargs(call),
)
if state_before == "done":
if result.appended:
await fire_quest_uncompleted(hass, database, instance_id)
return _with_errors(_handler)