fix(soft-delete): filter trashed rows across read/write paths
Drift-audit Group 3 (soft-delete lifecycle gaps). Trashed rows were leaking into reads and being mutated/resurrected by writes: - update SELECTs now exclude trashed rows: update_milestone, update_project, update_event, and get_milestone_in_project (the latter backs all four milestone routes). Mutating a trashed row silently persisted and reappeared on restore. - MCP get_recent (notes/projects/events) and list_tags now filter deleted_at IS NULL, so trashed items stop surfacing in the agent's bootstrap context and tag counts. - convert_task_to_note clears recurrence_rule + recurrence_next_spawn_at so a demoted note can't spawn children via the (now-live) sweep. - caldav pull skips locally-trashed events (by caldav_uid) instead of resurrecting them via update or creating a duplicate live copy. - trash _cascade now stamps the FULL sub-task subtree (iterative descent), not just direct children, so deeply nested sub-tasks restore as one batch. Test updated for the new descent query. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,7 +38,8 @@ async def get_recent(days: int = 7, limit: int = 25) -> dict:
|
||||
items: list[dict] = []
|
||||
async with async_session() as session:
|
||||
notes = (await session.execute(
|
||||
select(Note).where(Note.user_id == uid, Note.updated_at >= since)
|
||||
select(Note).where(Note.user_id == uid, Note.updated_at >= since,
|
||||
Note.deleted_at.is_(None))
|
||||
.order_by(Note.updated_at.desc()).limit(limit)
|
||||
)).scalars().all()
|
||||
for n in notes:
|
||||
@@ -50,7 +51,8 @@ async def get_recent(days: int = 7, limit: int = 25) -> dict:
|
||||
})
|
||||
projects = (await session.execute(
|
||||
select(Project).where(Project.user_id == uid,
|
||||
Project.updated_at >= since)
|
||||
Project.updated_at >= since,
|
||||
Project.deleted_at.is_(None))
|
||||
.order_by(Project.updated_at.desc()).limit(limit)
|
||||
)).scalars().all()
|
||||
for p in projects:
|
||||
@@ -62,7 +64,8 @@ async def get_recent(days: int = 7, limit: int = 25) -> dict:
|
||||
})
|
||||
events = (await session.execute(
|
||||
select(Event).where(Event.user_id == uid,
|
||||
Event.updated_at >= since)
|
||||
Event.updated_at >= since,
|
||||
Event.deleted_at.is_(None))
|
||||
.order_by(Event.updated_at.desc()).limit(limit)
|
||||
)).scalars().all()
|
||||
for e in events:
|
||||
|
||||
@@ -39,7 +39,7 @@ async def list_tags(limit: int = 50) -> dict:
|
||||
limit = max(1, min(limit, 200))
|
||||
async with async_session() as session:
|
||||
result = await session.execute(
|
||||
select(Note.tags).where(Note.user_id == uid)
|
||||
select(Note.tags).where(Note.user_id == uid, Note.deleted_at.is_(None))
|
||||
)
|
||||
tag_lists = [row[0] for row in result.all()]
|
||||
counts = _aggregate_tag_counts(tag_lists)
|
||||
|
||||
Reference in New Issue
Block a user