Files
kebab/docs/components
altair823 b60148b123 chore: PR #240 회차 2 리뷰 반영 — DOGFOOD 스니펫이 조용히 무시되는 옛 키였다
가장 나쁜 것부터.

1. 회차 1 에서 DOGFOOD §1.2 에 새로 넣은 config 스니펫이 `[image.ocr]` 였다.
   옛 키 자동 이관은 파일의 `schema_version` 이 5 보다 낮을 때만 돌고
   (`kebab-config/src/lib.rs` 의 `unwrap_or(1)` 게이트), `deny_unknown_fields`
   가 없어서 현행 v5 파일에 붙여넣으면 serde 가 통째로 버리고 경고도 안 낸다.
   `kebab doctor` 도 "config up to date" 라고 답한다. 격리 KB 로 확인: v5 +
   옛 키 → OCR 단계 자체가 없음(`parse 1ms · chunk 723ms · embed 0ms`),
   `[ingest.image.ocr]` 로 바꾸면 `ocr(ppocrv5-mobile-kor)` 가 돈다.
   그래서 같은 커밋이 추가한 1.2.f 가 no-op 이 될 뻔했다 — OCR 이 꺼지면 모든
   이미지가 "본문 0 자 + provenance 깨끗" 으로 나오는데, 이건 문서가 "글자
   없는 사진이라 정상" 이라고 읽으라고 적어 둔 바로 그 모양이다. 키를 고치고,
   같은 함정을 다음 사람이 안 밟게 §1.4 가 쓰는 형식의 주의 문단을 붙였다.
   1.2.f 에도 "진행 출력에 ocr 단계가 찍히는지 먼저 보라" 를 넣었다.

2. HOTFIXES 의 "v0.33.0 이전에 색인된" 이 한 릴리스 어긋났다. v0.33.0 태그가
   이 PR 의 base(5596d41) 자체이고 그 커밋이 `err={}` 를 담고 있다. 게다가
   스캔 PDF 페이지 렌더링(#232)이 v0.33.0 에서 처음 나갔으므로, 이 버그가
   망칠 수 있었던 스캔본은 사실상 전부 그 한 릴리스가 만든 기록이다. 문장대로
   감사하면 첫 LIKE 만 돌려 0 건을 보고 정반대 결론에 닿는다.

3. 상수 주석이 아직도 테스트가 위쪽 방향까지 잡는다고 말했다. 회차 1 은
   문장을 길게 바꿨을 뿐 주장은 그대로 뒀다. 위쪽은 `const _` 가 잡고 테스트는
   못 잡는다는 걸 그대로 적었다.

4. `{e:#}` 를 고정하는 테스트가 없었다. mock 오류가 단층이라 anyhow 가 `{}` 와
   `{:#}` 에서 똑같이 찍었고, 그래서 되돌려도 통과했다. mock 을 실제와 같은 두
   층으로 만들고 안쪽 원인을 단언한다. `{}` 로 되돌리면 실패하는 것 확인.

나머지:
- `pdf_ocr_apply.rs:475` 가 회차 1 반영 커밋 자신에 밀려 어긋났다 (같은 종류가
  한 PR 에서 두 번). 줄 번호를 빼고 함수명으로 고정했다.
- const-assert 주석이 안 잰 구간(17~39)을 잰 것처럼 말했다. 게다가 17 로
  올리면 새로 버려지는 건 폭 16 — 직접 비어 있다고 잰 폭이다.
- "재처리 대상은 PDF 전부" 정정이 HOTFIXES 에서 멈춰 ARCHITECTURE 와 컴포넌트
  README 에는 아직 "스캔본" 이라고 적혀 있었다.
- 컴포넌트 README 의 `engine_id() / run(...)` 은 존재한 적 없는 시그니처다.
  산문과 mermaid 다이어그램 양쪽을 실제 trait 에 맞췄다.
- §1.2 verify 의 `err=rec session run` 은 이미지 경로가 안 내는 문자열이다
  (KB 전체 집계용을 이미지 전용 절에 복사해 왔다).
- 영향 문서 집계 쿼리에 실행 시점이 빠져 있었다. bump 가 documents 행을
  purge 하므로 한 번 색인한 뒤에는 항상 0 이 나온다. "색인 전에 세라" 와,
  이미 색인했을 때 쓸 `pdf_ocr_events` 대안을 적었다.
- Gitea PR 본문을 현재 브랜치에 맞게 다시 썼다.

알고 남긴 것: 커밋 d6654ab 메시지의 "실제 하한은 4 였다" 는 틀렸다 (하한은 5,
4 는 가장 큰 실패 폭 — 같은 메시지 두 문단 뒤와 자기모순). 코드와 문서는 전부
정확하다. 고치려면 리뷰 중인 브랜치에 강제 푸시가 필요해서 두었다.

검증: 워크스페이스 1301 passed / 0 failed, clippy -D warnings 무경고.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 18:08:38 +09:00
..

Components

책임 단위 그룹별 contributor 향 상세. 사용자 향 grand picture 는 README.md, 상위 crate 의존 그래프 + 디렉토리 구조 + locked-in 결정은 docs/ARCHITECTURE.md, 진척도는 HANDOFF.md, per-task spec 은 tasks/INDEX.md.

각 그룹 페이지는 동일 템플릿: 구성 crate / 구조 다이어그램 / data flow 다이어그램 / 주요 type / 외부 의존 / 핵심 결정 (HOTFIXES + spec 의 "왜") / 관련 spec / HOTFIXES.

그룹 wiring

12 그룹 간 호출/의존 흐름. 점선 = Foundation 이 모두에 의존. UI 는 App facade 만 통해 다른 그룹 도달.

flowchart TB
    subgraph Surfaces ["UI surface"]
        UI["UI<br/>(cli + tui)"]
    end
    subgraph Orchestration ["orchestration"]
        AppFacade["App facade<br/>(kebab-app)"]
        RAG["RAG"]
        Eval["Eval"]
    end
    subgraph IngestPipe ["ingest pipeline"]
        Source["Source"]
        Parse["Parse"]
        NormChunk["Normalize+Chunk"]
    end
    subgraph IndexQuery ["index + retrieval"]
        Embed["Embed"]
        Store["Store"]
        Search["Search"]
    end
    subgraph Generation ["generation"]
        LLM["LLM"]
    end
    Foundation["Foundation<br/>(core + parse-types + config)"]

    UI --> AppFacade
    AppFacade --> Source --> Parse --> NormChunk
    NormChunk --> Store
    NormChunk --> Embed --> Store
    AppFacade --> Embed
    AppFacade --> Search
    Search --> Store
    Search --> Embed
    AppFacade --> RAG
    RAG --> Search
    RAG --> LLM
    RAG --> Store
    AppFacade --> Eval
    Eval --> AppFacade
    Eval --> Store

    Foundation -.-> Source
    Foundation -.-> Parse
    Foundation -.-> NormChunk
    Foundation -.-> Embed
    Foundation -.-> Store
    Foundation -.-> Search
    Foundation -.-> LLM
    Foundation -.-> RAG
    Foundation -.-> AppFacade
    Foundation -.-> UI
    Foundation -.-> Eval

그룹 목록

그룹 역할 페이지
Foundation 도메인 type + 설정 + parser IR. 모든 crate 의 zero-dep 토대. foundation/
Source 워크스페이스 walk + .kebabignore + BLAKE3 checksum → RawAsset. source/
Parse bytes → ParsedBlock (md) 또는 CanonicalDocument (pdf/image). OCR + caption 어댑터. parse/
Normalize+Chunk ParsedBlock → CanonicalDocument lift (markdown only) + 모든 미디어 → Vec<Chunk> (md/pdf 변종 chunker). normalize-chunk/
Store SQLite (V001-V005, FTS5, jobs, chat sessions) + LanceDB (per-model vector 테이블) two-phase write. store/
Embed Embedder trait + fastembed-rs 어댑터 (multilingual-e5-small 384d). embed/
Search lexical (FTS5 BM25) + vector (ANN) + hybrid (RRF) — Retriever trait 3 변종. search/
LLM LanguageModel trait + Ollama HTTP 어댑터 (gemma4:e4b default). streaming + cancel-safe. llm/
RAG retrieve → gate → pack → generate → cite-validate → persist 9 stage pipeline. multi-turn 지원. rag/
App facade kebab-app — 모든 UI binary 의 유일한 진입점. *_with_config companion 패턴. app-facade/
UI kebab-cli (--json wire envelope) + kebab-tui (4 패널 + Mode machine + cheatsheet). ui/
Eval golden query 회귀 평가 + run-vs-run compare. must_contain rule-based. eval/

진입 가이드

처음 읽는다면 (의존성 따라 bottom-up):

  1. Foundation — 다른 모든 페이지가 참조하는 type 정의. AssetId / DocumentId / Chunk / Citation / 5 version 등.
  2. Source → Parse → Normalize+Chunk → Store — ingest pipeline 흐름.
  3. Embed → Search — retrieval.
  4. LLM → RAG — generation.
  5. App facade — 위 전부 wiring.
  6. UI — facade 위.
  7. Eval — 독립.

특정 작업 별 진입:

  • 새 미디어 타입 추가 (예: epub) — Parse → Normalize+Chunk → Store (chunker_version) → App facade (라우팅).
  • 새 retrieval 모드 — Search → App facade (mode dispatch) → UI (--mode flag).
  • 새 LLM 어댑터 — kebab-core 의 LanguageModel trait 구현 + 새 kebab-llm-<provider> crate → App facade (config provider switch).
  • TUI 신규 pane — UI 만. Mode + Theme + InputBuffer 재사용.

다이어그램 제약

각 그룹 페이지의 다이어그램은 mermaid (Gitea / GitHub 자동 렌더). 페이지 별 최소 2개 — 구조 (type/trait/struct 관계) + data flow (입출력 흐름). 실제 코드와 시그니처 일치 — 작성 시 crates/kebab-<name>/src/lib.rs 직접 읽음 (추측 금지).