fix(dogfood): auto-purge stored docs for filesystem-deleted files #148
Reference in New Issue
Block a user
Delete Branch "fix/dogfood-file-deletion-auto-purge"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
요약
Multi-root dogfooding 결과 추가 issue —
rm a.md후kebab ingest가 stored doc/chunks/embeddings 를 그대로 두어 search 에 ghost citation 노출. 사용자 결정: "A만 fix (file deletion 자동 purge), config include scope 좁아짐은 explicit 명령으로".동작 차이
rm a.md→ ingestincludescope 좁아짐 (file 은 fs 에 살아있음)kebab reset명령 필요)filesystem 존재 확인이 두 케이스를 구분 — 안전 보장.
변경
kebab-core::DocumentStoretrait:all_workspace_paths()메서드 추가 (orphan reconciliation 용)kebab-store-sqlite::SqliteStore::all_workspace_paths구현 (SELECT workspace_path FROM documents)kebab-store-sqlite::purge_deleted_workspace_path(path) -> Vec<ChunkId>: doc + cascade chunks/blocks/embedding_records 삭제,'copied'면 storage 파일 best-effort 삭제, twin-file 보호 (SELECT COUNT(*) FROM documents WHERE asset_id = ?가 1 이상이면 asset row 보존)kebab-app::sweep_deleted_files: walker scan 후 per-asset loop 전 호출.stored_paths - scanned_paths의 각 path 에 대해 fs existence 확인 → 없을 때만 purge. vector store 가 wired 됐으면 chunk_ids 로 vector delete.corpus_revisionbump 으로 search cache invalidation.IngestReport.purged_deleted_files: u32필드 추가 (additive,#[serde(default)]로 back-compat).purged N표기 (count > 0 일 때만).회귀 테스트 (2건 추가)
file_deletion_auto_purge: 2 files ingest → 1 file 삭제 → re-ingest →purged_deleted_files=1, deleted file 이kebab list docs에서 사라짐, 그 content 검색 시 hit 없음.include_scope_narrowing_does_not_purge: 2 files ingest → config include 좁힘 (file 들 fs 에 살아있음) → re-ingest →purged_deleted_files=0. 사용자 데이터 보호 보장.검증
cargo test -p kebab-core --lib→ 57/57cargo test -p kebab-store-sqlite --lib→ 20/20cargo test -p kebab-app --lib→ 51/51cargo test -p kebab-app --test file_deletion_auto_purge→ 2/2 (new)cargo test -p kebab-app --test code_ingest_smoke→ 6/6 (regression 없음)cargo test -p kebab-app --test twin_files_idempotent→ 1/1 (regression 없음 — twin-file asset 보호 정상)cargo clippy --all-targets -- -D warningsclean영향
IngestReport.purged_deleted_files신규 필드,#[serde(default)]라 기존 consumer ���영향).🤖 Generated with Claude Code
회차 1 — 전반 설계와 테스트 커버리지는 견고하나, fs 존재 확인 로직에 데이터-안전성 버그 1건을 발견했습니다.
요약: sweep 로직·twin-file COUNT 순서·chunk_ids 수집 순서·vector store None 처리·corpus_revision 조건부 bump 모두 정확합니다. 테스트 2건(file_deletion_auto_purge + include_scope_narrowing_does_not_purge), 11개 파일 변경, snapshot 재베이킹, wire 필드 backward-compat — 모두 확인 완료.
필수 수정 1건:
abs.exists()는fs::metadata().is_ok()와 동일하여, 파일에 접근 권한이 없는 경우(NFS permission denied, 소유자 변경 등)false를 반환해 존재하는 파일을 purge합니다.try_exists().unwrap_or(true)로 교체해야 합니다.선택 수정 1건: CLI 출력에서
purged토큰이errors뒤에 위치해 순서가 다소 어색합니다.[안전성 — 중요]
abs.exists()는fs::metadata(self).is_ok()와 동일합니다. 파일에 읽기 권한이 없는 경우 (NFS permission denied, 소유자 변경 등)metadata()가Err를 반환하여exists()가false를 돌려줍니다 — 즉 파일이 디스크에 존재함에도 purge가 실행됩니다.보수적 접근이 필요한 이 코드에서는
try_exists().unwrap_or(true)를 써야 합니다:try_exists()는 1.63 stable부터 사용 가능하며 러스트 에디션 2024과 무관합니다.unwrap_or(true)덕분에 FS 오류 시 '파일 존재 가정' 원칙이 성립합니다.[설계 칭찬] non-fatal sweep 설계 (개별 파일 purge 실패 시 warn 로깅 후 continue)가 적절합니다. 단일 파일 DB 오류가 전체 ingest를 막지 않고, vector 삭제 실패 시 'orphan vector는
kebab reset --vector-only로 정리 가능'이라는 escape hatch도 명시되어 있습니다.@@ -0,0 +44,4 @@std::fs::write(&b_path, "// file b\nfn bravo() {}\n").unwrap();// First ingest — both must be New.let first = ingest_with_config_opts([칭찬]
include_scope_narrowing_does_not_purge테스트가 설계 제약 조건을 정확히 검증합니다.b_narrow.rs가 여전히 디스크에 존재하는 상태로 include glob만 좁혀서 두 번째 ingest를 실행한 뒤purged_deleted_files == 0을 단언하고, store에서도b_narrow.rs가 유지됨을 직접 확인합니다. 이 테스트가 존재하는 것만으로도 '설계 불변식이 회귀되지 않는다'는 신뢰를 줍니다.[Nit]
purged토큰이errors뒤에 붙어서 출력 순서가 약간 어색합니다:purged는 '처리 결과 카운트'에 가까우므로errors앞에 놓는 게 더 자연스럽습니다:필수는 아니지만 사용자 출력 가독성 측면에서 고려해볼 만합니다.
[칭찬] chunk_ids를 CASCADE DELETE 이전에 수집하는 순서 (
SELECT chunk_id→DELETE FROM documents→ vector 삭제)가 올바릅니다. FK CASCADE가 먼저 실행되면 chunk_ids를 잃어버리므로 이 순서가 핵심입니다.drop(stmt)호출로 statement를 명시적으로 닫고 DELETE를 진행하는 것도 좋은 방어 코드입니다.[칭찬] twin-file protection의 COUNT-after-DELETE 순서가 정확합니다.
DELETE FROM documents WHERE doc_id = ?실행 후에SELECT COUNT(*) FROM documents WHERE asset_id = ?를 조회하므로, 이 문서 자신은 이미 제거된 상태에서remaining_refs를 세게 됩니다. 같은 asset을 공유하는 다른 문서가 없을 때만remaining_refs == 0이 되어 asset row가 삭제됩니다. 정확한 구현입니다.회차 2 — 회차 1 blocking 지적(data-safety) 완전 반영. APPROVE.
round 1 에서 제기한 핵심 버그( 가 EACCES 등 FS 오류 시 false 를 반환해 파일을 wrongful purge 할 수 있음)를 로 교체해 보수적 처리로 확정했습니다. 인라인 코드 주석도 WHY 를 명확히 설명하도록 갱신되어 향후 유지보수에서 실수할 여지를 줄였습니다.
cosmetic nit (CLI summary 에서 purged 가 errors 뒤에 위치) 은 반영되지 않았으나, errors 가 먼저 오는 현재 순서도 기능적으로 문제없고 별도 커밋 비용이 수정 효익보다 크다는 판단으로 이해합니다 — 수용 가능합니다.
테스트 재확인:
인라인에 문서 불일치 nit 1건 남깁니다 (안전 무관, 후속 PR 처리 가능).
nit: 함수 수준 doc-comment 3번 항목(line ~1486)에
fs::exists()라는 구버전 표현이 남아 있습니다. 실제 구현은 이미try_exists().unwrap_or(true)로 교체됐으므로, doc-comment 도try_exists().unwrap_or(true)(또는 "보수적 존재 확인")로 맞추면 좋겠습니다. 안전에 영향 없는 문서 불일치이므로 후속 PR 에서 처리해도 무방합니다.