要約

  • RFC 9996は、シリアライズされたProtobufオブジェクト用にapplication/protobufとapplication/protobuf+jsonを登録した。.protoや個別のメッセージ型を登録する仕組みではない。
  • versionパラメーターが示すのはwire encodingの版であり、Proto2、Proto3、Edition、アプリケーションスキーマの改訂版を識別しない。
  • デコード成功は意味の一致を証明しない。メッセージ型とdescriptorの真正な結び付け、表現ごとの互換性試験、アプリケーションによる最終検証を別々に管理する必要がある。

監査チームが、数年前の与信判断を再現しようとしている。保存されていたのはProtobufのバイト列、application/protobufというContent-Type、そして当時の署名だけだった。現在のライブラリで読むと、メッセージは問題なくデコードされる。だが、当時使われたdescriptorは残っていない。現在のschemaでは、あるフィールド番号が「暫定限度額」を表す。旧システムでは同じ番号が「審査済み上限」だった可能性がある。

署名はバイト列が変わっていないことを示せる。メディアタイプはProtobuf binaryであることを示せる。それでも、組織は「何を承認したのか」を復元できない。失われたのはデータではなく、意味を選ぶ権限の記録である。

RFC 9996は2026年7月にInformational RFCとして公開された。Protocol Buffersに二つの正式なメディアタイプを与えた文書であり、外側の相互運用性を大きく改善する。その有用性を過大評価しないためには、登録された事実と、登録されなかった事実を分けて読む必要がある。

登録されたのはオブジェクトの表現

二つの名称は明快だ。application/protobufはbinary wire representation、application/protobuf+jsonはJSON mappingを示す。IANAのbinary登録とJSON登録により、API、メッセージング基盤、ゲートウェイ、保存層は、私的なx-名に頼らず形式を共有できる。

対象はserialized messageであり、IDLのソースファイルやschema definitionではない。binaryではbinary encodingが既定となる。+jsonはJSON encodingを用い、UTF-8を必要とする。任意のversionパラメーターはProtobufのwire encoding versionを示し、省略時はversion 1となる。受信側がそのwire versionに対応していなければ拒否しなければならない。

RFC 6838のメディアタイプ登録制度から見れば、これは適切に細い共通層である。形式名と処理上の注意を公開し、異なる実装が最低限の入口を共有する。RFC 9996はapplication/x-protobuf、application/x-protobuffer、application/x-protobuf+jsonなどをdeprecated aliasとして整理した。

一方、magic number、標準拡張子、fragment identifierはない。暗号化、完全性、送信者認証、圧縮、resource exhaustion対策も追加されない。Content-Typeが正しくても、送り手が正当とは限らず、payloadがそのendpointで許可されたメッセージ型とも限らない。

version=1を契約版と読んではならない

Protobufをめぐる「バージョン」には、別々の時計がある。wire encoding version、Proto2またはProto3、Edition 2023やEdition 2024、個別.protoのrevision、生成コードの版、runtime library、そしてアプリケーションreleaseである。

RFC 9996のパラメーターが指すのは最初の時計だけだ。IDLやruntime semanticsはwire formatと独立して進化できる。同じversion 1のバイト列でも、未知のenum valueをどのように保持し、公開し、再送するかはschemaとruntimeによって変わり得る。wire-compatibleであることは、意思決定が同じであることを意味しない。

したがって、資産台帳のprotobuf_version=1はほとんど情報を持たない。message type、descriptor digest、IDL generation、runtime buildを別欄にしなければ、変更時にどの層が結果を変えたのか追跡できない。仕様上の一つの数字に、組織が管理すべき四つの判断を代行させてはならない。

binaryはフィールド名を運ばない

公式encoding guideによれば、binary wireに現れる中心的な手掛かりはfield numberとwire typeである。ソース上のfield nameは含まれず、wire typeだけではdeclared typeを一意に復元できない。varintは整数、真偽値、enumかもしれない。length-delimitedは文字列、bytes、埋め込みmessage、packed repeated valuesのいずれでもあり得る。

ここでdescriptorが意味の辞書となる。問題は、辞書が違っても文章として読めてしまう場合があることだ。Protobuf parserはunknown fieldを飛ばせる。これは正しく管理されたmessage typeの進化には有効だが、誤ったschemaを選んだ際には異常を隠す。いくつかの番号が偶然一致し、残りが未知として消え、default valueが空所を埋めれば、下流にはもっともらしいobjectが届く。

schema bindingの方法はアプリケーションごとに異なってよい。URL pathやRPC methodが型を固定してもよい。generated client、API profile、署名付きdescriptor bundle、release manifest、管理されたregistryを使うこともできる。要件は、第三者が別の「読めるschema」を差し替えられず、後から同じ結び付きを検証できることである。

RFC 9205が示すように、HTTPアプリケーションはmedia typeだけで作られるわけではない。method、status、header、link relation、resource semanticsが組み合わさって挙動を定める。RFC 9996にmessage type registryがないことは欠陥ではない。各アプリケーションが自らのsemantic contractを明示する余地を残している。

ProtoJSONには別の互換性勘定がある

JSONならfield nameが見えるため、binaryより自明に思える。+json suffixは、exact semanticsを必要としない処理系がgeneric JSONとして扱えることを示す。RFC 6839とRFC 8259はその共通基盤を与える。

しかし、ProtoJSON format guideは、ProtoJSONがbinaryほどunknown fieldを支えないことを明記する。新しいfieldを送っただけで旧clientが拒否することがある。field nameとenum nameがwire上に出るため、renameは互換性を壊し得る。binaryからJSONを経てbinaryへ戻す途中でunknown fieldが失われれば、後段が理解できたはずの将来情報も消える。

Proto3の更新ガイドが削除済みfield numberとfield nameの予約を求めるのはこのためだ。番号を再利用すれば古いbinaryが新しい意味を持ち、名前を再利用すればJSONと生成コードの履歴が混線する。application/protobuf+jsonという正しい表示は、組織がこの規律を守ったことを保証しない。

互換性試験は、実際の経路に合わせる必要がある。binaryのproducerとconsumerだけを試しても、途中のobservability gatewayがJSON化してunknown fieldsを失えば、本番挙動は再現されない。形式の変換点は単なる便利機能ではなく、情報保持方針を決める境界である。

AnyのURLは信頼根ではない

Anyはtype URLとbytesを同じobjectに収めるため、型の自己記述に近く見える。RFC 9996は、URLからschemaをdereferenceする発想があった一方、広く使われる実装はその機能を支えていないと指摘する。実際にはURLをlocal registryの識別子として使う環境が多い。

仮にresolverがdescriptorを取得できても、発見と承認は別である。TLSで接続できたことはchannelの一部を証明するが、そのdescriptorがproducerとAPI ownerの承認版であることまでは証明しない。URLの内容は更新され、domainやrepositoryは移転し、online lookupは利用者が処理しているmessage typeを外部に知らせる可能性もある。

管理すべき単位は、type URL、descriptor hash、publisherまたはtrust root、取得経路、承認時点、適用可能なendpointの組である。resolverは候補を返せる。候補をproduction authorityへ昇格させるのは、別の統制判断でなければならない。

deterministicはcanonicalではない

保存と署名の設計では、もう一つの省略が起きやすい。Protocol Buffers serialization is not canonicalという公式説明どおり、意味が同じmessageでもbyte sequenceが同じとは限らない。field orderは普遍的に固定されない。

deterministic serializationは限定された条件で同じ実装の出力を安定させるが、language、build、library version、schemaの差を越えたcanonical formではない。hashは特定artifactのfingerprintにはなる。descriptorやruntime contextを外したabstract business objectの恒久IDにはならない。

長期保存では、bytesと署名だけでなく、message type、descriptor digest、runtime、serialization policyを一緒に保存すべきである。そうしなければ、改ざんされていないことは証明できても、何を意味していたかは証明できない。

ラベル、解釈、受理を一つの現実にしない

Heng LuのMinimum Initial Specificationは、RFC 9996の狭さを長所として読ませる。全員が共有すべきmedia labelとwire versionを共通化し、個別のmessage taxonomyやbusiness validationは、必要性が立証されるまでlocal decisionとして残す。

Reality Layersの観点では、Content-Typeは宣言、descriptorは解釈規則、decoded objectはruntimeの産物、state transitionは運用上の事実である。宣言が正しくても解釈が誤ることがあり、解釈が正しくてもpolicyが拒否することがある。

Running-Code Primacyが最後の証拠を置く場所は、parse、validate、authorize、commitを実行するsystemだ。registry entryだけでなく、どのdescriptorを用い、何を拒否し、どの状態を変更したかまで記録して初めて、実効的な契約を監査できる。