spec(p9-fb-01..03): ingest progress events + cancellation in §2.4a / §10 #51
Reference in New Issue
Block a user
Delete Branch "spec/p9-fb-01-progress-events"
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?
Summary
도그푸딩 후 추가된 long-running 작업 진행 표시 + cancel 정책을 frozen design 에 명시. p9-fb-01/02/03 (ingest progress callback / CLI display / TUI background) 의 spec PR — impl PR 들이 이어진다. p9-dogfooding-feedback.md 의 "spec PR vs impl PR" 정책에 따라 impl PR 들과 분리.
변경
신규 wire schema:
docs/wire-schema/v1/ingest_progress.schema.jsonkebab ingest --json의 line-delimited streaming event schema. discriminated bykind:scan_started→scan_completed→(asset_started < asset_finished)*→(completed | aborted).embed_batch_started/embed_batch_finished) 는 asset 이벤트 사이 임의 위치.ingest_report.v1그대로 — 외부 wrapper backward-compat.Frozen design §2.4a (신규)
IngestProgressEvent절. 이벤트 ordering /aborted의 idempotency / CLI 의 stderr (사람-친화) vs stdout (--jsonline-delimited) 분리 / TUI · desktop 의 in-memory mpsc 소비.Frozen design §10 (long-running 작업 절 추가)
두 invariant:
mpsc::Sender<IngestEvent>로 흘려보내고 CLI / TUI / desktop 이 각자 방식으로 소비.Option<Arc<AtomicBool>>cancel token, true 면 in-flight asset 마무리 후Abortedevent +Ok(IngestReport)정상 반환. 부분 commit 된 doc/chunk 는 idempotent 재실행으로 이어짐.kebab-coretrait (§7.2) 시그니처는 무영향 —kebab-appfacade 의 hidden parameter 로 추가.Out of scope (impl PR 들에서 처리)
kebab-app의IngestEventenum +ingest_with_config_progressimpl → p9-fb-01 PRTest plan
코드 변경 없음. doc + JSON Schema 만.
ingest_report.v1와 backward-compat (마지막 줄 형식 유지)회차 1 — nit 3건 + 칭찬 2건. spec PR (코드 무영향) 이라 표면 작음.
Actionable:
kind_result필드명 모호 →result권장. 예제 JSON 줄도 동시 fix.ts에format: "date-time"추가 — RFC 3339 자동 검증.frozen design §11 동결 범위 (wire schema 추가만, 기존 변경 X) 정확히 준수. p9-fb-04 cancel invariant (
Aborted가Err아닌Ok반환 + idempotent 재실행) 까지 spec 으로 끌어올린 게 좋음 — 후속 impl PR 의 review 부담 ↓.@@ -277,6 +277,27 @@ variant 별 해당 키만 채움. `path` 와 `uri` 는 항상 채움 (`uri` 는`--summary-only` 시 `items: null`.### 2.4a IngestProgressEvent(칭찬) §2.4a 의 신설 위치 — IngestReport (§2.4) 다음, DocSummary (§2.5) 앞 — 가 wire schema 흐름과 일관 (스캔 결과 → 진행 → 문서 목록). number suffix
a로 기존 §2.5+ 번호 유지 → frozen design §11 동결 범위 ("신규 wire schema 추가 OK") 와 일치.@@ -1302,0 +1325,4 @@초 단위 이상 걸리는 모든 명령 (`kebab ingest`, future `kebab eval run`, RAG streaming, embed 배치) 은 다음 두 invariant 를 지킨다:1. **진행 표시는 surface 별로 분리되되 source 는 단일.** facade (`kebab-app`) 가 progress event 를 `mpsc::Sender<IngestEvent>` (또는 그에 준하는 channel) 로 흘려보내고, CLI / TUI / desktop 이 각자 방식으로 소비. CLI 의 `--json` 모드는 §2.4a 의 line-delimited dump, 사람-친화 모드는 stderr spinner + 단계 라인. TUI 는 status bar 1 줄. desktop (P9-5) 는 progress widget.2. **cancel 은 cooperative + step boundary 에서 즉시 응답.** facade 가 `Option<Arc<AtomicBool>>` cancel token 받음. asset loop iteration / embed batch / vector upsert 같은 step boundary 마다 check, true 면 in-flight asset 마무리 후 `Aborted` event 발신 + `Ok(IngestReport)` 정상 반환 (Err 아님 — 정상 종료의 한 형태). 부분 commit 된 doc/chunk 는 SQLite 에 살아있어 재실행이 idempotent. CLI 는 SIGINT, TUI 는 `Esc` / `Ctrl-C` 가 cancel 신호.(칭찬) cancel 정책 명시:
Aborted가Err가 아닌Ok(IngestReport)정상 반환 + 부분 commit 된 doc/chunk 는 idempotent 재실행으로 이어진다는 contract 를 spec 옆에서 잡음. p9-fb-04 (cancellation) impl PR 의 핵심 invariant 를 spec 이 미리 frozen — review 시 "왜 Err 안 던지나" 의문 즉시 차단.@@ -1302,0 +1329,4 @@`kebab-core` trait (§7.2) 시그니처는 무영향 — progress / cancel 은 `kebab-app` facade 의 hidden parameter 로 추가 (`ingest_with_config_progress(..., progress: Option<Sender<IngestEvent>>, cancel: Option<Arc<AtomicBool>>)`).(nit / 가독성) §10 의 long-running 절 끝 +
kebab doctor 출력 (사람):시작 사이에 빈 줄 3 줄 (file line 1331 / 1332 / 1333) — 한 줄이면 충분. 다른 절 사이는 모두 1 줄.Why: markdown 자체 렌더링은 영향 없지만 raw spec 의 일관성 ↓.
How to apply: 빈 줄 2 줄 제거 (3 → 1).
@@ -0,0 +19,4 @@"completed","aborted"]},(보강 권장)
ts가 RFC 3339 timestamp 라고 description 에 명시했는데 JSON Schema 의format: "date-time"추가하면 자동 검증 가능. 외부 wrapper (Claude Code skill / MCP) 가 schema validator 통과 시 timestamp parse 실패 즉시 잡힘.Why: RFC 3339 vs ISO 8601 vs 임의 string 이 free-text description 으로만 구분되면 wrapper 가 다른 format 을 emit 할 위험.
How to apply:
"ts": { "type": "string", "description": ... }에"format": "date-time"한 줄 추가.@@ -0,0 +26,4 @@"idx": { "type": "integer", "minimum": 1, "description": "asset_started / asset_finished: 1-based index of the current asset within the scan." },"path": { "type": "string", "description": "asset_started: workspace-relative path of the asset being processed." },"media": { "type": "string", "description": "asset_started: media kind label (e.g. `markdown`, `pdf`, `image`)." },"kind_result": {(naming / 모호성)
kind_result필드명이 모호 — top-levelkind가 이미 event discriminator 라kind_result가 "무슨 kind?" 의문 유발.result또는outcome이 명확.Why:
asset_finishedevent 의 per-asset 결과 (new / updated / skipped / error) 인데kind_result는 "kind 의 result" 와 "result 가 kind" 둘 다로 읽힘.ingest_report.v1.items[].kind와 짝을 맞추는 의도였다면result가 더 명료 (또는 description 그대로 사용한outcome).How to apply: schema 의 필드명
kind_result→result로 변경 + 같은 변경을 §2.4a 의 예제 JSON 줄 (file line 290) 에도 적용. 두 곳 동시 fix 필요.회차 2 — 회차 1 nit 3건 모두 반영 (빈 줄 3→1, kind_result→result, ts format date-time). 추가 actionable 0. APPROVE.