コンテンツにスキップ

Cache Layer — `Rigor::Cache`

ステータス: 安定(v0.0.8で導入;現行ディスクリプタスキーマv3)。このドキュメントはキャッシュレイヤーの公開リード形を追跡します。以下のスライス(slice)はすべて着地し、v0.1.x全体で安定しています;ディスクリプタのSCHEMA_VERSIONはADR-10のgemバージョンごとのdependenciesスロットのために2へ、そしてRbsLoader.build_env_forが欠落したsignature_paths:名前空間を合成し始めたときに3へ引き上げられました(古いRigorによってmarshalされたRBS環境——それらのシグネチャを不活性なまま残してしまう——は再構築されます)。スライス1と2がすでに完成しています。Rigor::Cache::Descriptor(すべてのキャッシュ済み値が付随する基板)とRigor::Cache::Store(ディスクリプタ・プロデューサー・パラメータを消費してキャッシュ済みまたは新規計算済みの値を返すファイルシステムバックのストレージ)です。後続のスライスでは最初のキャッシュ済みプロデューサー(RBS環境ローダー)とCLI可観測フラグ(--cache-stats--clear-cache)を追加します。

このモジュールが実装するスキーマは以下によって固定されています。

  • docs/design/20260505-cache-slice-taxonomy.md — スロットごとのエントリーシェイプ(shape)・合成ルール・キャッシュキー導出・粒度ガイダンス。
  • docs/adr/6-cache-persistence-backend.md — バックエンド選択(バイナリエントリーのシャードディレクトリ)・ファイルフォーマット・アトミック性・ロッキング・立ち退きポリシー。

Rigor::Cache::Descriptor(v0.0.8スライス1)

Section titled “Rigor::Cache::Descriptor(v0.0.8スライス1)”

キャッシュ無効化ディスクリプタ — 4つのスロットを持つ純粋な値オブジェクトで、各スロットは型付きエントリーの配列です。

FileEntry :: { path: String, comparator: :digest|:mtime|:exists, value: String }
GemEntry :: { name: String, requirement: String, locked: String? }
PluginEntry :: { id: String, version: String, config_hash: String? }
ConfigEntry :: { key: String, value_hash: String }
DependencyEntry :: { gem_name: String, gem_version: String, mode: :disabled|:when_missing|:full }

各エントリーはキーワード引数で構築され、即座にフリーズされます。FileEntry#newはcomparatorのenumを検証し、DependencyEntry#newmodeのenumを検証し、それぞれ未知の値に対してArgumentErrorを発生させます。他のエントリーは任意の文字列コンテンツを受け入れます(その値は慣例上すでに正規化されたハッシュです)。DependencyEntryはADR-10のgemバージョンごとのスロットです: その(gem_name, gem_version, mode)のトリプルがオプトインの依存関係ソース推論キャッシュスライス(slice)をキー付けるので、Gemfile.lockのバンプやsource_inference:モード変更(dependency-source-inference.md)がちょうど影響を受けるgemだけを無効化します。

Descriptor.new(files: [], gems: [], plugins: [], configs: [], dependencies: [])

Section titled “Descriptor.new(files: [], gems: [], plugins: [], configs: [], dependencies: [])”

ディスクリプタを構築します。すべてのスロットはデフォルトで空配列になります。スロットはdupされてフリーズされるため、構築後に呼び出し元が変更することはできません。ディスクリプタ自体もフリーズされます。

Descriptor.compose(*descriptors) -> Descriptor

Section titled “Descriptor.compose(*descriptors) -> Descriptor”

任意の数のディスクリプタを1つのディスクリプタに合成します。スロットごとの合成ルールはキーによるユニオン(union、合併型とも)です。

  • filespathでグループ化します。グループ内のエントリーはより厳格なcomparatorを優先します(:digest > :mtime > :exists)。最も厳格なcomparatorの中で、すべてのエントリーがvalueについて合意していなければDescriptor::Conflictが発生します。
  • gemsnameでグループ化します。グループ内のすべてのエントリーは(requirement, locked)の下で構造的に等しくなければなりません。そうでなければConflictが発生します。
  • pluginsidでグループ化します。(version, config_hash)で同じ等値ルールが適用されます。
  • configskeyでグループ化します。value_hashで同じ等値ルールが適用されます。

自分自身のディスクリプタに重複した等しいエントリーを追加する単一の貢献者は無害です。composeはそれを折り畳みます。コンフリクトは例外的なケースです。呼び出し元(キャッシュレイヤー)はConflictを「このキャッシュスライスは再利用できない、削除する」として扱い、いずれかのコントリビューションを黙って選択することはしません。

descriptor.cache_key_for(producer_id:, params: {}) -> String

Section titled “descriptor.cache_key_for(producer_id:, params: {}) -> String”

プロデューサー・入力・ディスクリプタの組み合わせに対して標準的なhex SHA-256キャッシュキーを返します。キーは以下を組み込みます。

  1. Descriptor::SCHEMA_VERSION(現在は3 — v2はADR-10のgemバージョンごとのキャッシュスライスのためにdependenciesスロットを追加した;v3はbuild_env_forが欠落したsignature_paths:名前空間を合成し始める前にmarshalされたRBS環境を無効化する)。この定数をバンプするとすべてのキャッシュ済み値が無効化されます。
  2. producer_id(キャッシュスライスの名前空間となる安定した文字列)。
  3. params(プロデューサーの入力ハッシュ)。再帰的に正規化されます。ハッシュキーは文字列化してソートし、シンボルは文字列化し、配列は順序を保持します。
  4. ディスクリプタの正規ハッシュ形式。

構造的に同等なディスクリプタを同じproducer_idparamsで構築する2つの呼び出し元は、構築順に関係なく同一のキャッシュキーを生成します。

ディスクリプタを正規JSONバイト文字列(UTF-8、転送のためにバイナリエンコード)として返します。スロットは辞書順で現れ(configsfilesgemsplugins)、各スロット内のエントリーはキーフィールドでソートされます(filesならpathなど)。これにより2つの同等なディスクリプタは同一のバイト列を生成します。

Descriptor#==は正規バイト形式を比較するため、異なる順序で構築された2つのディスクリプタは等しく比較されます。#hash==と整合しているため、ディスクリプタはHashのキーとして使用できます。

コンストラクタシグネチャと合成セマンティクスはv0.0.xの公開リード形として安定しています。新しいスロット種(例: env_vars)の追加はtaxonomyドキュメントとADR-6に従いスキーマバージョンバンプになります。FileEntry::VALID_COMPARATORSへの新しいcomparatorの追加は加算的であり、バンプを必要としません。

永続化レイヤー(Rigor::Cache::Store、v0.0.8スライス2)とキャッシュプロデューサー統合は後続に続きます。このドキュメントは各スライスが着地するたびに更新されます。

Rigor::Cache::Store(v0.0.8スライス2)

Section titled “Rigor::Cache::Store(v0.0.8スライス2)”

ファイルシステムバックのキャッシュストア。ADR-6 § “Decisions in detail”が契約(contract)を固定します。このセクションはプロデューサーとCLIが消費する公開リード形を文書化します。

root(ディレクトリパス、通常は.rigor/cache)をルートにするストアを構築します。ディレクトリは積極的に作成されません。最初の書き込みでschema_version.txtマーカーとともに実体化されます。

store.fetch_or_compute(producer_id:, params:, descriptor:, serialize: nil, deserialize: nil) { ... } -> Object

Section titled “store.fetch_or_compute(producer_id:, params:, descriptor:, serialize: nil, deserialize: nil) { ... } -> Object”

プロデューサー向けの単一エントリーポイントです。

  • producer_id(String) — キャッシュ名前空間。[a-z][a-z0-9._-]*のみ受け入れます。この制約により、大小文字を区別しないファイルシステム上でもファイルシステムに適したディレクトリ名が保証されます。
  • params(Hash) — プロデューサーの入力引数。{Descriptor#cache_key_for}でキャッシュキーに組み込まれます。プロデューサーはキャッシュキー自体を導出しません。
  • descriptorRigor::Cache::Descriptor) — キャッシュ済み値の無効化ディスクリプタ。
  • serialize(callable、省略可能) — プロデューサーの戻り値をバイナリStringに変換します。デフォルトはMarshal.dump(value).bです。Marshalでクリーンでない戻り値(RBS::Locationメンバーを持つRBSネイティブオブジェクト・生のIOなど)を持つプロデューサーはシリアライザをMUST提供しなければなりません。
  • deserialize(callable、省略可能) — バイトをプロデューサーの値に戻します。デフォルトはMarshal.loadです。(serialize, deserialize)のペアはラウンドトリップをMUST保証しなければなりません。一方の戦略で読み込み他方で書き込むプロデューサーは自分のキャッシュスライスを破壊します。デシリアライザが発生させた例外(StandardError)はキャッシュミスとして扱われます。エントリーは破損とみなされ、プロデューサーブロックが再実行され、次の書き込みでそれが上書きされます。これは以下の読み込みフォールトトレランスルールと一致します。
  • ブロック(yield)はキャッシュミス時のみ呼び出されます。

キャッシュ済み値を返します(ヒット時はディスクからロード、ミス時はブロックが生成)。

読み込みフォールトトレランス

Section titled “読み込みフォールトトレランス”

以下のいずれかに遭遇した読み込みは黙ってキャッシュミスを返します。プロデューサーブロックが再実行され、次の書き込みで破損エントリーが上書きされます。

  • エントリーファイルが存在しない。
  • エントリーが最小エンベロープ(ヘッダー+トレーラー)より短い。
  • マジック+フォーマットバージョンヘッダーが一致しない。
  • 末尾のSHA-256が一致しない。
  • varint長さプレフィックスが不正。
  • Marshal.loadが発生させる(例: 受信側に未知のクラス、ペイロードが切れている、ABIスキュー)。

末尾のSHA-256は偶発的な破損(プロセスkillによる部分書き込み、FSエラー)を検出します。ADR-2の信頼済みgemの信頼モデルに従い、セキュリティ境界ではありません

<root>/schema_version.txtには単一の整数(現在はRigor::Cache::Descriptor::SCHEMA_VERSION)が格納されます。すべてのfetch_or_compute呼び出し時に以下を実行します。

  • マーカーがない → 現在のバージョンを書き込み、続行する。
  • マーカーが一致する → 続行する。
  • マーカーが異なる → <root>以下のすべてのエントリーを削除し(FileUtils.rm_rfで各子をunlink)、マーカーを書き直し、キャッシュが空であるかのように続行する。

SCHEMA_VERSIONのバンプにより、明示的なマイグレーションステップなしに次回実行でキャッシュファイルがすべて削除されます。

<root>/
schema_version.txt
<producer-id>/
<ab>/
<ab1234567890…>.entry

キャッシュキー(descriptor.cache_key_for(...)による64文字のhex SHA-256)は2文字のプレフィックスと62文字のサフィックスに分割され、ビジーなプロデューサーでもディレクトリごとのファンアウトが管理可能に保たれます。

書き込みは標準的なrename-into-placeの手順に従います。

  1. 宛先ディレクトリをmkdir -pで作成する。
  2. 宛先ファイルにflock(LOCK_EX)を取得する(必要ならO_CREAT|O_RDWRで作成する)。
  3. 隣接するtempファイル(<entry>.tmp.<pid>.<rand-hex>)にボディを書き込む。
  4. tempファイルをfsyncする。
  5. tempファイルを宛先にrenameする。
  6. 宛先ファイルディスクリプタをクローズしてロックを解放する。

読み取り側はロックしません。古いバージョン(常に完全にコミットされたエントリーであり、壊れた書き込みではない — POSIXが同一ファイルシステム上のrenameアトミック性を保証する)を参照することを許容します。宛先ファイルが存在するが空(O_CREATと最初の成功したrenameの間の短いウィンドウ)という状況に遭遇した読み取り側は、上記の読み込みフォールトトレランスルールに従いキャッシュミスとして扱います。

単一のエントリーファイルは以下のレイアウトです。

"RIGOR\x00\x01" 6 bytes — 5バイトマジック、1バイト区切り、1バイトフォーマットバージョン
varint ディスクリプターペイロードのバイト長
descriptor payload 正規JSON Descriptor(UTF-8、転送のためにバイナリエンコード)
varint 値ペイロードのバイト長
value payload プロデューサーが返したオブジェクトのMarshal.dump
sha256 32バイト — 直前のすべてのバイトの整合性ハッシュ

ディスクリプタと値は別々に格納されるため、将来のキャッシュ検査ツールがMarshal.loadのコストを払わずにディスクリプタだけを読み取れます。フォーマットバージョン(現在は1)はDescriptor::SCHEMA_VERSIONとは異なります。前者はバイトレイアウトを対象とし、後者はディスクリプタスキーマを対象とします。フォーマットバージョンのバンプは読み込みパスでエントリーを無効化します(ヘッダーの不一致 → キャッシュミス)。

バンドルされたRBSプロデューサー契約

Section titled “バンドルされたRBSプロデューサー契約”

以下に記述されるバンドルされたRBS由来のプロデューサー(RbsConstantTableRbsKnownClassNamesRbsClassAncestorTableRbsClassTypeParamNamesRbsInstanceDefinitions / RbsSingletonDefinitionsRbsEnvironment)はいずれも一つのシェイプを満たします ── すなわちfetch(loader:, store:)に応答し、キャッシュ済みまたは新たに計算された値を返すクラスオブジェクトです。これはsig/rigor/cache.rbsにおいて構造的インターフェース_CacheProducerとして成文化されています。これは構造的インターフェース(RBS/Goの意味での)であり、ADR-28のプロトコル契約ではなく、またplugin-cache-producers.mdのプラグイン側プロデューサーサーフェスとも区別されます。

fetch本体はプロデューサー間で同一です。すなわちRBSディスクリプタ(RbsDescriptor.build(loader))を構築し、それからstore.fetch_or_compute(producer_id:, params: {}, descriptor:)を呼び出してプロデューサーのcompute(loader)へyieldします。異なるのはPRODUCER_ID定数とcompute本体だけです。その共有された配線はRigor::Cache::RbsCacheProducer基底クラスに置かれます。プロデューサーはそれをサブクラス化し、自身のPRODUCER_IDと(privateな)self.compute(loader)をMUST宣言します。基底クラスはself::PRODUCER_IDを読むため、定数は具象サブクラス上で解決されます。以下のプロデューサーごとのセクションは、各プロデューサーのPRODUCER_IDcomputeの出力型、およびそれを読むcache_storeコンシューマーを規定します。

Rigor::Cache::RbsConstantTable(v0.0.8スライス3)

Section titled “Rigor::Cache::RbsConstantTable(v0.0.8スライス3)”

{Rigor::Cache::Store#fetch_or_compute}を通じて配線される最初のキャッシュ済みプロデューサー。プロデューサーID: "rbs.constant_type_table"

定数テーブルをRbsLoader#build_envではなく選んだ理由

Section titled “定数テーブルをRbsLoader#build_envではなく選んだ理由”

RBS::EnvironmentとそのトランジティブなASTノードはRBS::Locationインスタンスを保持します。RBS::Location_dump_dataを持たないC拡張クラスであるため、素直なMarshal.dump(env)TypeErrorを発生させます。RBS::Environmentそのものをキャッシュするには、Store上にカスタムシリアライザサーフェス(surface)を設けるか、すべての関連ノードをMarshal安全な形状に変換するスキーマ安定な中間形式を作るかが必要です。いずれもv0.0.8スライスの予算を超えます。ADR-6 § 8 “RBS::Environment serialisation”を参照してください。

v0.0.8スライスでは代わりに翻訳後の成果物をキャッシュします。すべてのRBS宣言済み定数をRigor::Type形式に翻訳した結果です。Rigor::Typeの値はMarshalのラウンドトリップが明確に定義された単純なフリーズ済み値オブジェクトであるため、キャッシュ機構はシリアライザの問題をブロックせずに実データで完全な読み書きサイクルを実行できます。

RbsConstantTable.fetch(loader:, store:) -> Hash{String => Rigor::Type}

Section titled “RbsConstantTable.fetch(loader:, store:) -> Hash{String => Rigor::Type}”

すべての正規定数名(トップレベルプレフィックス付き、例: "::Math::PI")を対応する翻訳済みRigor::Typeにマッピングするハッシュを返します。プロデューサーブロックはloader.each_constant_declを反復します(env.constant_declsから(name, entry)ペアをyieldします)。翻訳がRigor::Type::Botを返すか例外を発生させたエントリーはテーブルから除外されます。

loader.constant_typeの代わりにeach_constant_declを経由することで、プロデューサーが再帰リスクから解放されます。RbsLoader#constant_typecache_storeが設定されているときにキャッシュを参照するためです。

Rigor::Cache::RbsKnownClassNames(v0.0.9グループC)

Section titled “Rigor::Cache::RbsKnownClassNames(v0.0.9グループC)”

2番目のキャッシュ済みプロデューサー。環境に現在ロードされているすべてのRBS宣言済みクラス/モジュール/エイリアス名(トップレベルプレフィックス付き)の集合を、Marshal安全なSet<String>として実体化します。プロデューサーID: "rbs.known_class_names"

RbsKnownClassNames.fetch(loader:, store:) -> Set<String>

Section titled “RbsKnownClassNames.fetch(loader:, store:) -> Set<String>”

集合を返します。プロデューサーブロックはloader.each_known_class_nameを反復します(env.class_declsenv.class_alias_declsの両方を走査します)。イテレータ内のフェイルソフトなrescue StandardErrorにより、破損した環境はランを中断させるのではなく名前を返さないようになります。

RbsLoader#class_known?(name)は、ローダーがcache_store:付きで構築されている場合にキャッシュ済み集合を参照します。コールドランは集合を一度だけ構築して永続化します。ウォームラン(および同じStoreを共有する別のローダー)は環境走査を完全にスキップします。インプロセスの名前ごとキャッシュ(@class_known_cache)は単一のローダーインスタンス内での呼び出し間でポジティブとネガティブの両方の回答をメモ化します。ディスクキャッシュはコールドスタートの動作のみを変更し、ウォームなホットパスは変更しません。

Rigor::Cache::RbsClassAncestorTable(v0.0.9 B)

Section titled “Rigor::Cache::RbsClassAncestorTable(v0.0.9 B)”

3番目のキャッシュ済みプロデューサー。ロードされたすべてのクラス/モジュールのRBS宣言済み祖先チェーンを、トップレベルなしのクラス名でキー付けされたMarshal安全なHash<String, Array<String>>(例: "Integer"["Integer", "Numeric", "Comparable", "Object", "BasicObject"])として実体化します。プロデューサーID: "rbs.class_ancestor_table"

1つの祖先チェーンを構築するには、そのクラスに対して完全なRBS::DefinitionBuilder#build_instanceが必要です。これはクラスごとで最もコストの高いRBS操作です。テーブルをキャッシュすることで、ウォームプロセスは結果ハッシュのMarshal.loadのみを支払えます。後続のclass_orderingクエリはO(テーブルルックアップ+祖先リストメンバーシップチェック)になり、環境走査は発生しません。

RbsHierarchy#ancestor_namesloader.cache_storeが設定されている場合にキャッシュ済みテーブルを参照します。インプロセスの名前ごとキャッシュ(@ancestor_names_cache)は単一の階層インスタンス内での呼び出し間で結果をメモ化します。ディスクキャッシュはコールドスタートの動作のみを変更します。

Rigor::Cache::RbsClassTypeParamNames(v0.0.9 A)

Section titled “Rigor::Cache::RbsClassTypeParamNames(v0.0.9 A)”

4番目のキャッシュ済みプロデューサー。ロードされたすべてのクラスのRBS宣言済み型パラメータ名を、トップレベルなしのクラス名でキー付けされたMarshal安全なHash<String, Array<Symbol>>(例: "Array"[:Elem]"Hash"[:K, :V]"Integer"[])として実体化します。プロデューサーID: "rbs.class_type_param_names"

ディスパッチャーはレシーバーのtype_argsからメソッドの戻り型への代入マップを構築するたびに型パラメータ名を読み取ります。各エントリーは{RbsClassAncestorTable}と基になるRBS::DefinitionBuilder#build_instanceコストを共有します。両プロデューサーをウォームにすることで同じ定義セットが熱くなります。

RbsLoader#class_type_param_names(class_name)cache_storeが設定されている場合にキャッシュ済みテーブルを参照します。アクセサは呼び出し元がキャッシュ済みペイロードを変更できないように、フレッシュなArray.dupを返します。

Rigor::Cache::RbsEnvironment(v0.0.9 C2)

Section titled “Rigor::Cache::RbsEnvironment(v0.0.9 C2)”

5番目のキャッシュ済みプロデューサー — そして{Store#fetch_or_compute}のデフォルトMarshalパスを非Marshal安全なRBSネイティブ値に対して使う最初のもの。このプロデューサーはローダーの完全なbuild_env結果(from_loader + resolve_type_names後のRBS::Environment)をキャッシュします。コールドランはパース+解決コストを一度払って結果を永続化し、ウォームラン(および同じStoreを共有する別のローダー)はマーシャル済みblobをロードし、パース/解決段階を完全にスキップします。

プロデューサーID: "rbs.environment"。キャッシュディスクリプタは{RbsDescriptor.build}を再利用するため、シグネチャの変更やrbs gemのバンプにより、このプロデューサーと4つの翻訳後キャッシュが同時に無効化されます。

RbsEnvironment.fetch(loader:, store:) -> ::RBS::Environment

Section titled “RbsEnvironment.fetch(loader:, store:) -> ::RBS::Environment”

環境を返します。プロデューサーブロックはRigor::Environment::RbsLoader.build_env_for(libraries:, signature_paths:)を呼び出します。これはRbsLoader#build_envのステートレスなクラスメソッドの対応物であり、プロデューサーはローダーインスタンスを保持する必要がありません。

RBS::EnvironmentとそのトランジティブなASTノードはRBS::Locationインスタンスを保持します。rbs gemのC拡張RBS::Location_dump / _loadを提供しないため、素直なMarshal.dump(env)TypeErrorを発生させます。v0.0.9はキャッシュ機構が必要とする最小限のMarshalフックでRBS::Locationにパッチを当てます。

class RBS::Location
def _dump(_) = ""
def self._load(_) = new(buffer: ..., start_pos: 0, end_pos: 0)
end

このパッチは純粋に加算的(以前にTypeErrorを発生させていたディスパッチのためのメソッドを追加するだけ)で冪等です(method_defined?(:_dump)でゲートされます)。キャッシュされたRBS::Locationインスタンスはノードごとのソース位置情報を失いますが、Rigorのどの解析コードパスもRBS::Locationを参照しないため(すべての診断はPrism自身のロケーションを通じてフローします)、この損失は実用上無害です。キャッシュヒット後にLocationを読み込むコードパス(例: サードパーティツール)はクラッシュするのではなく、無害なゼロ範囲のセンチネルを参照します。

このパッチはlib/rigor/cache/rbs_environment_marshal_patch.rbにあり、プロデューサーによってrequireされます。プロデューサーが最初に参照されたときに1プロセスにつき一度だけロードされます。

RbsEnvironmentRbsConstantTableRbsKnownClassNamesRbsClassAncestorTableRbsClassTypeParamNamesと並存します。翻訳後キャッシュはカバーするルックアップをディスクから返答し、環境を実体化することはありません。RbsEnvironmentはそれ以外のすべて(例: RbsLoader#instance_methodsingleton_method)を、キャッシュ済み環境をRBSのDefinitionBuilderに渡すことで返答します。2つのレイヤーは合成されます。ウォームプロセスは既にキャッシュされたルックアップに対してenv構築・定数変換・祖先走査・型パラメータ走査のコストを払わず、まだキャッシュされていない少数のものに対してのみenvロード+クラスごとのDefinitionBuilderコストを払います。

RbsConstantTableRbsKnownClassNamesはどちらも同じRBS環境状態に依存するため、ディスクリプタビルダーを共有します。

Rigor::Cache::RbsDescriptor.build(loader)
# => Descriptor with:
# gems = [{ name: "rbs", requirement: ">= 0", locked: ::RBS::VERSION }]
# files = [...] # :digest entries for every .rbs under signature_paths
# configs = [{ key: "rbs.libraries", value_hash: SHA256(sorted-libraries) }]

ビルダーを共有することにより、シグネチャの変更またはrbs gemのバンプで、すべてのRBS由来のキャッシュ済みプロデューサーが同時に無効化されます。

cache_store下での定数ルックアップパス

Section titled “cache_store下での定数ルックアップパス”

Environment.for_project(..., cache_store:)Environmentが構築されると、すべての定数ルックアップパスがキャッシュを経由します。

  • Rigor::Reflection.constant_type_for(name, scope:) — 公開読み取りAPI。ソース内の定数が衝突時に優先されます。それ以外は以下にフォールスルーします。
  • Environment#constant_for_name(name)
  • Environment::RbsLoader#constant_type(name)constant_type_table[rbs_name.to_s]をチェックします(ローダーごとにメモ化され、RbsConstantTable.fetchを通じて生成されます)。

コールドキャッシュでの最初のルックアップはテーブル構築コストを一度払って結果を永続化します。ウォームラン(および同じStoreを共有する別のローダー)は環境走査を完全にスキップし、格納されたハッシュのMarshal.loadのみを払います。Store#fetch_or_computeへのparams引数は空です。プロデューサーが消費するすべての入力はすでにディスクリプタにエンコードされているためです({Cache::RbsDescriptor.build}を参照)。

キャッシュレイヤーはrigor checkに2つのCLIフラグを提供します。

解析ランの前に(カレントワーキングディレクトリ基準で).rigor/cacheディレクトリを削除します。ディレクトリが存在して削除された場合はCleared cache: .rigor/cacheを、何もなかった場合はCache already empty: .rigor/cacheを出力します。チェック自体は完了まで実行されます。

ランナーのCache::Storeのディスクインベントリとランタイムのヒット/ミス/書き込みカウンタの両方を出力します。出力サンプル:

Cache (root: .rigor/cache)
schema_version: 1
3 entries, 12.4 KiB
rbs.constant_type_table: 1 entries, 11.0 KiB
reflection.instance_method_definition: 2 entries, 1.4 KiB
this run: 5 hits, 1 miss, 1 write
rbs.constant_type_table: 5 hits, 1 miss, 1 write

キャッシュディレクトリが存在しない場合、schema_versionabsentと表示され、本文は(empty)を示します。ランナーにStoreがない場合(例: --no-cache下)、this run:セクションは省略されます。報告するインメモリ状態がないためです。

Storeのランごとカウンタのフリーズ済みスナップショットを返します。

{
hits: Integer,
misses: Integer,
writes: Integer,
by_producer: { producer_id => { hits:, misses:, writes: } }
}

カウンタはインメモリのみです。新しいStore.newごとにゼロからスタートします。#fetch_or_compute内でバンプされます。成功した読み取りは:hitsをインクリメントし、ミスは直ちに:missesをインクリメントし、プロデューサーブロックが返却してエントリーが永続化された後に:writesをインクリメントします。プロデューサーごとのカウントは合計を反映するため、呼び出し元は上記の内訳を報告できます。

--cache-statsを支えるクラスメソッド。以下を返します。

{
root: String, # キャッシュルートパス
schema_version: String | nil, # マーカーが存在しない場合はnil
total_entries: Integer,
total_bytes: Integer,
producers: [
{ id: String, entries: Integer, bytes: Integer },
...
]
}

プロデューサーはIDでソートされます。空のプロデューサーサブディレクトリはリストから除外されます。

Rigor::Analysis::Diagnostic上のコンパニオンスライス。このクラスはsource_family:キーワード(デフォルトはDiagnostic::DEFAULT_SOURCE_FAMILY、つまり:builtin)とqualified_ruleアクセサを追加します。

diagnostic = Rigor::Analysis::Diagnostic.new(
path: "lib/foo.rb", line: 12, column: 3,
message: "...", rule: "no-mutation",
source_family: "plugin.rigor-immutable"
)
diagnostic.source_family # => "plugin.rigor-immutable"
diagnostic.rule # => "no-mutation" (ベアのケバブケース識別子)
diagnostic.qualified_rule # => "plugin.rigor-immutable.no-mutation"
diagnostic.to_h # "source_family"と"rule"の両方を含む

ベアのruleアクセサはケバブケース識別子のままです。既存の設定や# rigor:disableの仕組みが引き続き動作します。qualified_ruleは、コンシューマーが明確な帰属を表示したい場合に使うべき名前空間付き識別子です。JSON出力(to_h)は両フィールドを並べて持つため、ダウンストリームのコンシューマーはどちらを使うか選択できます。

これはADR-2のプラグイン可観測性ストーリー(plugin.<id>rbs_extendedgenerated.<provider>)を、プラグインAPI自体をコミットせずに準備するものです。v0.0.8ではデフォルト以外のsource_familyを設定する本番の呼び出し元は存在しません。このサーフェスはプラグイン作者および将来のRBS拡張/生成ルール向けに予約されています。

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