(a) embed_with_cache 를 image/pdf/code 핸들러에 배선(markdown 전용→전미디어) — PR1.
(b) ocr/caption derivation_cache 네임스페이스로 OCR/caption 중간 산출물 캐싱 —
버전-캐스케이드 분리 + ollama-vision 비결정성 박제 — PR2.
리서치로 해소: provenance 는 wire/게이트에 없어 캐시 히트 시 재현 불필요(byte-identical
유지) · payload 는 full 구조체 serde(block 레벨까지 byte-identical) · derivation_cache_key_bytes
신설 · paddle ONNX 자산은 이미 레포 번들(다운로드 불필요) → 결정적 게이트 가능.
열린 항목: paddle 게이트용 text-bearing OCR 코퍼스 생성(R2, plan 에서 해소).
endpoint/det_model/rec_model/dict 는 default None 이라 reconcile 가 pdf 블록에
채우지 않는다(None 은 annotated-default 에서 누락). image 만 설정한 이 키를 공유
[ingest.ocr] 로 끌어올리면 resolve_ocr overlay 가 (medium_has(pdf,key)=false 라)
pdf 로 누출돼, pdf OCR endpoint/asset 경로가 v4 와 달라지고 det/rec/dict 는
ingest_config_signature 에 들어가 강제 재색인까지 유발했다. step_4_to_5 에서
Option 키는 pdf 도 명시한 경우에만 hoist 하도록 OPTION_OCR_KEYS 가드 추가.
기존 round-trip 테스트는 before=Config::from_file(v4) 가 이미 마이그레이션+resolve
를 거친 오염값이라 after==before tautology 였음 — pdf.det_model==None 명시 검증
추가 + 비대칭 image-only-endpoint 회귀 테스트 신설.
markdown ingest arm 이 그동안 유일하게 `App::extract_for` extractor
registry 를 우회하고 `kebab_parse_md::{parse_frontmatter, parse_blocks,
build_canonical_document}` free function 을 직접 호출했다 ("the single
biggest asymmetry"). 이를 image/pdf/code 와 동일하게 registry 경유로
통일.
- `kebab-parse-md` 에 `MarkdownExtractor` 신설 — 기존 free function 3종을
동일 순서·동일 인자로 감싸 `bytes → CanonicalDocument` 생산만 담당.
fm_span_end / count_lines_in / build_body_hints 헬퍼도 함께 이식.
- `App.extractors` registry 에 등록 (11 → 12 entry), markdown 이 `supports`
로 발견되도록 첫 entry 로 배치.
- `ExtractContext` 에 `source_id` / `source_trust` 필드 추가 — markdown
frontmatter 가 per-source trust 기본값을 override 하고 그 precedence 가
`parse_frontmatter` *내부*에서 결정되므로 ctx 가 carry 해야 함. 다른
extractor 는 None (post-extract 에서 source_id stamp 유지).
- 핸들러는 추출 stage 만 registry 로 이전 — version stamping / chunking /
embedding / store 는 그대로. IngestItem.warnings 는 pdf/code 처럼
`canonical.provenance` 의 Warning 이벤트에서 도출.
byte-identical 검증: parity gate-ingest (all-markdown 183 doc / 7676 chunk)
CHUNKS / SEARCH / ASK 모두 IDENTICAL. clippy 0, kebab-app + kebab-parse-md
test 전체 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
apply_env 의 KEBAB_* 매치 암을 103개→22개로 정리. 삭제된 암은 런타임에서
바꿀 일이 거의 없는 per-field 튜닝 노브(score_thresh, rrf_k, temperature,
multi_hop_*, nli_threshold, chunker_version, context_tokens 등) — struct
field 와 serde default 는 그대로 유지하므로 TOML 로는 여전히 설정 가능.
유지한 22개: endpoint×3, 모델명/프로바이더×5, 경로×2, 병렬도×2,
청킹 target/overlap×2, OCR 엔진/모델/언어+per-medium enabled×5,
caption enabled×1, search default_k×1, rag prompt_template_version×1.
영향 범위: struct field 삭제 없음(defaults 불변) → search/ask/chunk 출력
byte-identical 확인 (parity gate u2-surface IDENTICAL ✓).
docs: README KEBAB_* 항목을 실제 노출 키 목록으로 교체.
SMOKE.md config 예시를 ~30개 공통 키로 슬림화 + env 설명 갱신.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
Phase 2 Unit 1. OcrCfg(image) 13필드가 PdfOcrCfg(pdf) 와 전부 중복(image 고유
0, pdf 고유 4) + apply_env 에 KEBAB_IMAGE_OCR_*/KEBAB_PDF_OCR_* 27 arm 복제를
제거한다.
- 신규 SharedOcrEngineCfg(13 공유 필드, 전부 Option, default None) = [ingest.ocr].
엔진 설정 단일 출처. image/pdf 블록은 on/off 토글 + override.
- load-time resolution(Config::resolve_ocr, from_file 호출): 공유 필드가 Some 이고
미디어 블록이 그 키 미명시면 concrete OcrCfg/PdfOcrCfg 로 overlay(presence 는
toml::Value 로 판정; 미디어 > 공유 > 내장 default). struct 필드는 그대로 두고
엔진 필드에 #[serde(default)] 만 추가(slim 블록 파싱) → image(gemma4:e4b/1600)
vs pdf(qwen2.5vl:3b/2048) 미디어별 기본값 보존.
- resolver Config::image_ocr()/pdf_ocr() 추가. consumer(kebab-parse-image,
kebab-app build_*_ocr_engine·ingest gate·pdf_ocr_apply·ingest_config_signature)가
전부 경유 → god-struct 직접 read 제거.
- apply_env: 27 arm → 공유 KEBAB_OCR_* 12 arm(image+pdf 동시) + pdf 고유 4 arm +
미디어별 KEBAB_IMAGE_OCR_ENABLED/KEBAB_PDF_OCR_ENABLED.
- step_4_to_5: [ingest.image.ocr] 12 엔진 키를 [ingest.ocr] 로 move_table(enabled
제외). pdf 블록 무손상(reconcile 이 채워 공유 overlay 오염 X). annotated_default
도 동일 통합으로 v5 canonical 형상. CURRENT_SCHEMA_VERSION=5.
- v4→v5 round-trip 테스트(비-default image engine 보존 + pdf 오염 X + 멱등). effective
OCR 바이트 동일 → ingest_config_signature 불변 → 강제 재색인 없음.
검증: clippy --workspace --all-targets 0 / kebab-config·kebab-parse-image·
kebab-parse-pdf·kebab-app 테스트 pass. surface: README [ingest.ocr] 절 + SMOKE
config 블록 + DOGFOOD env + HOTFIXES dated entry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
사용자 필수 제약 반영: 각 수정 단위(PR/phase)마다 코어 기능(md ingest·검색·
single-hop RAG+citation)의 도그푸딩 품질이 직전 baseline 과 반드시 비슷해야 하며
회귀 시 머지 금지. baseline 동결 → per-unit `kebab eval compare` + ingest 출력
byte-diff=0 게이트. 이 게이트가 PR 분할 경계와 완료 정의를 지배.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
기능 추가로 산만해진 표면·동작·내부를 강하게 단순화하는 설계. 단일 사용자·pre-1.0
이점으로 능력은 (거의) 유지하되 조작 표면·코드 구조를 절반 이하로.
핵심:
- Cuts: TUI, multi-turn 세션, legacy RAG v1/v2, search LRU 캐시, candle 임베더,
ingest API 5변종→1 (crate 24→22).
- 스파인 4 crate 재작성: config god-struct→타입 슬라이스, ingest 4193줄 모놀리스→
stage 파이프라인, chunk 중앙 selector, rag 2633줄→합성 stage(순수 query→Answer).
- 표면: config 109→~30, env 97→~25, search 20플래그→~6(--filter).
- 불변식: ingest 출력 byte-identical(재색인 없음) + wire 출력 계약 불변(MCP/스킬 무영향).
brainstorming 7개 결정 기록. frozen contract 부분 supersede.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
R9700 GPU ollama(gemma3:4b + snowflake-arctic-embed2 @ .244)로 rag-v3 vs rag-v4
답변 비교. 타깃 실패 모드(저신뢰 jira 가 권위 wiki 를 wiki 인용 없이 덮어씀)는
두 버전 모두 0/34 — 이 모델/코퍼스에서 재현 안 됨. 품질 지표 전부 표본오차 내 구분
불가(wiki-grounded 32 vs 31, jira-correct 29 vs 28, 거부 4 vs 5). v4 는 무해(회귀
0) + 라벨 프롬프트 도달 확인이나 trust-steering 효과는 실패 모드 미재현으로 미입증.
유일 델타: v4 약간 더 간결(평균 인용 5.14→4.23). HOTFIXES + plan doc 검증 갱신.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
md-heading-v2(PR #209)의 oversize 분할을 PDF 청커에도 적용. v1.1 의 chunk_page 는
문장/문단 경계로만 잘라서 경계 없는 거대 페이지(빽빽한 scanned page 한 줄 OCR)가
통째로 한 청크 → strict 임베더 실패. v1.2 는 2-tier: tier-1(문장/문단 greedy +
overlap) 후 segment 가 max_chunk_tokens 초과면 tier-2 가 공유 text_pieces 로
재분할 → 모든 PDF 청크 ≤ 예산.
- 신규 공유 모듈 crate::oversize (text_pieces/char_pieces/BYTES_PER_TOKEN) — md 와
PDF 가 공유(단일 진실 공급원). md-heading-v2 는 호출만, 출력 byte-identical(md
라벨·동작 불변, parity 테스트 전부 통과).
- PdfPageV1Chunker { max_chunk_tokens } + policy_hash budget fold(md 동형) +
pdf_chunker_from_config(kebab-app). 신규 config 키 없음.
- 분할 조각 chunk_id 는 #c{segment_start}s{i}(미분할 단일 segment 는 bare
#c{segment_start} 유지 → 공통 경우 v1.1 동일).
- Page span: 분할 조각은 부모 segment 의 char 범위를 그대로 가짐(segment-granular,
md 동형). per-piece narrowing 은 text_pieces 의 줄 구분자 소실로 drift 하는
버그라 코드 리뷰 후 제거 — 회귀 테스트
oversize_pdf_page_with_newlines_splits_without_span_drift 로 잠금.
- chunker_version v1.1→v1.2 → 다음 ingest 에서 PDF 1회 자동 재청크(md/code 무영향).
검증: kebab-chunk lib 93 pass(span 회귀 포함), kebab-app green, clippy 0. 도그푸딩
(실험 KB, scanned PDF + arctic@Lemonade, budget 200): 625/625 errors=0,
scanned_page1 1→3 청크·scanned_page2 3→7 청크(둘 다 pdf-page-v1.2), 전 코퍼스
10215 청크 전부 ≤200. 버전 bump 은 follow-up 들과 함께 배치 릴리스에서 일괄.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
`Config::from_file` 에 `validate_chunking()` 추가 — 청킹 budget 의 명백히 깨진
조합을 load 시점에 reject(`validate_sources` 와 동일 패턴, `ConfigInvalid`):
- `target_tokens ≥ 16` (`MIN_CHUNK_TOKENS`)
- `overlap_tokens < target_tokens`
- `max_chunk_tokens ≥ target_tokens`
동기: md-heading-v2(PR #209)의 `max_chunk_tokens` 는 검증이 없어 `0` 같은
오설정이 청커 내부 `budget.max(1)` 클램프에 흡수돼 3-byte 청크 폭주(인덱스
bloat, 무에러)를 냈다 — reviewer 지적. 기존 `target_tokens`/`overlap_tokens`
도 미검증이라 세 필드를 한 번에 floor + 상호 제약으로 막는다.
valid config 무영향(동작·결과 불변), 깨진 config 만 명확한 메시지로 load 실패.
새 config 키·migration·동작 변경 없음 → patch-level. 테스트 6종(defaults pass +
reject 4 + e2e from_file).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
repo CLAUDE.md 에 섞여 있던 머신-specific 내용을 정리:
- 머신 용량/성능(target balloon GB 수치, "disk space is tight", cargo clean
루틴, `-j1` 의 RAM 고갈 사유)을 머신-레벨 global `~/.claude/CLAUDE.md`
(codex/gemini 심링크)로 일반 규칙화해 이전. repo 에는 프로젝트-specific
사실(무거운 dep 링크 → `-j1`, dev/test 프로파일 트림, 머지 후 cargo clean)만
남기고 머신 제약은 global 포인터로 위임.
- stale `/build/dogfood/` 데이터 보관소 섹션 제거 — 이 머신에 `/build` 는
존재하지 않았다(가공 경로). 실제 도그푸딩 store(`large_data/out/kebab-dogfood`,
`kebab-ab`)는 머신-로컬이라 global §디스크 + 프로젝트 메모리로 이전하고,
repo 에는 경로-무관 분류 정책(corpus/<format>/<category> 등)만 유지. 옛
username 경로(`/home/altair823/KnowledgeBase`) 등 stale forbidden 목록 삭제.
- crate 수 표기 정정: "~21"/"18 crates" → 실제 24.
repo CLAUDE.md 는 이제 머신 경로/용량 수치를 하드코딩하지 않는다(전부 global +
프로젝트 메모리 참조). global 은 프로젝트-무관 유지(kebab 미언급).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La