fix(dogfood): document-centric fetch_span + assets.workspace_path semantic doc #150
Reference in New Issue
Block a user
Delete Branch "fix/dogfood-asset-flip-flop-cleanup"
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 결과 #3 (asset table
workspace_pathflip-flop) 의 semantic cleanup. 실용 영향은 minor 였으나 (twin file 의fetch span1곳만 잠재 영향) PR #146 / #149 가 그어 둔 "document-centric lookup" 원칙을 마지막 caller 까지 일관 적용.진단
assets.asset_id= blake3 content hash (PRIMARY KEY) → twin files (동일 content / 다른 path) 가 한 asset row 공유 → UPSERTON CONFLICT(asset_id) DO UPDATE SET workspace_path = excluded가 매 ingest 마다workspace_pathflip-flop.try_skip_unchanged가 document-centric → flip-flop 무관 ✅fetch.rs:193::fetch_span의get_asset_by_workspace_path(&doc.workspace_path)— twin file 의 경우 잘못된 asset row 의media_type으로 PDF/audio 분기 판단 가능 (rare in practice, semantically incorrect)Fix
DocumentStore::get_asset(asset_id: &AssetId) -> Result<Option<RawAsset>>— asset_id 가 PRIMARY KEY 라 flip-flop-immune by construction.crates/kebab-store-sqlite::SqliteStore::get_asset구현 — 기존asset_from_row매퍼 재사용 (drift 없음).fetch.rs::fetch_spanrefactor —get_asset_by_workspace_path(&doc.workspace_path)→get_asset(&doc.source_asset_id)2-step lookup. 이미 fn 안에 doc 있음 (get_document_by_workspace_path호출 후).store.rs::upsert_asset_row) — "last-registered path" semantic 명시 + 미래 reader 가 flip-flop 을 "fix" 하려고 시도하지 않도록 안내.DocumentStore::get_asset_by_workspace_pathtrait method 와 SqliteStore 구현은 유지 — production caller 0이지만 store-sqlite round-trip test 2건이 사용 중. 보수적 선택 (deprecation 또는 후속 제거는 별도 cleanup).변경 (5 파일)
crates/kebab-core/src/traits.rs:get_assettrait method 추가 +AssetIdimportcrates/kebab-store-sqlite/src/documents.rs:get_asset구현 (기존asset_from_row재사용)crates/kebab-app/src/fetch.rs:fetch_span의 lookup refactorcrates/kebab-store-sqlite/src/store.rs: UPSERT doc-comment 갱신 (14 줄, last-registered semantic + 2-step 패턴 가이드)crates/kebab-app/tests/twin_files_fetch_span.rs: 회귀 테스트 (twin_files_fetch_span_uses_correct_asset) — 동일 content 2 md 파일 ingest → 둘 다fetch_span성공 → 재 ingest (flip-flop trigger) → 여전히 둘 다 성공검증
cargo test -p kebab-core --lib→ 57/57cargo test -p kebab-store-sqlite --lib→ 20/20cargo test -p kebab-app --test twin_files_fetch_span→ 1/1 (new)cargo test -p kebab-app --test twin_files_idempotent→ 1/1 (PR #146 invariant)cargo test -p kebab-app --test file_deletion_auto_purge→ 2/2 (PR #148 invariant)cargo test -p kebab-app --test reset_orphans→ 1/1 (PR #149 invariant)cargo test -p kebab-app --test code_ingest_smoke→ 6/6 (PR #142 invariant)cargo test -p kebab-app --lib→ 52/52cargo clippy -p kebab-core -p kebab-store-sqlite -p kebab-app --all-targets -- -D warningsclean영향
get_asset_by_workspace_path보존 (deprecation/제거는 별도).🤖 Generated with Claude Code
회차 1 — APPROVE (선택적 제안 1건)
확인 항목
DocumentStore::get_asset시그니처 (fn get_asset(&self, id: &AssetId) -> anyhow::Result<Option<RawAsset>>)AssetIdimport (crate::ids::{AssetId, ChunkId, DocumentId})WHERE asset_id = ?+asset_from_row재사용 (drift 불가)fetch.rs::fetch_span:get_document→doc.source_asset_id→get_asset2-stepget_asset_by_workspace_path보존 (trait + impl) + NOTE doc-comment 갱신RawAssetvskebab_core::Asset이름 충돌 없음 (pub use asset::RawAsset만 export)정확성
fetch_span은get_document(by doc_id)로doc을 가져온 직후doc.source_asset_id로get_asset을 호출합니다.doc.source_asset_id는 extractor의CanonicalDocument생성 시점에 고정된 PK 참조라 flip-flop과 완전히 무관합니다. 기존 버그 시나리오가 정확히 차단됩니다.테스트
kebab-core --lib: 57/57 ✅kebab-store-sqlite --lib: 20/20 ✅twin_files_fetch_span: 1/1 ✅ (새 회귀 커버리지)twin_files_idempotent: 1/1 ✅file_deletion_auto_purge: 2/2 ✅reset_orphans: 1/1 ✅code_ingest_smoke: 6/6 ✅kebab-app --lib: 52/52 ✅-D warnings): clean ✅선택 제안
get_asset_by_workspace_pathproduction caller 0개 →#[deprecated]어노테이션을 trait method에 붙이면 컴파일을 깨지 않고 미래 caller에게 컴파일 경고를 전달할 수 있습니다. round-trip 테스트 2건에#[allow(deprecated)]만 추가하면 됩니다. 지금 blocking이 아닌 별도 cleanup PR 후보입니다.@@ -0,0 +152,4 @@assert_eq!(report2.errors, 0, "no ingest errors on second run; report={report2:?}");// Re-open app after second ingest and verify span still works on both.let app2 = env.app();테스트 후반부(186–215줄)에서
ingest_with_config를 두 번째로 호출해assets.workspace_pathflip-flop을 강제로 발생시킨 뒤 양쪽 twin 모두fetch_span이 여전히 성공하는지 검증합니다. 이 재-ingest 패스가 바로 버그가 재현되던 시나리오이므로 단순 first-ingest 검증보다 훨씬 가치 있는 회귀 커버리지입니다.@@ -8,7 +8,7 @@ use serde_json::Value;use crate::asset::{RawAsset, WorkspacePath};use crate::chunk::Chunk;get_asset_by_workspace_path는 production caller가 0개입니다.#[deprecated(note = "Twin-file unsafe: assets.workspace_path flip-flops. Use get_asset(&doc.source_asset_id) instead.")]를 trait method에 붙이면 기존 round-trip 테스트 2건에#[allow(deprecated)]가 필요하지만, 컴파일은 유지되면서 미래 caller에게 컴파일 단계에서 경고가 전달됩니다. 지금 block은 아니고, 후속 cleanup PR 후보로만 남겨둡니다.get_asset구현이 기존asset_from_row헬퍼를 그대로 재사용하고 있어 두 쿼리 간 컬럼 목록 drift가 구조적으로 불가능한 점을 확인했습니다. doc-comment(SELECTs in get_asset and get_asset_by_workspace_path must both include all nine columns)까지 갱신돼 있어서 미래 기여자에게도 명확합니다.