仕様は依存ツリーのどこに位置するのか?
RFC 9110は数千の推移的依存関係を持つファントム依存であり、仕様が依存ツリー内でどのように位置付けられるかを考察する。
キーポイント
ソフトウェア依存関係管理において、言語仕様やプロトコル標準などの暗黙的依存が見過ごされ、重大な問題を引き起こす可能性がある
Spackパッケージマネージャーが7年かけて「コンパイラを依存グラフのノードとして明示化」する変更を実装し、ビルド時の問題を事前検出可能にした
TLS 1.3やUnicodeの仕様変更が、実装の互換性問題や予期せぬ副作用(例:エモジの表示変更)を引き起こす事例が示されている
影響分析・編集コメントを表示
影響分析
この記事は、現代のソフトウェア開発における依存関係管理の盲点を浮き彫りにし、仕様や標準の暗黙的依存を明示的に管理する必要性を提唱している。AIシステムの構築においても、モデルやフレームワークに加えて、基礎となる仕様やプロトコルのバージョン管理がシステムの安定性と再現性に重要であることを示唆している。
編集コメント
AIシステムの複雑化に伴い、モデル依存だけでなくランタイムや仕様の依存関係を可視化する「依存関係グラフの深化」が、再現可能なAI開発の鍵になるかもしれない。
あなたの Ruby gem は required_ruby_version >= 3.0 を宣言しています。
ランタイムは少なくともツールのどこかに現れます。あなたの HTTP ライブラリも RFC 9110 に依存し、JSON パーサーは ECMA-404 に、TLS 実装は RFC 8446 に依存していますが、それらのいずれもマニフェスト、ロックファイル、または SBOM(ソフトウェア部品表)には現れません。
ライブラリの依存関係には完全な扱いが施されます:マニフェストで宣言され、ロックファイルで固定され、SBOM で追跡され、スキャナで脆弱性チェックが行われます。一方、ランタイムのバージョンは1層下位に位置し、エコシステムごとに異なる方法で扱われます。Python には Requires-Python という仕組みがあります。
開発者は、これら単独では十分に信頼できないため、新しいレイヤーを次々と発明し続けています。Ruby プロジェクトでは required_ruby_version >= 3.0 が使われますが、ランタイムの実装自体はほとんど追跡されていません。JRuby 9.4 は RUBY_VERSION を報告しますが、それでも不十分です。
そのすべての下位には、仕様書自体が存在します。言語定義やプロトコル標準、エンコーディングルールなどですが、これらも依存グラフのどこにも現れません。
HPC パッケージマネージャーである Spack は、依存関係を暗黙的に放置したときに何が起きるか学ぶために7年間を費やしました。
Spack v1.0 以前では、コンパイラは依存グラフ内の実際のノードではなく、特別な「ノード属性」として扱われていました。これらは compilers.yaml で設定されます。
gcc-runtime のようなコンパイラのランタイムライブラリも同様に扱われます。
この問題を解決するアイデアは 2016 年に提出されました。動機となったのは、あるデバッグの事例です。システム管理者が大規模な依存関係グラフをインストールしましたが、gfortran が欠落しており、openmpi は Fortran サポートなしでビルドされ、その後、hypre がずっと後に失敗しました。パッケージが言語依存関係を宣言していれば、ビルドが始まる前に依存関係解決プロセス自体が不足しているコンパイラを検出できたはずです。
PR #45189("Turn compilers into nodes")がマージされたのは 2025 年 3月まで待たなければなりませんでした。Spack v1.0 では、C言語のような言語も
depends_on("c")
depends_on("cxx")
のように扱われます。
Nix は常にコンパイラをハッシュ化された依存関係として扱ってきました。すべての派生(derivation)は stdenv を通じてビルドツールを取得します。
仕様遷移
その境界で何が起きるのでしょうか?仕様が変わり、実装がそれに追随しなければならない場合です。結果はほとんどがきれいなものではありません。
Chrome と Firefox が 2017 年 2 月に TLS 1.3 のテストを有効にした際、失敗率は予想以上に高くなりました。Chrome から Gmail への接続では、TLS 1.2 では 98.3% の成功率でしたが、TLS 1.3 ではわずか 92.3% でした。犯人はミドルボックス(middlebox)でした。企業のプロキシやファイアウォールで、TLS ハンドシェイクのフィールドについてハードコードされた期待値を持っていたものです。TLS の仕様ではこれらのフィールドの変更を常に許可していましたが、長期間安定していたため、ミドルボックスの開発者はそれらを定数として扱っていました。
現在の TLS 1.3 は自身のバージョンについて嘘をついています。ClientHello は TLS 1.2 であると主張し、TLS 1.3 では不要なダミーの session_id および ChangeCipherSpec フィールドを含み、supported_versions を使用します。
Unicode は毎年おおよそ新しいバージョンをリリースしており、各バージョンでは既存の文字のプロパティを変更するだけでなく、新しい文字を追加することもあります。Chrome が ICU データを更新した際、レスラーと握手の絵文字は Emoji_Base 属性を失いました。
PyPI クラシファイアにより、パッケージは Programming Language :: Python :: 3 を宣言できます。
python_requires
Node.js における CommonJS と ES Modules は互換性のないモジュール仕様です。ESM は CJS をインポートできますが、CJS は require() を使用できません。
SMTP から ESMTP への移行はプロトコルレベルでネゴシエーションが行われ、クライアントは EHLO を送信します。
実行可能な仕様
Web Platform Tests には 56,000 以上のテストと 180 万のサブテストがあり、それぞれ W3C または WHATWG の仕様の特定のセクションにマッピングされています。WPT ダッシュボードでは、どのブラウザエンジンがどのテストをパスしたかが表示されます。TC39 の Test262 は ECMAScript に対して同様の役割を果たします。「CSS Grid Level 1 を実装しました」とブラウザチームが言う場合、実際には特定の WPT テストセットに合格していることを意味します。
これらのテストスイートは、プロース形式の RFC よりも依存関係として宣言できるものに近いです。これらはバージョン管理され、コミットハッシュを持つ具体的なアーティファクトです。仕様依存関係に対して PURL 型の識別子を必要とする場合、仕様ドキュメントのバージョンよりもテストスイートのバージョンの方が有用かもしれません:pkg:spec/w3c/wpt-css-grid@sha256:abc123
pkg:spec/w3c/css-grid@level-1
ただし、多くの仕様には適合性スイート自体が存在しません。IETF RFC は公式テストを同梱することはめったにありません。テストが存在する場合でも、標準化過程における相互運用性テストから生まれ、その後メンテナンスされなくなることがほとんどです。ほとんどのソフトウェアの依存チェーンは依然として「パッケージ → 実装 → プロセステキスト文書の暗黙的な理解」という構造になっています。
TypeScript の DefinitelyTyped エコシステムでは、ランタイム API に対してすでにこのようなアプローチが採用されています。@types/node
事実上の仕様
すべての仕様が標準化団体に存在するわけではありません。Node.js モジュール解決には正式な仕様がありません。これは Node の動作によって定義されており、誰かがそれを文書化するかどうかに関わらず、同じ方法でモジュールを解決するものはすべてその動作に依存しています。
Oracle は Java EE を Eclipse 財団に寄贈しましたが、Java の商標権は保持しました。これにより、Eclipse 財団が javax パッケージを変更することが妨げられました。
javax.persistence
仕様間の依存関係
仕様には独自の依存グラフが存在します。JSON は UTF-8 に依存し、その通じて Unicode に依存しています。HTTP は TLS の上にあり、TLS は X.509 と ASN.1 の上にあります。したがって、ASN.1 符号化に対する破壊的変更は、TLS 実装に波及し、そこから HTTP ライブラリを経由してネットワーク要求を行うすべてのものに影響を及ぼします。CSS Grid は Box Model(ボックスモデル)と Visual Formatting contexts(視覚フォーマット文脈)の上に構築されています。
rfcdeps ツールは、RFC エディタの XML インデックスから「obsoletes(廃止)」および「updates(更新)」ヘッダーを解析することでこれらの関係をグラフ化しますが、仕様グラフとソフトウェア依存関係グラフを接続する方法を持っていません。また、誰もそれを試したようには見えません。
パッケージ管理は命名の問題であり、仕様の命名問題はパッケージのそれよりも深刻です。
IETF は、仕様の新バージョンごとに完全に新しい番号を付与する連番方式を使用しています。RFC 9110 は RFC 7231 を廃止し、その RFC 7231 は RFC 2616 を廃止しました。「HTTP セマンティクス」を参照したい場合、どの RFC 番号を選ぶかを決める必要があり、その選択はバージョン範囲ではなく特定の時点点を符号化することになります。W3C は CSS にレベル(CSS Grid Level 1, Level 2)を使用し、古い仕様には番号付きバージョン(HTML 4.01)、成熟度段階(Working Draft, Candidate Recommendation, Recommendation)を使用します。WHATWG はバージョン管理を完全に放棄し、HTML はバージョン番号もスナップショットもない「Living Standard」として扱われています。ECMA は版数と年名を組み合わせて使用しています(ECMA-262 第 6 版は ES2015 とも呼ばれます)。ISO は、改正および訂正の層を持つ構造化識別子を使用します(ISO/IEC 5962:2021)
インターネットドラフト(draft-claise-semver-02)は、IETF 仕様にセマンティックバージョニング(semver)を適用し、RFC にパッケージが使用するのと同じ種類の機械比較可能なバージョン識別子を与えることを提案しましたが、採用されることなく期限切れとなりました。障壁は実際には技術的なものではなく、標準化団体は何十年も独自の方式でバージョン管理を行っており、その慣習はツールや引用慣行、組織プロセスに組み込まれています。IETF、W3C、WHATWG、ECMA、ISO、IEEE が共通のバージョニングスキームに合意することは、パッケージマネージャーがロックファイル形式に合意することよりもはるかに困難な調整問題です。
もし仕様に PURL に似たスキームを望むなら、pkg:spec/ietf/rfc9110@2022 のようなものが考えられます。
仕様を明示的に扱うには、問題を一度にすべて解決する必要はありません。データモデルの一部は既に存在しています:SPDX 3.0 には hasSpecification という概念が含まれています
原文を表示
Your Ruby gem declares required_ruby_version >= 3.0
Runtimes at least show up somewhere in the tooling. Your HTTP library also depends on RFC 9110, your JSON parser on ECMA-404, your TLS implementation on RFC 8446, and none of those appear in any manifest, lockfile, or SBOM.
Library dependencies get the full treatment: manifests declare them, lockfiles pin them, SBOMs track them, scanners check them for vulnerabilities. Runtime versions sit one layer down, handled differently by every ecosystem. Python has Requires-Python
required_ruby_version
Developers keep inventing new layers because none of these are reliable enough on their own. A Ruby project might have required_ruby_version >= 3.0
Runtime implementation is barely tracked at all. JRuby 9.4 reports RUBY_VERSION
And below all of this sit the specifications themselves, language definitions and protocol standards and encoding rules, none of which appear in any dependency graph.
Spack, the HPC package manager, spent seven years learning what happens when you leave a dependency implicit.
Before Spack v1.0, compilers were a special “node attribute” rather than actual nodes in the dependency graph. You configured them in compilers.yaml
Compiler runtime libraries like gcc-runtime
The idea to fix this was filed in 2016. The motivation came from a debugging story: a sysadmin installed a large dependency graph, gfortran was missing, openmpi built without Fortran support, and then hypre failed much later. If packages declared language dependencies, resolution itself would have caught the missing compiler before anything started building.
It took until March 2025 for PR #45189 (“Turn compilers into nodes”) to merge. In Spack v1.0, languages like c
depends_on("c")
depends_on("cxx")
Nix has always treated the compiler as a hashed dependency. Every derivation gets its build tools through stdenv
Spec transitions
What happens at that boundary, when a spec changes and the implementations have to follow? The results are rarely clean.
When Chrome and Firefox enabled TLS 1.3 for testing in February 2017, failure rates were unexpectedly high. Chrome-to-Gmail connections succeeded 98.3% of the time with TLS 1.2 but only 92.3% with TLS 1.3. The culprit was middleboxes: corporate proxies and firewalls that had hardcoded expectations about TLS handshake fields. The TLS spec always allowed those fields to change, but because they had been stable for so long, middlebox developers treated them as constants.
TLS 1.3 now lies about its own version. The ClientHello claims to be TLS 1.2, includes dummy session_id and ChangeCipherSpec fields that TLS 1.3 doesn’t need, and uses a supported_versions
Unicode releases new versions roughly annually, and each version can change character properties for existing characters, not just add new ones. When Chrome updated its ICU data, the wrestler and handshake emoji lost their Emoji_Base
PyPI classifiers let packages declare Programming Language :: Python :: 3
python_requires
CommonJS and ES Modules in Node.js are two incompatible module specs: ESM can import CJS, but CJS cannot require()
SMTP’s transition to ESMTP negotiates at the protocol level: clients send EHLO
Executable specs
Web Platform Tests has over 56,000 tests and 1.8 million subtests, each mapped to a specific section of a W3C or WHATWG specification. The WPT Dashboard shows which browser engines pass which tests. TC39’s Test262 does the same for ECMAScript. When a browser team says “we implement CSS Grid Level 1,” what they mean in practice is that they pass a specific set of WPT tests.
These test suites are closer to something you could declare as a dependency than any prose RFC. They’re versioned, concrete artifacts with commit hashes. If you wanted a PURL-like identifier for spec dependencies, the test suite version might be more useful than the spec document version: pkg:spec/w3c/wpt-css-grid@sha256:abc123
pkg:spec/w3c/css-grid@level-1
Most specs have no conformance suite at all, though. IETF RFCs rarely ship with official tests. Where tests exist, they tend to emerge from interoperability testing during standardization and then go unmaintained. The dependency chain for most software is still package -> implementation -> implicit understanding of a prose document
TypeScript’s DefinitelyTyped ecosystem already does something like this for runtime APIs. @types/node
De facto specifications
Not all specifications live in standards bodies. Node.js module resolution has no formal spec; it’s defined by Node’s behavior, and anything that resolves modules the same way is depending on that behavior whether or not anyone writes it down.
Oracle donated Java EE to the Eclipse Foundation but retained the Java trademark, which prevented the Eclipse Foundation from modifying the javax
javax.persistence
Spec-to-spec dependencies
Specifications have their own dependency graphs. JSON relies on UTF-8 and through it on Unicode. HTTP sits on TLS, which sits on X.509 and ASN.1, so a breaking change to ASN.1 encoding would ripple through TLS implementations into HTTP libraries and from there into everything that makes network requests. CSS Grid builds on the Box Model and Visual Formatting contexts.
The rfcdeps tool graphs these relationships by parsing the “obsoletes” and “updates” headers from the RFC Editor’s XML index, but it has no way to connect the spec graph to the software dependency graph, and nobody seems to have tried.
Package management is naming, and the naming problem for specifications is worse than for packages.
The IETF uses sequential numbers where a new version of a spec gets a new number entirely. RFC 9110 obsoletes RFC 7231, which obsoleted RFC 2616. If you want to reference “HTTP semantics,” you need to pick which RFC number, and that choice encodes a point in time rather than a version range. W3C uses levels for CSS (CSS Grid Level 1, Level 2), numbered versions for older specs (HTML 4.01), and maturity stages (Working Draft, Candidate Recommendation, Recommendation). WHATWG abandoned versioning entirely; HTML is a “Living Standard” with no version number and no snapshots. ECMA uses both edition numbers and year names (ECMA-262 6th Edition is also ES2015). ISO uses structured identifiers with amendment and corrigenda layers (ISO/IEC 5962:2021
An Internet-Draft (draft-claise-semver-02) proposed applying semver to IETF specifications, giving RFCs the same kind of machine-comparable version identifiers that packages use, but it expired without adoption. The barriers weren’t really technical; standards bodies have versioned things their own way for decades, and the conventions are embedded in their tooling, citation practices, and organizational processes. Getting the IETF, W3C, WHATWG, ECMA, ISO, and IEEE to agree on a common versioning scheme is a harder coordination problem than getting package managers to agree on lockfile formats.
If you wanted a PURL-like scheme for specifications, something like pkg:spec/ietf/rfc9110@2022
Making specs explicit doesn’t require solving the whole problem at once. Some of the data model already exists: SPDX 3.0 includes a hasSpecification
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み