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"]