ADR-35 override-rules — Mastodon false-positive verification
日付: 2026-05-29。コーパス: Mastodon(~/repo/ruby/rigor-survey/mastodon)、app + lib全体、1219個のRubyファイル。ADR-35スライス4の付随資料。
プロジェクト全体に対して、強制したstrict重大度プロファイルの下でrigor checkを実行した(Mastodon自身の.rigor.dist.ymlはlenientを使っており、3つのdef.override-*ルールすべてを:offへマップするため何も顕在化しない)。設定はdistファイルを踏襲し(同じpaths: / plugins:)、severity_profile: strictを加えた。診断をルール別に集計した。一時的な設定はあとで除去した。
| ルール | 発火数(strict) | 判定 |
|---|---|---|
def.override-return-widened | 0 | 正しく不活性 — アプリのクラスに著作されたRBSがない(WD1ゲート)。 |
def.override-param-narrowed | 0 | 同上 — 著作されたRBSがない。 |
def.override-visibility-reduced | 160 → 35 | 160件は本物の偽陽性クラスタ(修正済み)。35件の残余は真の可視性縮小。 |
可視性の偽陽性クラスタ(160件)とその修正
Section titled “可視性の偽陽性クラスタ(160件)とその修正”160件の発火はRailsのコンサーンパターンが大半を占めていた。app/controllers/concerns/配下のコンサーンがprivateなヘルパーメソッド(username_param、set_account、pundit_user、pagination_*、……)を定義し、それをincludeするコントローラーがそれらを — やはりprivateで — 再定義する。private → privateは縮小ではないので、これらは決して発火すべきではなかった。
根本原因: メソッドの可視性がファイル単位でのみ追跡されていた一方で、def-node / 親クラス / includesのインデックスはクロスファイルでシードされていた(ADR-24のプロジェクト事前パス)。そのため、あるコントローラーを解析するとき、親コンサーンのprivateな可視性(兄弟ファイルで宣言されている)がnilとして返り、ルール内のnil → :publicフォールバックが「public」な親を捏造して、偽のpublic→private「縮小」を生んでいた。
修正(エンジン):
Inference::ScopeIndexer.discovered_def_index_for_pathsが、すべてのプロジェクトファイルにまたがってmethod_visibilitiesも収集するようになった。merge_project_method_indexesが、ファイル単位の可視性をクロスファイルのシードに上書きする形でマージする(現在のファイルが自身のクラスについて権威を持ち、兄弟の親は保持される)。Analysis::Runner#seed_project_scopeがクロスファイルのテーブルをシードする。CheckRulesはエントリー欠落から:publicを捏造しなくなった — 親の可視性が不明な場合は沈黙へ退避する(WD7に従い偽陽性に対して安全)。
修正後: 160 → 35となり、クロスファイルの本物のケース(あるファイルでpublicな親、別のファイルでprivateなオーバーライド)が正しく発火するようになった(以前は|| :publicという幸運な偶然によってのみ「動いて」いた)。
残余35件の発火は真陽性
Section titled “残余35件の発火は真陽性”35件すべてが、エンジンがいまや正しく読み取る本物の可視性縮小である:
pundit_userはAuthorizationコンサーンで純粋にpublic(private修飾子なし)であり、コントローラーがそれをprivateでオーバーライドする。pagination_max_id/pagination_since_id/pagination_paramsはApi::Paginationで純粋にprotectedであり、コントローラーがそれらをprivateでオーバーライドする。respond_with_errorはApi::BaseControllerでprotected、privateにオーバーライドされている。
これらはRailsではスタイル的によくあるもので、おそらく無害だが、解析器の誤読ではなく本物の縮小である。これらはstrictの下でのみ顕在化する。Mastodonの実際のlenient設定が顕在化させるのはゼロ件である。
キャリブレーションの決定
Section titled “キャリブレーションの決定”3つのルールすべてについて、出荷した重大度マッピングを維持する: lenient → off、balanced → :warning、strict → :error。balanced → :errorへの昇格はしない — 残余の真陽性は典型的なRailsでは十分よく見られるため、balancedなプロジェクトをエラーにすると偽陽性の規律という価値とトレードオフになる。厳格なLSP可視性強制を望むチームは、severity_profile: strictまたはルール単位のseverity_overrides:エントリーでオプトインする。
リーチに関する注記(バグではない — 文書化された限界)
Section titled “リーチに関する注記(バグではない — 文書化された限界)”return/paramルールは両側に著作されたRBSを必要とする。sig/のないアプリは、これらからは何も見ない。これは意図したWD1ゲートである。- 名前的部分型チェックはロード済みのRubyクラス / その親を解決する。アプリ内のみのクラス階層は
:maybeへと退化し、沈黙を保つ。return/paramのリーチはコア / stdlib / ロード可能なgemの階層(例:Numeric/Integer)に及ぶ。プロジェクトRBSの親を認識する部分型パスはこれを拡張しうるが、保留である。
© 2026 TypedDuck. Licensed under CC BY-SA 4.0.