ADR-39 — Plugins may invoke their target library's safe methods directly
Status: Accepted, 2026-06-02.ルールとハーネスは実装され、検証済みです: Rigor::Plugin::Inflector(スライス2)は許可リスト + rescueハーネスを通して実際のActiveSupport::Inflectorを呼び出し、組み込みの近似を一切持ちません(近似は誤ったファクトを発行する → 偽陽性;gemが不在のときは例外を投げ、呼び出し側の分離境界が沈黙に劣化する)。そして3つのコンシューマー——rigor-actionpack・rigor-activerecord・rigor-rails-routes(スライス4)——がそれへ移行され、手書きの語形変化が削除されました。各プラグインのゴールデンマスター統合スペックおよび2つの主要なOSSコーパス(RedmineとMastodonのapp + libのrails-routes診断が前後でバイト単位同一)に対して、振る舞いを保存することが検証されました。フォローアップ監査が同じルールをさらに2つの語形変化の再実装(rigor-actionmailerのビューパスunderscore、rigor-factorybotのファクトリcamelize)と、語形変化以外のケースにも適用しました——rigor-rspec-railsは今やhave_http_status(:symbol)を、ベンダー化されたスナップショットではなく実際のRack::Utils::SYMBOL_TO_STATUS_CODEに対して検証します(Rackが不在のときは辞退)。これは、このルールが語形変化を超えて、プラグインがさもなければ近似していたであろう任意のターゲットライブラリのファクト(データテーブル)へ一般化することを確認します。延期(フォローオン):スライス3(プロジェクト独自の語形変化のためのconfig/initializers/inflections.rbの静的取り込み——デフォルトのActiveSupportルールセットが既に一般的なケースをカバーしている;長命なLSPプロセスにおけるプロジェクト横断のカスタムルール分離が未解決の設計ポイント)、Ruby::Box分離レイヤー(スライス5——ターゲットライブラリ呼び出しのために第一優先で選ばれた分離;§「ターゲットライブラリ呼び出しの分離」を参照)、および正確なバージョンのプロビジョニングフォールバック(バージョン間の振る舞いの差が観測された場合にのみ)。
Rigorプラグインが、エンジンが定数畳み込みのために既に使っているのと同じ境界づけられたハーネスを通して、ターゲットとするライブラリの純粋で許可リストに載ったメソッドをロードして呼び出すことを許す決定を記録します。これは、PHPStan拡張が解析対象アプリケーションのオートローダー内部で動作し、実際のフレームワーククラスを呼び出す方法のRuby版です。ルールは意図的に狭い: 信頼されたターゲットライブラリの純粋メソッドの呼び出しを許可しますが、ADR-2の解析対象アプリケーション自身のコードを実行することの禁止は緩めません。
動機となるコンシューマー: rigor-rails-routes / rigor-activerecord / rigor-actionpackが共有する語形変化ヘルパー(ActiveSupport::Inflector)——プラグインのボイラープレート計画 §0eを参照。そこでは手書きのsingularize / pluralizeのコピーを統一することが、まさにそれらがRailsの実際の語形変化ルールから逸脱するために偽陽性のリスクを負います。
Context
Section titled “Context”緊張関係にある2つの事実
Section titled “緊張関係にある2つの事実”-
ADR-2はアプリケーションコードの実行を禁じる。 ADR-2 §「Plugin Trust and I/O Policy」は次のように述べています: 「プラグインはアプリケーションコードを実行してはならない。解析されたRuby、RBS、生成されたシグネチャ、設定、依存関係メタデータ、キャッシュされたプラグインメタデータを検査してよい。」これはツールの静的解析としてのアイデンティティです——Rigorは解析対象のプロジェクトを決して実行しません。
-
エンジンは既に実際の純粋なライブラリメソッドを呼び出している。定数畳み込み層(
inference/method_dispatcher/constant_folding.rbとその*_folding.rbの兄弟)は、Rigorがリテラルから構築した値に対して実際のRubyメソッドを呼ぶことで、リテラル式を評価します:return nil unless PATHNAME_PURE_UNARY.include?(method_name) # allow-listreturn nil unless receiver.is_a?(Pathname) # Rigor-built valueresult = receiver.public_send(method_name, arg) # real invocationreturn nil unless foldable_constant_value?(result) # result checkType::Combinator.constant_of(result)rescue StandardError # total recoverynilカタログのコメントが契約を名指しします: 「純粋な … カタログ。各メソッドは … 実体化して安全な値を返さなければならず … のみを読み、グローバル状態を一切書かない。」したがってRigorの実働する安全モデルは既に、Rubyコア/標準ライブラリのメソッドに適用された純粋メソッドの許可リスト + Rigor由来の入力 + 結果チェック + rescueです。
これらが緊張関係にあるのは、「ライブラリメソッド」と「アプリケーションコード」が混同される場合だけです。両者は同じものではありません。定数畳み込みはString#upcase / Pathname#basenameを呼びます——これらはランタイムのメソッドであり、解析対象プロジェクト自身の定義ではありません。解析対象アプリケーションのコードは決して実行されません。
PHPStanが行っていること
Section titled “PHPStanが行っていること”PHPStan拡張は解析対象アプリケーションと同じプロセスにロードされ、アプリのオートローダーを通してクラスを解決します。拡張は日常的に実際のフレームワーククラスをインスタンス化し、リフレクションし、呼び出します(Doctrineのメタデータ、Symfonyのコンテナ定義、enumのケース)。PHPStanのbootstrapFilesがフレームワークをブートするので、これが機能します。解析対象のプロジェクトソースは依然として実行されません——が、それが依存するライブラリはロードされ呼び出されます。Rigorの定数畳み込み層は同じ形状であり、これまでコア/標準ライブラリに制限されてきただけです。
許可しない場合のコスト
Section titled “許可しない場合のコスト”プラグインはターゲットライブラリを呼び出せないため、それを再実装します。語形変化ヘルパーは最悪のケースです: rigor-activerecordとrigor-rails-routesはそれぞれ、Railsの語形変化器を近似する手書きのsingularize / pluralizeを出荷しています。これらの近似は:
- 実際のルールから乖離する — それらは一握りの規則的なケースとごく小さな不規則テーブルを扱う;Railsははるかに大きなデフォルト集合に加えて、プロジェクトが
config/initializers/inflections.rbで宣言したものを出荷する。 - 名前解決に供給される — 語形変化したモデル名/ルートヘルパー名が
unknown-helper/unknown-permit-key診断を駆動するので、乖離は表面的なものではない: それは動作するコードに対する偽陽性であり、Rigorが最も重く見るコストである。
コピーを統一すること(ボイラープレート計画 §0e)はこれを修正しません——それは1つの近似を選ぶだけです。本当の修正は、近似をやめることです。
Working Decision
Section titled “Working Decision”Rigorプラグインは、ターゲットとするライブラリへのランタイム依存を宣言し、そのライブラリの純粋メソッドを直接呼び出して結果を計算してよい(MAY)。ただし、そのようなすべての呼び出しが下記の境界づけられたハーネスを通ることを条件とする。ターゲットライブラリの安全なメソッドを呼び出すことは、ADR-2が禁じる意味での「アプリケーションコードの実行」ではない: ターゲットライブラリは、解析対象プロジェクト自身のソースとは区別される、信頼された宣言済みの依存である。
これはエンジンの定数畳み込み層をコア/標準ライブラリからプラグインの宣言されたターゲットライブラリへ一般化し、RigorのプラグインモデルをPHPStanのもの(拡張が実際のフレームワークを呼び出す)に揃えます。
安全ハーネス(呼び出しを許容可能にする契約)
Section titled “安全ハーネス(呼び出しを許容可能にする契約)”ターゲットライブラリのメソッドを呼び出すプラグインは、以下のすべてを満たさなければなりません(MUST):
- 明示的な許可リストによる純粋メソッド。プラグインは、呼び出すメソッドの正確な集合を宣言します(例:
singularize・pluralize・underscore・camelize・classify・tableize)。各々は副作用がなく、決定的で、可変なグローバル状態を一切読んではなりません。許可リストはgrep可能で監査可能なサーフェスです——決して動的なpublic_send(arbitrary_name)ではありません。 - Rigor由来の入力のみ。引数はRigorが構築した値(リテラルASTノードから読まれたString、ソースから導出された名前)です——決してプロジェクトコードを実行して得たオブジェクトではありません。
- 結果はデータであり、チェックされる。戻り値はプラグインが型/ファクトに変換するプレーンなデータ(String、Stringの配列)です;プラグインは使用前に形状を検証します。
- 辞退する、決して近似しない。ターゲットライブラリをロードできないとき、呼び出しは例外を投げます(あるいは呼び出し側が
available?チェックでゲートします)——手書きの近似にフォールバックしません。ライブラリの実際の振る舞いから乖離する近似は、まさにこのADR全体が回避するために存在する誤ったファクトです;「貢献なし」は推測ではなく沈黙を意味しなければなりません。例外は既存のプラグインごとのrescue/ 分離境界へ伝播するので、語形変化に依存するチェックは(誤ったものでもクラッシュでもなく)診断なし(カバレッジ低下)に劣化します。
いずれかの条項に失敗するメソッドは未サポートのまま(チェックは沈黙に劣化)です——近似によって答えられることは決してありません。
譲れない一線: アプリケーションコードは依然として決して実行されない
Section titled “譲れない一線: アプリケーションコードは依然として決して実行されない”ルールはターゲットライブラリを対象とし、解析対象プロジェクトは対象としません。語形変化器について具体的には:
ActiveSupport::Inflector.pluralize("person")を呼ぶ——許可(ターゲットライブラリの純粋メソッド)。- プロジェクトの
config/initializers/inflections.rbをロードして実行し、そのカスタム語形変化を学ぶ——禁止(それはアプリケーションコード)。プロジェクトのカスタムルールは、そのイニシャライザーの小さなDSL(inflect.irregular/plural/singular/uncountable/acronym)をPrismで静的にパースして得られます——ちょうどrigor-rails-routesが既にconfig/routes.rbを静的にパースするのと同じく——そして抽出したルールを実際の語形変化器のパブリックAPI経由で供給します。プロジェクトコードは一切実行されません。
ターゲットライブラリを利用可能にする
Section titled “ターゲットライブラリを利用可能にする”プラグインの自身のgemspecが依存を宣言します(語形変化器プラグインならspec.add_dependency "activesupport")。プラグインがインストールされると、そのターゲットはロードパス上にあります——依存はRigorコアではなくプラグインに属するので、Rigor自身のフットプリントは変わらず、そのプラグインを使わないプロジェクトは何も払いません。プラグインは必要な最も狭いエントリーポイントをrequireし(Rails全体ではなくrequire "active_support/inflector")、それを一度ロードします。
バージョン忠実度。デフォルトの振る舞い——互換性のある範囲に依存しライブラリ自身のルールを使う——で十分です。なぜなら、ライブラリの純粋関数の振る舞い(例: デフォルトの語形変化ルールセット)はバージョン間で安定しているからです;プロジェクト固有の乖離は、gemのバージョンではなく、静的に取り込まれた設定の中にあります。依存を解析対象プロジェクトが解決する正確なバージョンに固定すること(そのGemfile.lockを読み、そのバージョンをRigor管理の分離されたgemディレクトリにプロビジョニングする)は最大忠実度のフォールバックであり、バージョン間の振る舞いの差が観測された場合にのみ正当化されます。これはプロビジョニングのコスト(初回実行時のインストール、キャッシュ、オフラインCI/密閉されたFlakeとの緊張)を負うので、要求されるまで延期されます。
ターゲットライブラリ呼び出しの分離 — 選択可能な戦略(none / ruby_box / process)
Section titled “ターゲットライブラリ呼び出しの分離 — 選択可能な戦略(none / ruby_box / process)”ターゲットライブラリをRigorのプロセスにロードすることは、Rigor自身を汚染するリスクを負います——主にコアクラスへのモンキーパッチ(String / Hashを再オープンするgemは、Rigor自身のコードが動作するRubyを変える)とgemバージョンの衝突(Rubyはプロセスごとに1つのgemバージョンしか許さないので、ターゲットのバージョンがRigor自身のものと衝突しうる)を通じて。どれだけの分離がそのコストに見合うかはデプロイに依存するので、分離は設定可能な戦略(.rigor.ymlのplugins_isolation: / RIGOR_PLUGIN_ISOLATION環境変数)であり、1つのインターフェースの背後に3つのバックエンドを持ちます:
| 戦略 | 分離 | クラッシュ封じ込め | コスト | メモ |
|---|---|---|---|---|
process(fork)——デフォルト | 完全(別のOSプロセス) | あり(子のクラッシュは封じ込められる;親は辞退する) | 1 fork + IPC | 単一の永続ワーカーをfork(呼び出しごとではない)し、それがライブラリをロード + 呼び出し、データ(Marshal)をパイプ経由で返す。Rigorのforkモデル(ADR-15)を再利用。forkが利用できない場所ではnoneにフォールバック。 |
none(直接) | なし(メイン空間にロード) | なし | 最小 | 分離なし;信頼された純粋なライブラリがメイン空間にロードされる。forkのないプラットフォームのフォールバック、および明示的なオプトアウト。 |
ruby_box | モンキーパッチ + バージョン(ボックスごと) | なし(ネイティブクラッシュは依然としてプロセスを落とす) | 低(インプロセス) | Ruby 4.0のRuby::Box、RUBY_BOX=1(再exec)。実験的;上流のVM segfault(下記)でブロック中。 |
重要な対比: ruby_boxは正しさの汚染を分離するが、ボックス化された処理内のネイティブクラッシュは依然としてプロセス全体を落とす(観測済み——下記参照);processは真のクラッシュ封じ込めを与える。なぜなら処理は、親がそのSIGSEGVを生き延びて辞退に変える子の中で実行されるからです。したがってprocessがデフォルトです: 今日機能する最も堅牢な分離であり、永続ワーカー上での1 fork + 呼び出しごとのIPCというコストを伴います(ワーカーは一度forkされて再利用される——決して呼び出しごとに1 forkではありません)。forkが利用できない場所(Windows / JRuby)ではprocessはnoneにフォールバックするので、静かに劣化するのではなく語形変化が依然として機能します——ライブラリは信頼された純粋なものなので、forkベースの分離が得られないときはメイン空間のフォールバックが許容されます。
第一優先のインプロセスオプションは依然としてruby_box(名前空間分離、RUBY_BOX=1で有効化)です: ターゲットライブラリの呼び出しはRuby::Boxの内部で実行され、その境界はクラス/メソッド/定数の定義とコアクラスのモンキーパッチをボックスごとに分離し、同じgemの複数バージョンの共存を可能にします。
Flake Ruby(4.0.5)で検証済み: Ruby::Box.new + box.require + box.evalがgemをロードして呼び出し、返されたデータが境界を越え、ボックス内で定義されたモンキーパッチがメイン空間に漏れません。したがってボックスは、ターゲットライブラリがRigorを破壊的に汚染するのを防ぐのに十分な汚染境界です。(参照: Ruby::Box、インタプリタ自身の実験的警告が指すリンク。)
ボックスが買えるものと買えないもの(そのスコープは意図的に限定され、このADRのトラストモデルに合致します):
- 買える: Rigorへのコアクラスモンキーパッチの漏洩を防ぎ、ターゲットのバージョンをRigor自身のものと共存させ(そのため、最大忠実度の「プロジェクトの正確なバージョンを使う」パスがプロセス全体のバージョン衝突なしに実現可能になる)、定数/名前の衝突を封じ込める。これはルールが必要とする破壊的汚染の境界です。
- 買えない:
Ruby::Boxは明示的にセキュリティサンドボックスではありません——gemのコードは依然として同じOSプロセスで完全な特権で実行され(信頼できないコードの実行を安全にはできない)、同一Rubyバージョン/ネイティブ拡張(ABI)の境界を越えず、C拡張のクラッシュは依然としてプロセスを落とします。これらがここで許容されるのは、まさにルールが常に宣言された、信頼された、純粋なターゲットライブラリしか呼び出さないからです(信頼できないコードやネイティブロードの要件がない);より強い分離には別のOSプロセスが文書化された二次オプションです。
ステータス/計画。 Ruby::Boxは実験的で(インタプリタが「振る舞いが変わるかもしれない」警告を表示する)、プロセス起動フラグ(RUBY_BOX=1、ランタイムでは切り替え不可)です。フラグがプロセス全体に効くため、ターゲットライブラリだけを分離するということはRigorプロセス全体がボックスモードで動作することを意味します——ボックスをプロセス全体で有効化せずに「語形変化器だけをボックス化する」ことはできません。したがってそれを採用するとランチャーに手が入ります。
選択可能な戦略は着地済み、デフォルトはprocess: Plugin::Isolation(none / ruby_box / processのセレクター + バックエンド)、Rigor::Plugin::Box(ruby_boxラッパー)、Isolationを経由するPlugin::Inflectorのルーティング、そしてRIGOR_PLUGIN_ISOLATION=ruby_box(あるいはレガシーのRIGOR_BOX)のときのRUBY_BOX=1のもとでのexe/rigorの再exec。デフォルトのprocess戦略のもとでは、ターゲットライブラリの呼び出しは永続的なforkワーカーで実行されます——診断はメイン空間のパスと同一であり(環境変数を設定しない全スペックスイートとフルRedmine実行で検証済み;make verifyグリーン)、ワーカーは一度forkされて再利用されます。processはforkが利用できない場所ではnoneにフォールバックします。
ボックスパスの経験的ステータス(Ruby 4.0.5):まちまち。
- 分離メカニズムは検証済み——ターゲットライブラリがボックス内でロード + 応答し、メイン空間に漏れない;
Plugin::InflectorはRUBY_BOX=1のもとでボックスを経由してルーティングする(ユニットテスト済み)。 - 些細な
rigor checkはRUBY_BOX=1のもとで問題なく動作する。 - しかしフルの実世界解析はsegfaultしうる:
RUBY_BOX=1のもとでのRedmineappに対するrigor checkがクラッシュした(SIGSEGV)。一見、そのプロジェクト自身の不正な形式のsig/(RBS::DuplicatedDeclarationError)のエラーパスで——非ボックス実行はこれを適切に処理する。クラッシュはVMのメソッドルックアップパス(prepare_callable_method_entry)でのNULL参照であり、ユーザーのサブボックスなし(プロセス全体のRUBY_BOX=1のみ)で再現する——これはRigorのPlugin::Boxによって引き起こされるものではありません。Rubyのバグ報告ドラフトがdocs/notes/20260602-ruby-box-segfault-bug-report.mdにあります。
したがってprocess(fork)がデフォルトです——今日機能する本番対応の分離: フルのRedmine app実行(バイト単位同一の診断、segfaultなし)と環境変数を設定しない全スペックスイートで検証済みです。なぜならforkの境界が、ruby_boxを壊すクラッシュをまさに封じ込めるからです。forkが利用できない場所ではnoneにフォールバックします。ruby_boxは着地済みだが実験的としてゲートされています(選択可能だが、上記の上流Ruby::Box VMバグでブロック中);そのバグが修正されれば魅力的になります(より軽量、インプロセス、+ 正確なバージョンの共存)。noneは明示的なオプトアウト + forkのないフォールバックです。
エンジンサポート
Section titled “エンジンサポート”このパターンは十分に小さいので、新しいエンジンサーフェスは厳密には必要ありません——プラグインは自身の許可リストを保持し、ターゲットをrequireし、rescueできます。パターンが再発するなら、エンジンは薄いPlugin::Baseヘルパー(許可リスト + rescueを強制するsafe_invoke(receiver, method, *args)、畳み込みハーネスを反映)を提供してもよく(MAY)、ケイパビリティカタログ(ADR-37 §「capabilities」)はプラグインの宣言されたターゲットライブラリ + 許可リストを公開してもよい(MAY)ので、rigor plugins --capabilitiesがプラグインの呼び出す実際のメソッドを正確に示します。どちらも追加的であり、2番目のコンシューマーが現れるまで延期されます。
Slices
Section titled “Slices”- このADR — ルール + ハーネス + アプリケーションコードの一線を確立する。(ADR-2の包括的な「ライブラリ呼び出し禁止」という読みを改訂する;ADR-2のアプリケーションコード禁止は変更なし。)
- 実際の
ActiveSupport::Inflectorの上のPlugin::Inflector— 最初のコンシューマー。ハーネスを通して実際の語形変化器を呼び、近似を一切持たない同梱ヘルパー: gemが不在のときはInflector::Unavailableを投げる(呼び出し側の分離境界が捕捉する → 沈黙)、決して推測された語形変化ではない。これはボイラープレート計画 §0eの、偽陽性を減らす置き換えです。 config/initializers/inflections.rbの静的取り込み — カスタム語形変化DSLをPrismでパースして語形変化器に供給し、プロジェクト固有の不規則形が権威的になるようにする。クロスプラグインファクト(produces: :inflections)として公開され、routes / activerecord / actionpackが1つの共有された正しい語形変化器を消費する。- コンシューマーの移行 —
rigor-activerecord/rigor-rails-routes/rigor-actionpackが、手書きのsingularize/pluralize/underscoreのコピーを共有の語形変化器に落とす。各々は、ゴールデンマスター統合スペックおよびOSSサーベイ(rails-routesのMastodon / Redmine実行)に対して、振る舞いを保存するか改善することが検証される。語形変化は名前解決の診断に供給されるためである。
スライス2〜4は、ボイラープレート計画 §0eの具体的な着地であり、今や「近似を統一する」から「実際のライブラリを使う」へと再定義されています。
-
選択可能な分離戦略(§「ターゲットライブラリ呼び出しの分離」を参照)。
Plugin::Isolationは、RIGOR_PLUGIN_ISOLATION環境変数(exe/rigorが.rigor.ymlのplugins_isolation:からマップする)によって、共通のcall(feature:, receiver:, method:, args:)インターフェースの背後で3つのバックエンドの1つを選びます。デフォルトはprocess(forkが利用できない場所ではnoneにフォールバック)。3つすべて着地:none— メイン空間でのrequire+public_send(デフォルトパス)。ruby_box—Plugin::Boxの内部で呼び出す(選択されるとexe/rigorがRUBY_BOX=1のもとで再execする)。モンキーパッチ + バージョンを分離する;最大忠実度の「正確なgemバージョン」パスも解放する。実験的で、フル解析でsegfaultしうる(下記)——そのため使用可能だがゲートされている。process— ライブラリをロード + 呼び出し、データをMarshalパイプ経由で返す永続ワーカーをforkする;ワーカーのクラッシュ(SIGSEGVさえも)は封じ込められる(親はEOF /EPIPEを受け取り、辞退し、次の呼び出しで再生成する)。Rigorのforkモデル(ADR-15)を再利用する。検証済み:RIGOR_PLUGIN_ISOLATION=processのもとでのRedmineappに対するフルのrigor checkは、非分離実行とバイト単位同一のrails-routes診断で、segfaultなしに完了まで実行される——ボックスパスのクラッシュへの堅牢な答え。
Plugin::InflectorはIsolationを経由してルーティングします。デフォルト(process、forkなしではnoneにフォールバック)はインプロセスパスと同一の診断を生成します;noneは、最小コストのインプロセス呼び出しを好むプロジェクトのための明示的なオプトアウトです。
Relationship to other ADRs
Section titled “Relationship to other ADRs”- ADR-2 — その「プラグインはアプリケーションコードを実行してはならない」ルールを、解析対象アプリケーションのコード(依然として決して実行されない)と信頼されたターゲットライブラリの純粋メソッド(今やハーネスを通して呼び出し可能)を区別することで明確化する。Scope / Type / FactStore / IoBoundaryの契約は変更なし;これは許可された計算ソースを追加するものであり、プラグインがプロジェクトから読めるものを広げるものではない。
- ADR-31 — サプライチェーンポリシー。よく知られたターゲットgemへの依存を宣言してその純粋メソッドを呼び出すプラグインは、ユーザーがそのプラグインをインストールすることで既に受け入れているトラストエンベロープの中にある;許可リスト + rescueの規律がそれを境界づける。ADR-31のサードパーティ作者ルーティングは影響を受けない。
- 定数畳み込み(ADR-1の値束 / ディスパッチャー) — これはエンジンがコア/標準ライブラリのために使うのと同じ
public_send+ 許可リスト + rescueモデルを、プラグインのターゲットライブラリへ一般化したものである。畳み込み層自体への変更はない。 - ADR-37 — ケイパビリティカタログはプラグインの宣言されたターゲットライブラリ呼び出しを列挙でき、AI可読性のプロパティを保つ: エージェントは各プラグインが呼ぶ実際のメソッドを正確に見る。
- ADR-15 — ターゲットライブラリはプロセスごとに一度ロードされる;ワーカーごと(fork / 将来のRactor)のロードは、任意のRigor依存と同じ考慮事項であり、実行ごとの可変なディスパッチ状態を持たない。
Rejected / deferred alternatives
Section titled “Rejected / deferred alternatives”| 候補 | ステータス | 理由 |
|---|---|---|
| 語形変化の手書きを続ける(ボイラープレート §0eを重複排除のみとして) | Rejected | 1つの近似を選ぶだけ;実際の問題であるRailsの実ルールからの偽陽性の乖離が残る。 |
| ターゲットライブラリが不在のときのフォールバックとして組み込みの近似を保持する | Rejected | ライブラリの実ルールから乖離する近似は誤ったファクト(誤った語形変化名 → 偽のunknown-helper)を発行する。これはこのADRが除去するまさにその偽陽性である。不在は推測ではなく沈黙(例外 → 分離境界)に劣化しなければならない。ライブラリは宣言された依存なので、不在は通常のパスではなく設定ミスである。 |
プロジェクトのinflections.rbを実行してカスタムルールを学ぶ | Rejected | それはアプリケーションコードである;ADR-2はそれの実行を禁じる。そのDSLの静的なPrismパースは、プロジェクトコードを実行せずに同じルールを得る。 |
| 実行ごとにプロジェクトの正確なターゲットバージョンをプロビジョニングする(分離されたインストール) | Deferred | 最大の忠実度だが、インストール/キャッシュ/オフラインCI/密閉Flakeのコストを持ち込む;デフォルトの範囲依存 + 静的なカスタムルール取り込みが実際の忠実度のニーズをカバーする。追求するときの選ばれた手段はRuby::Box分離レイヤー(スライス5)であり、これは正確なバージョンをプロセス全体の衝突なしにRigor自身のものと共存させる。 |
| ターゲットライブラリ呼び出しを分離なしで実行する(現在の固定依存パス) | Accepted(暫定) | 機能し、正しさのためにはボックスを必要としない(呼び出されるライブラリは信頼された純粋なもの;語形変化ルールはバージョン安定)。それが着地すればRuby::Boxレイヤー(スライス5)に取って代わられ、汚染境界 + 正確なバージョンの共存を追加する。 |
Ruby::Box(名前空間分離)で分離する | Chosen(スライス5) | コアクラスモンキーパッチの漏洩を封じ込め + ターゲットのバージョンをRigor自身のものと共存させる——ルールが必要とする破壊的汚染の境界。セキュリティサンドボックスではなく、Rubyバージョン/ネイティブ拡張の境界を越えないが、ルールは宣言された信頼された純粋なライブラリしか呼ばないので、それは許容される;より強い分離には別のOSプロセスが二次オプション。実験的(RUBY_BOX=1)。 |
Rigorコア自身の依存にactivesupportを追加する | Rejected | 1つのプラグインのニーズのためにツールチェイン全体を重いgemに結合する;依存はプラグインのgemspecに属するので非ユーザーは何も払わない。 |
| 一般的な「任意のターゲットメソッドを呼ぶ」エスケープ(許可リストなし) | Rejected | ハーネスが防ぐために存在する、監査不能でおそらく不純なサーフェスを再導入する;動的なpublic_send(arbitrary)は決して許可されない。 |
Consequences
Section titled “Consequences”ポジティブ:
- プラグインは近似ではなくターゲットライブラリの実際の振る舞いで計算するので、あるクラスの偽陽性(語形変化駆動の名前解決)を、移転するのではなく除去する。
- プラグインモデルがPHPStanのもの(拡張が実際のフレームワークを呼び出す)に合致し、PHPStanから来る作者が期待するとおりになる。
- ルールは明示的でgrep可能な許可リスト + rescueによって境界づけられる。これはエンジンが定数畳み込みのために既に信頼しているのと同じモデルである。
- 解析対象アプリケーションのコードは依然として決して実行されない——静的解析としてのアイデンティティが保たれ、境界は暗黙ではなく書き下される。
ネガティブ:
- ルールを使うプラグインは、ターゲットgemへの実際のランタイム依存を増やす(ロードコスト、バージョン範囲のメンテナンス)。
- プラグインごとに文書化すべき新しいトラストの考慮事項: どのターゲットライブラリをロードしどのメソッドを呼ぶか(許可リストとオプションのケイパビリティカタログサーフェスによって緩和される)。
- 正確なバージョン忠実度のパスは、必要になればだが、密閉/オフライン実行と緊張するプロビジョニング機構を持つ。
© 2026 TypedDuck. Licensed under CC BY-SA 4.0.