目的
doc の削除処理に per-run 上限が無く、大量削除が 1 run に集中する。上限を入れる。あわせて、削除の teardown が surface ごとに独立していない構造を直す。
前提の訂正(2026-08-02)
本 issue は当初「doc の reap が graph edge を消さない」を第一の欠けとして立てたが、これは誤りだった。
doc_edges の writer は indexWikiEdges の 1 経路のみで、src_vector_id は wiki ページの wvid、dst_vector_id は wikiDocVectorId(repo, slug)。src/index.ts の backfill も WHERE type = 'wiki_doc' で絞っている。したがって d: prefix の doc vector id は doc_edges の端点になり得ず、消し残る edge は存在しない。
「wiki は消すのに doc は消さない」という非対称は defect ではなかった。wiki ページには edge があり repo doc には無い、というデータの違いに扱いが対応しているだけ。
誤りの形: wiki と doc のコードの差を読んで、その差がデータ上の欠陥を生んでいるかを確かめずに issue 本文へ書いた。
この不変条件は削除ループのコメントとして残す(「doc_edges の端点は両側とも wiki vector id。この不変条件が変わったらここに teardown を足すこと」)。no-op の DELETE は置かない — rules/model/subtractive-structural-beauty.md の (A) load-bearing / (B) push surplus に反する。
欠け 1: 削除ループに per-run 上限が無い
doc 削除ループは deletedDocs 全件を無条件に回す。1 件あたり 4 subrequest(Vectorize / FTS5 / graph / store)。
wiki 側は MAX_WIKI_DELETIONS_PER_REPO_PER_RUN(既定 5)で cap しており、理由が doc comment に明記されている——「Each reap fans out to ~4 subrequests, so an unbounded loop over a mass rename could exhaust the invocation budget on its own and starve every repo behind it (issue #184)」。
doc 側は同じ失敗形が塞がれていない。docs は LIGHT_CRON(0 * * * *)で issues / releases と同居し、1 invocation で 3 surface × 5 repo を捌くため予算は共有されている。
実例: github-webhook-mcp #243 / PR #244 で .claude/ 65 ファイル + Li+config.md を一度に削除した。この 66 件が次の LIGHT_CRON 1 回に集中する。
cap に伴う ETag の扱い
cap を入れると「残りは次 run で消える」ことが前提になるが、現状のままでは成立しない。cap で削除を残したまま tree ETag を前進させると、次 run は 304 で短絡し deletedDocs を見る前に return するため drain が止まる。
fetch 枠が既に持っている「枠切れなら旧 ETag 据え置き」を削除枠にも広げること。
欠け 2: teardown が surface ごとに独立していない
現状の doc 削除は外側 try が Vectorize を含むため、Vectorize が落ちると FTS5 行も store 行も残る。ユーザーが実際に retrieval するのは D1 側なので、取り残されて困るのはそちら。
wiki の reap は surface ごとに独立して teardown し、その理由も明記されている(「a Vectorize failure must not strand the D1 rows, which are the ones users actually retrieve」)。doc 側を同じ形に揃える。
制約
- doc の削除検出そのもの(
currentPaths 差分)は変えない
- cap を入れたうえで、残りが次 run で消える単調減少であること(wiki 側と同じ性質)
- wiki 側の既存挙動を変えないこと
受け入れ条件
- doc 削除ループに per-run 上限があり、上限を超えた分が次 run に持ち越されることがテストで固定されている
- 削除の backlog が残っている間 tree ETag が前進しないことがテストで固定されている(持ち越しが実際に成立することの担保)
- Vectorize の削除が失敗しても FTS5 行と store 行の削除が実行されることがテストで固定されている
- wiki 側の既存テストが無変更で pass する
想定変更箇所
src/poller.ts(doc 削除ループ)
- 対応するテスト
docs/(削除処理と枠の記述)
派生
目的
doc の削除処理に per-run 上限が無く、大量削除が 1 run に集中する。上限を入れる。あわせて、削除の teardown が surface ごとに独立していない構造を直す。
前提の訂正(2026-08-02)
本 issue は当初「doc の reap が graph edge を消さない」を第一の欠けとして立てたが、これは誤りだった。
doc_edgesの writer はindexWikiEdgesの 1 経路のみで、src_vector_idは wiki ページのwvid、dst_vector_idはwikiDocVectorId(repo, slug)。src/index.tsの backfill もWHERE type = 'wiki_doc'で絞っている。したがってd:prefix の doc vector id はdoc_edgesの端点になり得ず、消し残る edge は存在しない。「wiki は消すのに doc は消さない」という非対称は defect ではなかった。wiki ページには edge があり repo doc には無い、というデータの違いに扱いが対応しているだけ。
誤りの形: wiki と doc のコードの差を読んで、その差がデータ上の欠陥を生んでいるかを確かめずに issue 本文へ書いた。
この不変条件は削除ループのコメントとして残す(「
doc_edgesの端点は両側とも wiki vector id。この不変条件が変わったらここに teardown を足すこと」)。no-op の DELETE は置かない —rules/model/subtractive-structural-beauty.mdの (A) load-bearing / (B) push surplus に反する。欠け 1: 削除ループに per-run 上限が無い
doc 削除ループは
deletedDocs全件を無条件に回す。1 件あたり 4 subrequest(Vectorize / FTS5 / graph / store)。wiki 側は
MAX_WIKI_DELETIONS_PER_REPO_PER_RUN(既定 5)で cap しており、理由が doc comment に明記されている——「Each reap fans out to ~4 subrequests, so an unbounded loop over a mass rename could exhaust the invocation budget on its own and starve every repo behind it (issue #184)」。doc 側は同じ失敗形が塞がれていない。docs は
LIGHT_CRON(0 * * * *)で issues / releases と同居し、1 invocation で 3 surface × 5 repo を捌くため予算は共有されている。実例: github-webhook-mcp #243 / PR #244 で
.claude/65 ファイル +Li+config.mdを一度に削除した。この 66 件が次の LIGHT_CRON 1 回に集中する。cap に伴う ETag の扱い
cap を入れると「残りは次 run で消える」ことが前提になるが、現状のままでは成立しない。cap で削除を残したまま tree ETag を前進させると、次 run は 304 で短絡し
deletedDocsを見る前に return するため drain が止まる。fetch 枠が既に持っている「枠切れなら旧 ETag 据え置き」を削除枠にも広げること。
欠け 2: teardown が surface ごとに独立していない
現状の doc 削除は外側 try が Vectorize を含むため、Vectorize が落ちると FTS5 行も store 行も残る。ユーザーが実際に retrieval するのは D1 側なので、取り残されて困るのはそちら。
wiki の reap は surface ごとに独立して teardown し、その理由も明記されている(「a Vectorize failure must not strand the D1 rows, which are the ones users actually retrieve」)。doc 側を同じ形に揃える。
制約
currentPaths差分)は変えない受け入れ条件
想定変更箇所
src/poller.ts(doc 削除ループ)docs/(削除処理と枠の記述)派生
src/webhook.tsの push 経路 doc 削除が、修正前 poller と同じ構造(Vectorize 失敗で store 行が残る)のまま。webhook が push の一次経路で cron はその fallback なので、実際に多く通るのはあちら