要約

  • RFC 5182のSEARCH RETURN (SAVE)は、一つの可変な結果欄を更新する。NOになったSAVEやNOTSAVEDは、その欄を以前の値に戻すのではなく空にする。
  • 空の$は正しいmessage-setである。後続のFETCHやCOPYがOKでも、検索意図が保たれたことや一件でも処理したことは証明されない。

最初の検索は成功していた。次にクライアントは別の文字コードで条件を絞り、同じSAVEを付けた。サーバーはその文字コードを扱えずNOを返した。クライアントが元の集合へ戻ったつもりでCOPY $を送ると、コピーはゼロ件のままOKで終わる。

RFC 5182はまさにこの境界を例示している。SAVE付き検索の失敗は、古い結果を温存しない。検索結果変数を空にする。再試行するなら、前段の検索からやり直さなければならない。

これは特定製品の障害報告ではない。プロトコルの再試行規則と、業務上の「前回結果」という言葉が一致しない場面を示すための分析である。

往復を減らすための一つの欄

通常のIMAPでは、SEARCHの結果一覧をサーバーが返し、クライアントが解析してmessage-setへ組み直し、FETCH、STORE、COPY、SEARCH、UID EXPUNGEへ送り返す。SEARCHRESはこの往復を省く。サーバーがSEARCHRESを広告し、クライアントがSAVEを指定すると、結果は内部変数へ置かれ、後続コマンドは$で参照できる。

対応サーバーはESEARCHも実装する。ほかの結果オプションがなければ、SAVEは本来返るSEARCH応答まで抑制する。つまりクライアントは、具体的な一覧を受け取らずに次の処理へ進める。

この小ささが相互運用性の源である。同時に限界でもある。変数には利用者が付ける名前がなく、複数世代を並べて参照できず、検索理由や担当者を永続化する規定もない。新しいSAVEが成功すれば、値は置き換わる。

製品が「保存済み検索」を必要とするなら、製品自身が不変ID、条件、範囲、作成者、時刻、期待件数を持つべきだ。$は実行を速くする参照であって、その記録そのものではない。

失敗時に古い値を残さない理由

状態遷移は一律ではない。BADで終わるSEARCHは変数を変えない。SAVEのないSEARCHも、成功でもNOでも変えない。ところがSAVE付きSEARCHがNOなら、変数は空になる。

これは安全側の設計である。新しい集合を設定できなかったのに、同じ記号の裏に古い集合が残れば、削除や移動が過去の対象へ作用し得る。「更新に失敗したから前のものを使う」というローカルな期待より、空であることを明示する方を選んでいる。

サーバーは保存結果の資源が足りない場合、NOTSAVEDを伴うNOを返せる。その場合も空になる。RFCは、接続をまたいで状態を持つことがサービス拒否圧力を増やすと認めている。SEARCHRESの広告は、無制限の保存枠を予約する約束ではない。

再試行制御は、NO、BAD、NOTSAVEDを分けて記録しなければならない。SAVE失敗後にそのままSTOREやCOPYを続けることは、古い集合へのフォールバックではなく、空集合への操作である。

空集合でもプロトコルは成功する

検索がゼロ件なら変数は空になる。SELECTまたはEXAMINEが成功したときも空になる。新しいUIDVALIDITYが通知されたときも同じである。EXPUNGEによって最後のメンバーが消えることもある。

空の$は構文エラーではない。どのメッセージにも一致しない有効な集合として扱う。RFCの例ではCOPYがOKとなり、何もコピーしない。FETCHならFETCH応答がなくても最終行はOKになり得る。

だから成功率だけでは統制できない。業務が対象にした母集団、SEARCHが計算した集合、SAVE後に実際に保持された集合、消費時まで残った集合、最終的に作用した集合を分ける必要がある。ユーザーが見た結果はさらに別の証拠である。

保存、法的保全、セキュリティ自動化では、ゼロ件が正しい場合もある。問題はゼロそのものではなく、なぜゼロだったかを説明できないことだ。

選択とUIDVALIDITYが時代を区切る

SELECTまたはEXAMINEの成功で変数が空になるのは、メールボックス選択が番号の意味を定めるからである。UIDもメールボックス名とUIDVALIDITYの組み合わせを離れて永続的な同一性を持たない。新しいUIDVALIDITYは、古い世代の参照権を切る。

EXPUNGEは新しい検索なしに保存集合を変える。対象メッセージを除外し、内部表現がシーケンス番号なら残りの番号も調整する。作成時と消費時の集合が同一とは限らない。

さらに、$がUIDかシーケンス番号かは消費側が決める。SEARCHの結果をUID FETCHへ渡すことも、UID SEARCHの結果をFETCHへ渡すこともできる。生産側のコマンド名だけを記録しても、実際の解決空間は分からない。

証拠には、接続、選択メールボックス、UIDVALIDITY、生産コマンド、検索条件、結果オプション、受信順序、途中のEXPUNGE、消費コマンドを結び付ける必要がある。

パイプラインは順序証明を要求する

SAVEの直後に$を使うコマンドを送れば、両者には直接の依存関係が生まれる。サーバーは受信順を守る。外から見える意味を保てるなら、内部では条件を代入するなどの最適化を行える。

一つのSAVEの後ろにCOPYとSTOREを並べ、同じ集合へ順に適用することもできる。しかしSAVEを二つ送っても二つの名前付き結果にはならない。二番目が一番目を上書きする。

IMAPのtagは応答を対応付けるが、結果スロットの名前ではない。応答到着順だけで業務上の因果関係を復元してはいけない。各消費コマンドが、受信列のどのSAVEを生産者としたかを記録する。

RFC 5267のCONTEXTは別の契約であり、tagで更新コンテキストを識別し、変更を通知し、更新をキャンセルできる。最後の一結果を示すSEARCHRESへ、その役割を慣習だけで移してはならない。

オプションは保存範囲を変える

完全に成功した検索でも、SAVEが全件を入れるとは限らない。ESEARCHのSAVE MINは最小の一件、SAVE MAXは最大の一件、両方なら一件か二件だけを保存する。ALLまたはCOUNTが含まれると全一致集合を保存する。

RFC 9394のPARTIALは、ALLがない場合に部分窓と該当するMIN/MAXだけを入れる。RFC 9738のMESSAGELIMITは、制限で切られた検索結果だけを保存させる。後者の不完全性は既存のBTW記事が扱っている。本稿の境界は、制限がなくても戻り値オプションが保存集合の意味を決めることだ。

監査名「古いメール検索」から、全件、端点、ページのどれかは判定できない。実行された構文を証拠として残す必要がある。

最適化に歴史を語らせない

IANAはSEARCHRESを登録し、RFC 9051はその状態機械をIMAP4rev2へ取り込んでいる。これは共通言語の証明であって、特定サーバーの導入、クライアントの正しさ、資源の空き、業務完了の証明ではない。

不可逆な処理では、ローカルに不変な操作IDを作り、意図、検索条件、オプション、メールボックス、UIDVALIDITY、順序、リセット、EXPUNGE、保存件数、作用件数を残す。$はその操作を効率よく運ぶが、操作の所有者にはならない。

閉じる条件は、OKを受け取ったことではない。対象にした各メッセージと実際に作用した各メッセージの差を説明できることである。

情報源

  1. RFC 5182 HTML
  2. RFC 5182 プレーンテキスト
  3. RFC Editor 情報ページ
  4. IETF Datatracker 文書ページ
  5. IETF Datatracker 履歴
  6. IETF Datatracker 参照関係
  7. RFC 5182 正誤表
  8. RFC 9051 — IMAP4rev2
  9. RFC 9051 情報ページ
  10. RFC 4731 — ESEARCH
  11. RFC 4466 — IMAP ABNF集成
  12. RFC 4315 — UIDPLUS
  13. RFC 3501 — IMAP4rev1
  14. RFC 5267 — IMAP CONTEXT
  15. RFC 9738 — MESSAGELIMIT
  16. RFC 9394 — PARTIAL
  17. IANA IMAP能力レジストリ
  18. Heng Lu — 現実の層
  19. Heng Lu — 最小初期仕様と自発的採用
  20. Heng Lu — running codeの優先