chore: document project-wide soft delete pattern and add deleted_by column to all tables

This commit is contained in:
2026-06-11 20:36:47 +00:00
parent d0e2075e6d
commit 9a93debfc9
37 changed files with 1690 additions and 266 deletions
+22 -5
View File
@@ -44,7 +44,12 @@ class LessonStore:
def _ensure_indexes(self) -> None:
if TABLE not in self._db.tables:
return
self._db[TABLE].create_index(["owner_kind", "owner_id"])
table = self._db[TABLE]
if not table.has_column("deleted_at"):
table.create_column_by_example("deleted_at", "")
if not table.has_column("deleted_by"):
table.create_column_by_example("deleted_by", "")
table.create_index(["owner_kind", "owner_id"])
@property
def _table(self) -> Any:
@@ -57,7 +62,7 @@ class LessonStore:
def count(self) -> int:
if TABLE not in self._db.tables:
return 0
return self._table.count(**self._scope)
return self._table.count(deleted_at=None, **self._scope)
def add(
self, observation: str, conclusion: str, next_action: str, tags: str = ""
@@ -70,6 +75,8 @@ class LessonStore:
"tags": tags,
"created_at": to_iso(now_utc()),
"hits": 0,
"deleted_at": None,
"deleted_by": None,
**self._scope,
}
self._table.insert(record)
@@ -82,14 +89,24 @@ class LessonStore:
def all(self) -> list[dict[str, Any]]:
if TABLE not in self._db.tables:
return []
return list(self._table.find(**self._scope))
return list(self._table.find(deleted_at=None, **self._scope))
def delete(self, uid: str) -> bool:
if TABLE not in self._db.tables:
return False
deleted = self._table.delete(uid=uid, **self._scope)
row = self._table.find_one(uid=uid, deleted_at=None, **self._scope)
if not row:
return False
self._table.update(
{
"id": row["id"],
"deleted_at": to_iso(now_utc()),
"deleted_by": f"{self._owner_kind}:{self._owner_id}",
},
["id"],
)
self._dirty = True
return bool(deleted)
return True
def clear(self) -> int:
n = self.count()
+28 -7
View File
@@ -3,6 +3,7 @@
from __future__ import annotations
import logging
from datetime import datetime, timezone
from typing import Any
import dataset
@@ -34,6 +35,10 @@ class TaskStore:
if TABLE not in self._db.tables:
return
table = self._db[TABLE]
if not table.has_column("deleted_at"):
table.create_column_by_example("deleted_at", "")
if not table.has_column("deleted_by"):
table.create_column_by_example("deleted_by", "")
for columns in INDEXED_COLUMNS:
table.create_index(columns)
@@ -46,7 +51,9 @@ class TaskStore:
return {"owner_kind": self._owner_kind, "owner_id": self._owner_id}
def create(self, record: dict[str, Any]) -> None:
self._table.insert({**record, **self._scope})
self._table.insert(
{"deleted_at": None, "deleted_by": None, **record, **self._scope}
)
logger.info(
"Task created uid=%s owner=%s/%s",
record.get("uid"),
@@ -55,12 +62,13 @@ class TaskStore:
)
def get(self, uid: str) -> dict[str, Any] | None:
return self._table.find_one(uid=uid, **self._scope)
return self._table.find_one(uid=uid, deleted_at=None, **self._scope)
def list(
self, enabled_only: bool = False, status: str | None = None
) -> list[dict[str, Any]]:
criteria: dict[str, Any] = dict(self._scope)
criteria["deleted_at"] = None
if enabled_only:
criteria["enabled"] = True
if status:
@@ -73,14 +81,25 @@ class TaskStore:
logger.debug("Task updated uid=%s changes=%s", uid, list(changes))
def delete(self, uid: str) -> bool:
deleted = self._table.delete(uid=uid, **self._scope)
logger.info("Task deleted uid=%s ok=%s", uid, deleted)
return bool(deleted)
row = self._table.find_one(uid=uid, deleted_at=None, **self._scope)
if not row:
return False
self._table.update(
{
"uid": uid,
"deleted_at": datetime.now(timezone.utc).isoformat(),
"deleted_by": f"{self._owner_kind}:{self._owner_id}",
**self._scope,
},
["uid", "owner_kind", "owner_id"],
)
logger.info("Task soft-deleted uid=%s", uid)
return True
def recover_running(self) -> int:
if TABLE not in self._db.tables:
return 0
stuck = list(self._table.find(status="running", **self._scope))
stuck = list(self._table.find(status="running", deleted_at=None, **self._scope))
for row in stuck:
self._table.update(
{"uid": row["uid"], "status": "pending", **self._scope},
@@ -91,7 +110,9 @@ class TaskStore:
return len(stuck)
def due(self, now_iso: str) -> list[dict[str, Any]]:
rows = self._table.find(enabled=True, status="pending", **self._scope)
rows = self._table.find(
enabled=True, status="pending", deleted_at=None, **self._scope
)
return [
row
for row in rows
@@ -46,6 +46,10 @@ class VirtualToolStore:
if TABLE not in self._db.tables:
return
table = self._db[TABLE]
if not table.has_column("deleted_at"):
table.create_column_by_example("deleted_at", "")
if not table.has_column("deleted_by"):
table.create_column_by_example("deleted_by", "")
for columns in INDEXED_COLUMNS:
table.create_index(columns)
@@ -60,16 +64,23 @@ class VirtualToolStore:
def find(self, name: str) -> dict[str, Any] | None:
if TABLE not in self._db.tables:
return None
return self._table.find_one(name=name, **self._scope)
return self._table.find_one(name=name, deleted_at=None, **self._scope)
def list(self) -> list[dict[str, Any]]:
if TABLE not in self._db.tables:
return []
return list(self._table.find(**self._scope))
return list(self._table.find(deleted_at=None, **self._scope))
def create(self, record: dict[str, Any]) -> dict[str, Any]:
now = _now_iso()
record = {**record, **self._scope, "created_at": now, "updated_at": now}
record = {
"deleted_at": None,
"deleted_by": None,
**record,
**self._scope,
"created_at": now,
"updated_at": now,
}
self._table.insert(record)
logger.info(
"Virtual tool created name=%s owner=%s/%s",
@@ -85,9 +96,20 @@ class VirtualToolStore:
logger.debug("Virtual tool updated name=%s changes=%s", name, list(changes))
def delete(self, name: str) -> bool:
deleted = self._table.delete(name=name, **self._scope)
logger.info("Virtual tool deleted name=%s ok=%s", name, deleted)
return bool(deleted)
row = self._table.find_one(name=name, deleted_at=None, **self._scope)
if not row:
return False
self._table.update(
{
"name": name,
"deleted_at": _now_iso(),
"deleted_by": f"{self._owner_kind}:{self._owner_id}",
**self._scope,
},
["name", "owner_kind", "owner_id"],
)
logger.info("Virtual tool soft-deleted name=%s", name)
return True
def tool_schemas(self) -> list[dict[str, Any]]:
return [_schema_for(row) for row in self.list() if row.get("enabled", 1)]