ponytail-audit 의 작은 over-engineering 잔여 3건 (output/동작 불변):
1. FusionPolicy 1-arm enum 제거 — kebab-search 내부 전용(외부 importer 0)이던
`enum FusionPolicy { Rrf { k_rrf } }` 를 `HybridRetriever.k_rrf: u32` 로 inline.
parse_fusion 은 u32 반환. RRF 수식 `Σ 1/(k_rrf+rank)` byte-identical.
2. dual YAML 통일 — serde_yaml(0.9) 를 maintained fork serde_yaml_ng(0.10) 로 일원화.
k8s manifest chunker + eval golden loader 2 site 마이그레이션(API drop-in),
workspace dep 에서 serde_yaml 제거. parse 결과 불변.
3. NliVerifier::hypothesis_token_count 의 `Ok(0)` default 제거 → required.
prod OnnxNliVerifier 가 이미 override 라 동작 불변, MockNliVerifier 에 explicit 추가.
문서 동기화: search/eval/parse component README + ARCHITECTURE 의 FusionPolicy/
serde_yaml 참조를 현 상태로 갱신. net −37줄. 적대적 검증 2렌즈(output-identity +
completeness-build) 통과, 영향 crate 테스트 + clippy -D warnings 클린.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
kebab-embed/kebab-llm 은 "새 type 없음"을 자처한 순수 re-export 셸이었다
(trait 은 이미 kebab-core 소유, mock + test helper 만 보유). "kebab-core
재구성 시 안정 surface" 라는 명분은 1인 RAG 엔 speculative YAGNI. 흡수:
- kebab-core 에 default-OFF `mock` feature + src/mock.rs (MockEmbedder,
MockLanguageModel, assert_vector_shape/assert_unit_norm/assert_finish_chunk
을 kebab_core:: → crate:: import 만 바꿔 verbatim 이동).
- production import 2곳(kebab-embed-local, kebab-llm-local) +
test import 다수(search/rag/parse-image/embed-local) 를 kebab_core 로 repoint.
mock 쓰는 crate 는 dev-dep 에 features=["mock"] (default 빌드 무영향).
- shim 자체 테스트: mock 동작 테스트는 kebab-core/tests/ 로 이동,
reexports.rs(셸 재수출 테스트)는 폐기.
- crates/kebab-embed, crates/kebab-llm 삭제 + workspace member/deps 정리.
- ARCHITECTURE/HANDOFF/component README 의 crate 그래프·표·rationale 갱신
(22 → 20 crates). llm-local 의 broken intra-doc link 2건도 정리.
trait surface·동작 불변 (test-only + import-rename). workspace build 는 mock
default-OFF 라 mock 코드 미컴파일. 적대적 검증 3렌즈(build-test-integrity +
behavior-identity[mock byte-identical] + dead-crate-completeness) 통과,
clippy --workspace -D warnings 클린.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
리뷰 nit: collapsed AST chunk arm 의 .context(format!(...)) 는 성공 경로에서도
String 을 매번 할당. .with_context(|| format!(...)) 로 에러 경로에만 alloc.
(나머지 nit — 일반화된 error-text "CodeAstV1Chunker" — 은 리뷰어가 no-action:
테스트가 새 substring 으로 맞춰져 있고 7개 arm 과 .context prefix 통일됨.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
code_{c,cpp,go,java,js,kotlin,python,rust,ts}_ast_v1.rs 9개는 struct 이름과
const VERSION_LABEL 문자열만 다른 byte-identical 복붙이었다 (chunker 는
tree-sitter 를 안 쓰고 lang 은 SourceSpan::Code 데이터에서 흘러나옴). 하나로
통합:
- 신규 CodeAstV1Chunker { version_label: &'static str } + for_lang(lang) 생성자.
9개 lang → 기존 VERSION_LABEL 문자열을 verbatim 매핑 (rust→code-rust-ast-v1,
typescript→code-ts-ast-v1, javascript→code-js-ast-v1, ...). chunker_version()
이 라벨을 그대로 반환 → chunk_id (id_for_chunk) byte-identical → 재인덱싱·
버전 캐스케이드 0.
- chunk/policy_hash/make_chunk/split_oversize 는 code_rust_ast_v1.rs 에서 verbatim
이식. 인라인 유닛 테스트 1세트 + 9-lang 라벨 매핑 테스트 추가.
- 9개 src 파일 삭제, lib.rs 9 mod+pub use → 1.
- ingest.rs 의 두 디스패치(chunker_version 스탬프 ~2700 + .chunk() ~3298)에서
9개 AST arm 을 단일 arm 으로 collapse, for_lang(code_lang) 사용. 비-AST
chunker(K8s/Dockerfile/Manifest/TextParagraph) arm 은 무손상.
byte-identical 증명: 9개 골든 스냅샷 테스트(tests/code_*_ast_snapshot.rs)가
assertion/expected-data 수정 0으로 통과 — construction/import 줄만 변경.
net −3030줄 (20파일 +143/−3173). 적대적 검증 3렌즈(byte-identical-proof +
version-string-integrity[9 라벨 old==new] + completeness) 통과, clippy -D warnings 클린.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
in-process LRU search cache 는 spine 재작성(#214)에서 이미 삭제됐는데
그 비계(scaffold)만 남아 있었다. 잔존물 제거:
- App::search/search_uncached 붕괴 (search() 는 1줄 위임자였음)
- search_uncached_with_config facade + 그 테스트 삭제
- 죽은 `search --no-cache` / `search --explain` CLI 플래그 제거
(ask --explain 은 live — 건드리지 않음)
- 안 읽히던 RagCfg.explain_default config 필드 + fixture 제거
- 제거된 표면을 가리키던 stale 주석/문서(citation_helper/hybrid/
search·app-facade README/DOGFOOD/HANDOFF) 정정
코어 검색 출력은 byte-identical (search_uncached 본문이 search() 로
verbatim 이동). `search_cache: false` wire capability 는 유지 — 제거 시
schema.v1 breaking bump 이라 손해. 적대적 검증 2렌즈(correctness-preserved
+ wire/config-safe) 통과, dead-symbol-complete 가 잡은 5건 주석/문서 정정 완료.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
(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