Files
kebab/tasks/p9/p9-fb-01-ingest-progress-callback.md
altair823 eb331f9b29 feat(app): add IngestEvent + ingest_with_config_progress (p9-fb-01)
Streaming progress channel for ingest. Facade emits one IngestEvent per
step boundary into an optional `mpsc::Sender<IngestEvent>` injected by
the caller. CLI (p9-fb-02), TUI (p9-fb-03), and future desktop UI all
consume the same stream.

신규:
- crates/kebab-app/src/ingest_progress.rs: `IngestEvent` enum (`#[serde(tag
  = "kind", rename_all = "snake_case")]` matching wire schema
  ingest_progress.v1) + `AggregateCounts` struct + `media_label` helper
  + best-effort `emit` helper.
- ingest_with_config_progress(cfg, scope, summary_only, progress) —
  존재 시 `mpsc::Sender<IngestEvent>` 로 ScanStarted → ScanCompleted →
  (AssetStarted < AssetFinished)* → Completed 발신. dropped receiver
  는 silent absorb (hot path stall 금지).
- 기존 ingest_with_config 가 `progress=None` forwarding wrapper.

미적용 (계약 상 향후 task 가 채움):
- IngestEvent::Aborted: cancel token wiring 은 p9-fb-04.
- embed_batch_started / embed_batch_finished: spec 의 \"asset 이벤트 사이
  임의 위치\" 에 해당. v1 단순화 — asset 단위 해상도면 CLI / TUI 충분.

Test:
- 6 lib unit (media_label / serde discriminator / emit corner cases).
- 3 integration (이벤트 sequence 가 §2.4a invariant 준수 / forwarding
  wrapper / dropped receiver tolerance).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:44:34 +00:00

2.8 KiB

phase, component, task_id, title, status, depends_on, unblocks, contract_source, contract_sections, source_feedback
phase component task_id title status depends_on unblocks contract_source contract_sections source_feedback
P9 kebab-app + kebab-core p9-fb-01 Ingest progress callback / event channel in_progress
p9-fb-02
p9-fb-03
../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md
§7 ingest
§10 UX
p9-dogfooding-feedback.md item 1

p9-fb-01 — Ingest progress callback

Goal

kebab_app::ingest_with_config 가 진행 상황을 caller 에게 흘려보낼 수 있도록 progress callback (또는 mpsc Sender) 주입 surface 추가. CLI / TUI / desktop 셋 모두 같은 이벤트 stream 소비.

Why now

도그푸딩 시 ingest 가 1.8 초 묵음 후 결과만 출력 — hung 인지 빈 워크스페이스인지 구분 불가. progress event 가 모든 UI surface 의 prerequisite.

Allowed dependencies

  • 기존 kebab-app deps. 신규 X.
  • std::sync::mpsc 또는 crossbeam_channel.

Public surface

#[derive(Debug, Clone)]
pub enum IngestEvent {
    ScanStarted { root: PathBuf },
    ScanCompleted { total: u32 },
    AssetStarted { idx: u32, total: u32, path: String, media: MediaKind },
    AssetFinished { idx: u32, kind: IngestItemKind, chunks: u32 },
    EmbedBatchStarted { n_chunks: u32 },
    EmbedBatchFinished { n_chunks: u32, ms: u64 },
    Aborted { partial_counts: AggregateCounts },
    Completed { counts: AggregateCounts },
}

#[doc(hidden)]
pub fn ingest_with_config_progress(
    config: kebab_config::Config,
    scope: SourceScope,
    summary_only: bool,
    progress: Option<Sender<IngestEvent>>,
) -> anyhow::Result<IngestReport>;

기존 ingest_with_configprogress=None 으로 forwarding wrapper.

Behavior contract

  • progress event 발신은 best-effort. receiver drop 되면 이후 send 무시 (panic 금지).
  • 이벤트 ordering: ScanStarted < ScanCompleted < (AssetStarted < AssetFinished)* < Completed|Aborted. embed batch 는 asset 사이 임의 위치.
  • Aborted 이벤트는 cancellation token (p9-fb-04) trigger 시에만 발신. CLI / TUI 의 cancel 신호 wiring 은 각각 p9-fb-04, p9-fb-03 에서 구현.
  • --json CLI 는 line-delimited 형태로 dump (schema_version=ingest_progress.v1) — 별도 task (p9-fb-02).

Test plan

kind description
unit Sender<IngestEvent> 가 ScanStarted → ScanCompleted → Asset* → Completed 순서로 받는다
integration tmp workspace 3 md → 받은 이벤트 sequence 가 monotonic idx

DoD

  • cargo test -p kebab-app 통과
  • 기존 ingest_with_config 호출자 (CLI 단발 호출) 변경 없음
  • HOTFIXES 항목 X — 신기능, deviation 아님

Out of scope

  • progress event JSON 직렬화 (별도 wire schema task)
  • TUI 가 이벤트 소비해서 status bar 그리기 (p9-fb-03)