Files
kebab/IMPL_REPORT.md
altair823 b351523e51 docs(worktree): IMPL_BRIEF + IMPL_REPORT for ingest-progress-detail
작업 입력(brief)과 산출 증거(report: 변경/이벤트/exit-code 검증/smoke 샘플/
잔여 리스크). 메인 세션이 PR 정리 시 드롭 가능한 worktree 메타 아티팩트.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 13:58:33 +00:00

126 lines
7.5 KiB
Markdown

# IMPL REPORT — 상세 ingest 진행 로깅 (feat/ingest-progress-detail, v0.24.0)
## 요약
asset(문서) 단위뿐이던 ingest 진행 이벤트에 **asset 내부 phase 가시성**을
추가했다. 큰 문서 하나가 expansion(별칭 LLM, 청크당 순차)으로 수십 분 걸려도
진행바가 `1/N`에 멈춘 듯 보이던 문제를 해결 — 청크 수 · expansion 라이브
카운터 · phase별 소요시간을 노출한다. wire `ingest_progress.v1`
**additive(backward-compat)** 유지.
## 추가한 이벤트 (`crates/kebab-app/src/ingest_progress.rs` `IngestEvent`)
`#[serde(tag="kind", rename_all="snake_case")]` 이므로 신규 변이 추가 = wire v1
호환.
| 이벤트 | 필드 | emit 시점 / 경로 |
|--------|------|------------------|
| `asset_chunked` | `idx, total, chunks` | 청킹 직후(expansion/embed 전). markdown / image / pdf 세 경로 모두 |
| `expansion_progress` | `idx, total, done, chunks` | expansion 루프 중 **스로틀**(매 25청크 또는 ≥1s, 종료 시 `done==chunks` 1프레임 더). markdown, expansion enabled 시 |
| `asset_timings` | `idx, total, parse_ms, chunk_ms, expansion_ms, embed_ms, store_ms` | asset markdown 파이프라인 종료 시 1회. **markdown 경로만** |
### 설계 결정 — `AssetTimings` 이벤트 (vs `AssetFinished` 필드)
IMPL_BRIEF §1은 `AssetFinished`에 optional phase-timing 필드를, §2는 대안으로
신규 `AssetTimings` 이벤트(권장)를 제시했다. **후자를 택함**:
- `AssetFinished`는 호출부(`ingest_with_config_progress` 루프, lib.rs)에서
생성되는데 timing 데이터는 `ingest_one_asset` **내부**에만 존재한다. 필드를
채우려면 `kebab_core::IngestItem`(wire-stable struct) 변경 또는 별도 plumbing
이 필요.
- `ingest_one_asset`는 이미 `progress` 핸들을 들고 있어 새 이벤트를 직접 emit
하는 쪽이 crate 경계(`kebab-core` 불변, CLAUDE.md §Allowed/forbidden deps)도
지키고 더 깔끔.
- `AssetFinished`는 손대지 않음 → 기존 consumer 전부 무변경.
`AssetTimings`의 5개 phase 필드는 `u64`(이벤트 emit 시 항상 존재). markdown
경로만 emit하므로 optional 처리 불필요.
## 변경 파일
| 파일 | 변경 |
|------|------|
| `crates/kebab-app/src/ingest_progress.rs` | 3개 신규 변이 + ordering-invariant doc comment 갱신 + 직렬화 단위 테스트 3개 |
| `crates/kebab-app/src/lib.rs` | `ingest_one_asset`/`ingest_one_image_asset`/`ingest_one_pdf_asset` 시그니처에 `idx,total` 스레딩(image엔 `progress`도). markdown 경로: `Instant` phase 타이머(parse/chunk/expansion/embed/store) + `AssetChunked` 즉시 emit + expansion 루프 스로틀 `ExpansionProgress` + `AssetTimings` emit. image/pdf 경로: `AssetChunked` emit |
| `crates/kebab-cli/src/progress.rs` | `handle_human`에 3개 arm — `asset_chunked`→진행바 message `→ N chunks`, `expansion_progress`→message `별칭 확장 {done}/{chunks}`, `asset_timings``⏱ parse … · store …` 한 줄. `fmt_ms` 헬퍼(<1s=ms, ≥1s=1-decimal 초) + 단위 테스트. `--json``emit_json` 자동 처리 |
| `crates/kebab-tui/src/ingest_progress.rs` | reducer match에 3개 신규 변이 no-op arm(상태바는 per-asset 카운터만 추적) — 컴파일 유지 |
| `crates/kebab-app/tests/ingest_progress.rs` | `progress_event_sequence_matches_design_section_2_4a` 를 v0.24.0 순서 불변식(`AssetStarted < AssetChunked < [ExpansionProgress*] < AssetTimings < AssetFinished`)을 견고하게 검증하도록 재작성 |
| `docs/wire-schema/v1/ingest_progress.schema.json` | 신규 kind 3개 + 필드(`done, parse_ms, chunk_ms, expansion_ms, embed_ms, store_ms`) additive 기재. `chunks` description 확장 |
| `Cargo.toml` (+ `Cargo.lock`) | workspace version 0.23.1 → **0.24.0** (additive minor) |
| `tasks/HOTFIXES.md` | 2026-06-02 dated entry |
| `README.md` | ingest 명령 row에 진행 표시 한 줄 |
## 검증 (exit code)
전부 `CARGO_TARGET_DIR=/build/out/cargo-target/target`, `-j 4`.
| 게이트 | 명령 | 결과 |
|--------|------|------|
| clippy | `cargo clippy --workspace --all-targets -j 4 -- -D warnings` | **exit 0** ✅ |
| test | `cargo test -p kebab-app -p kebab-cli -j 4 --no-fail-fast` | **exit 0** ✅ — 312 passed, 0 failed |
신규 테스트 전부 통과:
`asset_chunked_serializes_with_discriminator`,
`expansion_progress_serializes_with_discriminator`,
`asset_timings_serializes_all_phase_fields`,
`progress_event_sequence_matches_design_section_2_4a`(재작성),
`fmt_ms_switches_unit_at_one_second`, 그리고 CLI 통합
(`ingest_json_emits_line_delimited_progress_then_report` 등 4개, 실제 바이너리로
새 이벤트가 `--json`/human stderr에 흐르는지 검증).
### 테스트 인프라 주의 (내 변경과 무관)
`cargo test`(fail-fast default)의 첫 실행은 재작성 전 `progress_event_sequence`
실패에서 멈췄다. 재작성 후 두 번째 실행에서 cargo가 더 진행하다
`crates/kebab-cli/tests/cli_error_wire.rs`의 2개 테스트가 **spawn 단계**
(`cmd.output().unwrap()`)에서 실패. 원인: 이 테스트(와 `ingest_progress_cli.rs`)
가 바이너리를 **하드코딩된 `<worktree>/target/debug/kebab`** 로 찾는데, 브리프가
강제한 `CARGO_TARGET_DIR=/build/out/cargo-target/target` 리다이렉트 때문에 실제
바이너리는 `/build/out/cargo-target/target/debug/kebab`에 빌드됨. 즉 **사전
존재하던 테스트-인프라 경로 가정 문제이지 내 코드 회귀가 아님**.
검증: gitignore된 `target` 심링크(`→ /build/out/cargo-target/target`)로 경로를
해결한 뒤 전체 재실행 → `cli_error_wire` 포함 **312 passed / 0 failed** (위 표).
심링크는 검증 후 제거(deliverable 아님, gitignore라 미커밋).
> 후속 권장: 해당 테스트들의 `kebab_bin()`을 `env!("CARGO_BIN_EXE_kebab")`로
> 교체하면 `CARGO_TARGET_DIR` 리다이렉트에 무관해진다. 본 작업 범위 밖이라
> 미수정.
### 실제 동작 확인 (smoke, provider 기본=embedder on / expansion off)
작은 2-문서 markdown corpus ingest:
`--json` stream kind counts:
`scan_started:1, scan_completed:1, asset_started:2, asset_chunked:2,
asset_timings:2, asset_finished:2, completed:1, ingest_report.v1:1`
`asset_chunked` 샘플:
```json
{"chunks":2,"idx":1,"kind":"asset_chunked","schema_version":"ingest_progress.v1","total":2,"ts":"2026-06-02T13:56:11Z"}
```
`asset_timings` 샘플:
```json
{"chunk_ms":676,"embed_ms":109,"expansion_ms":0,"idx":1,"kind":"asset_timings","parse_ms":6,"schema_version":"ingest_progress.v1","store_ms":920,"total":2,"ts":"2026-06-02T13:56:12Z"}
```
human-mode(`KEBAB_PROGRESS=plain`) stderr:
```
ingest: 1/2 → 2 chunks
⏱ parse 3ms · chunk 673ms · expand 0ms · embed 96ms · store 33ms
```
`expansion_progress`는 expansion 비활성(원격 LLM 미연결)이라 미발신 — 단위/통합
테스트로 충분히 커버(브리프 §검증 명시).
## 잔여 리스크 / 한계
- **image/pdf phase timing 없음**: 두 경로는 `asset_chunked`만 emit, `asset_timings`
미발신(phase shape가 OCR/caption이라 다름). 브리프가 허용한 범위.
- **expansion_progress 비-TTY 억제**: 스로틀에도 비-TTY human 모드에선 로그 폭주
방지로 기본 억제(진행바 message로 커버, `--json`은 전량 발신).
- **expansion_progress 실측 미검증**: 원격 GPU Ollama 필요. 코드 경로는 단위/통합
으로 검증했으나 라이브 카운터의 실제 rate/ETA 체감은 도그푸딩(원격 LLM 연결)
에서 별도 확인 권장.
- 커밋만 수행, push/PR 미수행(메인 세션 처리).