리뷰가 이 PR 의 핵심을 무너뜨리는 결함을 잡았다.
1) render_dpi 가 아무 일도 안 하고 있었다 (HIGH)
`set_maximum_*` 만 걸었는데, pdfium-render 에서 maximum 은 초과할 때만
줄이는 클램프이고 스케일이 아니다. 타깃도 배율도 없으면 스케일 1.0 —
1 pt → 1 px, 즉 **72 DPI** 로 렌더된다. 300 을 주든 1200 을 주든 산출물이
같았다. 렌더가 실패하지 않으니 도그푸딩도 통과해 버렸다.
실측 (govdocs1-000157-ccitt.pdf 5쪽):
maximum_* 만 (초안) 621×801 px 72 DPI
target + maximum_* (수정) 1588×2048 px 184 DPI
같은 뿌리로 종횡비도 깨져 있었다. 클램프만 걸리는 경로는
`do_maintain_aspect_ratio = false` 라 가로·세로가 독립적으로 잘린다.
600×800pt 페이지를 600px 예산으로 렌더하면 600×600 으로 세로가 25%
눌린 채 나왔고, 긴 변만 보던 테스트는 초록불이었다.
`render_dpi_changes_the_rendered_size` 와
`the_pixel_budget_is_respected_without_distorting_the_page` 로 고정했다.
`set_target_width` 한 줄을 되돌리면 둘 다 실패하는 것을 확인했다.
덧붙여 render_dpi 는 **요청**이고 max_pixels 가 이긴다. PDF 기본값
2048 이면 A4 는 175 DPI 언저리에서 잘린다. 기본값 300 이 그대로 나오지
않는다는 뜻이라 config·README·SMOKE 문구를 실제와 맞췄다.
2) /MediaBox 를 직접 파싱하고 있었다 (MEDIUM)
`/MediaBox` 는 상속 속성이고 대부분의 생산자가 `/Pages` 노드에 한 번만
쓴다. lopdf 0.32 에는 상속 해석 헬퍼가 없어서 그런 PDF 는 전부 조용히
A4 폴백을 탔다. `/UserUnit` 도 미반영이었다.
pdfium 이 이미 페이지 크기를 안다. 거기서 받으니 40여 줄이 사라지고
상속·UserUnit 문제가 함께 없어졌으며, kebab-app 이 lopdf 딕셔너리를
뒤지던 레이어링도 정리됐다.
3) 렌더러가 있으면 오히려 손해 보는 경우가 있었다 (MEDIUM)
페이지 하나만 렌더에 실패하면 곧장 skip 이었고 DCTDecode 경로를 시도하지
않았다. "렌더러 우선 + 폴백" 이 렌더러 유무 수준에서만 성립했던 것이다.
페이지 단위 폴백을 넣었다.
4) 렌더러를 설정한 사용자에게 틀린 지시가 나갔다 (MEDIUM)
pdfium 이 PDF 자체를 못 열면 모든 페이지가 no_renderer 로 보고되면서
"render_library 를 지정하라" 고 안내했다. `unopenable_pdf` 로 갈랐다.
5) ⊘ 줄 수와 ocr-skipped 카운트가 안 맞았다 (MEDIUM)
카운트는 래스터 실패만 세는데 OCR 엔진 실패도 화면에는 똑같이 ⊘ 로
찍혔다. 사유를 라벨에 적어 둘을 구분한다 — 이 구분이 바로 아래 도그푸딩
에서 실제로 값을 했다.
6) 잔가지 (LOW)
docs 의 pdf-text-v1 잔재 3곳, doctor hint 의 줄 이음이 무너져 생긴 여백.
정답 있는 한국어 스캔으로 인식률을 쟀다 (CCITT 3건, qwen2.5vl:3b):
namu-beulenda… 8쪽 CER 15.65%
namu-bihaengdae 8쪽 CER 12.55%
namu-gu-anoli 6쪽 CER 15.08%
전 페이지 OCR 성공, 건너뜀 0. 수정 전에는 세 문서 모두 본문 0 자였다.
엔진 선택이 결과를 가른다는 것도 알게 됐다. 처음에는 이 머신에 있던
gemma3:4b 로 쟀는데 래스터는 정상인데 출력이 원문과 무관한 환각이었고,
해상도가 올라가자 밀집 한국어 페이지에서 180초 타임아웃이 났다. 범용
멀티모달 모델은 OCR 엔진이 아니다 — 이때 5번의 새 라벨이 "래스터 없음"이
아니라 "OCR 엔진 실패"로 찍어 줘서 원인이 바로 갈렸다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017c9JwQq8ZkGvYjpKXMiDhF
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
docs/components/<group>/README.md 12 페이지 + 인덱스 작성. 각 그룹
페이지가 구성 crate 표 + 구조 mermaid + data flow mermaid + 주요
type/trait/함수 시그니처 + 외부 의존 + 핵심 결정 (HOTFIXES + spec
의 "왜" 통합) + 관련 spec/HOTFIXES 링크. 인덱스가 그룹 wiring
다이어그램 + 진입 가이드 보유.
ARCHITECTURE.md 의 ASCII crate 의존 그래프를 mermaid flowchart 로
교체 (등가 정보, Gitea/GitHub 자동 렌더). docs/components/ 진입
링크 추가.
이 layer 는 contributor 향 — 사용자 향 grand picture 는 README.md
의 logical-architecture diagram 그대로 유지. 진척도는 HANDOFF.md,
per-task spec 은 tasks/INDEX.md 가 기존대로 source of truth.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>