feat(kebab-chunk): P7-2 pdf-page-v1 chunker #38

Merged
altair823 merged 2 commits from feat/p7-2-pdf-page-chunker into main 2026-05-02 09:02:18 +00:00
Owner

요약

P7-2 PdfPageV1Chunker 추가. kebab-parse-pdf 가 emit 한 CanonicalDocument (블록당 한 페이지, 모두 SourceSpan::Page) 를 받아 페이지 경계를 절대 넘지 않는 Chunk 들을 생성. citation locality 가 §3.4 SourceSpan::Page 의 도입 이유인 만큼 cross-page splitting 은 구조적으로 차단.

핵심 결정

  • Splitting policy: 페이지가 target × BYTES_PER_TOKEN 안이면 한 chunk; 초과 시 \n\n (paragraph) / sentence-end 경계로 segment 화 후 greedy 누적. 다음 chunk prefix 에 overlap × BYTES_PER_TOKEN byte 만큼 직전 꼬리 prepend.
  • BYTES_PER_TOKEN = 3: md-heading-v1 실코드와 일치 (Korean ≈ 3 b/tok 커버, English over-estimate). spec literal 의 "/ 4" 는 그 자체로 md 와 어긋나 있어 일관성 쪽을 택함. cross-chunker policy_hash 동일성을 unit test 로 잠금.
  • chunk_id 충돌 가드 (§4.2 deviation): 한 페이지가 여러 chunk 로 분할되면 block_ids 가 모두 같아 §4.2 recipe 가 충돌. id_for_chunkpolicy_hash 입력만 per-chunk 로 format!("{base}#c{char_start}") 변형해 회피. recipe 자체는 불변. Chunk.policy_hash 필드는 base 유지 (workspace-wide 정책 lookup 호환).

두 deviation 모두 tasks/HOTFIXES.md 에 entry 추가.

테스트 (10개 신규, 22 total in kebab-chunk)

  • chunker_version_is_pdf_page_v1
  • three_page_small_emits_one_chunk_per_page — 페이지별 1 chunk + Page span 검증
  • one_page_huge_text_splits_into_multiple_chunks_with_overlap — overlap 적용 + 모든 chunk 가 page=1 + chunk_id 유일성
  • empty_page_produces_no_chunks_for_that_page
  • whitespace_only_page_skipped_too
  • non_pdf_doc_returns_error — Markdown shape (Line span) 거부
  • no_chunk_crosses_page_boundary — 4-page mixed-size doc 에서 page 가 chunk 순서로 non-decreasing
  • deterministic_chunk_ids_1000
  • snapshot_three_page_chunks_stable — version label + heading_path + Page span shape stable
  • policy_hash_matches_md_heading_v1_for_identical_policy — cross-chunker fingerprint identity

검증

  • cargo test -p kebab-chunk pdf 10 passed
  • cargo test -p kebab-chunk 22 passed (md-heading-v1 회귀 없음)
  • cargo clippy -p kebab-chunk --all-targets -- -D warnings clean
  • cargo check --workspace clean

Spec 매핑

  • 원본 spec: tasks/p7/p7-2-pdf-page-chunker.md
  • 디자인 §3.5 (Chunk), §4.2 (chunk_id recipe — deviation HOTFIXES), §0 Q3 (citation), §9 (versioning)
  • chunker_version = "pdf-page-v1"

Test plan

  • 단위 10개 통과
  • kebab-chunk 전체 22개 통과 (md-heading-v1 회귀 0)
  • clippy -D warnings 통과
  • workspace check 통과
  • 사용자 수동 검수 후 P7-3 / app wiring 머��� 후 실제 PDF 로 ingest → search 시도
## 요약 P7-2 `PdfPageV1Chunker` 추가. `kebab-parse-pdf` 가 emit 한 `CanonicalDocument` (블록당 한 페이지, 모두 `SourceSpan::Page`) 를 받아 **페이지 경계를 절대 넘지 않는** `Chunk` 들을 생성. citation locality 가 §3.4 `SourceSpan::Page` 의 도입 이유인 만큼 cross-page splitting 은 구조적으로 차단. ## 핵심 결정 - **Splitting policy**: 페이지가 `target × BYTES_PER_TOKEN` 안이면 한 chunk; 초과 시 `\n\n` (paragraph) / sentence-end 경계로 segment 화 후 greedy 누적. 다음 chunk prefix 에 `overlap × BYTES_PER_TOKEN` byte 만큼 직전 꼬리 prepend. - **`BYTES_PER_TOKEN = 3`**: md-heading-v1 실코드와 일치 (Korean ≈ 3 b/tok 커버, English over-estimate). spec literal 의 "/ 4" 는 그 자체로 md 와 어긋나 있어 일관성 쪽을 택함. cross-chunker `policy_hash` 동일성을 unit test 로 잠금. - **`chunk_id` 충돌 가드** (§4.2 deviation): 한 페이지가 여러 chunk 로 분할되면 `block_ids` 가 모두 같아 §4.2 recipe 가 충돌. `id_for_chunk` 의 `policy_hash` 입력만 per-chunk 로 `format!("{base}#c{char_start}")` 변형해 회피. recipe 자체는 불변. `Chunk.policy_hash` 필드는 base 유지 (workspace-wide 정책 lookup 호환). 두 deviation 모두 `tasks/HOTFIXES.md` 에 entry 추가. ## 테스트 (10개 신규, 22 total in kebab-chunk) - `chunker_version_is_pdf_page_v1` - `three_page_small_emits_one_chunk_per_page` — 페이지별 1 chunk + Page span 검증 - `one_page_huge_text_splits_into_multiple_chunks_with_overlap` — overlap 적용 + 모든 chunk 가 page=1 + chunk_id 유일성 - `empty_page_produces_no_chunks_for_that_page` - `whitespace_only_page_skipped_too` - `non_pdf_doc_returns_error` — Markdown shape (Line span) 거부 - `no_chunk_crosses_page_boundary` — 4-page mixed-size doc 에서 page 가 chunk 순서로 non-decreasing - `deterministic_chunk_ids_1000` - `snapshot_three_page_chunks_stable` — version label + heading_path + Page span shape stable - `policy_hash_matches_md_heading_v1_for_identical_policy` — cross-chunker fingerprint identity ## 검증 - `cargo test -p kebab-chunk pdf` 10 passed - `cargo test -p kebab-chunk` 22 passed (md-heading-v1 회귀 없음) - `cargo clippy -p kebab-chunk --all-targets -- -D warnings` clean - `cargo check --workspace` clean ## Spec 매핑 - 원본 spec: `tasks/p7/p7-2-pdf-page-chunker.md` - 디자인 §3.5 (Chunk), §4.2 (chunk_id recipe — deviation HOTFIXES), §0 Q3 (citation), §9 (versioning) - `chunker_version = "pdf-page-v1"` ## Test plan - [x] 단위 10개 통과 - [x] kebab-chunk 전체 22개 통과 (md-heading-v1 회귀 0) - [x] clippy `-D warnings` 통과 - [x] workspace check 통과 - [ ] 사용자 수동 검수 후 P7-3 / app wiring 머��� 후 실제 PDF 로 ingest → search 시도
altair823 added 1 commit 2026-05-02 08:52:13 +00:00
`PdfPageV1Chunker` 가 `kebab-parse-pdf` 가 emit 한
`CanonicalDocument` (블록당 한 페이지, 모두 `SourceSpan::Page`) 를 받아
페이지 경계를 절대 넘지 않는 `Chunk` 들을 생성. `chunker_version =
"pdf-page-v1"`.

핵심 동작:
- 페이지 텍스트가 `target_tokens × BYTES_PER_TOKEN` (= 3) 안이면 한
  덩어리. 초과 시 `\n\n` (paragraph) 또는 sentence-end 구두점 + whitespace
  경계를 segment 로 보고 greedy 누적, 기본 한 chunk 당 최소 한 segment.
- 다음 chunk 의 prefix 에 `overlap_tokens × BYTES_PER_TOKEN` 만큼의 직전
  꼬리를 prepend (char 단위, 이전 chunk 시작 너머로 backtrack 안 함).
- 빈/공백-only 페이지는 0 chunk (페이지의 `Provenance::Warning` 으로
  `kebab-parse-pdf` 단계에 이미 표시됨).
- 비-PDF doc (Block::Paragraph 가 아니거나 SourceSpan 이 Page 아님) →
  명시 에러.

Spec deviation (HOTFIXES 2026-05-02 P7-2):
- `chunk_id` 충돌 가드: 같은 페이지에서 여러 chunk 가 나오면 `block_ids`
  가 모두 같아 §4.2 recipe 가 충돌. `id_for_chunk` 의 `policy_hash` 인풋을
  per-chunk 로 `format!("{base}#c{char_start}")` 변형해 회피. recipe 자체는
  불변. `Chunk.policy_hash` 필드는 base 유지.
- `BYTES_PER_TOKEN = 3` (md-heading-v1 실제 코드와 일치). spec 본문은
  "/ 4" 라고 했지만 그 자체가 md-heading-v1 의 실코드와 어긋나 있어 일관성
  쪽을 택함. cross-chunker `policy_hash` 동일성 unit test 로 잠금.

테스트 (10개 신규):
- chunker_version label, 3-page small, 1-page huge + overlap + chunk_id
  유일성, empty page skip, whitespace-only skip, non-PDF error,
  cross-page boundary 절대 안 만들어짐, determinism (1000회), snapshot
  shape 안정, md-heading-v1 와 policy_hash 동일.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
claude-reviewer-01 requested changes 2026-05-02 08:54:03 +00:00
claude-reviewer-01 left a comment
Member

회차 1 — overlap walk-back cap 누락이 가장 중요. 병적 정책 (overlap >> target/2) 에서 chunk-explosion 위험. md-heading-v1 의 클램프 패턴을 동일하게 적용 필요. 그 외는 nit (silent u32 truncation, sentence-end 약어 케이스 doc 보강). 전체 디자인은 견고 — chunk_id 충돌 가드 + cross-chunker policy_hash 검증 + dev-dep 없는 합성 픽스처 모두 잘 정리됨.

회차 1 — overlap walk-back cap 누락이 가장 중요. 병적 정책 (overlap >> target/2) 에서 chunk-explosion 위험. md-heading-v1 의 클램프 패턴을 동일하게 적용 필요. 그 외는 nit (silent u32 truncation, sentence-end 약어 케이스 doc 보강). 전체 디자인은 견고 — chunk_id 충돌 가드 + cross-chunker policy_hash 검증 + dev-dep 없는 합성 픽스처 모두 잘 정리됨.
@@ -0,0 +136,4 @@
{
let span = SourceSpan::Page {
page: page_num,
char_start: Some(char_start as u32),

(nit / 보강) char_start as u32 / char_end as u32 가 silent truncation 입니다. PDF 한 페이지가 4 G chars 를 넘는 케이스는 현실적으로 없지만 (PDF spec 상 종이 1 장 한계), as 캐스팅은 fuzz / corrupted 파일에서 의미 없이 잘리고 잘못된 span 을 만듭니다.

How to apply: u32::try_from(char_start).expect("page chars fit in u32") 정도로 dev-build 에서 명시 panic. 본 task spec 의 char_start: Option<u32> 는 §3.4 의 SourceSpan::Page 시그니처로 변경 불가 — astry_from 로 의도 명시만 바꾸는 것이라 scope 안.

(nit / 보강) `char_start as u32` / `char_end as u32` 가 silent truncation 입니다. PDF 한 페이지가 4 G chars 를 넘는 케이스는 현실적으로 없지만 (PDF spec 상 종이 1 장 한계), `as` 캐스팅은 fuzz / corrupted 파일에서 의미 없이 잘리고 잘못된 span 을 만듭니다. How to apply: `u32::try_from(char_start).expect("page chars fit in u32")` 정도로 dev-build 에서 명시 panic. 본 task spec 의 `char_start: Option<u32>` 는 §3.4 의 SourceSpan::Page 시그니처로 변경 불가 — `as` → `try_from` 로 의도 명시만 바꾸는 것이라 scope 안.
@@ -0,0 +174,4 @@
}
/// Split a single page's text into ordered chunks, each represented as
/// `(char_start, char_end, text_slice)`. Char positions are within the

(칭찬) format!("{base_policy_hash}#c{char_start}") 라는 per-chunk policy_hash 변형이 §4.2 chunk_id recipe 자체를 손대지 않으면서 collision 을 푸는 가장 작은-surface 해법입니다. recipe 의 4 슬롯 (doc_id, chunker_version, block_ids, policy_hash) 중 "입력만" per-chunk 로 다양화하고 — Chunk.policy_hash 필드는 base 유지로 workspace 정책 lookup 호환 — 두 invariant 가 합쳐서 깔끔합니다. md-heading-v1 가 미래에 같은 패턴을 만나면 그대로 베껴 쓸 수 있는 모듈 boundary.

(칭찬) `format!("{base_policy_hash}#c{char_start}")` 라는 per-chunk policy_hash 변형이 §4.2 chunk_id recipe 자체를 손대지 않으면서 collision 을 푸는 가장 작은-surface 해법입니다. recipe 의 4 슬롯 (doc_id, chunker_version, block_ids, policy_hash) 중 "입력만" per-chunk 로 다양화하고 — `Chunk.policy_hash` 필드는 base 유지로 workspace 정책 lookup 호환 — 두 invariant 가 합쳐서 깔끔합니다. md-heading-v1 가 미래에 같은 패턴을 만나면 그대로 베껴 쓸 수 있는 모듈 boundary.
@@ -0,0 +200,4 @@
let is_paragraph_break = c == '\n' && nx == '\n';
let is_sentence_end =
matches!(c, '.' | '?' | '!') && nx.is_whitespace();
if (is_paragraph_break || is_sentence_end) && k + 2 <= n {

(nit / 문서화) sentence-end 휴리스틱이 "Mr. Smith", "i.e.", "e.g.", "Fig. 3" 등의 흔한 약어에서 spurious 경계를 만듭니다. 결과적으로 chunk 가 약어 직후에 잘려 retrieval 시 "Mr. Smith was…" 의 "Smith was…" 부분이 별도 chunk 로 떨어질 수 있습니다.

Why: spec § Risks 에 "sentence-splitting uses simple regex; languages without clear sentence punctuation may produce uneven chunks" 만 적혀 있는데, 약어 케이스도 같은 카테고리이므로 모듈 doc 에 한 줄 명시하는 편이 미래 reader 에게 친절합니다.

How to apply: 모듈 doc ## Splitting policy 섹션에 "common English abbreviations (Mr., i.e., e.g.) trigger spurious sentence boundaries — accepted v1 limit, full sentence segmentation lands with the P+ tokenizer slot" 같은 두 줄 추가.

(nit / 문서화) sentence-end 휴리스틱이 "Mr. Smith", "i.e.", "e.g.", "Fig. 3" 등의 흔한 약어에서 spurious 경계를 만듭니다. 결과적으로 chunk 가 약어 직후에 잘려 retrieval 시 "Mr. Smith was…" 의 "Smith was…" 부분이 별도 chunk 로 떨어질 수 있습니다. Why: spec § Risks 에 "sentence-splitting uses simple regex; languages without clear sentence punctuation may produce uneven chunks" 만 적혀 있는데, 약어 케이스도 같은 카테고리이므로 모듈 doc 에 한 줄 명시하는 편이 미래 reader 에게 친절합니다. How to apply: 모듈 doc `## Splitting policy` 섹션에 "common English abbreviations (`Mr.`, `i.e.`, `e.g.`) trigger spurious sentence boundaries — accepted v1 limit, full sentence segmentation lands with the P+ tokenizer slot" 같은 두 줄 추가.
@@ -0,0 +243,4 @@
let prev_min = prev.0;
let mut a = start;
let mut acc_o: usize = 0;
while a > prev_min {

(issue) overlap walk-back cap 가 부족합니다. 현재 prev_min = prev.0 (이전 chunk 의 시작 포함, overlap 까지 적용된 상태) 까지 backwalk 가 허용됩니다. overlap_bytes >> target_bytes/2 인 병적인 정책이 들어오면:

  • chunk 1: start=0, end=10, actual_start=0
  • chunk 2: start=10, end=20, overlap=200 → backwalk 200 bytes → prev_min=0 까지 → actual_start=0
    → chunk 2 의 text 가 chars[0..20] 으로 chunk 1 의 text 를 완전히 포함. 사실상 한 페이지가 1 chunk-같은-다른-chunk 를 무한 증식할 수 있는 패턴.

md-heading-v1 은 정확히 같은 함정을 seed_budget = overlap_tokens.min(target_tokens / 2) 로 막아둔 history 가 있습니다 (md_heading_v1.rs:258overlap_clamped_when_overlap_exceeds_target 회귀 테스트 — 본 PR 의 deviation 노트에서 이미 md-heading-v1 와 일관성을 맞추고 있는 만큼 이 가드도 같이 들여오는 게 자연스럽습니다).

Why: 사용자가 실수로 overlap_tokens >= target_tokens 정책을 넣었을 때 chunk-explosion 으로 KB 디스크 / 임베딩 비용이 폭발하는 걸 막는 운영 가드입니다.

How to apply: chunk 메서드 진입부에서 한 줄 clamp + 같은 패턴의 회귀 테스트 추가:

let overlap_bytes = policy.overlap_tokens
    .saturating_mul(BYTES_PER_TOKEN)
    .min(target_bytes / 2);

그리고 pdf_page_overlap_clamped_when_exceeds_target 같은 unit test 로 overlap=200, target=50 케이스에서 chunk 가 actual_start <= chunks[i-1].1 - overlap_clamped_chars 정도로 안정 되는지 확인.

(issue) overlap walk-back cap 가 부족합니다. 현재 `prev_min = prev.0` (이전 chunk 의 *시작* 포함, overlap 까지 적용된 상태) 까지 backwalk 가 허용됩니다. `overlap_bytes >> target_bytes/2` 인 병적인 정책이 들어오면: - chunk 1: start=0, end=10, actual_start=0 - chunk 2: start=10, end=20, overlap=200 → backwalk 200 bytes → `prev_min=0` 까지 → `actual_start=0` → chunk 2 의 text 가 `chars[0..20]` 으로 chunk 1 의 text 를 *완전히 포함*. 사실상 한 페이지가 1 chunk-같은-다른-chunk 를 무한 증식할 수 있는 패턴. `md-heading-v1` 은 정확히 같은 함정을 `seed_budget = overlap_tokens.min(target_tokens / 2)` 로 막아둔 history 가 있습니다 (`md_heading_v1.rs:258` 의 `overlap_clamped_when_overlap_exceeds_target` 회귀 테스트 — 본 PR 의 deviation 노트에서 이미 md-heading-v1 와 일관성을 맞추고 있는 만큼 이 가드도 같이 들여오는 게 자연스럽습니다). Why: 사용자가 실수로 `overlap_tokens >= target_tokens` 정책을 넣었을 때 chunk-explosion 으로 KB 디스크 / 임베딩 비용이 폭발하는 걸 막는 운영 가드입니다. How to apply: chunk 메서드 진입부에서 한 줄 clamp + 같은 패턴의 회귀 테스트 추가: ```rust let overlap_bytes = policy.overlap_tokens .saturating_mul(BYTES_PER_TOKEN) .min(target_bytes / 2); ``` 그리고 `pdf_page_overlap_clamped_when_exceeds_target` 같은 unit test 로 `overlap=200, target=50` 케이스에서 chunk 가 `actual_start <= chunks[i-1].1 - overlap_clamped_chars` 정도로 안정 되는지 확인.
@@ -0,0 +354,4 @@
let chunks = PdfPageV1Chunker
.chunk(&doc, &default_policy(500, 80))
.unwrap();
assert_eq!(chunks.len(), 3);

(칭찬) 합성 make_pdf_doc(pages: &[&str]) 헬퍼가 kebab-parse-pdf 를 dev-dep 으로 끌어오지 않고 직접 Block::Paragraph + SourceSpan::Page 를 생성합니다. spec § Forbidden deps 의 "kebab-parse-pdf" 제약을 dev-dep level 까지 일관되게 지키는 동시에, 본 chunker 의 input shape 가 정확히 무엇인지 reader 가 같은 파일에서 한 눈에 볼 수 있게 됩니다 — kebab-parse-pdf 의 미래 변경에 영향 받지 않는 self-contained test 입니다.

(칭찬) 합성 `make_pdf_doc(pages: &[&str])` 헬퍼가 `kebab-parse-pdf` 를 dev-dep 으로 끌어오지 않고 직접 `Block::Paragraph` + `SourceSpan::Page` 를 생성합니다. spec § Forbidden deps 의 "kebab-parse-pdf" 제약을 dev-dep level 까지 일관되게 지키는 동시에, 본 chunker 의 input shape 가 정확히 무엇인지 reader 가 같은 파일에서 한 눈에 볼 수 있게 됩니다 — `kebab-parse-pdf` 의 미래 변경에 영향 받지 않는 self-contained test 입니다.
@@ -0,0 +507,4 @@
// must claim exactly one page in its single source_span.
let big_x = "x".repeat(2000);
let big_y = "y".repeat(800);
let pages = vec![

(칭찬) policy_hash_matches_md_heading_v1_for_identical_policy 가 cross-chunker fingerprint 동일성을 unit test 로 잠근 게 좋습니다. 본 PR 의 BYTES_PER_TOKEN deviation 노트가 "md-heading-v1 와 calibration 일치" 라고 주장하는데, 그 주장이 실제로 한 슬롯 (policy_hash) 에서라도 검증된다는 사실이 미래에 누군가 BYTES_PER_TOKEN 만 다시 4 로 되돌리려고 할 때 즉시 빨갛게 만듭니다.

(칭찬) `policy_hash_matches_md_heading_v1_for_identical_policy` 가 cross-chunker fingerprint 동일성을 unit test 로 잠근 게 좋습니다. 본 PR 의 BYTES_PER_TOKEN deviation 노트가 "md-heading-v1 와 calibration 일치" 라고 주장하는데, 그 주장이 실제로 한 슬롯 (`policy_hash`) 에서라도 검증된다는 사실이 미래에 누군가 BYTES_PER_TOKEN 만 다시 4 로 되돌리려고 할 때 즉시 빨갛게 만듭니다.
@@ -14,6 +14,26 @@ historical contract that was implemented; this file accumulates the
deltas so phase 5+ readers can find the live behavior without diffing
git history.
## 2026-05-02 — P7-2 pdf-page-v1: chunk_id collision + BYTES_PER_TOKEN

(칭찬) HOTFIXES entry 가 두 가지 deviation (chunk_id 충돌 가드, BYTES_PER_TOKEN=3 vs 4) 을 각각 "Symptom → Root cause → Fix → Trust note → Amends" 의 동일한 형식으로 정리한 게 좋습니다. 특히 BYTES_PER_TOKEN 케이스에서 spec literal 이 md-heading-v1 의 실코드와 어긋나 있다는 사실 ("spec's '/4' claim is inconsistent with the implementation it claims to match") 을 직시한 것이 중요합니다 — frozen task spec 을 retroactively 손대지 않고 HOTFIXES 가 살아있는 source of truth 라는 워크스페이스 컨벤션을 정확히 활용한 사례.

(칭찬) HOTFIXES entry 가 두 가지 deviation (`chunk_id` 충돌 가드, `BYTES_PER_TOKEN=3 vs 4`) 을 각각 "Symptom → Root cause → Fix → Trust note → Amends" 의 동일한 형식으로 정리한 게 좋습니다. 특히 `BYTES_PER_TOKEN` 케이스에서 spec literal 이 `md-heading-v1` 의 실코드와 어긋나 있다는 사실 ("spec's '/4' claim is inconsistent with the implementation it claims to match") 을 직시한 것이 중요합니다 — frozen task spec 을 retroactively 손대지 않고 HOTFIXES 가 살아있는 source of truth 라는 워크스페이스 컨벤션을 정확히 활용한 사례.
altair823 added 1 commit 2026-05-02 08:55:11 +00:00
- chunk 진입부에 overlap clamp 추가 (`target_bytes / 2` 상한). 병적 정책
  (`overlap_tokens >= target_tokens`) 에서 chunk 가 직전 chunk 의 텍스트를
  완전히 재발행하던 위험 차단. md-heading-v1 의 `seed_budget = overlap_tokens
  .min(target/2)` 가드 패턴과 일치. 회귀 테스트 `overlap_clamped_when
  _overlap_exceeds_target` 추가 — `actual_start` 가 인접 chunk 사이에
  엄격 증가하는지 검증.
- `char_start as u32` / `char_end as u32` silent truncation → `try_from
  ::expect` 로 corrupted input 시 명시 panic.
- 모듈 doc 의 `## Splitting policy` 에 약어 케이스 (`Mr.` / `i.e.` 등) +
  overlap clamp 두 항목 명시.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
claude-reviewer-01 approved these changes 2026-05-02 08:55:42 +00:00
claude-reviewer-01 left a comment
Member

회차 2 — 회차 1 지적 3건 (overlap clamp, u32 silent truncation, 약어 케이스 doc) 모두 반영 + overlap clamp 회귀 테스트 추가. clippy clean, 11 pdf tests + 22 chunk tests pass. 머지 가능.

회차 2 — 회차 1 지적 3건 (overlap clamp, u32 silent truncation, 약어 케이스 doc) 모두 반영 + overlap clamp 회귀 테스트 추가. clippy clean, 11 pdf tests + 22 chunk tests pass. 머지 가능.
@@ -0,0 +156,4 @@
// is preferable to an off-by-2^32 span.
let char_start_u32 = u32::try_from(char_start)
.expect("page chars fit in u32");
let char_end_u32 =

(칭찬) target_bytes / 2 clamp 가 정확히 md-heading-v1 의 seed_budget = overlap_tokens.min(target_tokens / 2) 와 같은 형태로 들어갔습니다. 두 chunker 가 같은 invariant 를 같은 표현으로 보장하면 미래에 누군가 한 쪽만 만지면 즉시 발견할 수 있는 symmetry — 본 PR 의 cross-chunker 일관성 약속 (BYTES_PER_TOKEN 동일 + policy_hash 동일성 unit test) 라인을 한 줄 더 강화합니다.

(칭찬) `target_bytes / 2` clamp 가 정확히 md-heading-v1 의 `seed_budget = overlap_tokens.min(target_tokens / 2)` 와 같은 형태로 들어갔습니다. 두 chunker 가 같은 invariant 를 같은 표현으로 보장하면 미래에 누군가 한 쪽만 만지면 즉시 발견할 수 있는 symmetry — 본 PR 의 cross-chunker 일관성 약속 (BYTES_PER_TOKEN 동일 + policy_hash 동일성 unit test) 라인을 한 줄 더 강화합니다.
@@ -0,0 +185,4 @@
});
}
}

(칭찬) as u32u32::try_from(...).expect(...) + 코멘트 (PDF chars-per-page comfortably fits in u32 (a single page maxes out around ~10k chars even for dense typography)) 로 silent truncation 의도성을 명시한 게 좋습니다. fuzzed / corrupted 페이지에서 의미 없는 잘림 대신 명시 panic — debugging 친화적이고, expect 메시지가 invariant 를 그대로 코드 안에 박아둡니다.

(칭찬) `as u32` → `u32::try_from(...).expect(...)` + 코멘트 (`PDF chars-per-page comfortably fits in u32 (a single page maxes out around ~10k chars even for dense typography)`) 로 silent truncation 의도성을 명시한 게 좋습니다. fuzzed / corrupted 페이지에서 의미 없는 잘림 대신 명시 panic — debugging 친화적이고, expect 메시지가 invariant 를 그대로 코드 안에 박아둡니다.
@@ -0,0 +667,4 @@
// Cross-chunker policy fingerprint identity — important so a
// workspace-wide "show me chunks with policy_hash = X" query
// covers both chunkers without per-chunker logic.
let p = default_policy(500, 80);

(칭찬) overlap_clamped_when_overlap_exceeds_targetactual_start 가 인접 chunk 사이에 엄격 증가 하는지 직접 단정합니다. 단순히 chunk 개수만 보지 않고 "chunk N 이 chunk N-1 을 포함하지 않는다" 라는 invariant 를 표현해서, 미래에 누군가 clamp 를 잘못 풀어 chunk-explosion 을 다시 만들면 정확한 위치에서 빨갛게 됩니다.

(칭찬) `overlap_clamped_when_overlap_exceeds_target` 가 `actual_start` 가 인접 chunk 사이에 *엄격 증가* 하는지 직접 단정합니다. 단순히 chunk 개수만 보지 않고 "chunk N 이 chunk N-1 을 포함하지 않는다" 라는 invariant 를 표현해서, 미래에 누군가 clamp 를 잘못 풀어 chunk-explosion 을 다시 만들면 정확한 위치에서 빨갛게 됩니다.
altair823 merged commit b711152035 into main 2026-05-02 09:02:18 +00:00
altair823 deleted branch feat/p7-2-pdf-page-chunker 2026-05-02 09:02:19 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: altair823-org/kebab#38