Skip to content

fix(docs): tear down doc reap surfaces independently and cap it per run [poller, docs, tests] - #205

Merged
liplus-lin-lay merged 2 commits into
mainfrom
203-bugdocs-the-doc-reap-leaves-graph-edges-behind-and-has-no-per-run-cap
Aug 2, 2026
Merged

fix(docs): tear down doc reap surfaces independently and cap it per run [poller, docs, tests]#205
liplus-lin-lay merged 2 commits into
mainfrom
203-bugdocs-the-doc-reap-leaves-graph-edges-behind-and-has-no-per-run-cap

Conversation

@liplus-lin-lay

@liplus-lin-lay liplus-lin-lay commented Aug 2, 2026

Copy link
Copy Markdown
Member

Closes #203

何をしたか

doc 削除ループの実在の欠陥 2 つを直した。

1. surface ごとの独立 teardown

従来は 1 件を 1 本の外側 try で包んでいたため、Vectorize が落ちると store 行が残る。FTS5 だけは内側 try で守られていたので、**Vectorize 失敗時に「FTS5 だけ消えて store 行が残る」**という中途半端な状態になる。wiki reap と同じく surface ごとに独立して teardown する形に変更した(Vectorize / D1 FTS5 / store row の 3 面)。

2. per-run cap

MAX_DOC_DELETIONS_PER_REPO_PER_RUN(既定 5)を追加。1 件あたり 3 subrequest なので、5削除 × 3 × 5repo = 75。docs surface の既存 fetch 枠(約 250)に上乗せしても LIGHT_CRON worst case は 825 程度で 1000 の内側。

cap 追加に伴って ETag の扱いも直した。 cap で残した削除は、次 run で ETag が一致して 304 になると deletedDocs を見る前に return してしまい、tree がたまたま変わるまで drain が止まる。fetch 枠が既に持っていた「枠切れなら旧 ETag を据え置く」保持を削除枠にも広げた。分岐が 2 本あった watermark 書き込みは 1 本に畳んだ。cap を入れた以上これが無いと成立しない、対の変更。

issue 本文「欠け 1」の訂正 — graph edge の teardown は入れていない

issue は「削除された doc の graph edge が edge table に残り続ける」としていたが、そのような行は存在しない。

  • doc_edges への writer は indexWikiEdges の 1 経路のみ(src/pipeline/embed-doc.ts:199、wiki 分岐の中)
  • src 側は page の wvid、dst 側も extractMentionEdgeswikiDocVectorId で算出(src/graph.ts:70
  • 候補 slug は knownWikiSlugsWHERE type = 'wiki_doc' で絞る(src/graph.ts:85
  • src/index.ts の backfill も同じ条件で絞る
  • docs/0-requirements.md の Graph Index 節も "src/dst are deterministic wiki vector IDs" と明記

d: prefix の doc vector ID は端点になりえないので、deleteEdgesForVector の呼び出しは 0 行にしかマッチしない。入れていない。 初版では「将来 doc 側の edge writer が入ったときのための対称性」として入れていたが、これは rules/model/subtractive-structural-beauty.md の Detection signs が名指しする push surplus の形そのもので、裁定により撤回した。前提が崩れた以上「非対称」も defect ではない — wiki ページには edge があり repo doc には無い、というデータの違いに対して扱いが違うのは正しい。

知識は残した: 削除ループのコメントに「doc_edges の端点は両側とも wiki vector ID であり doc vector ID は該当しない(indexWikiEdges が唯一の writer)。この不変条件が変わったらここに teardown を足すこと」を置いた。docs の Graph Index 節にも同じ 1 文を追記。

テスト

src/poller-docs.test.ts を新規追加(src/poller-wiki.test.ts と同じ mock 構成)。テストのため pollDocs を export した。

挙動 3 本(修正前コードに対して fail することを実測確認済み):

  • Vectorize が失敗しても FTS5 / store の teardown が実行されること
  • 11 件削除 → 5 / 5 / 1 の 3 run で drain し、重複も取りこぼしも無いこと
  • backlog がある間 ETag が据え置かれ、次 run が If-None-Match 無しで tree を取り直すこと

control 4 本(修正前コードでも pass、境界の固定):

  • 削除対象だけが 3 面すべてで同一 docVectorId により teardown され、残る doc は無傷であること
  • FTS5 が失敗しても store の DELETE に到達すること
  • 削除ゼロの run では reap 側が一切呼ばれないこと
  • 枠内で終わった run では ETag が進むこと

全 178 件 green、tsc --noEmit clean。

変更していないもの

  • 削除検出(currentPaths 差分)
  • wiki 側の挙動(poller-wiki.test.ts 無変更で pass)

別軸として残したもの

webhook 経路の doc 削除(src/webhook.ts:490-515)に、今回直した「外側 try 1 本」の構造がそのまま残っている。webhook は push の一次経路で cron はその fallback なので実際に多く通るのはこちら。issue の想定変更箇所が src/poller.ts 限定だったため #206 に分離した(edge 追加は不要である旨も明記済み)。

…cs, tests]

The repository-docs reap tore down three surfaces (Vectorize, D1 FTS5, store
row) while the wiki reap tore down four, and it walked the whole deleted set
with no per-run budget. Both gaps are closed by adopting the wiki reap's shape.

- deleteEdgesForVector is now called for every reaped doc, and the four
  surfaces are torn down independently so a Vectorize failure no longer
  strands the D1 rows users retrieve.
- MAX_DOC_DELETIONS_PER_REPO_PER_RUN (5) caps the reap. At ~4 subrequests per
  deletion an unbounded loop over a mass deletion could exhaust the light
  cron's invocation budget alone; a single PR removing 66 .md files puts all
  of them on one run.
- The tree ETag is held back while deletions are outstanding. Without this the
  next run answers 304 and returns before it looks at the leftover, so the
  drain would stall until the tree changed again. Same hold the fetch cap
  already used; the two branches are now one write.

pollDocs is exported for the new test file.

edge 削除は溜まったものを流すためではなく、wiki 側との対称性のために入れて
いる。doc_edges の端点は現状どちらも wiki vector ID なので doc vector ID は
1 行も一致せず、この DELETE は no-op になる。issue 本文の「edge table に溜ま
り続ける」という記述はこの点で実測と食い違っており、PR 本文に訂正を書いた。
効果は将来 doc 側の edge writer が入ったときに「vector を削除する」の意味が
2 経路で同じであり続けること。

Refs #203
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 2, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
github-rag-mcp 7533ae8 Aug 02 2026, 12:21 PM

…cs, tests]

deleteEdgesForVector on a doc vector ID matches zero rows: indexWikiEdges is
the only doc_edges writer, its src id is the page's wvid and the dst id it
computes is a wikiDocVectorId too, and the backfill in index.ts filters
WHERE type = 'wiki_doc'. A doc vector id cannot be an endpoint.

The call is removed. Justifying it as symmetry against a future doc-edge
writer was a push-surplus argument for a statement with no behavior behind it.
With the premise gone the asymmetry is not a defect either: wiki pages have
edges and repository docs do not, so the two reaps differing is the data
differing, not the handling.

The knowledge is kept as a comment on the delete loop, naming the invariant
and what to do if it changes. Per-deletion fan-out is 3 subrequests, not 4;
the budget arithmetic in the constant's doc comment is corrected to match.

Unchanged from the prior commit, all three load-bearing on real defects:
per-surface independent teardown, MAX_DOC_DELETIONS_PER_REPO_PER_RUN, and the
ETag hold that keeps the drain moving once the cap exists.

Tests drop the edge assertions and pin those three instead, plus a new case
fixing that an FTS5 failure still reaches the store DELETE.

親側の裁定による。subtractive-structural-beauty の (A)(B) 適用で、0 行にしか
マッチしない DELETE は behavior に load-bearing でないため削除。知識だけ
コメントとして残す形に寄せた。

Refs #203
@liplus-lin-lay liplus-lin-lay changed the title fix(docs): reap graph edges and cap doc deletions per run [poller, docs, tests] fix(docs): tear down doc reap surfaces independently and cap it per run [poller, docs, tests] Aug 2, 2026

@liplus-lin-lay liplus-lin-lay left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-review (auto mode, parent agent)

前提の訂正を経た PR であることの記録

初回実装は issue #203 の当初本文どおり deleteEdgesForVector を doc 削除ループへ足していた。実装者がその過程で「該当する edge は存在しない」と報告し、親側で独立に確認して裁定した。

確認内容: doc_edges の writer は indexWikiEdges の 1 経路のみ。src_vector_id は wiki ページの wviddst_vector_idwikiDocVectorId(repo, slug)src/index.ts の backfill も WHERE type = 'wiki_doc' で絞る。したがって d: prefix の doc vector ID は端点になり得ない。

issue #203 の当初の「欠け 1」は私が書いた前提の誤りだった。 wiki と doc の扱いの差は defect ではなく、データの違いに扱いが対応していただけ。issue 本文とタイトルは訂正済み。

no-op の DELETE を落とした根拠は rules/model/subtractive-structural-beauty.md の (A) と (B) — 0 行にしかマッチしない文は behavior に load-bearing でなく、「将来 doc が端点になったら」という正当化は Detection signs が名指しする push surplus の形。不変条件はコメントと docs に残し、情報だけ保存して dead な文を落とす形にした。

受け入れ条件

  • per-run 上限があり、超過分が次 run に持ち越される — 満たす。MAX_DOC_DELETIONS_PER_REPO_PER_RUN = 5。11 件を 5-5-1 の 3 run で重複も取りこぼしも無く drain することがテストで固定
  • backlog 中は tree ETag が前進しない — 満たす。fetch 枠が既に持っていた据え置きを削除枠へ広げ、分岐 2 本を 1 本に畳んでいる。cap を入れた以上これが無いと 304 短絡で drain が止まるので、持ち越しの成立そのものを担保する条件
  • Vectorize 失敗でも FTS5 / store の削除が実行される — 満たす。surface ごとの独立 teardown に変更。従来は外側 try が Vectorize を含み、落ちると D1 側が両方残る構造だった
  • wiki 側の既存テストが無変更で pass — 満たす

数値の整合

fan-out が 4 → 3 subrequest になったのに伴い、定数 doc comment の予算計算(5 × 3 × 5 = 75、LIGHT_CRON worst case 約 825)と docs / テストのコメントが実数に揃えられている。撤回した edge 呼び出しの分を計算に残していない点を確認。

テストの寄せ直し

baseline(export だけ足した修正前コード)に対する測り直しで、挙動 3 本 fail / control 4 本 pass。維持した 3 点それぞれに fail するテストが 1 本ずつ対応しているので、回帰を捕まえる側と不変条件を固定する側の分離が保たれている。control が 1 本増えている(FTS5 失敗時に store DELETE へ到達)のは独立 teardown の裏側の固定で、妥当。

scope

PR タイトルは squash commit タイトルになるため、実態(edge を含まない)に合わせて実装者が変更済み。本文にも撤回理由が残っている。派生 issue #206 の「edge も入れるか判断」という記述も、撤回した論拠が次の実装へ伝播しないよう「対象外」へ書き換えられている。撤回の波及先まで潰されている点は評価する。

brake

user repo の PR で Li+ source を触っていないため brake 1 / brake 2 の対象外。

次のステップ

auto mode につき human gate なし。self-review pass → merge。

@liplus-lin-lay
liplus-lin-lay merged commit d27aa07 into main Aug 2, 2026
3 checks passed
@liplus-lin-lay
liplus-lin-lay deleted the 203-bugdocs-the-doc-reap-leaves-graph-edges-behind-and-has-no-per-run-cap branch August 2, 2026 12:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(docs): the doc reap has no per-run cap and its teardown is not surface-independent

1 participant