Add hybrid admin delete for quest definitions

Owner decision 2026-09-26: "delete if there's no history,
retire/deactivate if there is". A definition with no completion event
on any instance is hard-deleted (instances, assignees, windows, the row
and its unreferenced rule) in one transaction under the connection
lock; one with history is retired via set_quest_definition_active and
everything is preserved (D-005).

- core: QuestDefinitionsDao.delete_if_no_history (atomic history check +
  deletes) and quest_definitions.delete_quest_definition
- api: DELETE /api/v1/admin/quest-definitions/{id} on the admin router
- admin: Delete task control with a hybrid-explaining confirm in the
  edit sheet; the list removes or shows the row inactive
- docs: ADMIN-SPEC Definitions section

Task 3efb07b4-7bfa-45af-b36a-57d81249a39d

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-26 12:31:10 -04:00
co-authored by Claude Opus 5.5
parent b1c14ed7d9
commit ed230961e0
11 changed files with 792 additions and 41 deletions
+6 -3
View File
@@ -996,8 +996,9 @@ def test_definition_set_active_missing_returns_zero(tmp_path) -> None:
assert _with_db(tmp_path, "definition-set-active-missing.db")(_body) == 0
def test_definition_no_delete_method_exists(tmp_path) -> None:
"""Guardrail: definitions are deactivated, never hard-deleted."""
def test_definition_only_delete_method_is_history_guarded(tmp_path) -> None:
"""Guardrail: the ONLY definition delete path is the history-guarded
one (owner decision 2026-09-26); there is no unconditional delete."""
async def _body(database, rules, definitions, children, child):
import inspect
@@ -1008,7 +1009,9 @@ def test_definition_no_delete_method_exists(tmp_path) -> None:
)
}
assert "delete" not in methods
assert not any(name.startswith("delete") for name in methods)
assert {
name for name in methods if name.startswith("delete")
} == {"delete_if_no_history"}
return methods
_with_db(tmp_path, "definition-no-delete.db")(_body)