From ba0b2032cc8271be2f45b0a4582ed4a229dd0878 Mon Sep 17 00:00:00 2001 From: Joshua Date: Sat, 26 Sep 2026 12:40:44 -0400 Subject: [PATCH] Correct retirement docs: deactivation clears future open instances Retirement preserves the definition row, schedule rule, assignees, windows and all durable completion history, but like any deactivation it regenerates, so future uncompleted instances are cleared and none are recreated while inactive. Fixes the "deletes nothing / all instances preserved" wording in the core docstrings, the admin DELETE route description, ADMIN-SPEC and the retire test's docstring (review finding NQ-262-1). No behavioural change. Co-Authored-By: Claude Opus 5.5 --- api/routes_admin.py | 10 ++++++-- .../nestquest/core/quest_definitions.py | 24 +++++++++++++------ design/ADMIN-SPEC.md | 11 ++++++--- tests/test_quest_definitions.py | 7 +++--- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/api/routes_admin.py b/api/routes_admin.py index 0dc64c2..6a00d48 100644 --- a/api/routes_admin.py +++ b/api/routes_admin.py @@ -208,8 +208,14 @@ retire/deactivate if there is"): :func:`nestquest_core.quest_definitions.delete_quest_definition`. A definition with NO completion event on any instance is hard-deleted (instances, assignees, windows, the row and its unreferenced rule, in - one core transaction); one WITH history is retired by deactivation - and everything is preserved. The response reports which happened + one core transaction); one WITH history is retired by deactivation. + Retirement preserves the definition row, schedule rule, assignees, + windows and ALL durable completion history; like any deactivation it + regenerates, so future UNCOMPLETED instances are cleared (rolling, + regenerable rows) and none are recreated while the definition is + inactive — the retired task stops generating and shows no open work. + Durable instance history lives only in the append-only + ``completion_events``. The response reports which happened (``deleted`` / ``retired``) and carries the retired definition, or ``null`` when deleted. An unknown id is 404. diff --git a/custom_components/nestquest/core/quest_definitions.py b/custom_components/nestquest/core/quest_definitions.py index 1e07692..b4764a5 100644 --- a/custom_components/nestquest/core/quest_definitions.py +++ b/custom_components/nestquest/core/quest_definitions.py @@ -525,10 +525,15 @@ async def set_quest_definition_active( completion history (Feature 06 guardrail; a history-free definition may instead be hard-deleted by :func:`delete_quest_definition`): a deactivated definition stops future instance generation but its - row, schedule rule, assignees and windows all survive, and existing - instances plus completion history are never touched. Reactivation - re-enables future generation; generating or removing instances is - Feature 07 and does not happen here. + row, schedule rule, assignees and windows all survive, and its + durable completion history (the append-only ``completion_events``) + is never touched. Like every Feature 06 config change, the flip is + followed by :func:`~.materialize.regenerate_for_definition`: the + definition's future (today onward) UNCOMPLETED instances are cleared + — they are rolling, regenerable rows — and, while the definition is + inactive, none are re-materialized. Completed and past instances + are kept. Reactivation re-enables generation, and the same + regeneration re-materializes the rolling horizon. ``definition_id`` must be a plain int (bools and floats rejected, since SQLite would bind ``True`` onto definition 1) and @@ -598,9 +603,14 @@ async def delete_quest_definition( :meth:`~.dao_rules.QuestDefinitionsDao.delete_if_no_history` — the history check is inside that transaction, so a completion recorded concurrently can never be deleted (D-005). When history exists, - nothing is deleted: the definition is retired through - :func:`set_quest_definition_active` (``False``), preserving the row, - rule, assignees, windows, instances and completion history. + the definition is retired through :func:`set_quest_definition_active` + (``False``). Retirement preserves the definition row, schedule + rule, assignees, windows and ALL durable completion history. Like + any deactivation it regenerates, so future UNCOMPLETED instances are + cleared (they are rolling, regenerable rows) and none are recreated + while the definition is inactive — the retired task stops generating + and no longer shows open work. Durable instance history lives only + in the append-only ``completion_events``. ``definition_id`` must be a plain int (bools and floats rejected). Raises ValueError naming the field when the id is malformed or the diff --git a/design/ADMIN-SPEC.md b/design/ADMIN-SPEC.md index 77cab54..b337c07 100644 --- a/design/ADMIN-SPEC.md +++ b/design/ADMIN-SPEC.md @@ -91,9 +91,14 @@ otherwise it is retired (deactivated) and its history is kept." — with `Go bac danger-filled `Delete task`. Confirming calls `DELETE /api/v1/admin/quest-definitions/{id}` (admin plane only; the kids' panel has no equivalent control). Hybrid outcome (D-017): no completion event on any instance → the definition, its instances, assignees, windows and -unreferenced rule are hard-deleted and the row leaves the list; otherwise nothing is -deleted, the definition is deactivated and the row stays, shown inactive. An error keeps -the sheet open with the API's detail. +unreferenced rule are hard-deleted and the row leaves the list; otherwise the definition +is retired (deactivated) and the row stays, shown inactive. Retirement preserves the +definition row, schedule rule, assignees, windows and all durable completion history. Like +any deactivation it regenerates, so future uncompleted instances are cleared (they are +rolling, regenerable rows) and none are recreated while the definition is inactive — the +retired task stops generating and no longer shows open work. Durable instance history lives +only in the append-only `completion_events`. An error keeps the sheet open with the API's +detail. ### 3.3 Icon picker Each definition carries one optional icon, shown on both surfaces. The picker offers three diff --git a/tests/test_quest_definitions.py b/tests/test_quest_definitions.py index ee7f54c..65bcc69 100644 --- a/tests/test_quest_definitions.py +++ b/tests/test_quest_definitions.py @@ -2212,9 +2212,10 @@ def test_delete_without_history_keeps_a_shared_rule(tmp_path) -> None: def test_delete_with_history_retires_and_preserves_everything( tmp_path, ) -> None: - """A completion event on ANY instance: nothing is deleted; the - definition is deactivated and its row, rule, assignees, windows, - instance and completion history all remain.""" + """A completion event on ANY instance: the definition is retired + (deactivated) and its row, rule, assignees, windows and completion + history all remain. The completed instance survives; only future + UNCOMPLETED instances are cleared by the deactivation's regeneration.""" async def _body(database, children, rules, definitions, child): created = await create_quest_definition( database, "Brush teeth", _daily_rule(), [child.id], ["morning"]