コンテンツにスキップ

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.ymllenientを使っており、3つのdef.override-*ルールすべてを:offへマップするため何も顕在化しない)。設定はdistファイルを踏襲し(同じpaths: / plugins:)、severity_profile: strictを加えた。診断をルール別に集計した。一時的な設定はあとで除去した。

ルール発火数(strict)判定
def.override-return-widened0正しく不活性 — アプリのクラスに著作されたRBSがない(WD1ゲート)。
def.override-param-narrowed0同上 — 著作されたRBSがない。
def.override-visibility-reduced160 → 35160件は本物の偽陽性クラスタ(修正済み)。35件の残余は真の可視性縮小。

可視性の偽陽性クラスタ(160件)とその修正

Section titled “可視性の偽陽性クラスタ(160件)とその修正”

160件の発火はRailsのコンサーンパターンが大半を占めていた。app/controllers/concerns/配下のコンサーンがprivateなヘルパーメソッド(username_paramset_accountpundit_userpagination_*、……)を定義し、それを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件すべてが、エンジンがいまや正しく読み取る本物の可視性縮小である:

  • pundit_userAuthorizationコンサーンで純粋にpublicprivate修飾子なし)であり、コントローラーがそれをprivateでオーバーライドする。
  • pagination_max_id / pagination_since_id / pagination_paramsApi::Paginationで純粋にprotectedであり、コントローラーがそれらをprivateでオーバーライドする。
  • respond_with_errorApi::BaseControllerでprotected、privateにオーバーライドされている。

これらはRailsではスタイル的によくあるもので、おそらく無害だが、解析器の誤読ではなく本物の縮小である。これらはstrictの下でのみ顕在化する。Mastodonの実際のlenient設定が顕在化させるのはゼロ件である。

3つのルールすべてについて、出荷した重大度マッピングを維持する: lenient → offbalanced → :warningstrict → :errorbalanced → :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.