fix(kebab-app): p9-fb-23 — Incremental ingest (skip unchanged docs) #98

Merged
altair823 merged 15 commits from fix/p9-fb-23-incremental-ingest into main 2026-05-05 02:22:38 +00:00
Owner

요약

도그푸딩 피드백: "새 문서들이 폴더에 추가되면 ingest 시 변하지 않은 문서는 다시 ingest 하지 않고 변하거나 새로 추가된 문서만 처리하고 싶어."

kebab ingest 가 변경 / 신규 doc 만 처리하도록 개선. 변하지 않은 doc 은 parse / chunk / embed / vector upsert 모두 회피 — 비용 dominator (fastembed) 가 변경된 / 새 doc 에만 발생.

Skip 조건 (4개 모두 만족)

  1. 신규 blake3 == assets.checksum (스캔 중 재계산).
  2. documents.parser_version == 현 active.
  3. documents.last_chunker_version == 현 active.
  4. documents.last_embedding_version == 현 active (None == None 도 match).

위 중 하나라도 mismatch → 정상 path. IngestOpts.force_reingest=true → skip 우회 강제 재처리.

주요 변경

kebab-core

  • IngestItemKind::Unchanged variant 신규 (기존 Skipped 와 의미 분리).
  • IngestReport.unchanged: u32 + 각종 wrapper 카운터.
  • CanonicalDocumentlast_chunker_version: Option<ChunkerVersion> + last_embedding_version: Option<EmbeddingVersion> 필드 추가 (14 construction site 모두 None 으로 backwards-compat).
  • DocumentStore::get_asset_by_workspace_path(&WorkspacePath) -> Option<RawAsset> trait method 신규.

kebab-store-sqlite

  • V006 migration: documents 테이블에 두 column 추가 (nullable).
  • put_document SQL + bindings 확장, get_document row mapper 확장.
  • get_asset_by_workspace_path impl + DRY 한 asset_from_row helper.

kebab-app

  • IngestOpts { progress, cancel, force_reingest } struct (AskOpts 패턴). 신규 entry ingest_with_config_opts. 기존 ingest_with_config_* wrapper 보존.
  • 정상 path: CanonicalDocument 에 현 chunker + embedding version stamp (md / image / pdf 세 flow 모두).
  • 신규 `try_skip_unchanged` helper — 4 조건 검사 + 통과 시 IngestItem { kind: Unchanged, .. } 반환. asset 루프가 Unchanged 에 대해 aggregate.unchanged += 1 + IngestEvent::AssetFinished{result: Unchanged} emit + parse/chunk/embed/vector upsert 모두 회피.

kebab-cli

  • --force-reingest flag 신규 — skip 우회 강제 재처리.

kebab-tui

  • status_line final / aborted 라인 모두 unchanged=N 노출.

Wire schema

  • ingest_report.v1unchanged (integer, minimum 0) 필드 additive — v1 호환 유지.

테스트

  • 신규 ~10 (incremental_ingest 통합 2: unchanged path / force_reingest, sqlite store 단위 4: round-trip version stamps + None stamps + get_asset_by_workspace_path roundtrip + missing path None, app ingest 통합 1: 첫 ingest stamps, IngestOpts default 동작 1, image/pdf re-ingest unchanged 변경 2).
  • 기존 ingest 통합 테스트 3 건 (`ingest_idempotent_on_second_run` 등) 의 assertion 갱신: 동일 bytes 재 ingest 가 `Updated` → `Unchanged` 로 의미 정정.
  • `cargo test --workspace -j 1` → 723 passed, 0 failed.
  • `cargo clippy --workspace --all-targets -- -D warnings` clean.

Spec contract impact

  • design §9 versioning cascade 의 명시적 동작 추가 — parser/chunker/embedder version bump 시 다음 ingest 가 자동으로 모든 doc 을 `Updated` 로 처리. 기존엔 silently 새 version 으로 overwrite (idempotent UPSERT) 였으나 본 변경으로 explicit refresh + 비용 회피 모두 보장.
  • design §3.x IngestReport / §2.4a IngestEvent 에 `Unchanged` variant 추가 (additive, wire v1 호환).

참조 문서

  • Design spec: `docs/superpowers/specs/2026-05-04-p9-fb-23-incremental-ingest-design.md`
  • Implementation plan: `docs/superpowers/plans/2026-05-04-p9-fb-23-incremental-ingest.md` (10 task TDD)
  • Per-task spec: `tasks/p9/p9-fb-23-incremental-ingest.md`
  • Live deviations: `tasks/HOTFIXES.md` `2026-05-04 — p9-fb-23`

Known limitation (deferred)

  • Mtime-based pre-hash skip 미구현 (blake3 streaming 은 매 scan 마다 무조건 발생). 큰 corpus 최적화 후속 가능.
  • Watch-mode (실시간 file change detection) 후속 task.
  • Stale skip risk: 사용자가 외부 도구 (Ollama 모델 swap 등) 로 embedder 바꾸고도 config 의 `models.embedding.id` 갱신 안 하면 silently skip. `kebab doctor` 가 mismatch 감지 → 권고하는 후속 task 가능.
## 요약 도그푸딩 피드백: \"새 문서들이 폴더에 추가되면 ingest 시 변하지 않은 문서는 다시 ingest 하지 않고 변하거나 새로 추가된 문서만 처리하고 싶어.\" `kebab ingest` 가 변경 / 신규 doc 만 처리하도록 개선. 변하지 않은 doc 은 parse / chunk / embed / vector upsert 모두 회피 — 비용 dominator (fastembed) 가 변경된 / 새 doc 에만 발생. ## Skip 조건 (4개 모두 만족) 1. 신규 blake3 == `assets.checksum` (스캔 중 재계산). 2. `documents.parser_version` == 현 active. 3. `documents.last_chunker_version` == 현 active. 4. `documents.last_embedding_version` == 현 active (None == None 도 match). 위 중 하나라도 mismatch → 정상 path. `IngestOpts.force_reingest=true` → skip 우회 강제 재처리. ## 주요 변경 ### `kebab-core` - `IngestItemKind::Unchanged` variant 신규 (기존 `Skipped` 와 의미 분리). - `IngestReport.unchanged: u32` + 각종 wrapper 카운터. - `CanonicalDocument` 에 `last_chunker_version: Option<ChunkerVersion>` + `last_embedding_version: Option<EmbeddingVersion>` 필드 추가 (14 construction site 모두 None 으로 backwards-compat). - `DocumentStore::get_asset_by_workspace_path(&WorkspacePath) -> Option<RawAsset>` trait method 신규. ### `kebab-store-sqlite` - V006 migration: `documents` 테이블에 두 column 추가 (nullable). - `put_document` SQL + bindings 확장, `get_document` row mapper 확장. - `get_asset_by_workspace_path` impl + DRY 한 `asset_from_row` helper. ### `kebab-app` - `IngestOpts { progress, cancel, force_reingest }` struct (`AskOpts` 패턴). 신규 entry `ingest_with_config_opts`. 기존 `ingest_with_config_*` wrapper 보존. - 정상 path: `CanonicalDocument` 에 현 chunker + embedding version stamp (md / image / pdf 세 flow 모두). - 신규 \`try_skip_unchanged\` helper — 4 조건 검사 + 통과 시 `IngestItem { kind: Unchanged, .. }` 반환. asset 루프가 `Unchanged` 에 대해 `aggregate.unchanged += 1` + `IngestEvent::AssetFinished{result: Unchanged}` emit + parse/chunk/embed/vector upsert 모두 회피. ### `kebab-cli` - `--force-reingest` flag 신규 — skip 우회 강제 재처리. ### `kebab-tui` - `status_line` final / aborted 라인 모두 `unchanged=N` 노출. ### Wire schema - `ingest_report.v1` 에 `unchanged` (integer, minimum 0) 필드 additive — v1 호환 유지. ## 테스트 - 신규 ~10 (incremental_ingest 통합 2: unchanged path / force_reingest, sqlite store 단위 4: round-trip version stamps + None stamps + get_asset_by_workspace_path roundtrip + missing path None, app ingest 통합 1: 첫 ingest stamps, IngestOpts default 동작 1, image/pdf re-ingest unchanged 변경 2). - 기존 ingest 통합 테스트 3 건 (\`ingest_idempotent_on_second_run\` 등) 의 assertion 갱신: 동일 bytes 재 ingest 가 \`Updated\` → \`Unchanged\` 로 의미 정정. - \`cargo test --workspace -j 1\` → 723 passed, 0 failed. - \`cargo clippy --workspace --all-targets -- -D warnings\` clean. ## Spec contract impact - design §9 versioning cascade 의 명시적 동작 추가 — parser/chunker/embedder version bump 시 다음 ingest 가 자동으로 모든 doc 을 \`Updated\` 로 처리. 기존엔 silently 새 version 으로 overwrite (idempotent UPSERT) 였으나 본 변경으로 explicit refresh + 비용 회피 모두 보장. - design §3.x IngestReport / §2.4a IngestEvent 에 \`Unchanged\` variant 추가 (additive, wire v1 호환). ## 참조 문서 - Design spec: \`docs/superpowers/specs/2026-05-04-p9-fb-23-incremental-ingest-design.md\` - Implementation plan: \`docs/superpowers/plans/2026-05-04-p9-fb-23-incremental-ingest.md\` (10 task TDD) - Per-task spec: \`tasks/p9/p9-fb-23-incremental-ingest.md\` - Live deviations: \`tasks/HOTFIXES.md\` \`2026-05-04 — p9-fb-23\` ## Known limitation (deferred) - Mtime-based pre-hash skip 미구현 (blake3 streaming 은 매 scan 마다 무조건 발생). 큰 corpus 최적화 후속 가능. - Watch-mode (실시간 file change detection) 후속 task. - Stale skip risk: 사용자가 외부 도구 (Ollama 모델 swap 등) 로 embedder 바꾸고도 config 의 \`models.embedding.id\` 갱신 안 하면 silently skip. \`kebab doctor\` 가 mismatch 감지 → 권고하는 후속 task 가능.
altair823 added 14 commits 2026-05-04 18:28:34 +00:00
도그푸딩 피드백: 변경/신규 doc 만 ingest, 변하지 않은 문서는 skip.

설계 핵심:

- Skip 조건 4 개 (full version cascade): blake3 checksum + parser_version
  + chunker_version + embedding_version 모두 일치 시 parse/chunk/embed/
  vector upsert 회피. 비용 dominator (fastembed) 가 변경된 / 새 doc 에만.
- SQLite V006 migration — `documents` 에 `last_chunker_version` +
  `last_embedding_version` column 추가. 기존 row NULL → 첫 ingest 강제
  재처리 (안전 default).
- `IngestItemKind::Unchanged` enum variant 신규 (기존 `Skipped` 와
  의미 분리 — `Skipped` 는 media-type 필터, `Unchanged` 는 모든 versions
  match).
- `IngestReport` + `AggregateCounts` 에 `unchanged: u32` 필드 추가.
  wire schema additive — v1 호환 유지.
- `--force-reingest` flag — skip 무시하고 강제 재처리.
- TUI status_line final 에 `unchanged=N` 노출 (p9-fb-24 status bar
  dynamic slot 자동 cascade).

Spec status `planned`. 다음 단계: writing-plans skill 로 implementation
plan 작성.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Spec → 10-step plan, TDD per task (failing test → impl → pass → commit).

Tasks:
1. IngestItemKind::Unchanged + IngestReport.unchanged + AggregateCounts.unchanged + wire schema additive
2. CanonicalDocument 에 last_chunker_version + last_embedding_version Option 필드 추가 + 14 callers None 채움
3. V006 migration + SQLite put/get_document round-trip 신규 컬럼
4. DocumentStore::get_asset_by_workspace_path trait + SQLite impl
5. ingest pipeline 이 CanonicalDocument 에 현 chunker/embedding version stamp (no skip yet)
6. IngestOpts { progress, cancel, force_reingest } struct + ingest_with_config_opts entry (AskOpts 패턴)
7. asset 루프 early-skip 블록 (4 조건 match → Unchanged + continue)
8. CLI --force-reingest flag
9. TUI status_line 에 unchanged=N 노출
10. docs sync — README + HANDOFF + HOTFIXES + INDEX + per-task spec

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
reviewer-flagged: aa2a6ea claimed build clean but missed:
- crates/kebab-store-sqlite/tests/ingest_report_snapshot.rs (test fixture)
- crates/kebab-cli/src/wire.rs (test fixture)
- crates/kebab-store-sqlite/snapshots/ingest_report.snapshot.json (snapshot)

All three add `unchanged: 0` (or `\"unchanged\": 0`) to match the new
IngestReport.unchanged field. cargo clippy --workspace --all-targets
-- -D warnings now clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add V006__incremental_ingest.sql to persist last_chunker_version and
last_embedding_version on the documents table. Wire both columns into
upsert_document (INSERT + ON CONFLICT UPDATE) and get_document (SELECT +
row mapper), replacing the previous hardcoded None. Add two round-trip
tests in tests/incremental_ingest.rs covering the set and None cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add `DocumentStore::get_asset_by_workspace_path` trait method to
`kebab-core` and implement it on `SqliteStore` via a private
`asset_from_row` helper. Used by the incremental-ingest skip path to
compare a freshly-computed blake3 checksum against the persisted row
without a full round-trip through `put_asset_with_bytes`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All three ingest flows (markdown, image, pdf) now set
last_chunker_version and last_embedding_version on the CanonicalDocument
before calling put_document, giving Task 7's skip detection the data it
needs on the second run. No skip path is added yet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the per-asset incremental-ingest skip block to all three flows
(markdown / image / pdf). When `IngestOpts::force_reingest = false`
AND the asset's blake3 checksum + parser/chunker/embedding versions
all match the existing DB record, ingest emits
`AssetFinished { result: Unchanged }`, bumps `aggregate.unchanged`,
and skips parse / chunk / embed / vector upsert entirely.

Shared `try_skip_unchanged` helper performs the four checks; per-flow
callers supply the active parser_version + chunker_version + optional
embedding_version. `force_reingest = true` bypasses the skip path so
`incremental_ingest::force_reingest_bypasses_skip` still sees `Updated`.

Tests:
- new `incremental_ingest.rs` covers both paths.
- existing `ingest_idempotent_on_second_run` /
  `re_ingest_image_produces_*` / `re_ingest_identical_pdf_produces_*`
  updated to assert `Unchanged` on identical-bytes re-ingest (the
  pre-task behaviour was `Updated`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `--force-reingest` to the `ingest` subcommand and wires it
through `IngestOpts` into `ingest_with_config_opts`, bypassing the
per-asset early-skip path when set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Updates the terminal (completed) and aborted branches of status_line
to include the unchanged counter alongside new/updated/skipped, so
users can see how many assets were skipped via the incremental-ingest
early-skip path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Also fixes snapshot drift in code-and-table.canonical.snapshot.json
introduced by task 2 (CanonicalDocument gains last_chunker_version +
last_embedding_version fields).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Update user-facing docs to reflect incremental ingest feature:
README ingest row gains incremental skip + --force-reingest description,
HANDOFF adds summary entry, HOTFIXES adds detailed deviation entry,
INDEX links the new per-task spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
claude-reviewer-01 requested changes 2026-05-04 18:30:34 +00:00
claude-reviewer-01 left a comment
Member

회차 1 — 10-task TDD 분해 깔끔. 4 조건 skip 로직이 의도대로 분리 (force_reingest off → checksum → doc lookup → chunker version → embedder version) + 세 flow (md/image/pdf) 모두 동일 패턴. try_skip_unchanged helper 추출로 DRY. tracing::debug 도 strategic placement — 디버깅 시 skip path 진입 추적 가능. 723 워크스페이스 테스트 통과 + clippy clean.

actionable 4건:

  1. asset_from_row 의 positional column 인덱스 → named (row.get("asset_id")) 로 전환 — SELECT 순서 변경 회귀 방지.
  2. byte_len: i64 as u64 cast → u64::try_from(...) 로 silent overflow 방지.
  3. try_skip_unchangedapp.sqlite.* 직접 호출 — DocumentStore trait 경유 권장 (Task 4 가 추가한 trait method 의 일관 사용).
  4. HOTFIXES 약 10 신규 카운트 → 실제 8 신규 + 3 갱신 으로 정정. 영속 기록.

3번은 기존 코드의 store 호출 패턴 (trait 경유 vs concrete) 따르는 게 우선 — 일관성 깨면 도리어 노이즈.

회차 1 — 10-task TDD 분해 깔끔. 4 조건 skip 로직이 의도대로 분리 (force_reingest off → checksum → doc lookup → chunker version → embedder version) + 세 flow (md/image/pdf) 모두 동일 패턴. `try_skip_unchanged` helper 추출로 DRY. tracing::debug 도 strategic placement — 디버깅 시 skip path 진입 추적 가능. 723 워크스페이스 테스트 통과 + clippy clean. actionable 4건: 1. `asset_from_row` 의 positional column 인덱스 → named (`row.get("asset_id")`) 로 전환 — SELECT 순서 변경 회귀 방지. 2. `byte_len: i64 as u64` cast → `u64::try_from(...)` 로 silent overflow 방지. 3. `try_skip_unchanged` 가 `app.sqlite.*` 직접 호출 — `DocumentStore` trait 경유 권장 (Task 4 가 추가한 trait method 의 일관 사용). 4. HOTFIXES `약 10 신규` 카운트 → 실제 8 신규 + 3 갱신 으로 정정. 영속 기록. 3번은 기존 코드의 store 호출 패턴 (trait 경유 vs concrete) 따르는 게 우선 — 일관성 깨면 도리어 노이즈.
@@ -254,3 +272,3 @@
) -> anyhow::Result<IngestReport> {
let progress = progress.as_ref();
let progress = opts.progress.as_ref();
let cancelled = || {

try_skip_unchangedapp.sqlite.get_asset_by_workspace_path / app.sqlite.get_document 으로 concrete SqliteStore 를 직접 부름. 본 PR 의 Task 4 가 이 메서드들을 DocumentStore trait 에 추가했으므로 trait 경유로 부르는 게 일관 (dyn DocumentStore 추상화 보존):

use kebab_core::DocumentStore;
let store: &dyn DocumentStore = &app.sqlite;
store.get_asset_by_workspace_path(...)

현재 single-impl (SqliteStore) 환경에서 동작은 동일하지만 테스트 fake / 다른 백엔드 도입 시 문제. 기존 kebab-app::lib.rs 의 다른 store 호출 패턴을 따라야 (e.g. 기존 app.sqlite.put_document 호출이 trait 우회 vs trait 경유 어느 쪽인지 확인 — 일관성 우선).

`try_skip_unchanged` 가 `app.sqlite.get_asset_by_workspace_path` / `app.sqlite.get_document` 으로 concrete `SqliteStore` 를 직접 부름. 본 PR 의 Task 4 가 이 메서드들을 `DocumentStore` trait 에 추가했으므로 trait 경유로 부르는 게 일관 (`dyn DocumentStore` 추상화 보존): ```rust use kebab_core::DocumentStore; let store: &dyn DocumentStore = &app.sqlite; store.get_asset_by_workspace_path(...) ``` 현재 single-impl (SqliteStore) 환경에서 동작은 동일하지만 테스트 fake / 다른 백엔드 도입 시 문제. 기존 `kebab-app::lib.rs` 의 다른 store 호출 패턴을 따라야 (e.g. 기존 `app.sqlite.put_document` 호출이 trait 우회 vs trait 경유 어느 쪽인지 확인 — 일관성 우선).

asset_from_row 가 positional row.get(0) ~ row.get(8) 로 column 을 인덱스 접근. SELECT 순서 변경 시 silently 잘못된 column 매핑 → runtime data corruption (예: media_type 자리에 source_uri 가 들어감).

named access 권장:

let asset_id: String = row.get("asset_id")?;
let source_uri_raw: String = row.get("source_uri")?;
// ...

rusqlite 의 row.get(name) overload 가 column-name lookup 지원. 비용 무시 (HashMap lookup), SELECT order 와 row mapping 디커플링.

같은 패턴이 document_row_from_sql 등 기존 helper 에 적용돼 있는지 확인 — 일관성 유지 위해 이 파일의 다른 row mapper 패턴 따라야.

`asset_from_row` 가 positional `row.get(0)` ~ `row.get(8)` 로 column 을 인덱스 접근. SELECT 순서 변경 시 silently 잘못된 column 매핑 → runtime data corruption (예: media_type 자리에 source_uri 가 들어감). named access 권장: ```rust let asset_id: String = row.get("asset_id")?; let source_uri_raw: String = row.get("source_uri")?; // ... ``` rusqlite 의 `row.get(name)` overload 가 column-name lookup 지원. 비용 무시 (HashMap lookup), SELECT order 와 row mapping 디커플링. 같은 패턴이 `document_row_from_sql` 등 기존 helper 에 적용돼 있는지 확인 — 일관성 유지 위해 이 파일의 다른 row mapper 패턴 따라야.

byte_len: i64 as u64 cast 가 음수 입력에 대해 silently overflow (음수 → very large u64). SQLite 가 negative 를 저장할 일 없지만 corrupt DB 또는 외부 도구로 수정된 경우 panic 보다 더 위험한 silent corruption.

안전한 cast:

let byte_len: i64 = row.get("byte_len")?;
let byte_len = u64::try_from(byte_len)
    .map_err(|e| rusqlite::Error::FromSqlConversionFailure(
        4, rusqlite::types::Type::Integer, Box::new(e)))?;

첫 번째 nit (named column access) 와 함께 적용하면 둘 다 동일 row mapper 안에서 정합.

`byte_len: i64 as u64` cast 가 음수 입력에 대해 silently overflow (음수 → very large u64). SQLite 가 negative 를 저장할 일 없지만 corrupt DB 또는 외부 도구로 수정된 경우 panic 보다 더 위험한 silent corruption. 안전한 cast: ```rust let byte_len: i64 = row.get("byte_len")?; let byte_len = u64::try_from(byte_len) .map_err(|e| rusqlite::Error::FromSqlConversionFailure( 4, rusqlite::types::Type::Integer, Box::new(e)))?; ``` 첫 번째 nit (named column access) 와 함께 적용하면 둘 다 동일 row mapper 안에서 정합.
@@ -17,0 +22,4 @@
- SQLite V006 migration — `documents``last_chunker_version` + `last_embedding_version` TEXT (nullable) 추가. 기존 row 는 NULL → 첫 번째 ingest 시 항상 mismatch → 강제 재처리 (안전 default).
- `kebab-core::IngestItemKind::Unchanged` variant 신규 (기존 `Skipped` 와 의미 분리: `Skipped` = media-type 필터, `Unchanged` = 모든 versions match).
- `IngestReport.unchanged: u32` + `AggregateCounts.unchanged: u32` 신규. wire schema `ingest_report.v1``unchanged` 필드 additive (v1 호환 유지).

테스트 카운트 "약 10 신규" 가 실제와 맞지 않습니다.

  • crates/kebab-app/tests/incremental_ingest.rs 신규 2
  • crates/kebab-app/tests/ingest_lexical.rs 신규 2 (ingest_with_config_opts_default_*, ingest_stamps_chunker_version_*)
  • crates/kebab-store-sqlite/tests/incremental_ingest.rs 신규 4 (round-trip stamps × 2 + get_asset_by_workspace_path × 2)
  • 합 = 8 신규

별도로 image_pipeline.rs / pdf_pipeline.rs / ingest_lexical.rs::ingest_idempotent_on_second_run 3 건은 assertion 만 갱신 (Updated → Unchanged) — 신규 테스트가 아닌 갱신.

"약 10 신규" → "8 신규 + 3 기존 갱신" 으로 정정 권장. 영속 기록 정확도.

테스트 카운트 "약 10 신규" 가 실제와 맞지 않습니다. - `crates/kebab-app/tests/incremental_ingest.rs` 신규 2 - `crates/kebab-app/tests/ingest_lexical.rs` 신규 2 (`ingest_with_config_opts_default_*`, `ingest_stamps_chunker_version_*`) - `crates/kebab-store-sqlite/tests/incremental_ingest.rs` 신규 4 (round-trip stamps × 2 + get_asset_by_workspace_path × 2) - 합 = **8 신규** 별도로 `image_pipeline.rs` / `pdf_pipeline.rs` / `ingest_lexical.rs::ingest_idempotent_on_second_run` 3 건은 assertion 만 갱신 (Updated → Unchanged) — 신규 테스트가 아닌 갱신. "약 10 신규" → "8 신규 + 3 기존 갱신" 으로 정정 권장. 영속 기록 정확도.
altair823 added 1 commit 2026-05-04 18:33:51 +00:00
claude-reviewer-01 approved these changes 2026-05-04 18:34:36 +00:00
claude-reviewer-01 left a comment
Member

회차 2 — 회차 1 의 4 건 모두 수렴.

  1. asset_from_row named column access (9 column) + doc 코멘트 정정.
  2. byte_lenu64::try_from 안전 cast + FromSqlConversionFailure 매핑.
  3. try_skip_unchanged trait 미전환 결정: 기존 kebab-app/src/lib.rs 의 store 호출 (lines 586/634/772/1635) 모두 concrete app.sqlite.* 패턴이라 일관성 우선 — 합리.
  4. HOTFIXES 카운트 8 신규 + 3 갱신 으로 file-level breakdown 까지 정확.

10-task TDD 분해 + skip 4 조건 cascade + 세 flow (md/image/pdf) 통일 + tracing observability + force_reingest 우회 — 회귀 표면 잘 분리됨. 723 워크스페이스 테스트 통과 + clippy clean. 비용 dominator (fastembed) 가 변경된 / 새 doc 에만 발생 — 사용자 도그푸딩 피드백의 핵심 해소.

머지 동의.

회차 2 — 회차 1 의 4 건 모두 수렴. 1. `asset_from_row` named column access (9 column) + doc 코멘트 정정. 2. `byte_len` 의 `u64::try_from` 안전 cast + `FromSqlConversionFailure` 매핑. 3. `try_skip_unchanged` trait 미전환 결정: 기존 `kebab-app/src/lib.rs` 의 store 호출 (lines 586/634/772/1635) 모두 concrete `app.sqlite.*` 패턴이라 일관성 우선 — 합리. 4. HOTFIXES 카운트 `8 신규 + 3 갱신` 으로 file-level breakdown 까지 정확. 10-task TDD 분해 + skip 4 조건 cascade + 세 flow (md/image/pdf) 통일 + tracing observability + force_reingest 우회 — 회귀 표면 잘 분리됨. 723 워크스페이스 테스트 통과 + clippy clean. 비용 dominator (fastembed) 가 변경된 / 새 doc 에만 발생 — 사용자 도그푸딩 피드백의 핵심 해소. 머지 동의.
@@ -478,0 +519,4 @@
let media_type_json: String = row.get("media_type")?;
let byte_len: i64 = row.get("byte_len")?;
let checksum_raw: String = row.get("checksum")?;
let storage_kind: String = row.get("storage_kind")?;

named-column 전환 깔끔. 9 개 column 모두 schema 이름으로 표기 — SELECT 순서 변경 시 자동 자기-도큐먼트. doc 코멘트도 "column names are self-documenting" 으로 정정해 좋음.

named-column 전환 깔끔. 9 개 column 모두 schema 이름으로 표기 — SELECT 순서 변경 시 자동 자기-도큐먼트. doc 코멘트도 "column names are self-documenting" 으로 정정해 좋음.
@@ -478,0 +555,4 @@
source_uri,
workspace_path,
media_type,
byte_len: u64::try_from(byte_len)

u64::try_from(byte_len) + FromSqlConversionFailure 매핑 → silent overflow 방지. inline 코멘트가 "index parameter unused but type requires number" 명시 — rusqlite Error variant 의 history quirk 를 explainer 로 남긴 게 후속 reader 친화.

`u64::try_from(byte_len)` + `FromSqlConversionFailure` 매핑 → silent overflow 방지. inline 코멘트가 "index parameter unused but type requires number" 명시 — rusqlite Error variant 의 history quirk 를 explainer 로 남긴 게 후속 reader 친화.
altair823 merged commit 2319206522 into main 2026-05-05 02:22:38 +00:00
altair823 deleted branch fix/p9-fb-23-incremental-ingest 2026-05-05 02:22:39 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: altair823-org/kebab#98