Skip to content

fix(rag): stop hard-deleting shared chunks; wire soft-delete cleanup#520

Open
abdulrafey1 wants to merge 13 commits into
rafey/migrate-permission-routes-to-central-exception-managementfrom
rafey/bugfix-in-rag-chunk-delete
Open

fix(rag): stop hard-deleting shared chunks; wire soft-delete cleanup#520
abdulrafey1 wants to merge 13 commits into
rafey/migrate-permission-routes-to-central-exception-managementfrom
rafey/bugfix-in-rag-chunk-delete

Conversation

@abdulrafey1

Copy link
Copy Markdown
Contributor

Closes: [RAG] ChunkStoreService.delete_by_source deletes chunk rows instead of only their links

What

Stop delete_by_source from hard-deleting DocumentChunk rows shared across documents, and wire every Google Drive delete path to soft-delete its linked Document so the reference-counted RAG cleanup job actually runs.

Changes

  • fix(rag): remove dangerous delete_by_source chunk deletion
  • feat(googledrive): add soft_delete_drive_file helper
  • fix(googledrive): soft-delete linked Document on file delete
  • fix(googledrive): soft-delete linked Documents on folder delete
  • fix(googledrive): soft-delete linked Document on folder sync removal
  • test(googledrive): exercise folder-delete over multiple files

Background

ChunkStoreService.delete_by_source issued DELETE FROM rag_document_chunks WHERE source_name = .... Because ingestion deduplicates chunks by content hash, one DocumentChunk row can be linked (via DocumentChunkLink) to several documents — so hard-deleting on one document's removal would destroy chunks other documents still share. The method had no production callers and bypassed the sanctioned reference-counted cleanup in sparkth/rag/cleanup.py.

It was removed rather than rewritten: an inline unlink-only rewrite (as originally proposed on the issue) would instead leak orphaned chunks forever, because cleanup.py only discovers chunks reachable through soft-deleted documents' links.

The sanctioned path is soft-delete driven — soft-delete a Document, and the cleanup_deleted_documents job removes that document's links and deletes only chunks with no remaining link to a live document (shared chunks preserved). That chain was dormant because the Google Drive delete routes soft-deleted the DriveFile but never its linked Document. This PR adds one helper, soft_delete_drive_file, and routes all three DriveFile soft-delete sites (delete_file, delete_folder, folder-sync removal) through it. Each DriveFile owns its own Document, so soft-deleting the file's own Document is safe — chunk sharing is ref-counted at the chunk-link level by the cleanup job.

How to Test

  1. Run the full backend suite: make test.backend.pytest → all pass (1369).
  2. Focused suites: uv run pytest tests/rag/test_cleanup.py sparkth/plugins/googledrive/tests/test_routes.py sparkth/plugins/googledrive/tests/test_utils.py -v → green (shared-chunk survival + all three wired delete paths + helper unit tests).
  3. Confirm the dangerous method is gone: grep -rn "delete_by_source" sparkth/ tests/ → no matches.
  4. Types + lint: make mypy (strict, clean) and make lint.backend (clean).
  5. Behavioural end-to-end: ingest a Google Drive file so a Document + chunks exist, DELETE the file, confirm Document.is_deleted becomes True, then run make rag-cleanup — the document's links are removed and only chunks with no remaining live-document link are deleted; a chunk shared with another live document survives.

Notes

  • No migration, no breaking change, no new env var, no dependency bump.
  • This branch is stacked on rafey/migrate-permission-routes-to-central-exception-management and targets it (not main); GitHub will auto-close [RAG] ChunkStoreService.delete_by_source deletes chunk rows instead of only their links #456 once the change reaches main.
  • Out of scope (candidate follow-ups): ChunkStoreService.get_sources is also dead code; and the cleanup job (cleanup_deleted_documents) is only exposed via make rag-cleanup — it is not yet wired to a deployed schedule.

This PR description was written with the assistance of an LLM (Claude).

abdulrafey1 and others added 13 commits July 8, 2026 20:36
Introduce HTTP-agnostic whitelist domain exceptions and register their type->status
mappings in the API layer, dropping the inline try/except from the route handlers. The
service now owns the IntegrityError race translation (rollback + log + re-raise). Closes
#494.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
delete_by_source hard-deleted DocumentChunk rows by source_name, which would
destroy chunks that other documents still share (chunks are deduped by content
hash). It had no production callers and bypassed the reference-counted cleanup
in sparkth/rag/cleanup.py, which is the sanctioned deletion path and already
preserves shared chunks. Refs #456.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Soft-deletes a DriveFile and its linked Document so the RAG cleanup job can
collect the file's orphaned chunks while preserving shared ones. Single home
for the delete->cleanup wiring used by the file/folder delete paths. Refs #456.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deleting a drive file now soft-deletes its linked Document, so the RAG cleanup
job collects the file's orphaned chunks instead of leaving them forever. Refs #456.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deleting a folder now soft-deletes each contained file's Document, so the RAG
cleanup job collects their orphaned chunks. Refs #456.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a file disappears from Drive during folder refresh, its Document is now
soft-deleted alongside the DriveFile, so the RAG cleanup job collects its
orphaned chunks. Completes the delete->cleanup wiring. Refs #456.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Strengthen test_delete_soft_deletes_linked_documents to seed two files with
linked Documents so the delete loop's multi-file behavior is actually
verified, matching the test's plural intent. Refs #456.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@abdulrafey1 abdulrafey1 self-assigned this Jul 9, 2026
@abdulrafey1 abdulrafey1 added the bug Something isn't working label Jul 10, 2026
@abdulrafey1
abdulrafey1 force-pushed the rafey/migrate-permission-routes-to-central-exception-management branch from e470e1c to 7d45971 Compare July 10, 2026 09:18

@zaira-bibi zaira-bibi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants