目的
現行 vector-id 体系より前の世代の doc ベクトルが Vectorize に残っており、削除経路から到達できない。同一ファイルが二重に索引された状態が常態化している。一回性の掃除で解消する。
前提(2026-08-02 実測)
事実 1: 生きているファイルが doc 行を 2 本持つ
repo: Liplus-Project/github-webhook-mcp / type: doc:
doc_path
行 A (updated_at)
行 B (updated_at)
README.md
2026-06-26
2026-04-10
docs/installation.md
2026-04-28
2026-04-10
docs/0-requirements.md
2026-06-26
2026-04-10
同一 path の upsert が既存行を上書きせず 2 行になっている以上、両者の vector id は異なる。
事実 2: 2026-04-10 の層は現行 D1 に存在し得ない
現行 D1(github-rag-fts)の作成日は 2026-04-19 。D1 実測(search_docs / type='doc' / 当該 repo)は 2026-04-19 が 3 行、04-21 が 2 行、06-26 が 4 行の計 9 行で、2026-04-10 の行は 1 本も無い 。にもかかわらず検索は 2026-04-10 の doc を返す。その層は Vectorize にのみ存在する 。
事実 3: 原因は vector-id 体系の移行
移行コミット = 215e2e2(2026-04-19 、fix(pipeline): hash vector IDs to stay under Vectorize 64 byte cap / PR #84 )。
移行前(215e2e2~1:src/pipeline.ts):
export function vectorId ( repo : string , number : number ) : string {
return `${ repo } #${ number } ` ;
}
export function releaseVectorId ( repo : string , tagName : string ) : string {
return `${ repo } #release-${ tagName } ` ;
}
export function docVectorId ( repo : string , path : string ) : string {
return `${ repo } #doc-${ path } ` ;
}
現行は {prefix}:{base64url(sha256(parts.join(NUL)))}(src/pipeline/vector-id.ts)。両者に衝突は無い。
削除経路(src/poller.ts の doc 削除、src/webhook.ts の removed 処理)はいずれも現行 docVectorId で id を計算するため、旧世代には永久に到達できない 。
日付が三点で整合する: vector-id 移行 = 2026-04-19 / 現行 D1 作成 = 2026-04-19 / 孤児層の updated_at = 2026-04-10(移行前)。孤児層は移行前に索引され、移行後に一度も再索引されていないベクトル。
実害
範囲の限定
移行は全 surface の id を変えているので issue / release にも及んでいる可能性があるが、実測で確認できているのは doc のみ 。
updated_at の意味は type ごとに違う。release v0.3.2 は updated_at: 2026-04-06(移行前)だが sparse からも引けており現行 FTS に存在する = release 自身の日付であって索引時刻ではない。この差があるため日付だけで他 surface の孤児を判定できない。
doc 以外の孤児の有無は未確認。実装前に surface ごとの確認を行うこと。 確認できない surface は本 issue の対象に含めない。
方針(採用)
一回性の掃除を admin endpoint として置く。再 embed は伴わない。
POST /admin/purge-legacy-vectors?repo=owner/repo[&dry_run=true][&surface=doc]
対象 repo の現行ツリーから .md を列挙し、各 path について旧式 {repo}#doc-{path} を計算
VECTORIZE.deleteByIds にバッチで渡す(存在しない id は無害)
dry_run=true では件数だけ返し、削除しない
per-run 上限を持たせる(bug(docs): the doc reap has no per-run cap and its teardown is not surface-independent #203 と同じ理由 = subrequest 予算)
ツリーから既に消えた path のために、明示 paths を body で渡せる口を用意する
残差 : ツリーから消えた path の旧 id は列挙できない。判明している分(今回の .claude/ 65 本)は明示 paths で一回流す。旧式で索引された時期は閉じているため、この残差集合は今後増えない。
採らなかった案:
制約
再 embed を伴わないこと(掃除であって再構築ではない)
現行世代のベクトルを消さないこと。旧式 id と現行 id は形式が異なるので、計算した旧式 id 以外を触らない
dry_run を既定にしない場合でも、削除件数をレスポンスに含めること
受け入れ条件
dry_run=true で削除件数が返り、Vectorize に変更が無いことがテストで固定されている
実行後、README.md / docs/installation.md / docs/0-requirements.md の doc 行が 1 本ずつになる(実測で確認)
実行後、.claude/CLAUDE.md が検索から消える(実測で確認)
per-run 上限があり、上限を超えた分が呼び出し側に分かる形で返る
想定変更箇所
src/index.ts(admin endpoint 追加)
src/pipeline/vector-id.ts または新規モジュール(旧式 id の再構成関数。現行 id 生成とは分離すること)
対応するテスト
docs/installation.md / .ja.md(admin endpoint の説明)
参考
目的
現行 vector-id 体系より前の世代の doc ベクトルが Vectorize に残っており、削除経路から到達できない。同一ファイルが二重に索引された状態が常態化している。一回性の掃除で解消する。
前提(2026-08-02 実測)
事実 1: 生きているファイルが doc 行を 2 本持つ
repo: Liplus-Project/github-webhook-mcp/type: doc:README.mddocs/installation.mddocs/0-requirements.md同一 path の upsert が既存行を上書きせず 2 行になっている以上、両者の vector id は異なる。
事実 2: 2026-04-10 の層は現行 D1 に存在し得ない
現行 D1(
github-rag-fts)の作成日は 2026-04-19。D1 実測(search_docs/type='doc'/ 当該 repo)は 2026-04-19 が 3 行、04-21 が 2 行、06-26 が 4 行の計 9 行で、2026-04-10 の行は 1 本も無い。にもかかわらず検索は 2026-04-10 の doc を返す。その層は Vectorize にのみ存在する。事実 3: 原因は vector-id 体系の移行
移行コミット =
215e2e2(2026-04-19、fix(pipeline): hash vector IDs to stay under Vectorize 64 byte cap/ PR #84)。移行前(
215e2e2~1:src/pipeline.ts):現行は
{prefix}:{base64url(sha256(parts.join(NUL)))}(src/pipeline/vector-id.ts)。両者に衝突は無い。削除経路(
src/poller.tsの doc 削除、src/webhook.tsの removed 処理)はいずれも現行docVectorIdで id を計算するため、旧世代には永久に到達できない。日付が三点で整合する: vector-id 移行 = 2026-04-19 / 現行 D1 作成 = 2026-04-19 / 孤児層の
updated_at= 2026-04-10(移行前)。孤児層は移行前に索引され、移行後に一度も再索引されていないベクトル。実害
top_kの意味は守られるが、プールの実効幅は減る.claude/65 ファイルを削除した直後、D1 FTS 側の.claude/doc 行は 0 件になった(現行世代は正しく reap された)が、検索は依然として.claude/CLAUDE.md(updated_at 2026-04-10、dense のみ)を返す範囲の限定
移行は全 surface の id を変えているので issue / release にも及んでいる可能性があるが、実測で確認できているのは doc のみ。
updated_atの意味は type ごとに違う。releasev0.3.2はupdated_at: 2026-04-06(移行前)だが sparse からも引けており現行 FTS に存在する = release 自身の日付であって索引時刻ではない。この差があるため日付だけで他 surface の孤児を判定できない。doc 以外の孤児の有無は未確認。実装前に surface ごとの確認を行うこと。 確認できない surface は本 issue の対象に含めない。
方針(採用)
一回性の掃除を admin endpoint として置く。再 embed は伴わない。
POST /admin/purge-legacy-vectors?repo=owner/repo[&dry_run=true][&surface=doc].mdを列挙し、各 path について旧式{repo}#doc-{path}を計算VECTORIZE.deleteByIdsにバッチで渡す(存在しない id は無害)dry_run=trueでは件数だけ返し、削除しないpathsを body で渡せる口を用意する残差: ツリーから消えた path の旧 id は列挙できない。判明している分(今回の
.claude/65 本)は明示pathsで一回流す。旧式で索引された時期は閉じているため、この残差集合は今後増えない。採らなかった案:
制約
dry_runを既定にしない場合でも、削除件数をレスポンスに含めること受け入れ条件
dry_run=trueで削除件数が返り、Vectorize に変更が無いことがテストで固定されているREADME.md/docs/installation.md/docs/0-requirements.mdの doc 行が 1 本ずつになる(実測で確認).claude/CLAUDE.mdが検索から消える(実測で確認)想定変更箇所
src/index.ts(admin endpoint 追加)src/pipeline/vector-id.tsまたは新規モジュール(旧式 id の再構成関数。現行 id 生成とは分離すること)docs/installation.md/.ja.md(admin endpoint の説明)参考
parts.join()が空白区切りに見えたがodで NUL バイトを確認しコードは正しい。同じ見誤りは bug(vector-id): parts joined with space, not the documented NUL separator #166 で起票・close 済み。再起票しないこと