3회차 리뷰가 취소 처리에 CRITICAL/HIGH 가 없음을 확인하고(특히 break 후에도
`flush_vector_deletes` 가 돌아 고아 벡터가 안 생기는 것, `examined` 가 네
경계에서 모두 맞는 것) 머지 가능으로 결론냈다. 남은 지적 다섯을 반영한다.
1) 빈 스캔 + sweep 취소가 Aborted 가 아니라 Completed 로 보고됐다 (MEDIUM)
`was_cancelled` 는 asset 루프 **본문**에서만 세팅되는데, 스캔이 0건이면
본문이 한 번도 안 돈다. 하필 그게 sweep 이 가장 커지는 경우다 — 스캔이
0건이면 저장된 모든 경로가 후보이므로, "색인된 디렉토리를 통째로 지우고
재색인 → 긴 sweep → Ctrl-C" 가 정확히 이 구멍에 들어간다. 사용자는
취소했는데 "완료"를 본다.
플래그로 시드한다. `cancelling_a_sweep_with_nothing_left_to_scan_still_
reports_aborted` 로 고정했는데, 처음 쓴 버전은 워크스페이스 상위만
지워서 픽스처가 하위 디렉토리에 남았고 asset 루프가 한 번 돌아 **되돌려도
통과했다**. 재귀 삭제로 고치고, 전제(`ScanCompleted { total: 0 }`)를
어서션으로 박았다. 시드를 되돌리면 실패하는 것을 확인했다.
2) CLI 테스트의 position 어서션에 탐지력이 없었다 (LOW)
두 시나리오를 따로 돌려서 두 번째에 `SweepProgress` 가 없었고, position 은
`SweepStarted` 의 `set_position(0)` 이후 계속 0 이었다. 즉
`dress_bar_for_assets` 안의 `set_position(0)` 을 지워도 통과했다.
하나로 합쳐 sweep 이 position 을 3 까지 올린 뒤 복구를 본다.
3) 그 테스트 주석이 실제 범위보다 넓게 주장했다 (LOW)
"하트비트 키까지 고정한다" 고 적었는데 실제로는 length/position 만 본다.
indicatif 가 스타일을 되읽을 방법을 주지 않으므로, 그 절반은
`dress_bar_for_assets` 가 양쪽 phase 의 유일한 옷 입히는 자리라는 사실에
기댄다 — 주석을 그렇게 고쳐 적었다.
4) 취소 계약 독 주석이 stale 했다 (LOW)
`ingest_with_config` 의 §10 계약에 "in-flight asset 이 끝나고 이후는
스킵" 만 있고 sweep 이 이제 취소를 본다는 사실이 없었다.
5) DOGFOOD §1.8 이 자기 모순이었다 (LOW)
`sweep_completed.checked == total` 을 단정하고 바로 다음 줄에서 취소 시엔
다르다고 했다. 앞줄에 "취소 없이 완주하면" 을 붙였다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017c9JwQq8ZkGvYjpKXMiDhF
2회차 리뷰가 1회차 지적 6건 모두 실질 해결됐음을 확인하고 머지 가능으로
결론냈다. 새로 나온 것 중 값이 있는 것을 반영한다.
1) sweep 이 취소 플래그를 보지 않았다 (MEDIUM)
CLI 의 첫 Ctrl-C 는 "aborting after current asset" 을 찍고 AtomicBool 을
세운다. 그런데 `sweep_deleted_files` 는 그걸 인자로 받지도 검사하지도
않았다. 즉 긴 sweep 중에는 그 안내가 사실이 아니었고, 사용자에게 남은
수단은 두 번째 Ctrl-C 뿐인데 그건 `exit(130)` 이라 버퍼에 쌓인 벡터
삭제(최대 5,000)가 고아로 남는다.
이슈 #228 자체가 "sweep 중 Ctrl-C 를 세 번 눌러 죽였다" 는 보고다.
눌렀을 때의 동작을 그대로 둔 채 표시만 고치는 건 절반만 고친 것이다.
루프 상단에서 검사하고 break 한다. `sweep_completed.checked` 는 예고한
`total` 이 아니라 실제 검사한 수로 나간다 — total 을 그대로 쓰면 하지
않은 일을 했다고 보고하는 셈이다. `a_cancelled_sweep_stops_and_reports_
only_what_it_examined` 로 고정.
2) 1회차 HIGH 가 테스트로 고정되지 않았다 (MEDIUM)
바 상태 오염은 "phase 를 하나 더 추가하면 또 밟는" 유형인데 회귀 가드가
없었다. 실제로 한 번 배포될 뻔했다. `sweep_hands_the_bar_back_to_the_
asset_loop` 이 sweep 중에는 후보 수를, 끝난 뒤에는 asset 수를 세는지
본다. `ProgressMode::Human { tty: false, quiet: true }` 면 바가 hidden
draw target 으로 살아 있어 터미널 없이도 상태를 볼 수 있다.
3) 잔가지 (LOW)
- `SweepProgress` 독 주석이 `removed` 를 두 경우로만 설명했다. 이번
PR 이 만든 세 번째 경우(purge 실패)가 빠져 있었다. 스키마 쪽은 이미
맞게 적혀 있었다.
- `sweep_deleted_files` 의 기존 독 주석이 "purge 실패가 per-file 레벨
에서 error 로 집계된다" 고 적었는데 사실이 아니다. 어떤 카운터에도
안 들어가고 `IngestReport.errors` 에도 반영되지 않는다. 실패 경로를
손댄 김에 고쳤다.
- HOTFIXES 와 DOGFOOD 가 `purge_failed` 와 취소 동작을 언급하지 않았다.
HOTFIXES 가 live SoT 인데 실제 표면보다 좁게 적힌 상태였다.
미반영: `SweepCompleted` 가 TTY 에서 `bar.println` 대신 stderr 로 직접
쓰는 것(기존 `AssetTimings`/`PdfOcr*` 과 같은 패턴이라 신규 회귀가 아니고,
바꾸려면 셋을 같이 옮겨야 한다). `ScanCompleted` 에서 스타일을 길이보다
먼저 세팅하는 순서가 이론상 `0/u64::MAX` 프레임을 허용한다는 지적 — 창이
마이크로초이고 `SweepCompleted` 쪽에서는 새 순서가 더 낫다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017c9JwQq8ZkGvYjpKXMiDhF
리뷰 두 건에서 나온 지적을 반영한다.
1) sweep 이 끝나도 asset 진행바가 복구되지 않았다 (HIGH)
sweep 은 asset 진행바를 빌려 쓰면서 자기 라벨과 자기(더 작은) 총계를
씌운다. 그런데 `AssetStarted` 는 위치와 메시지만 세팅하고 길이·스타일은
건드리지 않는다. 그래서 sweep 이 한 번 돌면 그 뒤 색인 구간 전체가
`sweep [====] 4213/21` 로 그려졌다 — 라벨도 분모도 틀린다.
더 나쁜 건 스타일 교체가 v0.26.1 의 커스텀 키 `{asset_elapsed}` 를 같이
날린다는 점이다. 느린 asset 에서 `(Ns)` 가 도는 게 "멈춘 게 아님"의 유일한
신호인데 sweep 이 그걸 없앤다. 이 PR 이 sweep 구간에서 없앤 "hang 처럼
보임" 을 asset 구간에 새로 만드는 셈이었다.
바 세팅을 `dress_bar_for_assets` 로 빼고 `ScanCompleted` 와
`SweepCompleted` 양쪽에서 부른다. 스타일을 길이보다 먼저 세팅하는데,
indicatif 가 두 호출 사이에 다시 그릴 수 있어 과도기 프레임이 최소한
올바른 라벨을 달게 하기 위해서다.
TTY 전용이라 비-TTY 실측만 보고 있어서 놓쳤다. pty 로 재현해 고친 뒤
`ingest [====] 16/17 doc9.md` 로 나오는 것을 확인했다.
2) docs/DOGFOOD.md §1.8 이 갱신되지 않았다 (MEDIUM)
`--json` 이벤트 순서 목록에 sweep 3종이 빠져 있었다. verify 항목에
sweep 분모·연속성과 "sweep 뒤 진행바가 asset 분모로 돌아오는가" 를 넣었다
— 1번이 정확히 그 항목이 있었으면 잡혔을 결함이다.
3) ingest_progress.rs 의 ordering invariant 주석이 stale 했다 (MEDIUM)
§2.4a 순서 블록에 sweep 이 없었다. 설계 문서 자체는 frozen baseline 이고
HOTFIXES dated entry 가 있으니 규약상 문제없지만 코드 주석은 living 이다.
4) 잔가지 (LOW)
- purge 실패가 "디스크에 남아 있어 그냥 뒀다" 와 구분되지 않았다. 둘 다
`removed: false` 이고 ndjson 에는 아무것도 안 남아,
`sweep_summary` 의 `checked - purged` 차이로도 못 가른다. 사후 기록이
로그뿐이라는 게 이 PR 의 전제이므로 `purge_failed` 를 추가했다.
- `SweepProgress` 가 purge 할 때만 바 메시지를 세팅하고 비우지 않아,
마지막 purge 경로가 이후 후보를 훑는 내내 남았다. 안 지울 때는 비운다.
- 주석과 스키마가 신규 이벤트를 `v0.33.0` 이라고 적었는데, CLAUDE.md 의
bump 규칙상 이 변경은 patch 다 (additive-only wire + 관측성 개선, 새
명령·플래그·config 없음, 검색·색인 결과 불변 — 선례가 asset_phase).
`v0.32.1` 로 고쳤다.
미반영: `SweepCompleted` 가 TTY 에서 `bar.println` 대신 stderr 에 직접
쓰는 것 — 기존 `AssetTimings`/`PdfOcr*` 이 같은 패턴이라 신규 회귀가 아니고,
바꾸려면 그 셋을 같이 옮겨야 해서 별 건이다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017c9JwQq8ZkGvYjpKXMiDhF
in-process LRU search cache 는 spine 재작성(#214)에서 이미 삭제됐는데
그 비계(scaffold)만 남아 있었다. 잔존물 제거:
- App::search/search_uncached 붕괴 (search() 는 1줄 위임자였음)
- search_uncached_with_config facade + 그 테스트 삭제
- 죽은 `search --no-cache` / `search --explain` CLI 플래그 제거
(ask --explain 은 live — 건드리지 않음)
- 안 읽히던 RagCfg.explain_default config 필드 + fixture 제거
- 제거된 표면을 가리키던 stale 주석/문서(citation_helper/hybrid/
search·app-facade README/DOGFOOD/HANDOFF) 정정
코어 검색 출력은 byte-identical (search_uncached 본문이 search() 로
verbatim 이동). `search_cache: false` wire capability 는 유지 — 제거 시
schema.v1 breaking bump 이라 손해. 적대적 검증 2렌즈(correctness-preserved
+ wire/config-safe) 통과, dead-symbol-complete 가 잡은 5건 주석/문서 정정 완료.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
Phase 2 Unit 1. OcrCfg(image) 13필드가 PdfOcrCfg(pdf) 와 전부 중복(image 고유
0, pdf 고유 4) + apply_env 에 KEBAB_IMAGE_OCR_*/KEBAB_PDF_OCR_* 27 arm 복제를
제거한다.
- 신규 SharedOcrEngineCfg(13 공유 필드, 전부 Option, default None) = [ingest.ocr].
엔진 설정 단일 출처. image/pdf 블록은 on/off 토글 + override.
- load-time resolution(Config::resolve_ocr, from_file 호출): 공유 필드가 Some 이고
미디어 블록이 그 키 미명시면 concrete OcrCfg/PdfOcrCfg 로 overlay(presence 는
toml::Value 로 판정; 미디어 > 공유 > 내장 default). struct 필드는 그대로 두고
엔진 필드에 #[serde(default)] 만 추가(slim 블록 파싱) → image(gemma4:e4b/1600)
vs pdf(qwen2.5vl:3b/2048) 미디어별 기본값 보존.
- resolver Config::image_ocr()/pdf_ocr() 추가. consumer(kebab-parse-image,
kebab-app build_*_ocr_engine·ingest gate·pdf_ocr_apply·ingest_config_signature)가
전부 경유 → god-struct 직접 read 제거.
- apply_env: 27 arm → 공유 KEBAB_OCR_* 12 arm(image+pdf 동시) + pdf 고유 4 arm +
미디어별 KEBAB_IMAGE_OCR_ENABLED/KEBAB_PDF_OCR_ENABLED.
- step_4_to_5: [ingest.image.ocr] 12 엔진 키를 [ingest.ocr] 로 move_table(enabled
제외). pdf 블록 무손상(reconcile 이 채워 공유 overlay 오염 X). annotated_default
도 동일 통합으로 v5 canonical 형상. CURRENT_SCHEMA_VERSION=5.
- v4→v5 round-trip 테스트(비-default image engine 보존 + pdf 오염 X + 멱등). effective
OCR 바이트 동일 → ingest_config_signature 불변 → 강제 재색인 없음.
검증: clippy --workspace --all-targets 0 / kebab-config·kebab-parse-image·
kebab-parse-pdf·kebab-app 테스트 pass. surface: README [ingest.ocr] 절 + SMOKE
config 블록 + DOGFOOD env + HOTFIXES dated entry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
eval --config facade 패치로 dogfood KB 직접 평가 가능해짐에 따라
§10 Eval 에 §10.2 검색 품질 baseline 섹션 추가.
- golden suite 실행 명령 (hybrid + lexical eval run → aggregate)
- v0.20.2 metric baseline 표 (hybrid hit@3=1.0 / MRR=0.833)
- 정성 체크리스트 (한국어 2자 hit@3, empty=0, MRR 임계치)
- golden 큐레이션 절차 + dispatch.py 오류 교훈
- §10.1 로 기존 basic eval run 재구성
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3-round dogfood-driven fix cycle 의 산출물:
- bugfix1 (Bug #2/#3/#4): spec 964 line + plan 848 line
- bugfix2 (Bug #6/#7, #8 falsified): spec 308 line + plan 388 line
- bugfix3 (Bug #9/#10/#11/#13/#14, #12 falsified): spec 410 line + plan 1043 line
- docs/DOGFOOD.md: 전방위 dogfood checklist 의 전체 (§0 environment ~ §13 reference corpus)
각 round 의 spec/plan 가 critic + verifier round 2 closure ACCEPT 후 frozen. dogfood-driven evidence 기반.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>