目的
release 面と docs 面で、embed に失敗した item が二度と再試行されない経路を閉じる。どちらも ETag の保存判定が「見送りの理由」を一部しか見ていない。
確認済みの事実(2026-08-03 実装読みで確定。実測は未実施)
#215 で issue / PR 面に入れた不変条件は holdEtag = capped || stats.retryBoundary !== undefined ——やり残しがあるどの理由でも ETag を保存しないという形。残り 2 面はこの形になっていない。
release 面(pollReleases)
ETag 保存の分岐は upsertBudgetExhausted のみを見ている(src/poller.ts の if (!upsertBudgetExhausted))。#149 で「upsert cap に当たった run では fresh ETag を書かない(次 cron が 304 で短絡して bodyHash 空のまま見送られた release を拾えなくなる)」として入った guard。
しかし見送りが発生する理由は upsert cap だけではない:
- embed 失敗(
result.failed)が guard の外。processAndUpsertRelease は失敗時に bodyHash: "" を書いて retry の印を付けるが、この run は upsertBudgetExhausted が false のままなので fresh ETag が保存される。次 cron は If-None-Match が一致して 304 → pollReleases は早期 return → 印の付いた release は、いずれかの release が変更されるまで拾われない。
- embedding cap(
MAX_EMBEDDINGS_PER_RUN)の分岐も guard の外だが、MAX_RELEASE_UPSERTS_PER_REPO_PER_RUN = 10 が MAX_EMBEDDINGS_PER_RUN = 50 より小さいため upsert cap が必ず先に当たる。現在の定数では到達不能。
docs 面(pollDocs)
ETag 保存の分岐は holdEtag = fetchBudgetExhausted || deleteBudgetExhausted。release 面より一歩進んでいて 2 つの cap を見ているが、同じ穴が残っている:
- embed 失敗(
failed++)が guard の外。次 run は保持された ETag で tree を取り直すが、embed に失敗した doc は blobSha が store 側で未更新のまま「変更あり」として再検出される……のは tree fetch が 200 を返す場合のみ。fresh ETag を保存してしまうと 304 で早期 return し、再検出の機会そのものが来ない。
- embedding cap の分岐は
break するだけで flag を立てない。ただし MAX_DOC_FETCHES_PER_REPO_PER_RUN = 10 < MAX_EMBEDDINGS_PER_RUN = 50 なので、release 面と同様に到達不能。
なお docs 面は fresh ETag を捨てる代わりに直前の ETag を保持する(issue 面は空にする)。どちらも機能する——cap に当たった run は 200 で tree を取れている=その時点で tree は storedEtag と異なるので、次 run も 200 になる。修正時にこの方式を変える必要はない。
監査済みで穴が無い面
修正方針
- 両面の ETag 保存判定を「やり残しがあるか」で決める。
#215 の holdEtag と同じ形に揃える——release 面は upsertBudgetExhausted || failed > 0、docs 面は fetchBudgetExhausted || deleteBudgetExhausted || failed > 0。3 面で判定の形が揃うことが要点で、面ごとに違う条件を持たせない。
- 到達不能な embedding cap 分岐を両面で決着させる。 削除するか、定数の関係(fetch/upsert cap < embedding cap)を明示して分岐が届かないことを読める形にするか。どちらでもよいが両面で同じ判断にし、理由をコメントに残すこと。 現状は「届かない分岐が黙って残っている」状態で、次に定数を触る者が関係に気づけない。
制約
対象ファイル
背景
#210 の原因特定中に release 面で見つかり、その後 #211 の着手前に ETag 保存箇所を全面監査して docs 面でも同型を確認した。3 面のうち 1 面(issue / PR)だけが #215 で正しい形になっており、残り 2 面が取り残されていた形。
目的
release 面と docs 面で、embed に失敗した item が二度と再試行されない経路を閉じる。どちらも ETag の保存判定が「見送りの理由」を一部しか見ていない。
確認済みの事実(2026-08-03 実装読みで確定。実測は未実施)
#215 で issue / PR 面に入れた不変条件は
holdEtag = capped || stats.retryBoundary !== undefined——やり残しがあるどの理由でも ETag を保存しないという形。残り 2 面はこの形になっていない。release 面(
pollReleases)ETag 保存の分岐は
upsertBudgetExhaustedのみを見ている(src/poller.tsのif (!upsertBudgetExhausted))。#149 で「upsert cap に当たった run では fresh ETag を書かない(次 cron が 304 で短絡してbodyHash空のまま見送られた release を拾えなくなる)」として入った guard。しかし見送りが発生する理由は upsert cap だけではない:
result.failed)が guard の外。processAndUpsertReleaseは失敗時にbodyHash: ""を書いて retry の印を付けるが、この run はupsertBudgetExhaustedが false のままなので fresh ETag が保存される。次 cron はIf-None-Matchが一致して 304 →pollReleasesは早期 return → 印の付いた release は、いずれかの release が変更されるまで拾われない。MAX_EMBEDDINGS_PER_RUN)の分岐も guard の外だが、MAX_RELEASE_UPSERTS_PER_REPO_PER_RUN = 10がMAX_EMBEDDINGS_PER_RUN = 50より小さいため upsert cap が必ず先に当たる。現在の定数では到達不能。docs 面(
pollDocs)ETag 保存の分岐は
holdEtag = fetchBudgetExhausted || deleteBudgetExhausted。release 面より一歩進んでいて 2 つの cap を見ているが、同じ穴が残っている:failed++)が guard の外。次 run は保持された ETag で tree を取り直すが、embed に失敗した doc はblobShaが store 側で未更新のまま「変更あり」として再検出される……のは tree fetch が 200 を返す場合のみ。fresh ETag を保存してしまうと 304 で早期 return し、再検出の機会そのものが来ない。breakするだけで flag を立てない。ただしMAX_DOC_FETCHES_PER_REPO_PER_RUN = 10<MAX_EMBEDDINGS_PER_RUN = 50なので、release 面と同様に到達不能。なお docs 面は fresh ETag を捨てる代わりに直前の ETag を保持する(issue 面は空にする)。どちらも機能する——cap に当たった run は 200 で tree を取れている=その時点で tree は
storedEtagと異なるので、次 run も 200 になる。修正時にこの方式を変える必要はない。監査済みで穴が無い面
pollComments): HTTP ETag を使わず、直近 N 件の parent を毎 run 歩き直す方式なので同じ穴は無い。etag: slugは walk の cursor であって HTTP ETag ではない。修正方針
#215のholdEtagと同じ形に揃える——release 面はupsertBudgetExhausted || failed > 0、docs 面はfetchBudgetExhausted || deleteBudgetExhausted || failed > 0。3 面で判定の形が揃うことが要点で、面ごとに違う条件を持たせない。制約
対象ファイル
src/poller.ts(pollReleases/pollDocsの ETag 保存分岐、到達不能分岐)docs/0-requirements.md/docs/0-requirements.ja.md(ETag の不変条件を書いている箇所)src/poller.test.ts/src/poller-docs.test.tsに fix(poller): hold the issue watermark at the first uningested item [poller, pipeline, index, docs, tests] #215 と同型のものがある)背景
#210 の原因特定中に release 面で見つかり、その後 #211 の着手前に ETag 保存箇所を全面監査して docs 面でも同型を確認した。3 面のうち 1 面(issue / PR)だけが #215 で正しい形になっており、残り 2 面が取り残されていた形。