コンテンツにスキップ

ADR-16 — マクロ / DSL展開基板

ステータス: Accepted — フロア + 精度プロモーション着地(スライス(slice)1〜7 + 6a/6b)、スライス5b + ユーティリティ型戻り値のためのADR-13リゾルバチェイン配線は需要に先送り、2026-05-15。

Rails(ActiveSupport::Concern、ActiveStorage attachedマクロ)、AASM、Devise、GraphQL-Ruby、factory_bot、Sinatra、Sequel、Redmineをカバーするライブラリごとのサーベイdocs/notes/20260515-macro-expansion-library-survey.mdが発端。基板フロアは14のコミット(584ae85…d7b1943)にわたって配信;ADR-12(dry-rbパッケージング)は引き続き予約;このADRは並行して座り、それに依存しません。スライスごとのステータス詳細は § 実装のスライス分けに記載。

RigorのオープンワークアイテムO2(ROADMAP)はv0.1.5以降「マクロテンプレート + heredoc-Ruby展開」のプレースホルダーを運んできました。動機付けとなる実世界のケースは、tDiary Coreのinstance_evalでロードされるプラグインファイル(レガシープラグインでファイルあたり35 FP)とRailsジェネレータテンプレート(.rbとして出荷されるがERB補間を含む)でした。ライブラリごとのサーベイがその全体像を広いRuby DSLゾーに拡張し、共有基板が正当化されるか、それがどんな形を取るべきかを判定しました。

サーベイは2つの強いシグナルを生成しました。

シグナル1 — 調査されたDSL形状の大部分は3つの繰り返し現れる展開パターンを共有する。マクロスタイルの展開が実行可能なサブシステム(GraphQL-Rubyは除外 — スキーマグラフレコーダー、Sequelカラムアクセサも除外 — データベースオラクルが必要)のみをカウント:

パターンサーベイサイト説明
(i) ブロック-as-メソッドSinatra get/post/…、AASM event :x do、ActiveStorage included do、factory_bot factory :user do(定義側)、Redmine Redmine::Plugin.register :id doクラスレベルDSL呼び出しに渡されたブロックが、宣言されたself-typeを持つメソッド本体(またはDSLレコーダー)として実行される。Sinatra::Base#generate_methodは文字通りdefine_method(name, &block); remove_methodする。
(ii) リテラルシンボルでパラメータ化されたheredocテンプレートActiveStorage has_one_attached :avataractivestorage/lib/active_storage/attached/model.rb:111-126)、Devise define_helperslib/devise/controllers/helpers.rb:116-134)、Devise Mapping.add_module述語synth(lib/devise/mapping.rb:113-119)、Redmine Setting.define_settingapp/models/setting.rb:333-350)、Redmine acts_as_eventlib/plugins/acts_as_event/lib/acts_as_event.rb:48-62)、Redmine LabelledFormBuilderlib/redmine/views/labelled_form_builder.rb:25-33class_eval <<~SRC … SRC heredocが、ソースから見えるリテラル引数を補間する名前を持つN個のメソッド定義を発行する。
(iii) バンドルされたモジュールレジストリ経由のトレイトインライニングDeviseモデル側(devise :database_authenticatable, …)、AASM state :x / event :x、Sequel one_to_many :posts、AASM-via-Concern、Devise-via-Concernクラスレベルの呼び出しがシンボルを列挙;アナライザーは各シンボルをモジュール / メソッド名テーブルにマップするバンドルされたレジストリを参照し、呼び出し元クラスに貢献を適用する。

第4のパターン — (iv)宣言されたself下の外部Rubyファイルインクルージョン — はRedmineのwebhook_payload.rbinstance_eval(File.read(path), path, 1)app/models/webhook_payload.rb:71)とtDiary Coreのプラグインローダーに現れます。これはRubyの服を着たPHPStanのスタブファイルパターンです: 解析されたソースツリーの外で出荷されたファイルで、宣言されたレシーバー型と一緒に呼び出しサイトに貼り付けられたかのように解析されます。

支配的なパターンは(ii);最もクリーンなのは(i);最もレジストリ形なのは(iii);最も境界形なのは(iv)。サーベイは調査されたプロジェクト全体で真の「パターン(d)」ランタイムデータ文字列evalの事例を発見しませんでした — すべてのclass_eval / evalサイトは、ソースから見えるリテラルまたは1段のインダイレクション(YAML、プラグインレジストリ、兄弟定数)経由で到達可能な値のいずれかを補間します。

シグナル2 — ActiveSupport::Concernincluded do … endは再ターゲティングの関心事であり、第5の展開パターンではないactivesupport/lib/active_support/concern.rb:138class_eval(&@_included_block)は、includerをselfとしてブロックを実行する。ブロック内にすでに存在するDSL呼び出し(has_one_attachedhas_many、AASM state、Deviseのモジュールごとのincluded do)はincluderに対して発火する;アナライザーの仕事はブロックのレキシカルレシーバーを再バインドすることであり、別様に展開することではない。Concernをパターン(i)〜(iv)に対する再ターゲット演算子として扱うと、基板を小さく保てる。

Rigorにおけるマクロ形の処理の現状。マクロDSLに触れるすべての既存の例プラグインは独自のウォーカーを実装:

  • rigor-activestoragehas_one_attached :name呼び出しを歩き、合成アクセサに戻り型を貢献する。
  • rigor-activerecordbelongs_to / has_oneを歩き、Nominal[Target] | nilを貢献する。
  • rigor-factorybotFactoryBot.create(:user)を歩き、ファクトリーレジストリから戻り型を貢献する。
  • rigor-statesmanはステートマシンDSLを歩き、ステートごと / イベントごとのメソッドファクト(fact)を貢献する。

ウォーカーは基板を共有しない: 各々が(a)コールASTからのリテラルシンボル抽出、(b)名前補間、(c)レジストリストレージ、(d)Plugin::Base#flow_contribution_forとの統合を再実装する。ウォーカーごとのアプローチはプラグイン数で線形にスケールし、サーベイが今や共通であることを実証する定型句を新しいプラグイン作者ごとに再導出することを強制する。

ADR-16は3つのステークホルダー役割を区別し、それぞれ基板、ライブラリごとのプラグイン、フォールバックヒューリスティックに異なるオーディエンスを持つ。これらを混同したことが、以前の「主要オーディエンス: プラグイン作者」という定式化を生んだ。これは2つの異なる集団を取り違えていた。

役割何を消費するか
ライブラリ開発者メタプログラミング形のライブラリのメンテナー(例: AASMコアチーム、dry-rbチーム、Railsコンポーネントメンテナー)基板 — 自分たちのDSLの呼び出し形状を一度宣言するため、ライブラリにバンドルされるか、兄弟のrigor-<lib>プラグインgem経由で。
ライブラリユーザーライブラリに依存するアプリケーション作者rigor-<lib>プラグイン(存在するとき)、.rigor.ymlplugins:リスト経由で有効化。彼らは基板を直接見ない。
rigor自身、フォールバックとして依存ライブラリに対してrigor-<lib>が有効化されていないプロジェクト内のアナライザーライブラリのマクロを最低限の品質で認識するベストエフォートの基板形ヒューリスティック(安価に達成可能なとき)。

基板はライブラリ開発者のための宣言的著作レイヤーです。v0.1.xの正準配信ビークルは別個のrigor-<lib>プラグインgemで、依存するアプリケーションが.rigor.ymlplugins:リスト経由で有効化します。ライブラリバンドルされた宣言(fooの自身のgem内で出荷されるlib/foo/rigor.rb)はもっともらしい将来の配信ビークルですが、このADRでは追求しません — ライブラリメンテナー(dry-rb、AASM、Devise、…)との上流協調はv0.1.xで計画されていません。gemspec依存関係検査経由の自動有効化(「プロジェクトはaasmに依存している → 自動的にrigor-aasmを有効化する」)は別個の先送りされた決定です。

アプリケーション作者はオーディエンスではない — 彼らはプラグインをインストールし、基板エントリーは書かない。

伝統的なADR-2手書きプラグインルートは引き続き利用可能;基板はサーベイが繰り返し現れることを示すパターン(Tier A〜D)のための便利オプションです。

アプリケーション固有の自家製マクロDSL(例: 内部のMyCompany::DSL.define_setting)はADR-16のスコープ外。自家製DSLを今日型付けしたいプロジェクトは、手書きプラグインをローカルgem(Bundlerのpath:ソース)でラップする — 既存のADR-2パス。.rigor.ymlpath:形のプラグインエントリーを受け入れてこれを楽にすべきかどうかは、将来のADRに先送り;このADRはいずれの方向にもコミットしない。

副次的目的(ベストエフォート、要件ではない

Section titled “副次的目的(ベストエフォート、要件ではない)”

アプリケーションがライブラリfooに依存するがrigor-fooが有効化されていないとき、基板は最低限の品質でfooのマクロを認識するためにヒューリスティックパターンマッチを適用してもよい(MAY) — 例えば、リテラルシンボル補間を持つheredoc class_evalを検出し、rigor-foo宣言を要求せずに見えるメソッド名に対して合成リーダーを発行する。

このパスは3つのハード制約に縛られる:

  • パフォーマンスバウンド。副次的認識は「未認識マクロを無視」ベースライン(baseline)が払うであろう以上の測定可能なwall-clockコストを追加してはならない(MUST NOT)。ヒューリスティックがウォームキャッシュrigor checkプロファイルに測定可能な減速を強制するなら、それは落とされる。ADR-15に従い、アナライザーの推論コストが支配的なシェア(約50%);副次的パスはそれを成長させられない。
  • 排除rigor-fooプラグインが有効化されているとき、副次的ヒューリスティックはそのライブラリの呼び出しサイトで並行して実行されない。2つは解釈コストを制限するため使用サイトで相互排他的。専用プラグインが常に勝つ;rigorは二重処理しない。
  • 正確性保証なし。副次的パスは「ベストエフォート」;精密な型付けが必要なアプリケーション作者は専用プラグインをインストールする。ヒューリスティック由来のファクトはmacro.heuristic.<id>provenanceマーカーを運び、下流の消費者(診断、--explain)がプラグイン著作のファクトとヒューリスティック由来のものを区別できるようにする。

副次的目的は願望であり、義務ではない。基板は、ヒューリスティック検出が実装されなくても、主要目的(ライブラリ開発者の宣言)を出荷する。WD12がトレードオフの詳細を記録する。

ライブラリごとの専用プラグインは引き続き望ましい形のまま。各メタプログラミング提供ライブラリ(Rails、AASM、Devise、Sequel、dry-types、…)は独自のrigor-<lib>プラグインを取得する — 既存のrigor-activestorage / rigor-activerecord / rigor-statesmanプラグインと同じモデル。ADR-16はそのデフォルトを変更しない;繰り返される名前補間 / レジストリ / 呼び出し形状の配管を基板に吸収することで、そのようなプラグインを著作するコストを下げる。

docs/notes/20260515-macro-expansion-library-survey.mdで調査されたライブラリ: 各々が将来のライブラリユーザー向けプラグインのターゲット。基板がそれらのプラグインを著作するのを安価にする。カバレッジマップは § 決定 § 計画されたライブラリごとのプラグインで固定。基板展開に合わないDSLを持つライブラリ(GraphQL-Ruby、Sequelカラムアクセサ)も将来のプラグインを取得する — 彼らは新しい基板の代わりに通常のADR-2プラグイン契約(contract)に乗るだけ。「基板なし」パスはファーストクラスの選択肢のまま。

  • 基板の再利用。heredocテンプレートDSL(支配的なパターン)を狙う新しいプラグインの作者は、ASTウォーキングと名前補間を手書きするのではなく、テンプレートとリテラルパラメータを宣言すべき。
  • 衛生。展開出力はソースAST + プラグイン宣言レジストリの純粋関数。Ruby実行なし、展開時IOなし、ランタイム状態の観察なし。
  • 既存プラグイン契約との合成可能性(ADR-2、ADR-7ADR-9)。基板は既存の拡張APIのに座る;既存のプラグインは変更されずに動作し続け、新しいプラグインはマニフェスト上の宣言的登録経由で基板使用にオプトインする。
  • Ractor安全性ADR-15)。基板出力は構築時にRactor.shareable?。Ractorごとの実体化はフェーズ3aのプラグインブループリント規約に従う。
  • ActiveSupport::Concernとの合成可能性included doの再ターゲティングウォーカーが、DSL呼び出しがconcernの遅延ブロックでラップされたときに基板使用プラグインを正しく発火させる。
  • キャッシュ可能性。展開された合成ASTはADR-6に従う決定論的なディスクリプタでCache::Storeに参加する。
  • フロアファーストの発行と長期願望。基板のv0.1.x出荷ターゲットはフロア: 基板影響を受けるコードはクリーンにパースされ、識別子が解決される(構文エラーや未定義メソッド / 定数への参照がキャッチされる);合成メソッドDynamic[T]戻り値で発行される。完全なTier発行形状(例: Tier CのN行発行テーブルと精密な戻り型)はロードマップ上に残る理想的な機能ラインだが、このADRが初期に提供することにコミットするものではない。シーリングに到達するために費やされる不釣り合いなコストは拒否される;フロアに到達することが成果物。WD13が論拠とフロア / シーリング契約が許可するものを記録する。
  • 任意のRubyコードの実行。heredoc本体上のインタープリタなし、ユーザー側テンプレートのevalなし。基板入力はASTリテラル + プラグイン宣言レジストリ。
  • GraphQL-Rubyを解決すること。サーベイは、それがProcString#constantize遅延型をファーストクラス入力として持つスキーマグラフレコーダーであることを実証する。将来のrigor-graphqlプラグインはマクロ展開ではなくスキーマ解決パスが必要。明示的に除外。
  • Sequelカラムアクセサを解決すること。ライブデータベーススキーマに依存。具体的なユーザー需要が表面化したときに別個のADRがスキーマオラクルに対処する。
  • ERBテンプレート化された.rbファイル<%= … %>補間を出荷するRailsジェネレータtemplates/*.rb)。隣接するが別個: 基板が発火するにファイル名パターン検出とERB対応パーサパスが必要。このADRの基板を下流で消費する別個のROADMAP項目として追跡。
  • すべての現在のプラグインを移行させること。基板はオプトイン。plugins/rigor-activestorage/などの既存のウォーカーは引き続き動作する;移行は基板のリーチを実証するフォローアップ演習。

既存のプラグインマニフェストを通じて登録される4ティア展開基板をランディングする。明示的な第5パターン(Concern再ターゲティング)はティアを追加するのではなくAS::Concernウォーカーを拡張することで処理する。動的戻り型拡張(factory_bot形状)はすでにADR-2 + ADR-9でカバーされており、対称性のためにここで再記述するだけ — 新しい仕組みなし。

Tier A — ブロック-as-メソッド(Sinatra形)

Section titled “Tier A — ブロック-as-メソッド(Sinatra形)”

プラグインの宣言: 名前付き形状のクラスレベルDSL呼び出しは、そのブロックを呼び出し元クラスのインスタンスメソッドにプロモートし、selfはそのクラスのインスタンスとして型付けされる。

class RigorSinatra < Rigor::Plugin::Base
manifest(
id: "sinatra",
block_as_methods: [
# X < Sinatra::Base かつ X.get(path, &block) が呼ばれるとき、
# ブロックは X 上のインスタンスメソッドで self : X となる。
Macro::BlockAsMethod.new(
receiver_constraint: "Sinatra::Base",
verbs: %i[get post put delete head options patch link unlink],
self_type: :receiver_instance,
scope_methods: [:params, :request, :response, :env, :app,
:erb, :redirect, :halt, :session, :headers,
:content_type, :body, :status]
)
]
)
end

ブロックASTは書き換えられない。基板はブロックのレキシカルスコープに注釈を付けるので、推論エンジンがself : Sinatra::Baseサブクラスとして見て、宣言されたscope_methodsが裸の識別子として利用可能になる。ヘルパーとアクセサはSinatra::BaseのRBSから来る — プラグインはそれらを再宣言しない。

リーチ: Sinatra、RSpecネストコンテキスト(フォローアッププラグイン)、factory_bot定義側factory :user do … endブロック(レコーダーで、メソッド本体ではない — ただしレシーバー型付けプリミティブは共有される)。

Tier B — バンドルされたレジストリ経由のトレイトインライニング(Devise形)

Section titled “Tier B — バンドルされたレジストリ経由のトレイトインライニング(Devise形)”

プラグインの宣言: クラスレベルDSL呼び出しがシンボルを列挙;各シンボルがバンドルされたレジストリを通じてModule定数と貢献のリスト(インスタンスメソッドファクト、クラスメソッドファクト、ファクトテーブルとして事前記録されたincluded do副作用)に解決される。レジストリ宣言順序で呼び出し元クラスに適用される。

class RigorDevise < Rigor::Plugin::Base
manifest(
id: "devise",
trait_registries: [
Macro::TraitRegistry.new(
call_shape: { receiver_constraint: "ActiveRecord::Base",
method_name: :devise },
symbol_arg_position: :rest,
modules_by_symbol: {
database_authenticatable: "Devise::Models::DatabaseAuthenticatable",
recoverable: "Devise::Models::Recoverable",
rememberable: "Devise::Models::Rememberable",
# …
},
always_included: ["Devise::Models::Authenticatable"],
sort_key: ALL_ORDER # lib/devise/modules.rb をミラー
)
]
)
end

基板の動作:

  • 各シンボル引数についてモジュール定数をルックアップ;呼び出し元クラスにinclude Devise::Models::Xに等価なincludeファクトを発行。
  • 各モジュールについて、バンドルされたレジストリはincluded_doダイジェスト — モジュールのincluded doブロックが追加したであろうメソッドファクトの静的リスト — を宣言してもよい(MAY)。プラグインリリースごとに1度著作され、ランタイムでは再生されない。
  • クラスレベル貢献(extend ClassMethods)は同じ形に従い、モジュールごとに別個のclass_methods_moduleエントリーを持つ。

リーチ: Deviseモデル側(devise :…)、AASM state :x / event :x(生成されたメソッドテーブル上のトレイトレジストリとして再キャストされたとき)、Sequelアソシエーション(one_to_many :posts → 固定メソッド名テーブル)、Deviseルート側(呼び出し形状がdevise_for :resourcesを指す)。

Tier C — リテラルシンボルパラメータ付きheredocテンプレート展開

Section titled “Tier C — リテラルシンボルパラメータ付きheredocテンプレート展開”

プラグインの宣言: クラスレベルDSL呼び出しが、名前がソースから見えるリテラル引数を補間するN個の合成メソッド定義を発行する。基板は呼び出し元クラスに添付された合成ASTノードを発行する。

class RigorActivestorage < Rigor::Plugin::Base
manifest(
id: "activestorage",
heredoc_templates: [
Macro::HeredocTemplate.new(
call_shape: { receiver_constraint: "ActiveRecord::Base",
method_name: :has_one_attached },
symbol_arg_position: 0,
emit: [
{ name: "#{name}", returns: "ActiveStorage::Attached::One" },
{ name: "#{name}=", params: [{ name: :attachable,
type: "ActiveStorage::Attachable" }],
returns: "void" },
{ name: "#{name}_attachment",
returns: "ActiveStorage::Attachment?" },
{ name: "#{name}_blob",
returns: "ActiveStorage::Blob?" }
],
class_level_emit: [
{ name: "with_attached_#{name}",
returns: "ActiveRecord::Relation[T]" }
]
)
]
)
end

基板は合成Type::Methodキャリア(carrier、v0.1.5のType::BoundMethod作業で導入された内部のRigor::Type::Method形状に従う)を生成し、それらを呼び出し元クラスのメソッドディスパッチャーに登録する。キャリアは診断provenanceのためにsynthetic: trueとしてフラグされる。

制約: すべての名前補間はソースから見えるリテラルSymbolまたはStringに解決されなければならない(MUST)。非リテラル引数(has_one_attached(some_method))は既存のプラグインウォーカーフック(または処理なし)にフォールスルーする。

WD13に従うv0.1.xの精度姿勢。Tier Cはフロアで出荷: 合成メソッドは無条件に発行される;emit:テーブルのreturns:文字列はマニフェストに記録されるが解決されない。各合成メソッドはmacro.tier_c.unresolved-return :infoprovenanceマーカー付きでDynamic[T]を返す。returns:文字列をADR-13のPlugin::TypeNodeResolverチェイン経由でルーティングして精密な戻り値を生成することがシーリング — スライス6(精度プロモーション)のロードマップターゲットで、v0.1.xのコミットメントではない。作者のreturns:宣言は今日書くコストがゼロで、リゾルバフックアップがランディングしたとき精度をアンロックする。

リーチ: ActiveStorage attachedマクロ、Deviseマッピングごとのヘルパーカルテット(current_useruser_signed_in?authenticate_user!user_session)、RedmineのSetting.define_setting、Redmineのacts_as_eventLabelledFormBuilder heredocs、dry-struct attribute :name, T(教科書のTier C消費者)。

Tier D — 宣言されたself下の外部Rubyファイルインクルージョン

Section titled “Tier D — 宣言されたself下の外部Rubyファイルインクルージョン”

プラグインの宣言: globにマッチするファイルが、宣言された呼び出しサイトに本体が貼り付けられたかのように評価され、selfは宣言されたクラスとして型付けされる。基板はファイルのASTをバインドされたレシーバー型と一緒に解析に追加する。

class RigorRedminePayloads < Rigor::Plugin::Base
manifest(
id: "redmine-webhook-payloads",
external_files: [
Macro::ExternalFile.new(
glob: "config/webhooks/*.rb",
receiver_type: "Redmine::WebhookPayload",
# ファイル内でアクセス可能なivars:
bound_ivars: { "@event" => "Symbol",
"@issue" => "Issue?",
"@user" => "User" }
)
]
)
end

最も近いアナログ: PHPStanスタブファイル。仕組みはランタイムでのinstance_eval(File.read(path), path, 1);静的には、ファイルの本体は一度パースされ、プラグイン宣言レシーバー / ivar型付けコンテキストと一緒に解析ファイルセットに追加される。実行なし。

リーチ: Redmine webhookペイロードテンプレート、tDiary Coreのinstance_evalでロードされるプラグインファイル(misc/plugin/category-legacy.rbと兄弟 — オリジナルのO2動機ケース)。

Concern再ターゲティング(ティアではない — ウォーカー拡張)

Section titled “Concern再ターゲティング(ティアではない — ウォーカー拡張)”

ActiveSupport::Concern.included do … endactivesupport/lib/active_support/concern.rb:138で遅延class_eval(&block)として実装されている。アナライザーはすでに(部分的に)included doを歩いている;そのウォーカーを拡張して、遅延ブロックがTier-A / Tier-B / Tier-C呼び出しを含む場合、基板がincluderを呼び出し元クラスとして発火するようにする。

新しいマニフェストエントリーなし。Concernウォーカーはrigor-activesupport-core-extファミリーまたはexamples/下の兄弟の一部。

形状なぜティアではないか
レジストリからの動的戻り型(factory_botのcreate(:user)ADR-2動的戻り型拡張 + ADR-9ファクトストアですでにカバーされている。rigor-factorybotは動作する。基板のギャップなし。
GraphQL-RubyフィールドDSLDSLはメソッドを発行しない。Schema::Member走査traversalを再実装するスキーマ解決パスが必要 — 需要が表面化したら別個のADR。
Sequelカラムアクセサライブデータベーススキーマが必要。別個のADR。
ERBテンプレート化された.rbファイル基板が発火する前にパース時ERBパスが必要。Tier D + ERBフロントエンドを消費する別個のROADMAP項目として追跡。
パターン(d)ランタイムデータ文字列evalサーベイは実世界の例を発見しなかった。1つが表面化したら、基板ティアではなくDynamic[T]縮退で処理する。

計画されたライブラリごとのプラグイン

Section titled “計画されたライブラリごとのプラグイン”

トレーサビリティのため — これらは基板が奉仕するために設計されたプラグイン。各々が基板(ティアを命名する行)または通常のADR-2拡張API(「基板なし」行)のいずれかを消費する将来のライブラリユーザー向けプラグイン。各々の著作はユーザー承認にゲートされる;基板は特定のものにコミットすることなくそれらを有効化する。「authored」とマークされた行はすでにexamples/下に存在し、基板への移行候補。

副次的ヒューリスティックとの相互作用(§ オーディエンスと目的、WD11): 下記のいずれかの行が.rigor.ymlplugins:で有効化されているとき、基板の副次的目的ヒューリスティックはそのライブラリの呼び出しサイトで並行して実行されない。専用プラグインがそのクレームされた呼び出し形状を所有する;ヒューリスティックは専用プラグインが設定されていないライブラリでのみ発火する。

プラグイン消費するティアサーベイ参照ステータス
rigor-sinatraASinatraセクションまだ著作されていない。基板スライス1検証ターゲット(新規プラグイン;競合するウォーカーなし)。
rigor-deviseB(モデル側)+ C(マッピングごとのコントローラーヘルパー)Deviseセクションまだ著作されていない。基板スライス3検証ターゲット。バンドルされたモジュールレジストリがlib/devise/modules.rbをミラーする。
rigor-aasmB(ステート / イベントメソッドテーブル)AASMセクションまだ著作されていない。rigor-statesmanの兄弟。
rigor-sequelB(アソシエーション + plugin :nameレジストリ)Sequelセクションまだ著作されていない。カラムアクセサは別個のスキーマオラクルADRに先送り。
rigor-activestorage—(WD13に従い移行先送り)ActiveStorageセクション著作済み。基板の品質がマッチするまで手書きウォーカーにとどまる。サーベイは正典のTier Cとして識別するが、基板v1での移行はリグレッションになるだろう(WD13フロア / シーリングに従い)。
rigor-redmine-payloads(作業名)D(外部のinstance_evalされたRubyファイル)RedmineサイトEまだ著作されていない。基板スライス5検証ターゲット。tDiaryのプラグインローダーが兄弟ケース。
rigor-redmine-settings(作業名)C(YAML駆動の名前セット + バンドルされたトリプレットテンプレート)RedmineサイトCまだ著作されていない。プロジェクト側のmonkey-patch事前評価メモリノートとフォローアップとしてペア。
rigor-graphqlなし — 基板を消費しないGraphQL-Rubyセクションまだ著作されていない。ADR-2ファクト貢献フックを使う;マクロ基板は適用されない(スキーマグラフレコーダー)。需要駆動。
rigor-factorybotなし — 基板を消費しないfactory_botセクション著作済み。ADR-2 + ADR-9(レジストリ + 動的戻り型)を使う。基板移行は計画されていない;形状が合わない。
rigor-dry-typesC(バンドルされたcore.rbレジストリ経由の定数発行;const_setフレーバーでのtier C) + ADR-2 Dry::Types[<literal>]の動的戻り型 + ` & > .optional .constrained .constructor`のキャリア代数処理dry-typesセクション
rigor-dry-structC(attribute :name, T → 5行発行テーブル: リーダー / スキーマキー / to_h行 / [:key]アクセス / .new(name:) kwarg) + ネストされたattribute :x do … endブロックのTier Adry-structセクションまだ著作されていない。基板スライス2主要検証ターゲット(教科書のTier C、競合するウォーカーなし)。属性ごとのTキャリアのためにrigor-dry-types消費する。
rigor-dry-schemaA(ブロックはDry::Schema::DSL上でinstance_evalを実行;素の単語サーフェス(surface)を宣言 — required / optional / value / filled / maybe / each / array) + key → typeマップを構築するASTレコーダー + Processor#call(input) -> Result[T]上のADR-2動的戻り型ルールdry-schemaセクションまだ著作されていない。キーごとの型解決のためにrigor-dry-types消費する。スキーマクラス自体はメソッド拡張されない;価値はプロセッサの戻り形状の型付けにある。
rigor-activerecord(既存)—(WD13に従い移行先送り)著作済み。手書きウォーカーにとどまる。Tier B移行は将来作業で、ADR-16のスライシングの一部ではない。
rigor-statesman(既存)—(WD13に従い移行先送り)著作済み。同上。

テーブルが明示的にする2つの事実:

  • 「基板なし」行(rigor-graphqlrigor-factorybot)が重要。基板がオプトインであり、基板が合わないライブラリを狙うプラグインが既存の拡張API経由で引き続き動作することを確認する。プラグインは基板を強制されない。
  • 既存の著作済みプラグインは移行候補であり、移行義務ではない。移行はプラグインのコードサーフェスを実証的に削減するときのみ着地する;それ以外は手書きウォーカーにとどまる。§ 実装スライシングのスライス6は、プラグインごとにそれを判断するために正確に存在する。

各ティアは不変量セットを共有する:

  • ソースAST + プラグイン宣言レジストリの純粋関数。Ruby実行なし、IOなし、解析されたプロジェクトのrequireなし。
  • ソース可視性前提条件。Tier CとTier Bはパラメータ引数がリテラルSymbol / String / Array-of-Symbolであることを要求する。非リテラル呼び出しはplugin.<id>.non-literal-argument :infoprovenanceマーカーを生成しフォールスルーする。
  • 基板生成の合成キャリアはprovenanceを運ぶ。Tier A〜Dで発行されるすべてのType::Method / Type::*キャリアは、プラグインid、ティア、呼び出しサイト、パラメータ値を命名するMacro::Provenanceレコードを運ぶ。--explainモードで表面化される。
  • キャッシュ可能性。基板出力は、プラグインマニフェストダイジェスト + 呼び出しサイトASTディスクリプタでキーされた既存のCache::Storeに参加する。ADR-6に従い、キャッシュはバイナリエントリーのシャードディレクトリ;基板出力は通常のプラグインキャッシュスロットと並んで座る。
  • Ractor安全性。出力は構築時にRactor.shareable?;Ractorごとの実体化はADR-15フェーズ3aのブループリント / レジストリ分割に従う。
  • ティア間の競合なし。単一の呼び出しサイトはプラグインごとに最大1つのティアに参加する。2つのプラグインが同じ呼び出し形状に対して登録してもよい(MAY);登録順による最初勝ちはADR-13のTypeNode-リゾルバ規約と一致する。plugin.<id>.macro-shadow :info診断が競合を表面化する。

ADR-2(拡張API)。基板は既存のPlugin::Base#flow_contribution_for / dynamic_return_type / type_specifyingフックのにある。新しいマニフェストエントリー(block_as_methodstrait_registriesheredoc_templatesexternal_files)は、同等の手書きウォーカーを合成する宣言的ショートカット。プラグインは宣言的マニフェストエントリーを手書きのflow_contribution_forコールバックと混在させてもよい(MAY);基板はエントリーごとにオプトイン。

ADR-9(クロスプラグインAPI)。基板生成のファクトは既存のPlugin::FactStoreを通じて公開可能。Tier Bのバンドルされたレジストリは、まさにADR-9ファクトストア契約の消費者側 — 例えば、rigor-devise:devise_mappingsを公開し、rigor-rails-routesから:helper_tableを消費して、プロジェクト全体でどのマッピング名が存在するかを知ってもよい(MAY)。

ADR-13(TypeNodeリゾルバ)。素直に直交。ADR-13は%a{rigor:v1:…}ペイロード内のパース時型名解決。ADR-16はクラスレベルDSL呼び出しのASTレベル展開。2つは合成する: 基板発行の合成メソッドは%a{rigor:v1:return: …}で綴られた戻り型を持ってもよい(MAY)、それがADR-13リゾルバチェインを通る。

ADR-15(Ractor並行性)。基板のティアごとのレジストリは、Plugin::Blueprintに登録された凍結されたRactor.shareable?キャリア(ADR-15フェーズ3aに従う)。Ractorごとの実体化は他のプラグインコンポーネントと同様に進む。

ADR-6(キャッシュ永続化バックエンド)。基板出力は、(plugin_manifest_digest, tier_id, call_site_ast_digest)でキーされたディスクリプタで呼び出しサイトごとにキャッシュされる。新しいバックエンドなし;シャードディレクトリストレージを再利用。

ADR-0 / ADR-1(RBS正準、インラインDSLなし)。基板はアプリケーションコードに新しいユーザー可視DSLを導入しない。プラグイン作者は自分のプラグインgemにRubyを書く;ユーザーは.rigor.ymlplugins:リスト経由でオプトインする。

パブリックAPIドリフトサーフェス

Section titled “パブリックAPIドリフトサーフェス”

このADRは(実装が着地したとき)次を追加:

  • Rigor::Plugin::Macro::BlockAsMethod(新しい凍結値クラス)。
  • Rigor::Plugin::Macro::TraitRegistry(新しい凍結値クラス)。
  • Rigor::Plugin::Macro::HeredocTemplate(新しい凍結値クラス)。
  • Rigor::Plugin::Macro::ExternalFile(新しい凍結値クラス)。
  • Rigor::Plugin::Macro::Provenance(新しい凍結値クラス)。
  • Rigor::Plugin::Manifest#block_as_methods#trait_registries#heredoc_templates#external_files(4つの新しいattr_readers;デフォルト[])。
  • Rigor::Type::Method#synthetic?(新しいattr;デフォルトfalse)。
  • 新しい診断識別子:
    • plugin.<id>.non-literal-argument:info;パラメータがリテラルでなかったため基板が展開できなかった)。
    • plugin.<id>.macro-shadow:info;2つのプラグインが同じ呼び出し形状をクレーム;最初勝ちが適用)。
    • plugin.<id>.unresolved-module:warning;Tier Bレジストリが解析環境で解決しないモジュール定数を参照)。
    • plugin.<id>.external-file-missing:warning;Tier Dのglobがスキャン時にゼロファイルにマッチ)。

すべての更新は実装と同じコミットでspec/rigor/public_api_drift_spec.rbにランディング。

各スライスはWD13フロア / シーリング契約を尊重する: 最初にフロア(パース + 識別子解決)を出荷し、段階的にシーリングへプロモートする。

  1. Tier A — ブロック-as-メソッド。✅ 着地(スライス1a 584ae85、1b 16460b5、1c 92d4755)。Plugin::Macro::BlockAsMethod値クラス + Plugin::Manifest#block_as_methodsフィールド;ExpressionTyperのブロック戻り値パスに配線されたエンジンフック(Rigor::Inference::MacroBlockSelfType);動作プラグインplugins/rigor-sinatra/
  2. Tier C — heredocテンプレート展開。✅ 着地(スライス2a b77c101、2b 9251916、2c e65ff9b)。Plugin::Macro::HeredocTemplate値クラス + Plugin::Manifest#heredoc_templatesフィールド;新しいRigor::Inference::SyntheticMethod / SyntheticMethodIndex / SyntheticMethodScanner基板 + RBSとdep-sourceの間の新しいtry_synthetic_methodディスパッチャーティア;動作プラグインplugins/rigor-dry-struct/。WD13フロアに従い: 合成メソッドはDynamic[T]を返す;精密な戻り型プロモーションはスライス6の作業(先送り)。rigor-dry-typesは将来のコンパニオン(現在の基板がまだモデル化していない別個のTier-C-as-const_setプリミティブ)として記載。
  3. Tier B — トレイトインライニングレジストリ。✅ 着地(スライス3a 26b1fe4、3b 17846a7、3c ba1b61a)。Plugin::Macro::TraitRegistry値クラス + Plugin::Manifest#trait_registriesフィールド;スキャナがincludeされた各モジュールのRBSインスタンスメソッドを既存のSyntheticMethodIndexにメソッドごとに展開して拡張する(将来の精度プロモーションのためのorigin_module:provenance付き);動作プラグインplugins/rigor-devise/(バンドルされたレジストリがDeviseのlib/devise/modules.rb戦略テーブルをミラー — 11エントリーに加えて常にincludeされるDevise::Models::Authenticatable)。
  4. Concern再ターゲティングウォーカー。✅ 着地bdbccdd)。SyntheticMethodScannerextend ActiveSupport::Concern + included do ... endモジュールを認識;クラス本体がinclude Mするとき、Mの遅延DSL呼び出しがincludeするクラスに対してリプレイされる。スライス4フロア: 定数パスinclude Mのみ、1ホップのみ、class_methods do ... endは先送り。
  5. Tier D — 外部ファイルインクルージョン。⚠️ 契約のみ(スライス5a 56706a5);スライス5bエンジン統合は需要に先送りPlugin::Macro::ExternalFile値クラス + Plugin::Manifest#external_filesフィールドが着地しManifest#to_h経由でラウンドトリップ;マッチしたファイルを歩く + トップレベルのself_typeをナローイング(narrowing) + bound_ivarsを事前バインドするエンジンフックは、具体的なプラグインターゲット(Redmine webhookペイロード、tDiaryプラグインローダー)にトリガーされる将来のスライスにキュー。プラグイン作者は今日Tier Dエントリーを宣言できる;基板はまだそれらに作用しない。
  6. 精度プロモーション。✅ フロアで着地(スライス6a-TierB d174fff、6b-TierC d7b1943)。2つのパス:
    • 6a-TierB。SyntheticMethodがprovenance内にorigin_module:を記録するとき(スライス3bスキャナからのTier B発行)、既存のRbsDispatch経由でNominal[origin_module]上で呼び出しを再ディスパッチする。モジュールの著作RBS戻り型が勝つ;モジュールがenv内にないときはDynamic[T]にフォールバック。Deviseのvalid_password?untypedではなくboolを返す。
    • 6b-TierC。SyntheticMethodが素のreturn_type:文字列を記録するとき(マニフェストの発行テーブルからのTier C発行)、environment.nominal_for_name(return_type)経由でルックアップする。合成リーダーはクラスがRBS env内にあるときNominal[<class>]を返す;それ以外はDynamic[T]にフォールバック。Tier Bのプレースホルダー"untyped"とRBSスタイルの"void"はTier Cパスから除外され、それらはTier Bブランチまたはフロアに自己ルーティングする。 パラメータ化形式(Array[String]Hash[K, V])とプラグイン提供のユーティリティ型名(Pick<T, K>)は、ADR-13の完全なPlugin::TypeNodeResolverチェイン経由でルーティングする必要がある — まだ先送り、ADR-13スライス3時点でチェインは%a{rigor:v1:…}ペイロード用にのみ配線されており、基板マニフェストのreturns:文字列用ではないため。フォローアップは具体的なプラグイン作者がユーティリティ型形の基板戻り値を必要とするときにトリガーされる。
  7. ドキュメント。✅ 着地0359152)。ハンドブック章docs/handbook/09-plugins.md §「マクロ / DSL展開基板(ADR-16)」が4ティア + Concern再ターゲティング + フロア/シーリングフレーミング + 決定マトリックスを紹介;skills/rigor-plugin-author/SKILL.mdフェーズ2が「ステップ2A — マクロ基板を最初に試す」 / 「ステップ2B — 手書きウォーカー」に分割;ROADMAP / CURRENT_WORK O2が「キュー」から「基板フロア着地」に再フレーム;examples/README.md比較テーブルが3つの新しい基板消費者行を成長。

ADR-16スコープ外(将来のイテレーションに先送り)

  • 既存プラグインの移行rigor-activestoragerigor-activerecordrigor-statesmanrigor-actionpack)。基板の出力品質が既存の手書きウォーカーの品質にマッチまたは超えるとき(WD13の「プロモーションでリグレッションなし」契約に従う)に移行は着地する。手書きウォーカーは今日動作するコードを出荷する;移行は基板義務ではなく、実証された同等性に依存するプラグインごとの選択。将来作業として追跡、スライス6+ではない。
  • 副次的ヒューリスティック検出器(§ オーディエンスと目的、WD12)。その具体的な形(プレパス対ASTパスゲート)はスライス1〜5が使用シグナルを生成した後にのみ最終決定される。

WD1 — なぜ4ティアで、統合マクロ評価器ではないのか?

Section titled “WD1 — なぜ4ティアで、統合マクロ評価器ではないのか?”

任意の文字列に対するclass_eval / define_method / instance_evalを解釈する単一のLisp風マクロ評価器evaluatorは、(a)heredoc本体をパース、(b)補間値を置換、(c)結果を再パース、(d)ターゲットクラスにバインドする必要がある。それらのステップのうち3つは、入力がソースから見えるリテラルである場合にのみ決定論的 — それがまさに各ティアが宣言的に強制するもの。4つのティアはサーベイが実際に発見したパターン;より一般的な評価器は、サーベイが実コードで発生しないことを示すパターン(d)(ランタイムデータ文字列eval)を処理するコストを運ぶ。

WD2 — なぜ展開はナローイングファクトを直接ではなく合成AST / 型キャリアを生成するのか?

Section titled “WD2 — なぜ展開はナローイングファクトを直接ではなく合成AST / 型キャリアを生成するのか?”

呼び出しサイトに添付されたナローイングファクトは「この式は型Xを持つ」を説明する。合成メソッドキャリアは「このクラスは型Xのメソッドを持つ」を説明する — それはMが他所、別の行、おそらく別のファイルで呼ばれるときにrigorが回答する必要がある質問。Tier Cは下流の呼び出しサイト解析が正しく発火するように合成メソッドを発行する必要がある。Tier Aの注釈はブロックに添付されたファクト(ブロックがメソッド形)で、Sinatraのブロックが他所から呼ばれないため十分。2つの契約は共存する。

WD3 — なぜ汎用ASTマクロ評価ではなくプラグイン宣言なのか?

Section titled “WD3 — なぜ汎用ASTマクロ評価ではなくプラグイン宣言なのか?”

汎用評価器は任意のRubyのclass_eval(SRC, __FILE__, __LINE__)を理解する必要がある。3つの問題: (1)補間コンテキスト(#{name}が何に解決されるか)は囲むレキシカルスコープに依存し、それは無限;(2)class_evalのレシーバーは計算される可能性がある(サーベイはこれが稀だが現実であることを示す);(3)heredoc本体自体がメタプログラミングを含む可能性がある。プラグイン宣言テンプレートは3つすべてをサイドステップする: プラグイン作者が呼び出し形状、パラメータ位置、出力にコミットし、基板がASTの配管を処理する。トレードオフは、新しい各DSLがプラグインを必要とすること;サーベイは「型付けする価値のあるDSL」の宇宙が小さく既知であるため、これが許容できると示唆する。

WD4 — パイプラインのどこで展開が実行されるか?

Section titled “WD4 — パイプラインのどこで展開が実行されるか?”

通常のメソッド呼び出し解析とインラインがデフォルト。Rubyのセマンティクスはランタイムディスパッチのみ(コンパイル時フェーズなし);rigorの既存パイプラインはそれをConfiguration → Environment::Reflection構築 → Runnerでミラーし、明示的なプロジェクトロードフェーズを持たない。プリプロセスフェーズを追加することは独自の正当化を要求する構造的変更 — 基板は強制すべきではない。

ティアごとのデフォルト発行ルール:

  • Tier A(ブロック-as-メソッド): インライン。ファイルごとの解析中にブロックが見られたとき、基板はそのレキシカルスコープに注釈を付け、推論エンジンがselfを宣言されたレシーバークラスのインスタンスとして扱うようにする。
  • Tier B(トレイトインライニング): インライン。devise :foo, :barが見られたとき、モジュールをルックアップし解析サイトでファクトを貢献する。
  • Tier C(heredocテンプレート発行): プレパスが必要な場合があるhas_one_attached :avatarが発行する合成メソッドは、user.avatarを呼び出すのファイルから見える必要がある。ファイル解析順序が重要。基板の選択肢: (i)起動時にプロジェクトをスキャンしてTier C呼び出しサイトだけを収集し、ファイルごとの推論の前に合成キャリアを発行する1パススキャン;(ii)最初のファイルごとのパスがTier C発行を収集し、2番目が完全な推論を行う2パスモデル;(iii)合成メソッドを必要とする最初の呼び出しサイト解決時の遅延発行。実装スライス2が具体的なコストが測定されたときに1つを選ぶ;契約は「Tier C合成メソッドはクロスファイルから見える」を述べ、どのメカニズムがそれを配信するかではない。
  • Tier D(外部ファイルインクルージョン): 宣言されたglobにマッチする起動時の1回限りのスキャン;マッチしたファイルは宣言されたレシーバー型付けコンテキストと一緒に解析ファイルセットに追加される。

将来のティアまたは測定が専用プリプロセスフェーズを正当化する場合、その導入は基板が運ぶデフォルトではなく、別個の狭い決定。WD13が関連するコスト規律制約をカバーする。

基板のキャッシュキー(ADR-6に従う)は、ティアが選ぶ発行戦略をカバーする。Tier Cプレパス出力と遅延発行出力は同じ論理的合成キャリアを生成する;キャッシュは戦略ではなく合成キャリア自体を保存する。

WD5 — 展開はキャッシュとどう相互作用するか?

Section titled “WD5 — 展開はキャッシュとどう相互作用するか?”

各基板出力は(plugin_manifest_digest, tier_id, call_site_ast_digest)でキーされた凍結値オブジェクト。キャッシュ無効化は既存のルールに従う: プラグインのマニフェストを変更するとそのすべての基板スロットが無効化される;呼び出しサイト引数リテラルを変更するとそのスロットのみが無効化される。LRU排出(ADR-6に従い先送り)は一様に適用される。

第2のキャッシュキー — concern_retarget_chain — はConcern再ターゲティングされた貢献のためのincluderチェインを記録する。これは、module M内の同じincluded do { has_one_attached :avatar }Mをincludeするクラスによって異なる合成キャリアを生成するため必要。

WD6 — 展開はADR-15 Ractor分離とどう相互作用するか?

Section titled “WD6 — 展開はADR-15 Ractor分離とどう相互作用するか?”

基板レジストリはPlugin::Blueprintの一部で、構築時にRactor.shareable?。各Ractorは共有ブループリントから自身のPlugin::RegistryをADR-15フェーズ3aに従って実体化する;基板出力(合成キャリア)は同じワーカーごとの実体化に従う。プレパスはワーカーがスポーンする前にメインRactorで1度実行され、凍結された基板出力マップを構築する;ワーカーはRbsLoader#prewarmスタイルの共有経由でマップを消費する。

WD7 — なぜConcern再ターゲティングをTier Eとして処理しないのか?

Section titled “WD7 — なぜConcern再ターゲティングをTier Eとして処理しないのか?”

included do遅延class_evalで、新しい発行形ではない。ブロック内の本体はすでに他のウォーカー(Tier AまたはTier Cプラグインウォーカー)が処理方法を知っているRuby。必要な唯一の新しい動作は「子呼び出しを展開するときにブロックのselfをincluderに再バインドする」こと。それは既存のAS::Concernウォーカー内に収まる;Tier Eエントリーを追加するとウォーカーの責務を重複させる。

WD8 — なぜTier Dはivar型付けを許可するがローカル変数の型付けは許可しないのか?

Section titled “WD8 — なぜTier Dはivar型付けを許可するがローカル変数の型付けは許可しないのか?”

プラグイン宣言のbound_ivarsは、instance_eval(File.read(...))の呼び出し元がロード前に@-ivarsを設定するランタイム規約をモデル化する。対照的に、ローカル変数はファイルにスコープされ、ファイル自身によってレキシカルに宣言される — プラグイン宣言は必要ない。プラグインがローカル変数型をオーバーライドできるようにすることは、基板が触れるべきよりも強い契約であるレキシカルスコープを破る。

WD9 — なぜ非リテラル引数の診断マーカーは:warningではなく:infoなのか?

Section titled “WD9 — なぜ非リテラル引数の診断マーカーは:warningではなく:infoなのか?”

非リテラル引数(has_one_attached(some_method))は珍しいが正当なRuby。基板の正しい応答は「これは展開できない;フォールバックする」。それは警告すべき欠陥ではなく — 境界。より厳格な動作を望むプラグイン作者は、provenanceマーカーをエスカレートするカスタムルールを公開できる。

WD10 — なぜ「ライブラリごとの専用プラグイン」が「基板駆動の単一プラグイン」ではなくデフォルトのままなのか?

Section titled “WD10 — なぜ「ライブラリごとの専用プラグイン」が「基板駆動の単一プラグイン」ではなくデフォルトのままなのか?”

各ライブラリの呼び出し形状、レジストリコンテンツ、エッジケース(例: DeviseのDevise.add_moduleランタイムレジストリ、AASMのnamespace:オプション、Sequelの:methods_moduleオーバーライド)は、そのライブラリのメンテナンスサーフェスに属する。複数のライブラリを1つの巨大なプラグインgemにバンドルすると、リリースサイクルが結合し、git subtree split可能性が複雑化し(Railsプラグインロードマップを参照)、Gemfile選択が混乱する(Sequel + SinatraのプロジェクトがDeviseインフラもプルすることになる)。基板の仕事は各ライブラリごとのプラグインを安価に著作することにする;プラグイン数を縮小するためのビークルではない。§ 計画されたライブラリごとのプラグインの2つの「基板なし」行(rigor-graphqlrigor-factorybot)が反対側の対称境界を確認する: 基板が合わないライブラリも、通常のADR-2契約経由で独自の専用プラグインを取得する。

WD11 — なぜ専用プラグインと基板ヒューリスティックは使用サイトで相互排他的なのか?

Section titled “WD11 — なぜ専用プラグインと基板ヒューリスティックは使用サイトで相互排他的なのか?”

副次的目的(専用プラグインなしのヒューリスティック認識)と専用プラグインパスは同じ答えへの代替ルート: 両方ともリテラルシンボルDSL呼び出しから合成キャリアを計算する。両方を同じ呼び出しサイトで発火させると、(a)同じ答えを2回計算してアナライザー時間を浪費する、(b)2つのルートが不一致のときにファクトマージポリシーを要求する(専用プラグインが常により正確 — 二次ソース裁定に価値はない)、(c)キャッシュで二重カウントする。

排除は1つのルール: rigor-fooプラグインが呼び出し形状の所有権を宣言(プラグインのマニフェスト経由で)したとき、ヒューリスティック検出器はfooのgem依存ツリー内のファイルに対してその形状をスキップする。仕組みは起動時に有効化されたプラグインのマニフェストから設定される「クレームされた呼び出し形状」セット;ヒューリスティック検出器は実行前にセットを参照する。

排除は一方向: ヒューリスティックは専用プラグイン出力をオーバーライドまたは拡張しない。プラグインが特定の呼び出しに対して何も生成しない(例: 引数が非リテラルのため)場合、ヒューリスティックも引き継がない — 両方とも適切なprovenanceでDynamic[T]にフォールスルーする。

WD12 — なぜ副次的目的はベストエフォートで、ハード要件ではないのか?

Section titled “WD12 — なぜ副次的目的はベストエフォートで、ハード要件ではないのか?”

2つの理由: スコープとコスト。

スコープ。副次的ヒューリスティックが検出できるパターンは、宣言されたプラグインのために基板が公開するパターンと不完全に重複する。Tier C(heredoc + リテラルシンボル補間)は、表面パターンが構文的に区別可能なため、ヒューリスティックで最も検出しやすい。Tier B(バンドルされたレジストリ経由のトレイトインライニング)はずっと難しい — ヒューリスティックは、どのモジュールかを伝えるレジストリなしに「このメソッドはシンボルを列挙し、モジュールをincludeする」を認識する必要がある。Tier A(ブロック-as-メソッド)は構文的には自明だが、宣言されたself-typeなしには役に立たない。Tier D(外部ファイルインクルージョン)は設定されたパスなしには検出不可能。だから「プラグインなしの最小努力認識」は基板パターンのサブセットに対してのみ現実的。

コスト。検出コストは、基板パターンであり得るが実際はそうでないすべての呼び出しサイトに対して支払われる — ヒューリスティックに対する呼び出しごとのプローブ。今日rigorの推論ループはADR-15 § コンテキストに従いwall-clockの約50%;各呼び出しサイトに無条件の基板パターンプローブを追加するとそのシェアが成長する。§ オーディエンスと目的のパフォーマンスバウンドに従い、副次的パスの正味wall-clock影響はゼロまたは無視可能でなければならない。それはヒューリスティックを、(a)プロジェクトロードプレパス中に1度実行して小さな候補セットを生成するか、(b)既存の呼び出し形状マッチ以上のコストを払わない構文的ゲートに委ねるか(例: 「呼び出しのレシーバークラスはModuleANDメソッドはclass_evalAND最初の引数はheredocノード」 — すでにASTパスにある3述語ゲート)に制約する。

両方のルートは実装可能だがいずれも必要ではない。基板の主要目的(宣言されたプラグイン著作の便利さ)は単独で立つ。副次的目的はヒューリスティックがパフォーマンスバウンドをクリアした場合に出荷される;それ以外は基板は宣言されたプラグイン専用。

WD13 — フロアファーストの配信;シーリングは長期的な願望のまま

Section titled “WD13 — フロアファーストの配信;シーリングは長期的な願望のまま”

WD12は副次的ヒューリスティックのベストエフォート姿勢をカバーする。WD13は主要目的に適用される同じコスト規律を記録する: v0.1.xの基準は意図的にフロアで、シーリングではない。完全な精密発行形はロードマップ上に理想的な機能ラインとして残る;このADRはそれに到達することにコミットしない。精度に費やされる不釣り合いな実装コストは拒否される。

v0.1.x出荷フロア(ティアごと)

  • 4ティアすべてにわたって、基板影響を受けるコードは最低限クリーンにパースし、囲む名前スコープに対して識別子が解決される必要がある。具体的には: class_evalに渡されたheredoc本体は少なくともPrismパースされる;その中の素の識別子参照は、基板宣言のレシーバーの可視メソッド + 推論されたレキシカルスコープに対してチェックされる;構文エラーまたは存在しないメソッドへの参照は通常のrigor診断として表面化する。
  • Tier C特に: 各ケースで合成メソッドを発行し、Dynamic[T]戻り値とmacro.<tier>.unresolved-return :infoprovenanceマーカーを付ける。下流のuser.avatar.foo呼び出しは既存のDynamic[T]ルールを通じてフォールスルーする — 現在の「プラグインなし」ベースラインに対するリグレッションではない。
  • Tier B: 具体的なModule定数に解決失敗するレジストリエントリーは、シンボルを完全に落とすのではなく、macro.tier_b.unresolved-moduleprovenance付きのDynamic[T]インスタンス / クラスメソッド貢献を発行する。

このフロアは基板のv0.1.xスライスが出荷するもの。リゾルバフックアップ、ジェネリックパラメータ処理、シーリングが必要とする任意のインフラにゲートされない。

長期シーリング(ロードマップ、コミットメントではない)。完全なTier発行精度: ADR-13のPlugin::TypeNodeResolverチェイン経由で解決されたreturns:を持つTier CのN行発行テーブル;精密なインスタンス / クラスメソッドファクトを貢献するTier Bのincluded doダイジェスト;完全に型付けされたTier Aの宣言されたscope_methodsサーフェス;バインドされたレシーバー下で完全な推論精度で解析されたTier Dのファイル本体。これらの各々は望ましく個別に到達可能 — 実装スライスが正味プラスのコスト / 価値を実証するとき — だがいずれもADR-16の配信要件ではない。シーリングは、後続ADR / 将来のスライスがこの契約を無効化することなく個々の行をプロモートできる場所。

なぜこの契約か。3つの理由。

  1. 基板のユーザー向け価値は全か無かではない。フロア(名前発行 + eval’d本体のパースチェック + 識別子解決)はサーベイのオープン診断シェアの大部分をキャプチャする(Redmine 35 FP / fileケースは精密な戻り型付けではなく、未宣言レシーバー上のメソッド不在で支配される)。精密な戻り型は望ましいがフロアに従属する — 基準は提案された完全な発行形よりも本当に低い。
  2. 実装コストは非線形。発行テーブルのreturns:文字列をADR-13の既存Plugin::TypeNodeResolverチェインを経由してルーティングすることはスライス。リゾルバをすべての前方参照 / ジェネリックパラメータエッジケースに対してロバストにすることはもっとずっと多い。フロアファースト契約は、基板がそのエッジケースセットを解決することにコミットせずに出荷できるようにする。
  3. プロモーションは前方互換。スライス2(Tier C MVP)は名前 + Dynamic[T]戻り値を出荷する。後のイテレーションが行を精密な戻り値にプロモートするとき、バージョンNのユーザーコードはリグレッションしない — Dynamic[T]は具体的な型に広がる、逆ではない。

契約が許可しないもの。基板の発行が静かに間違った型を生成すること。ティアが戻り型を精密に解決できない場合、解決なしのプラグイン作者のreturns:文字列からの「ベストゲス」型ではなく、Dynamic[T]とprovenanceマーカーを発行しなければならない(MUST)。非対称性はADR-5堅牢性に従う: rigorの著作出力は戻り値に対して厳格。フロアは「精度を優雅に失う」であり、「精度を捏造する」ではない。

候補ステータス理由
class_eval / define_method / instance_evalを解釈する単一の汎用Lispマクロ評価器拒否(WD1)任意のRubyを評価する必要;セキュリティ + 決定性リスク;サーベイはパターン(d)が発生しないことを示す。
プラグインごとのウォーカーのみ、共有基板なし拒否ライブラリごとの専用プラグインがデフォルトののまま(WD10を参照)だが、それらの中のウォーカーは名前補間とレジストリ配管を重複させる — サーベイはマクロDSLサイトの約80%がTier B/C形状を共有することを示す。基板はプラグインの下の便利レイヤーで、それらを置き換えるものではない。
すべてのメタプログラミングライブラリをカバーする基板駆動の単一プラグイン拒否(WD10)リリースサイクルを結合し、subtree-split可能性を破り、Gemfile選択を混乱させる。ライブラリごとのプラグインがデフォルトのまま;基板は共有著作レイヤー。
専用プラグインAND基板ヒューリスティックを同じ呼び出しサイトで実行し、ファクトをマージ拒否(WD11)同じ答えを2回計算してアナライザー時間を浪費する;2つのルートが不一致のときにファクトマージポリシーを要求する(専用プラグインが常により正確);キャッシュで二重カウント。使用サイトでの排除がよりシンプルなルール。
副次的ヒューリスティックをすべての基板パターンに対してハード要件にする拒否(WD12)検出コストは、基板パターンであり得るすべての呼び出しサイトに対して支払われる。Tier B / Dヒューリスティックも、基板の主要目的がすでに要求するレジストリ / パス設定なしには実装不可能。基板の主要目的(宣言されたプラグイン著作)は単独で立つ;ヒューリスティックは達成可能なときにベストエフォートで出荷。
具体的なユーザー需要まで完全に先送り拒否サーベイは具体的な需要を実証: Redmine 35 FP / file(レガシープラグイン)、Deviseモデル側にプラグインなし、AASMは到達可能だが実装されていない。
Tier Cのみ(heredocテンプレート)、A/B/Dをスキップ拒否Tier A(Sinatra形)はマニフェスト配管の最もシンプルな検証ケース;Tier B(Devise形)はエコシステムプラグインの最高のエルゴノミクスペイオフ;Tier Dはオリジナルの動機O2ケースのブロックを解除する。Tier Cのみの出荷はサーベイが正当化するよりも基板のリーチを狭くする。
Concern再ターゲティングをTier Eとして扱う拒否(WD7)再ターゲティングは新しい発行形ではない;他のティアの上に合成する。
クロスプラグインマクロシャドウ競合のためのプラグイン優先度レジストリ拒否登録順による最初勝ちはADR-13のTypeNodeリゾルバ規約とADR-2のdiagnostics_for_file規約と一致する。優先度レジストリはプラグインgemを結合する。
基板はナローイングファクトのみを発行し、合成メソッドはない拒否(WD2)Tier Cは遠くの呼び出しサイトが解決できるように合成メソッドを発行する必要がある。マクロ呼び出しサイトに添付されたファクトは、user.avatarが5ファイル離れて呼ばれるときに役立たない。
Prism ASTノード型に対して直接基板を著作、Macro::*値オブジェクトなし拒否値オブジェクトはプラグインがマニフェストレイヤーで展開意図を宣言できるようにする;生のPrismクエリで置き換えると、ASTの配管が各プラグインに戻される(それがまさに基板が存在を回避するもの)。
基板をランディングするがすべての既存プラグインの移行を強制拒否既存のウォーカーは動作するコードを出荷する。移行はプラグインごとの選択;基板の価値は採用によって判断され、churnを強制することではない。
  • 発行テーブルのreturns:文字列解決パス。Tier C発行エントリーは戻り型を文字列として綴る("ActiveStorage::Attached::One""Dry::Types::Hash::Schema")。精密な解決が望まれるとき(WD13のシーリングに従う)、基板はこれらをADR-13のPlugin::TypeNodeResolverチェイン経由でルーティングする — %a{rigor:v1:return: …}ペイロードを解決するのと同じチェイン。基板のプロモーションパス(§ 実装スライシングのスライス6)は: (a)発行テーブル文字列を%a{rigor:v1:return: …}ペイロードかのようにADR-13のリゾルバに供給;(b)チェインが具体的なTypeを返したら合成メソッドにインストール、(c)それ以外はDynamic[T]にフォールバックしmacro.<tier>.unresolved-returnを発行。基板は独自の文字列から型へのパーサを導入しない;ADR-13のチェインの再利用が「Rigor名 → Type」の単一の真実のソースとしてリゾルバを保つ。オープン詳細: 前方参照解決(まだロードされていない型を命名するreturns:文字列)はADR-13がすでに指定する同じLateBoundType扱いを取得する;スライス6が開始したときに文書化。

  • Tier Cテンプレートはシグネチャだけでなくメソッド本体もサポートすべきか? ActiveStorageのwith_attached_avatarスコープはjoins(:avatar_attachment).joins(:avatar_blob)する実際の本体を持つ。今日rigorはスコープの戻り型を本体をモデル化せずに型付けする。質問: 基板は本体形状を表現することで十分な利益を得るか? スライス2(Tier C MVP)に先送り — シグネチャのみの発行から始め、具体的な消費者が本体型付けを必要とする場合に再検討。

  • クロスプラグイン形状マージングSinatra::Baseに対してTier Aを登録する2つのプラグイン(例: rigor-sinatra + 仮想のrigor-sinatra-extras) — scope_methodsはリストマージすべきか、完全に最初勝ちすべきか? 現在のADRは登録順による最初勝ち + plugin.<id>.macro-shadow :info診断;ADR-13のTypeNodeリゾルバチェインと同じ規約。組成的マージングの具体的なユースケースはまだ実体化していないので、フィールドごとのマージセマンティクス(scope_methodsはマージ、modules_by_symbolは最初勝ち)は先送り。具体的な拡張プラグインが表面化したときに再検討。

  • アプリケーションローカルプラグインパス。基板の主要オーディエンスはライブラリ開発者で、アプリケーション作者ではない。今日、自家製DSLを型付けしたいアプリケーションは、Bundler path:-ソースのローカルgemと手書きウォーカーを使う — 既存のADR-2パス。.rigor.ymlpath:形のプラグインエントリーを成長させてこれを楽にすべきかどうかは、別個のADR-2 config-スキーマ決定で、ADR-16の問題ではない。将来の決定者が質問がこの契約の外側に存在することを知るためにここに文書化。

  • Tier Bレジストリはシンボルごとのオプション駆動発行をサポートすべきか? Deviseのavailable_configsセッター(lib/devise/models.rb:97-103)はオプションハッシュを読み、モジュールごとのセッターを呼び出す。質問: 基板のTraitRegistryは「オプションハッシュごとの副作用」プリミティブを必要とするか、プラグインがそれをflow_contribution_for経由で処理するか? スライス3(Tier B MVP)に先送り — それなしで始め、Deviseプラグインがそれを必要とするかどうかを見る。

  • Tier Dのinstance_eval-on-a-blockとinstance_eval-on-a-fileのスコープルール。Rubyの2つは微妙に異なるレシーバーバインディングセマンティクスを持つ(ファイル形はブロック形と異なる方法で__FILE__ / __LINE__を設定する)。質問: 基板は気にするか? スライス5(Tier D MVP)に先送り — サーベイケース(Redmine webhookペイロード)はファイル形を排他的に使う。

  • Concern再ターゲティングはプラグインごとにオプトインすべきか、Tier A/B/Cエントリーを登録するすべてのプラグインに対してデフォルトオンか? デフォルトオンはエルゴノミクスな選択(プラグイン作者はラップされたConcernケースを考える必要がない)だが、キャッシングへのフィードバック効果を生成する(すべての基板出力がConcernチェインキー次元を獲得する)。スライス4に先送り。

  • 依存ソース推論ティア(ADR-10)との相互作用。gemがRBSなしで出荷され、rigorのソースウォーカーがdefからDynamic[T]戻り値を貢献する場合、基板はそれらのdefを展開候補として見るか? 答えはおそらくno — ADR-10は異なるティアに座り、マクロ呼び出しサイトではなくシグネチャのためにgemソースを歩く。スライス1で確認。

  • ADR-11 Sorbet入力との相互作用。Tier Cテンプレートが発行するメソッド上のSorbet sigブロックは、基板の合成戻り型と競合すべきではない。質問: 基板が優先するか、Sorbet入力がオーバーライドするか? 決定: Sorbet入力がオーバーライドする(ユーザー著作);基板はフォールバックになる。スライス2で文書化。

  • 副次的ヒューリスティックの具体的な形(WD12)。2つの候補形: (a)すべてのclass_eval / module_eval / define_method呼び出しサイトに対するプロジェクトロードプレパスで、macro.heuristic.candidateprovenance付きの候補セットを生成;(b)最も安価な構文パターン(リテラルシンボル補間を持つheredoc-arg class_eval)でのみトリガーする推論中のASTパスゲート。決定先送り。両方とも主要基板から独立して出荷可能;いずれもコミットされない。スライス順序はヒューリスティックをスライス6(既存プラグイン移行)のに置くため、宣言されたプラグイン経由の基板採用がヒューリスティックのスコープが最終決定される前に観察される。

  • 排除セットの粒度(WD11)。今日のクレームされた形状ルールは「rigor-fooが有効化されている場合、fooのgemツリー内のファイルでヒューリスティックをスキップ」。オープンクエスチョン: ライブラリがアプリケーションバンドルされている(gemバンドルされていない)場合、例えばlib/myapp/extensions/下の内部monkey-patch、排除の単位は何か? 候補: 呼び出し形状キーごと、ロードパスごと、requireパスごと。スライス7(ヒューリスティック出荷)に決定先送り;基板の主要目的はこのルールが定義されることを必要としない。

  • docs/notes/20260518-matsumoto-2010-cfa-rigor-review.md — 松本&南出2010のSemiRubyは、ブロックをラムダとして、return / breakを静的にスコープされたタグ付きのthrow / catchとして形式化する。ADR-16のTier A(ブロック-as-メソッド)とディスパッチャーの非局所脱出処理が独立に収束しているのと同じ翻訳形状である。Sinatraのget '/foo' do … endに対する「合成されるべき正しいメソッドは何か?」という問いがいつか機械的に検証可能な仕様を必要とすることになれば、外部の形式的意味論の参照として有用である。
  • 2026-05-15 — 初期提案。ライブラリごとのサーベイ(docs/notes/20260515-macro-expansion-library-survey.md)を形式的なADRに進めるユーザー要求が発端。解決: 4ティア展開基板(ブロック-as-メソッド / トレイトインライニングレジストリ / heredocテンプレート / 外部ファイル)プラスConcern再ターゲティングウォーカー拡張;マニフェストエントリー経由でプラグインごとにオプトイン;基板出力は既存のキャッシュ、Ractor、プラグイン契約に参加。
  • 2026-05-15 — オーディエンスと目的を明確化。基板はメタプログラミング形ライブラリ(またはアプリケーション固有のマクロDSL)を狙うプラグイン作者のための開発者体験レイヤー;ライブラリごとの専用プラグインがデフォルトの形のまま。§ オーディエンスと目的、§ 決定 § 計画されたライブラリごとのプラグイン(8つの調査されたライブラリ全体のカバレッジマップ)、WD10、「ユニバーサルプラグイン」代替案行を追加。基板展開に合わないDSLを持つ調査されたライブラリ(GraphQL-Ruby、Sequelカラムアクセサ)も通常のADR-2契約経由で将来の専用プラグインを取得する。
  • 2026-05-15 — サーベイをdry-rb三人組(dry-types / dry-schema / dry-struct)をカバーするように拡張。§ 計画されたライブラリごとのプラグインに3行追加(rigor-dry-typesrigor-dry-structrigor-dry-schema);rigor-dry-typesは他の2つの共有依存関係で、gem依存関係グラフを1対1でミラー。サーベイノートのクロスライブラリサマリーと観察セクションが対応する行を獲得。パッケージング戦略は引き続きADR-12にゲート;ライブラリごとの基板形状はここで固定。
  • 2026-05-15 — 3つのステークホルダー役割(ライブラリ開発者 / ライブラリユーザー / フォールバックとしてのrigor)を区別するためにオーディエンスフレーミングを洗練。基板の副次的目的 — 専用のrigor-<lib>プラグインが有効化されていないときのマクロパターンのベストエフォートヒューリスティック認識 — を追加、使用サイトでのハードな相互排除ルール(WD11)とハードなパフォーマンスバウンド(WD12)に縛られる。副次的目的は願望で、義務ではない;基板の主要目的(宣言されたプラグイン著作の便利さ)は独立して出荷する。WD10を更新、WD11 / WD12を追加、検討された代替案を拒否された2つのバリアントで拡張、ヒューリスティックの具体的な形と排除セットの粒度に関する2つのオープンクエスチョンを追加。
  • 2026-05-15 — コストバウンドのベストエフォート姿勢(WD13)を固定。基板は安価な場所では完全なTier発行形状(例: 精密な戻り型を持つTier CのN行テーブル)を目指す;精密な解決が実装コストを膨張させるところでは定義されたフロア — 「基板影響を受けるコードはクリーンにパースされ、識別子が解決される」 — に優雅に縮退する。合成メソッド名は無条件に発行される;未解決の戻り型はmacro.<tier>.unresolved-return :infoprovenance付きでDynamic[T]に縮退、捏造された精度はない(ADR-5に従う)。WD4を更新(インラインバイデフォルトのパイプライン位置、ティアが必須としたときのみプレパス)。スライシング修正: スライス2(Tier C MVP)は既存のrigor-activestorageウォーカーを移行するのではなく、新規rigor-dry-struct MVPをターゲット(基板の品質がマッチするまで移行を先送り)。スライス6を、発行テーブルのreturns:文字列をADR-13のPlugin::TypeNodeResolverチェイン経由でルーティングする精度プロモーションスライスとして再定義。既存プラグイン移行はもはやADR-16の義務ではない;同等性が実証されたときにプラグインごとに出荷。発行文字列解決パス、クロスプラグインマージング、アプリケーションローカルパス(最後は明示的にスコープ外、別個のADR-2 config-スキーマ決定に)に関するオープンクエスチョンを拡張。
  • 2026-05-15 — WD13 / ゴール / Tier Cの再重み付け: フロアはv0.1.xの配信コミットメント、シーリングはロードマップ上の長期的な願望(このADRの配信要件ではない)。以前の定式化(「安価ならシーリングを目指し、高価ならフロアに縮退」)はシーリングがデフォルトターゲットであるかのように読めた;実用的な解釈は「基準はフロア;シーリングは将来のスライスがプロモートできる場所」。どのティア発行行もv0.1.x精度コミットメントを運ばない — マニフェストテーブルのreturns:宣言は今日記録され、リゾルバフックアップがランディングしたとき後でアンロックされる。
  • 2026-05-15 — 基板フロア着地。ステータスが「proposed」から「accepted — フロア着地(スライス1〜7)、スライス5b + スライス6は需要に先送り」にプロモート。12のコミット(584ae85から9d54955)がTier A/B/Cエンジン統合 + Tier D契約 + Concern再ターゲティング + 3つの動作消費者プラグイン(rigor-sinatrarigor-dry-structrigor-devise) + ハンドブックドキュメントを配信。§ 実装のスライス分けが各スライスに着地 / 先送りステータスと発端コミットを注釈。プラグイン例の数が21から24にバンプ。残りのフォローアップ(Tier Dエンジン、精度プロモーション)は需要駆動のまま。
  • 2026-05-15 — スライス6精度プロモーション着地。2つのコミット(d174fffスライス6a-TierB、d7b1943スライス6b-TierC)。Tier B発行が既存のRbsDispatch.try_dispatch経由でorigin_module:provenanceを通じてプロモート(モジュールの著作RBS戻り値が勝つ — Deviseのvalid_password?Dynamic[T]の代わりにboolを返す)。Tier C発行が素のクラス名に対してenvironment.nominal_for_name(return_type)経由でプロモート(合成リーダーは、マニフェストのreturns:文字列がRBS env内で解決されたときにNominal[<class>]を返す)。先送りスライスノートが固定していた3つのオープン設計判断(NameScope供給、Tier B対Tier Cプロモーションパス、キャッシング)は、最小スコープパスを通じて解決: 既存のRbsDispatchキャッシュを超えるキャッシュレイヤーなし、origin_moduleとreturn_typeの間のマッチごとの自己ルーティング、ADR-13リゾルバチェイン配線なし(素のnominal_for_nameが現在のTier Cマニフェスト — rigor-dry-struct、ActiveStorage attachedなど — に十分、それらは非パラメータ化クラス名を使う)。パラメータ化形式(Array[String]Hash[K, V])とプラグイン提供のユーティリティ型名(Pick<T, K>)をADR-13のPlugin::TypeNodeResolverチェイン経由でルーティングすることは将来のイテレーションのまま、具体的な需要にゲート。ステータスが「accepted — フロア + 精度プロモーション着地(スライス1〜7 + 6a/6b)、スライス5b + ユーティリティ型戻り値のためのADR-13リゾルバチェイン配線は需要に先送り」にプロモート。

© 2026 TypedDuck. Licensed under CC BY-SA 4.0.