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
4.8 KiB
title, created, status, extends, follows, contract_sections, design_doc_change
| title | created | status | extends | follows | contract_sections | design_doc_change | ||||
|---|---|---|---|---|---|---|---|---|---|---|
| pdf-page-v1.2 — PDF 페이지 oversize 청크 분할 (shared oversize module) | 2026-06-24 | implemented | tasks/p7/ (pdf-page-v1), HOTFIXES 2026-05-27 (pdf-page-v1.1) | docs/superpowers/plans/2026-06-24-md-heading-v2-oversize-split.md |
|
none |
pdf-page-v1.2 — PDF oversize 청크 분할
문제
md-heading-v2(PR #209)가 markdown/이미지-OCR 텍스트의 예산 초과 청크를 분할하게
했지만, PDF 는 별도 청커 pdf-page-v1.1 을 쓴다. v1.1 의 chunk_page 는
문장(.?!)/문단(\n\n) 경계로만 자르므로, 경계 없는 거대 페이지(빽빽한
scanned page 가 한 줄로 OCR 된 경우)는 통째로 한 청크가 되어 strict 임베더(AMD
Lemonade)에서 임베드 실패 — md 와 동일한 hole 이 PDF 에 잔존했다(v1.1 module
doc 에 "accepted limit" 으로 명시돼 있었음).
설계 — 공유 모듈 + 2-tier
1. 공유 crate::oversize 모듈
md-heading-v2 의 분할 primitive text_pieces(줄 경계)·char_pieces(UTF-8 char
경계)·BYTES_PER_TOKEN=3 을 crates/kebab-chunk/src/oversize.rs 로 추출,
pub(crate) 로 두 청커가 공유. md-heading-v2 는 이제 crate::oversize:: 를 호출
— 출력 byte-identical(md parity 테스트 전부 통과, md 라벨 불변). 단일 진실
공급원(DRY)으로 세 번째 복사 방지.
2. pdf-page-v1.2 의 2-tier
- Tier 1(기존):
chunk_page의 문장/문단 greedy + overlap. 경계 풍부한 페이지는 그대로 — chunk_id 안정. - Tier 2(신규): tier-1 이 내준 segment slice 가
max_chunk_tokens(공유 config, byte/3, default 4000)를 넘으면crate::oversize::text_pieces로 재분할 → 모든 PDF 청크 ≤ 예산 보장(char fallback 이 경계 없는 페이지도 bound). PdfPageV1Chunker { max_chunk_tokens }(이전 unit struct),policy_hash()에 budget fold(md 와 동일 8-LE-byte append),pdf_chunker_from_config(kebab-app)로 config 주입. 신규 config 키 없음 —max_chunk_tokens공유.
3. chunk_id sub-piece 스킴
분할 조각은 동일 page block_id 공유 → id 충돌. 기존 #c{segment_start} 접미사를
tier-2 에서 #c{segment_start}s{i} 로 확장(i = segment 내 0-based). 단일(미분할)
segment 는 bare #c{segment_start} 유지 → 미분할 PDF 청크의 hash 컴포넌트는
v1.1 과 동일(공통 경우 churn 최소). segment_start 가 segment-unique + i 가
segment 내 unique → 페이지 전역 고유.
4. Page source_span — segment-granular (중요한 설계 결정)
분할 조각의 SourceSpan::Page char 범위를 per-piece 로 정밀 narrow 하려던 최초
구현은 버그였다: text_pieces 가 줄 분할 시 piece 사이의 '\n' 구분자를
소실(각 split 경계에서 consume, 인접 piece 어디에도 없음)시키므로, piece char
수를 합산하는 running offset 이 줄 경계마다 1씩 earlier 로 drift(코드 리뷰
실증: "aaaa\nbbbb\ncccc" → drift 2). 게다가 한 줄이 char-split 되면 그 piece 들
사이엔 구분자가 없어 "boundary 당 +1" 보정도 불가능(Vec<String> 만으로 복구
불가). → md-heading-v2 와 동일하게 모든 sub-piece 가 부모 segment 의
char_start..seg_char_end 를 그대로 갖는다(segment-granular). citation 은 올바른
페이지 영역을 가리키며 절대 drift 하지 않는다(per-piece 보다 coarse 하나
never wrong). 미분할 단일 piece 는 정확히 segment span → v1.1 과 동일.
5. 버전 cascade
VERSION_LABEL pdf-page-v1.1 → pdf-page-v1.2 → 다음 plain kebab ingest
에서 PDF 자산 1회 자동 재청크(skip-check mismatch). markdown/code 무영향.
max_chunk_tokens 는 이미 ingest_config_signature 공통 prefix 에 있어 budget
변경 시 PDF 재색인이 작동(단 v1.1 은 값을 무시했음 → 이제 실효).
검증
단위 테스트(crates/kebab-chunk/src/pdf_page_v1.rs): oversize_pdf_page_splits
(경계 없는 페이지 → tier-2 char-split, 각 ≤ budget),
oversize_pdf_page_with_newlines_splits_without_span_drift(줄 포함 페이지 — span
drift 회귀 잠금), non_oversize_pdf_unchanged(미분할 = v1.1 동일),
policy_hash_matches_md_heading_v2_for_identical_policy_and_budget,
budget_in_policy_hash. crate::oversize roundtrip 테스트 2종. md parity 테스트
전부 통과(md 무변경). kebab-chunk lib 93 pass / kebab-app green / clippy -D warnings 0.
도그푸딩(실험 KB, scanned PDF + arctic@Lemonade): [HOTFIXES 2026-06-24 pdf-page-v1.2 entry 참조].
버전
Cargo.toml workspace version: minor bump(사용자-visible — 빽빽한 scanned PDF 가
분할되어 검색 hit 분리 + strict 임베더 호환). follow-up #1/#2/#3 와 함께 배치
릴리스에서 일괄.