docs(tasks): P6-4 image ingest wiring task spec #35
Reference in New Issue
Block a user
Delete Branch "spec/p6-4-image-ingest-wiring"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
요약
P6 phase 의 미완 구간 —
kebab-app::ingest가 markdown 만 dispatch 해서 P6-1/2/3 라이브러리가 CLI 에서 보이지 않음 — 을 별도 component task 로 spec 화. P6-1/2/3 의 frozen contract 는 그대로 두고 wiring 만 본 task 의 contract 로 진행.본 PR 은 spec only — 구현은 별도 PR (
feat/p6-4-image-ingest-wiring) 로 진행 예정.사용자 brainstorming 반영 결정
kebab-chunk::md_heading_v1에 image-only document 분기 추가, 단일 합성 청크 emit.<alt>\n\n<ocr.joined>\n\n<caption.text>, 라벨 없음, 빈 부분 drop.ImageExtractor::extract성공이면 doc 저장, OCR/caption 부분 실패는 Provenance Warning +errors카운터 미증가.&dyn LanguageModel공유.Region { x, y, w, h }source span. xywh fragment URI 표기는 v1 미적용.MediaType::Other→ 기존 markdown-only 와 동일하게skipped+=1.변경 파일
Test plan (spec 자체 검증)
후속
본 spec 머지 후
feat/p6-4-image-ingest-wiring브랜치로 implementation PR 진행 (kebab-app deps 추가 + dispatch 분기 + chunker image-only 분기 + 통합 테스트 + SMOKE.md 갱신).🤖 Generated with Claude Code
회차 1 — spec 의 큰 그림은 견고합니다. brainstorming 의 결정 (옵션 A 청킹 / β 청크 텍스트 / Lenient 실패 정책 / LM 1회 빌드 / P6-5 미시작 / 책 P7 이관) 모두 정확히 반영되었고, Risks/notes 섹션의 "determinism stress" 처럼 P6-1 contract 와의 일관성까지 짚어 둔 점도 좋습니다.
머지 전에 정리해야 할 actionable 항목들:
사실 오류 (반드시 수정):
Chunk구조체의section_label필드가 실재하지 않고, span 필드는source_spans: Vec<SourceSpan>단수가 아니라 복수 — implementation 이 spec 그대로 따르면 컴파일 실패.컨벤션 불일치:
kebab-app의 현재 Cargo.toml 과 일치 안 함 (kebab-search,kebab-llm,kebab-rag등 이미 deps 인 항목 누락).kebab-tui,kebab-desktop,kebab-eval명시 필요.완성도 보강:
OllamaVisionOcr도 동일 1회 빌드 정책 명시.max_parallel_extractors가 image dispatch 에서 어떻게 honor 되는지 한 줄 명시 (5000장 ingest 시간 추정 가능하게).Cosmetic:
is_image_only_document의 P6-1 contract 와의 관계 doc-comment 한 줄 (defensive 의도 명시).칭찬 — "Out of scope" 절이 8개 항목을 명시적으로 호명하면서 각각 "왜 본 task scope 가 아닌지 + 어떤 후속 task / phase 에서 다뤄지는지" 까지 짚어 둔 점이 좋습니다. P+ 에서 누군가 "왜 P6-4 가 long-OCR splitting 안 함?" 의문이 들 때 spec 만 보고 답이 나옵니다.
@@ -0,0 +30,4 @@- `kebab-chunk` (image-document branch in `md-heading-v1`)- `kebab-store-sqlite`, `kebab-store-vector`, `kebab-embed-local`- `anyhow`, `serde_json`, `tracing`Allowed dependencies 리스트가
kebab-app의 현재 Cargo.toml 과 정확히 일치하지 않습니다 —kebab-search(이미 deps),kebab-llm(이미 deps),kebab-rag(이미 deps),kebab-embed(이미 deps) 가 빠져 있습니다. 본 task 가 그들을 새로 import 하지 않더라도 spec 의 "Allowed" 는 "이 crate 가 의존할 수 있는 surface 의 전체 그림" 으로 P6-1/P6-2/P6-3 가 사용한 컨벤션입니다.수정 권장:
그 후 "NEW" 라벨로 본 task 가 실제 추가하는 것 (
kebab-parse-image) 을 강조하면 reader 가 변경점을 즉시 파악합니다.@@ -0,0 +32,4 @@- `anyhow`, `serde_json`, `tracing`## Forbidden dependenciesForbidden dependencies 가 "New crates outside the existing kebab-app dep set" 한 줄로 추상적입니다. P6-1/P6-2/P6-3 모두 명시적 forbidden list 를 둡니다. 본 task 도 같은 컨벤션으로 명시 권장:
이러면 implementation PR 리뷰 시 "왜 이 import 가 들어왔지?" 가 명확해집니다.
@@ -0,0 +67,4 @@}fn is_image_only_document(doc: &CanonicalDocument) -> bool {doc.blocks.len() == 1(작은 명확성)
is_image_only_document의 doc-comment 에 P6-1 contract 와의 관계를 한 줄 추가 권장 — 현재는 reader 가 "P6-1 이 이미 항상 단일 ImageRef block 만 emit 하는데 왜 chunker 에서 다시 검사?" 의문이 들 수 있습니다.사소하지만 P+ 에서 image-embedded-in-markdown 같은 케이스가 합류했을 때 가드의 의도가 자명해집니다.
@@ -0,0 +127,4 @@6. If `config.image.caption.enabled`:- Build the LM **once per ingest session** (not per asset) — see "LM construction" below.- Call `apply_caption(&*llm, &bytes, block, lang_hint, &cfg, &mut events)`.- Failure → same lenient policy as OCR: warn, log, continue. `block.caption` stays `None`.Behavior contract 의 ingest dispatch 절이
config.indexing.max_parallel_extractors와의 관계를 명시하지 않습니다. spec Out of scope 에서 "기존 knob 그대로 적용" 이라 했지만, 실제로 본 task 가 image dispatch 에서 이 knob 을 어떻게 honor 하는지 정확히 안 보입니다.현재
kebab-app의 markdown 경로가 이 knob 을 어떻게 쓰는지 (rayon? tokio? 단순 sequential?) 빠르게 확인해서 spec 에 한 줄 추가 권장:또는 "markdown 경로가 현재 sequential 이므로 image 도 sequential" 임을 명시. reader 가 "5000장 PNG ingest 시간 추정" 같은 산수를 spec 만 보고 가능하게.
@@ -0,0 +142,4 @@- `reqwest::blocking::Client` inside `OllamaLanguageModel` is internally `Arc`-shared, so reusing one instance across hundreds of asset iterations is correct and cheap.### Chunking (kebab-chunk md-heading-v1)LM construction 절이 caption 의
OllamaLanguageModel만 다룹니다.OllamaVisionOcr도reqwest::blocking::Client를 내부에 가진 동일 구조라서 ingest session 당 1회 빌드해야 합니다 (자산마다 Client 새로 만들면 connection pool 낭비). 한 줄 추가 권장:LM construction 섹션 제목도 "LM / OCR engine construction" 정도로 일반화 권장.
@@ -0,0 +149,4 @@- `chunk.block_ids = vec![block.common.block_id.clone()]`.- `chunk.heading_path = vec![]` (image documents have no heading hierarchy).- `chunk.section_label = None`.- `chunk.source_span = block.common.source_span.clone()` (the `Region { x, y, w, h }` from P6-1).사실 오류입니다 —
kebab_core::Chunk구조체에section_label필드가 없고, span 필드는source_span단수가 아니라source_spans: Vec<SourceSpan>입니다 (crates/kebab-core/src/chunk.rs:16-26):spec 의 두 줄을 수정 권장:
implementation PR 에서 이 spec 을 그대로 따르면 컴파일 실패합니다. spec contract 가 frozen 되기 전에 잡는 게 좋습니다.
@@ -0,0 +195,4 @@The opt-in real-Ollama integration test from P6-2 / P6-3 stays inside `kebab-parse-image`; this task's integration tests use wiremock so `cargo test --workspace` stays hermetic.## Definition of DoneDefinition of Done 항목들이 spec PR (본 PR) 과 implementation PR 의 항목을 섞어 두었습니다. spec 자체는 "문서가 작성되고 INDEX 가 갱신됨" 으로 끝나야 머지 가능 —
cargo test,kebab ingest, SMOKE.md 수정 같은 항목은 implementation PR 의 DoD 입니다.분할 권장:
spec 이 머지 가능한 단위인지 implementation 후에야 가능한지 reader 가 한 번에 인지 가능.
- Allowed dependencies 를 kebab-app 의 현재 Cargo.toml 과 정합되도록 보강 (kebab-search / kebab-llm / kebab-rag / kebab-embed 누락 추가). 본 task 가 새로 추가하는 deps 인 `kebab-parse-image` 만 \"NEW\" 라벨로 강조. - Forbidden dependencies 를 추상적 한 줄에서 명시 리스트로 교체: `kebab-tui` / `kebab-desktop` (UI layering), `kebab-eval` (cycle), 본 crate 안 image-specific 비즈니스 로직 (kebab-parse-image 가 이미 처리). P6-1/2/3 spec 의 컨벤션과 통일. - Public surface 의 `Chunk` 사실 오류 정정: • `chunk.section_label = None` 줄 삭제 (필드 없음) • `chunk.source_span = ...` → `chunk.source_spans = vec![...]` (실제 필드명 + Vec 타입 반영) • `token_estimate` / `policy_hash` 채움 정책 추가. - LM construction 절을 \"LM / OCR engine construction\" 으로 일반화 + OCR 어댑터도 ingest session 당 1회 빌드 정책 명시. - Behavior contract 에 \"Parallelism\" 새 절 추가 — 현재 markdown branch 가 sequential 임 + 본 task 도 동일 + 5000장 OCR 시간 추정치까지 명시. 책 P7 이관 신호와 일관. - Definition of Done 을 spec PR (이 PR — 모두 완료된 항목) 과 implementation PR (후속) 으로 분할. spec PR 의 머지 가능 시점 명확. - `is_image_only_document` 의 doc-comment 추가 — P6-1 contract 가 이미 단일 ImageRef block 보장하지만 chunker 측 가드의 defensive 의도 명시. 본 PR 은 spec only — implementation 은 후속 PR.회차 2 — 회차 1 의 7개 항목 (Allowed deps 정합화 / Forbidden deps 명시 / Chunk 사실 오류 정정 / OCR adapter 1회 빌드 / Parallelism 절 / DoD 분할 / defensive guard doc) 모두 정확히 반영되었습니다. 추가 actionable 한 지적이 보이지 않습니다.
전체 회차 요약:
본 spec 은 contract 단위로 머지 가능합니다. 사용자가 Gitea UI 에서 머지해 주세요:
#35
"Delete branch after merge" 체크 권장. 로컬 브랜치는 머지 후
git fetch -p+git branch -d spec/p6-4-image-ingest-wiring로 정리.머지 후 후속: implementation PR (
feat/p6-4-image-ingest-wiring브랜치) 로 spec 의 "Implementation PR" DoD 항목 (kebab-app dispatch + kebab-chunk 분기 + 통합 테스트 + SMOKE.md) 진행.@@ -0,0 +184,4 @@- When `config.image.ocr.enabled = false` AND `config.image.caption.enabled = false`, the image is still extracted, stored, and chunked — the user gets EXIF + dimensions + filename indexed, with empty OCR / caption.- The synthesized chunk text falls back to just the filename. Lexical search on filenames still works; vector search produces a best-effort embedding from a one-line input.### Failure semantics summary(칭찬) Parallelism 새 절이 "현재 markdown 도 sequential" 사실 + "5000장 × per-call latency 의 4-7시간 범위" 추정을 구체적 수치로 박은 게 좋습니다. brainstorming 단계에서 사용자가 "책은 PDF 로 가겠다" 결정한 배경을 spec 만 보고도 다시 인지할 수 있어 미래 reader 가 "왜 P6-5 가 없지?" 의문이 들 때 답이 한 줄로 나옵니다.
@@ -0,0 +212,4 @@| integration | Determinism: ingest the same PNG twice → identical `doc_id`, `chunk_id` (P1 idempotency contract holds) | inline || integration | OCR Ollama returns 503 → asset still indexed; `block.ocr = None`; provenance has Warning event; `errors` counter NOT incremented; ingest returns Ok | wiremock || integration | Caption Ollama returns 503 → asset still indexed; `block.caption = None`; provenance Warning; `errors` not incremented | wiremock || integration | `image.ocr.enabled = false` AND `image.caption.enabled = false` → image still indexed; chunk text = filename only | inline |(칭찬)
chunk.source_spans/token_estimate/policy_hash의 정확한 필드명 + 채움 정책이 한 자리에 모였습니다. 특히policy_hash가 markdown 과 동일 값을 공유한다는 명시는 "policy 변경 시 image chunk 도 함께 invalidate" 라는 §4.2 ID recipe 의 의도를 implementation 단계에서 그대로 따라가게 만드는 가드입니다.(칭찬) Definition of Done 의 spec PR / implementation PR 분할 + 본 PR 의 spec 항목은 모두 [x] 로 자체 마킹된 상태가 좋습니다. reviewer 가 "이 PR 머지 가능 여부" 와 "후속 PR 에서 검증할 항목" 을 한눈에 분리할 수 있고, implementation PR 작성자가 spec PR 의 항목을 다시 처리하지 않게 막아 줍니다.