작업 입력(brief)과 산출 증거(report: 변경/이벤트/exit-code 검증/smoke 샘플/ 잔여 리스크). 메인 세션이 PR 정리 시 드롭 가능한 worktree 메타 아티팩트. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.5 KiB
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 샘플:
{"chunks":2,"idx":1,"kind":"asset_chunked","schema_version":"ingest_progress.v1","total":2,"ts":"2026-06-02T13:56:11Z"}
asset_timings 샘플:
{"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 미수행(메인 세션 처리).