Skip to content

Rows indexed while open keep a stale open state after the item is closed #209

Description

@liplus-lin-lay

目的

索引済みの issue / PR 行のうち、GitHub 側では close 済みなのに search_docs.stateopen のまま残っている行を実態へ揃える。あわせて、同じ取り残しが再発する経路を閉じる。

確認済みの事実(2026-08-03 実測)

D1 search_docs と GitHub 実物の突き合わせ:

repo 索引 open (issue / PR) 実 open (issue / PR) 取り残し
Liplus-Project/liplus-language 165 / 114 33 / 0 132 / 114
Liplus-Project/github-rag-mcp 10 / 12 0 / 0 10 / 12
Liplus-Project/github-webhook-mcp 12 / 9 2 / 0 10 / 9

境界が一点にそろっている。 liplus-language の issue 側の取り残しは #1027#1604 に限られ、#1605 以降はゼロ(SQL で確定: state='open' かつ実 open 集合に無い行の MAX(number) = 1604)。

ずれる条件は「索引した後に close された」こと。 反証として #600#630 を実物と全件突き合わせたところ、索引にある 23 件は type・state とも全て正しかった。close 済みの状態で索引された行は正しく、open の状態で索引され後から close された行だけがずれる。

原因(2026-08-03 実装から確定)

state 反映を担う経路は src/pipeline/embed-issue.ts の hash-skip path。body hash が一致した場合に metadata 差分を検出し、Vectorize metadata と search_docs を更新する(FTS mirror は #86 / 2026-04-19 から在る)。ここに 2 つの欠陥が重なっている。

欠陥 1 — mirror 書き込みが例外で落ちていた(#175 で解消済み)

search_docs への書き込みは src/fts.ts upsertFtsRowON CONFLICT (vector_id) DO UPDATE#175(migration 0005)以前は search_docs の UPDATE trigger が反対側 tokenizer index へ存在しない row の delete command を送っており、既存行への UPDATE が例外で落ちていた。例外は catch (ftsErr) に「非致命・次の body 変更で追いつく」として飲まれる。

境界の裏取り(#175 の deploy 時刻と一致):

事象 時刻 (JST) 取り残しに含まれるか
#175 merge (d40898e) 2026-07-29 12:11
liplus-language #1604 close 2026-07-29 12:18 含まれる(取り残し最大番号)
liplus-language #1610 close 2026-07-29 13:58 含まれない

merge 直後の数分(deploy 完了前)に close された #1604 までが取り残し、deploy 後に close された #1610 は正常。#1605 は現在も open なので取り残しではなく正しい open。→ 恒久修正は #175 で既に効いている。 本 issue に残る恒久側の作業は欠陥 2 のみ。

欠陥 2 — 失敗した mirror が二度と再試行されない

差分検出の基準は IssueStore (Durable Object) の既存 record であり、これは mirror 書き込みより に更新される(hash-skip path は先に POST /upsert を打つ)。一度 IssueStore が closed になると次回以降 metadataChanged が false になり、mirror は永久に再試行されない。catch のコメントが言う「次の body 変更で追いつく」は state だけが変わった行には成り立たない(body は変わらないので二度と来ない)。

副次: FTS mirror の呼び出しが vectors.length > 0 && vectors[0].values の内側にネストしているため、vector が欠けている行では sparse 側の state 更新ごとスキップされる(vector 欠落自体は #210 の範囲)。

修正方針

  1. 恒久(欠陥 2): mirror 書き込みの成否が確定するまで差分検出の基準を進めない。あるいは mirror 失敗を記録して次回 poll で再試行させる。現状の「基準を先に進める」順序が再試行を殺している。あわせて FTS mirror を Vectorize 成功 branch の外へ出す。
  2. backfill(既存の取り残し行): embedding 呼び出しは不要と確定した。state は GitHub の list API から取れ、search_docs.state は UPDATE、Vectorize 側は getByIds で既存 values を取り直して metadata だけ差し替えた再 upsert で足りる。よって従量コストは発生せず、go-sign 待ちの対象外。
  3. vector が存在しない行は Index covers only about 45 percent of issue and PR history #210 の欠落側なので本 issue では触らない。

制約

  • 索引そのものの欠落(履歴の半分しか入っていない件)は Index covers only about 45 percent of issue and PR history #210 で扱う。本 issue の範囲は「索引済み行の state」のみ。
  • backfill は admin endpoint として実装する(/admin/reset-hashes は full re-embedding を起こすので使えない)。

対象ファイル

  • src/pipeline/embed-issue.ts(hash-skip path の順序と mirror のネスト)
  • src/index.ts(backfill admin endpoint の追加)
  • docs/0-requirements.md / docs/0-requirements.ja.md
  • 回帰テスト

背景

Li+ 側の過去判断照会(skills/evolution-judgment-learning)で RAG を引いた際、2026-05-13 に close 済みの liplus-language #1283open として返り発覚した。閉じた設計判断が「まだ生きている検討事項」として後続の検索へ再供給される経路になる。

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug動いていない、壊れているdone役目完了、orchestration (review / merge / close) フェーズ待ちready本文が実装開始できる形まで収束している状態。ただし更新は継続可能

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions