Module: Rigor::Analysis::RunCacheKey

Defined in:
lib/rigor/analysis/run_cache_key.rb

Overview

ADR-45 / ADR-87 WD4 — the stable run-result cache KEY, built in ONE place so the miss path (the Runner) and the boot-slimming hit path (the RunCacheProbe) can never drift out of key agreement. The key reads the stable inputs known before analysis: the rbs gem version, the resolved RBS library list, a digest of the whole resolved configuration, the engine + schema + --explain triple, and the analyzed-path SET.

The ONLY difference between the two callers is the rbs_config_entries slot: the Runner passes the loader's RbsDescriptor.config_entries (which include a rbs.virtual_rbs entry when a plugin's source_rbs_synthesizer contributed one), while the probe passes #libraries_config_entries — reconstructed from config alone, WITHOUT building the RBS environment or loading any plugin. A project whose plugins DO synthesise virtual RBS therefore produces a probe key that omits that entry, so the probe simply misses and the full path takes over (sound: never a wrong hit, only a forgone fast lane). Slot order is irrelevant — Cache::Descriptor#to_canonical_hash sorts configs by key.

Constant Summary collapse

RUN_DIAGNOSTICS_PRODUCER_ID =
"analysis.run-diagnostics"
GENERATION_CAP =

The run-result producer's declared compaction budget (Cache::Store#evict! pass 2). Whole-project, but unlike the rbs.* producers several generations can be live at once: the paths key slot means one entry per analyzed-path SET, so rigor check over the whole project, over lib, and over a single file are three separate live generations. 16 is a judgement call sized for that churn; it is a cap on GENERATIONS, not on correctness — over-evicting here costs a recompute, never a wrong answer. Measured 2026-07-25 against a real project's .rigor/cache: the cap does bind (60 distinct path-set generations observed, differing in the paths slot alone), with no evidence yet on how often an evicted generation is asked for again — see issue #151.

16

Class Method Summary collapse

Class Method Details

.config_entry(key, payload) ⇒ Object



65
66
67
# File 'lib/rigor/analysis/run_cache_key.rb', line 65

def config_entry(key, payload)
  Cache::Descriptor::ConfigEntry.new(key: key, value_hash: Digest::SHA256.hexdigest(payload))
end

.descriptor(configuration:, files:, explain:, rbs_config_entries:) ⇒ Object

Parameters:

  • rbs_config_entries (Array<Cache::Descriptor::ConfigEntry>)

    the RBS-derived config slots (rbs.libraries [+ rbs.virtual_rbs]). nil on any failure so a malformed key disables the cache.



51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/rigor/analysis/run_cache_key.rb', line 51

def descriptor(configuration:, files:, explain:, rbs_config_entries:)
  Cache::Descriptor.new(
    gems: [Cache::RbsDescriptor.rbs_gem_entry],
    configs: rbs_config_entries + [
      config_entry("configuration", Marshal.dump(configuration.to_h)),
      config_entry("engine",
                   "#{Rigor::VERSION}:#{Cache::Descriptor::SCHEMA_VERSION}:#{explain}"),
      config_entry("paths", files.sort.join("\n"))
    ]
  )
rescue StandardError
  nil
end

.libraries_config_entries(configuration) ⇒ Object

The rbs.libraries config slot reconstructed from configuration alone — byte-identical to the loader's RbsDescriptor.libraries_entry(loader.libraries) because Environment.for_project merges exactly DEFAULT_LIBRARIES + config.libraries (uniq) into loader.libraries.



72
73
74
75
# File 'lib/rigor/analysis/run_cache_key.rb', line 72

def libraries_config_entries(configuration)
  merged = (Environment::DEFAULT_LIBRARIES + configuration.libraries.map(&:to_s)).uniq
  [Cache::RbsDescriptor.libraries_entry(merged)]
end