feat(app): #228 삭제 sweep 을 진행바·ndjson 로그에 노출 #236

Merged
altair823 merged 4 commits from feat/sweep-progress-events into main 2026-08-16 13:08:25 +00:00
Owner

요약

sweep_deleted_files 는 walker 가 끝난 직후, asset 루프가 시작되기 전에 돈다. 이 구간이 관측 가능한 신호를 하나도 내지 않았다. 진행바는 walker 총계(0/12115)를 표시한 채 멈춰 보이고, ndjson 로그는 0바이트로 남는다. tracing::info! 은 나가지만 wire 이벤트가 아니라 사용자가 볼 산출물이 없다.

프로세스는 CPU 100% 로 정상 동작 중인데 밖에서는 hang 과 구별할 수 없다. 이슈가 보고한 대로 실제 도그푸딩에서 세 번 연속 Ctrl-C 로 죽였다.

ingest_progress.v1 에 세 이벤트를 추가한다 (additive — 기존 소비자는 모르는 kind 를 무시한다):

이벤트 필드
sweep_started total
sweep_progress idx, total, path, removed
sweep_completed checked, purged, ms

totalall_workspace_paths() 에서 이번 스캔이 덮은 경로를 뺀 값이라 루프 진입 전에 확정 분모가 나온다 (이슈 제안 1 그대로). removed 는 "정말 없어서 문서를 지웠다" 와 "아직 디스크에 있어 그대로 뒀다" 를 가른다 — 수천 건을 훑고 하나도 안 지우는 sweep 이 있으므로, 지울 때만 움직이는 진행바는 바로 그 경우에 다시 멈춰 보인다.

purged 를 두 이벤트에서 다른 타입으로 쓰지 않으려고 sweep_progress 쪽은 removed(bool), sweep_completed 쪽은 purged(정수) 로 이름을 나눴다. 같은 wire 키가 두 타입을 갖는 건 소비자 입장에서 함정이다.

ndjson 로그에는 purge { ts, doc_path }sweep_summary { ts, checked, purged, ms } 를 추가한다 (이슈 제안 2 가 요청한 형태). 로그가 유일한 사후 기록이다 — tracing 은 stderr 로 흘러가고 남지 않는다.

CLI 는 sweep 을 asset 진행바와 별 phase 로 그린다. 후보 12k 를 훑는 일과 asset 12k 를 색인하는 일은 분모가 다른 별개의 작업이라, 카운터를 공유하면 두 번째 구간이 처음부터 다시 시작하는 것처럼 보인다. 비-TTY 는 실제로 지운 것만 줄로 찍는다 — 검사한 후보마다 한 줄이면 그대로 둔 경로들이 run 의 진짜 출력을 덮는다.

설계: docs/superpowers/specs/2026-04-27-kebab-final-form-design.md §2.4a

곁다리 — clippy 게이트가 붉었다

cargo clippy --workspace --all-targets -- -D warnings 가 main 에서 실패하고 있었다. 툴체인이 올라가면서 새 lint 둘(question_mark, manual_assert_eq)이 기존 코드에 걸린 것이고 각각 한 줄이다.

방치하면 안 되는 이유를 이번에 겪었다. kebab-parse-code 가 먼저 실패해서 뒤 크레이트가 아예 컴파일되지 않았고, 그 그늘에 PR #235 에서 내가 넣은 unnested_or_patterns 위반이 숨어 있었다. 게이트가 붉으면 새 위반이 안 보인다. 셋 다 여기서 고친다.

검증

문서 30건을 색인하고 21건을 지운 뒤 재색인:

ingest: sweeping 21 deleted-file candidates…
  purged doc11.md
  … (21줄)
ingest: sweep complete (checked=21 purged=21 in 134ms)

--json (실제 출력):

{"kind":"sweep_started","schema_version":"ingest_progress.v1","total":5,"ts":"…"}
{"idx":1,"kind":"sweep_progress","path":"doc11.md","removed":true,"schema_version":"ingest_progress.v1","total":5,"ts":"…"}
{"checked":5,"kind":"sweep_completed","ms":132,"purged":5,"schema_version":"ingest_progress.v1","ts":"…"}

ndjson 로그 (이슈가 보고한 0바이트가 아니다):

{"kind":"purge","ts":"…","doc_path":"doc11.md"}
{"kind":"sweep_summary","ts":"…","checked":5,"purged":5,"ms":132}
  • cargo test --workspace --no-fail-fast 녹색 (187 ok / 0 FAILED)
  • cargo clippy --workspace --all-targets -- -D warnings 녹색 — 이 세션 들어 처음이다

시험 항목 (Test Plan)

crates/kebab-app/tests/sweep_progress.rs (신규):

  • sweep_emits_a_bounded_phase_with_one_event_per_candidate — 분모가 후보 수와 맞는가, 진행 이벤트의 idx 가 1..=total 로 빠짐없이 연속인가, 순서가 SweepStarted < SweepProgress* < SweepCompleted 인가. 진행바는 길이를 먼저 알아야 위치를 그릴 수 있다.
  • a_sweep_that_purges_nothing_still_announces_itselfinclude 를 좁혀 후보는 있는데 purge 는 0인 경우. "checked 12115, purged 0" 이 사용자가 못 듣고 있던 바로 그 답이므로, 0-purge sweep 도 구간을 알려야 한다.

crates/kebab-app/tests/ingest_log_smoke.rs:

  • ingest_log_records_the_deleted_file_sweep — 로그에 purge 줄이 어느 문서였는지와 함께 남는가, sweep_summarychecked/purged/ms 를 들고 있는가. ms 는 이 구간이 run 의 병목이었는지를 사용자가 판단하는 근거다.
  • 기존 ingest_log_smokevalid_kinds 화이트리스트에 새 두 kind 추가.

비범위

reset --orphans-only — 이슈가 참고로 적은 같은 구조의 루프다. reset 에는 진행 채널 자체가 없어서 sweep 하나를 위해 배선을 새로 깔아야 하는데, #229#230 이 머지된 지금 이 경로의 문서당 비용이 약 800배 떨어져 "몇 시간 무표시" 상황이 애초에 안 나온다. 필요해지면 별 건으로 다룬다.

버전 영향

wire schema 의 additive minor 변경(신규 이벤트 kind, 기존 소비자 무영향)이라 CLAUDE.md §Versioning cascade 기준 major bump 대상이 아니다. 새 서브커맨드·플래그·config 키도 없고 검색·색인 결과도 그대로다 — 관측성 개선이므로 patch 쪽이다.

Assisted-by: Claude Code

## 요약 `sweep_deleted_files` 는 walker 가 끝난 직후, asset 루프가 시작되기 전에 돈다. 이 구간이 **관측 가능한 신호를 하나도 내지 않았다**. 진행바는 walker 총계(`0/12115`)를 표시한 채 멈춰 보이고, ndjson 로그는 0바이트로 남는다. `tracing::info!` 은 나가지만 wire 이벤트가 아니라 사용자가 볼 산출물이 없다. 프로세스는 CPU 100% 로 정상 동작 중인데 밖에서는 hang 과 구별할 수 없다. 이슈가 보고한 대로 실제 도그푸딩에서 세 번 연속 Ctrl-C 로 죽였다. `ingest_progress.v1` 에 세 이벤트를 **추가**한다 (additive — 기존 소비자는 모르는 `kind` 를 무시한다): | 이벤트 | 필드 | |---|---| | `sweep_started` | `total` | | `sweep_progress` | `idx`, `total`, `path`, `removed` | | `sweep_completed` | `checked`, `purged`, `ms` | `total` 은 `all_workspace_paths()` 에서 이번 스캔이 덮은 경로를 뺀 값이라 루프 진입 전에 확정 분모가 나온다 (이슈 제안 1 그대로). `removed` 는 "정말 없어서 문서를 지웠다" 와 "아직 디스크에 있어 그대로 뒀다" 를 가른다 — 수천 건을 훑고 하나도 안 지우는 sweep 이 있으므로, 지울 때만 움직이는 진행바는 바로 그 경우에 다시 멈춰 보인다. `purged` 를 두 이벤트에서 다른 타입으로 쓰지 않으려고 `sweep_progress` 쪽은 `removed`(bool), `sweep_completed` 쪽은 `purged`(정수) 로 이름을 나눴다. 같은 wire 키가 두 타입을 갖는 건 소비자 입장에서 함정이다. ndjson 로그에는 `purge { ts, doc_path }` 와 `sweep_summary { ts, checked, purged, ms }` 를 추가한다 (이슈 제안 2 가 요청한 형태). 로그가 유일한 사후 기록이다 — tracing 은 stderr 로 흘러가고 남지 않는다. CLI 는 sweep 을 asset 진행바와 **별 phase** 로 그린다. 후보 12k 를 훑는 일과 asset 12k 를 색인하는 일은 분모가 다른 별개의 작업이라, 카운터를 공유하면 두 번째 구간이 처음부터 다시 시작하는 것처럼 보인다. 비-TTY 는 실제로 지운 것만 줄로 찍는다 — 검사한 후보마다 한 줄이면 그대로 둔 경로들이 run 의 진짜 출력을 덮는다. 설계: `docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` §2.4a ### 곁다리 — clippy 게이트가 붉었다 `cargo clippy --workspace --all-targets -- -D warnings` 가 main 에서 실패하고 있었다. 툴체인이 올라가면서 새 lint 둘(`question_mark`, `manual_assert_eq`)이 기존 코드에 걸린 것이고 각각 한 줄이다. 방치하면 안 되는 이유를 이번에 겪었다. `kebab-parse-code` 가 먼저 실패해서 뒤 크레이트가 아예 컴파일되지 않았고, **그 그늘에 PR #235 에서 내가 넣은 `unnested_or_patterns` 위반이 숨어 있었다**. 게이트가 붉으면 새 위반이 안 보인다. 셋 다 여기서 고친다. ## 검증 문서 30건을 색인하고 21건을 지운 뒤 재색인: ``` ingest: sweeping 21 deleted-file candidates… purged doc11.md … (21줄) ingest: sweep complete (checked=21 purged=21 in 134ms) ``` `--json` (실제 출력): ```json {"kind":"sweep_started","schema_version":"ingest_progress.v1","total":5,"ts":"…"} {"idx":1,"kind":"sweep_progress","path":"doc11.md","removed":true,"schema_version":"ingest_progress.v1","total":5,"ts":"…"} {"checked":5,"kind":"sweep_completed","ms":132,"purged":5,"schema_version":"ingest_progress.v1","ts":"…"} ``` ndjson 로그 (이슈가 보고한 0바이트가 아니다): ```json {"kind":"purge","ts":"…","doc_path":"doc11.md"} {"kind":"sweep_summary","ts":"…","checked":5,"purged":5,"ms":132} ``` - `cargo test --workspace --no-fail-fast` 녹색 (187 ok / 0 FAILED) - `cargo clippy --workspace --all-targets -- -D warnings` **녹색** — 이 세션 들어 처음이다 ## 시험 항목 (Test Plan) `crates/kebab-app/tests/sweep_progress.rs` (신규): - [x] `sweep_emits_a_bounded_phase_with_one_event_per_candidate` — 분모가 후보 수와 맞는가, 진행 이벤트의 `idx` 가 1..=total 로 빠짐없이 연속인가, 순서가 `SweepStarted` < `SweepProgress*` < `SweepCompleted` 인가. 진행바는 길이를 먼저 알아야 위치를 그릴 수 있다. - [x] `a_sweep_that_purges_nothing_still_announces_itself` — `include` 를 좁혀 후보는 있는데 purge 는 0인 경우. "checked 12115, purged 0" 이 사용자가 못 듣고 있던 바로 그 답이므로, 0-purge sweep 도 구간을 알려야 한다. `crates/kebab-app/tests/ingest_log_smoke.rs`: - [x] `ingest_log_records_the_deleted_file_sweep` — 로그에 `purge` 줄이 어느 문서였는지와 함께 남는가, `sweep_summary` 가 `checked`/`purged`/`ms` 를 들고 있는가. `ms` 는 이 구간이 run 의 병목이었는지를 사용자가 판단하는 근거다. - [x] 기존 `ingest_log_smoke` 의 `valid_kinds` 화이트리스트에 새 두 kind 추가. ## 비범위 `reset --orphans-only` — 이슈가 참고로 적은 같은 구조의 루프다. reset 에는 진행 채널 자체가 없어서 sweep 하나를 위해 배선을 새로 깔아야 하는데, #229 와 #230 이 머지된 지금 이 경로의 문서당 비용이 약 800배 떨어져 "몇 시간 무표시" 상황이 애초에 안 나온다. 필요해지면 별 건으로 다룬다. ## 버전 영향 wire schema 의 **additive minor** 변경(신규 이벤트 kind, 기존 소비자 무영향)이라 CLAUDE.md §Versioning cascade 기준 major bump 대상이 아니다. 새 서브커맨드·플래그·config 키도 없고 검색·색인 결과도 그대로다 — 관측성 개선이므로 patch 쪽이다. Assisted-by: Claude Code
altair823 added 1 commit 2026-08-16 11:58:14 +00:00
`sweep_deleted_files` 는 walker 가 끝난 직후 asset 루프 전에 도는데, 이
구간이 관측 가능한 신호를 하나도 내지 않았다. 진행바는 walker 총계
(`0/12115`)를 표시한 채 멈춰 보이고 ndjson 로그는 0바이트로 남는다.
`tracing::info!` 은 나가지만 wire 이벤트가 아니라 사용자가 볼 산출물이
없다. 프로세스는 CPU 100% 로 정상 동작 중인데 밖에서는 hang 과 구별할 수
없고, 실제 도그푸딩에서 세 번 연속 Ctrl-C 로 죽였다.

`ingest_progress.v1` 에 세 이벤트를 추가한다 (additive — 기존 소비자는
모르는 kind 를 무시한다).

  sweep_started   { total }
  sweep_progress  { idx, total, path, removed }
  sweep_completed { checked, purged, ms }

`total` 은 `all_workspace_paths()` 에서 이번 스캔이 덮은 경로를 뺀 값이라
루프 진입 전에 확정 분모가 나온다 (이슈 제안 1). `removed` 는 "정말 없어서
문서를 지웠다" 와 "아직 디스크에 있어 그대로 뒀다" 를 가른다 — 수천 건을
훑고 하나도 안 지우는 sweep 이 있으므로, 지울 때만 움직이는 진행바는 바로
그 경우에 다시 멈춰 보인다.

`purged` 를 두 이벤트에서 다른 타입으로 쓰지 않으려고 sweep_progress 쪽은
`removed`(bool), sweep_completed 쪽은 `purged`(정수) 로 이름을 나눴다.
같은 wire 키가 두 타입을 갖는 건 소비자 입장에서 함정이다.

ndjson 로그에는 `purge { ts, doc_path }` 와 `sweep_summary { ts, checked,
purged, ms }` 를 추가한다 (이슈 제안 2 가 요청한 형태). 로그가 유일한 사후
기록이다 — tracing 은 stderr 로 흘러가고 남지 않는다.

CLI 는 sweep 을 asset 진행바와 별 phase 로 그린다. 후보 12k 를 훑는 일과
asset 12k 를 색인하는 일은 분모가 다른 별개의 작업이라 카운터를 공유하면
두 번째 구간이 처음부터 다시 시작하는 것처럼 보인다. 비-TTY 는 실제로
지운 것만 줄로 찍는다 — 검사한 후보마다 한 줄이면 그대로 둔 경로들이
run 의 진짜 출력을 덮는다.

실측 (문서 30건 색인 → 21건 삭제 → 재색인):

  ingest: sweeping 21 deleted-file candidates…
    purged doc11.md
    … (21줄)
  ingest: sweep complete (checked=21 purged=21 in 134ms)

`--json` 은 세 이벤트를 ingest_progress.v1 로 내보내고, ndjson 로그에는
purge 21줄 + sweep_summary 1줄이 남는다.

이슈가 참고로 적은 `reset --orphans-only` 는 그대로 뒀다. reset 에는 진행
채널 자체가 없어 sweep 하나를 위해 배선을 새로 깔아야 하는데, #229#230 이 머지된 지금 이 경로의 문서당 비용이 약 800배 떨어져 "몇 시간
무표시" 상황이 애초에 안 나온다.

곁다리 — clippy 게이트가 붉었다:

`cargo clippy --workspace --all-targets -- -D warnings` 가 main 에서
실패하고 있었다. 툴체인이 올라가면서 새 lint 둘(`question_mark`,
`manual_assert_eq`)이 기존 코드에 걸린 것이고 각각 한 줄이다. 방치하면 안
되는 이유를 이번에 겪었다 — `kebab-parse-code` 가 먼저 실패해 뒤 크레이트가
아예 컴파일되지 않았고, 그 그늘에 PR #235 에서 내가 넣은
`unnested_or_patterns` 위반이 숨어 있었다. 셋 다 여기서 고친다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017c9JwQq8ZkGvYjpKXMiDhF
altair823 added 1 commit 2026-08-16 12:18:19 +00:00
리뷰 두 건에서 나온 지적을 반영한다.

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
altair823 added 1 commit 2026-08-16 12:40:25 +00:00
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
altair823 added 1 commit 2026-08-16 13:02:05 +00:00
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
altair823 merged commit 9107f353df into main 2026-08-16 13:08:25 +00:00
altair823 deleted branch feat/sweep-progress-events 2026-08-16 13:08:26 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: altair823-org/kebab#236