rigortype 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/builtins/ruby_core/date.yml +6 -3
  4. data/data/builtins/ruby_core/enumerable.yml +1 -0
  5. data/data/builtins/ruby_core/exception.yml +2 -1
  6. data/data/builtins/ruby_core/file.yml +28 -14
  7. data/data/builtins/ruby_core/hash.yml +5 -2
  8. data/data/builtins/ruby_core/io.yml +33 -16
  9. data/data/builtins/ruby_core/random.yml +2 -1
  10. data/data/builtins/ruby_core/re.yml +6 -3
  11. data/data/builtins/ruby_core/struct.yml +2 -0
  12. data/data/builtins/ruby_core/time.yml +2 -1
  13. data/data/gem_overlay/activesupport/core_ext.rbs +409 -11
  14. data/docs/handbook/11-sig-gen.md +24 -14
  15. data/docs/manual/02-cli-reference.md +23 -7
  16. data/docs/manual/04-diagnostics.md +1 -1
  17. data/docs/manual/05-inspecting-types.md +20 -8
  18. data/docs/manual/07-plugins.md +4 -0
  19. data/docs/manual/plugins/rigor-actionpack.md +61 -0
  20. data/docs/manual/plugins/rigor-activerecord.md +74 -1
  21. data/docs/manual/plugins/rigor-activesupport-core-ext.md +114 -12
  22. data/docs/manual/plugins/rigor-sidekiq.md +22 -0
  23. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +12 -0
  24. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +98 -0
  25. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -3
  26. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +199 -0
  27. data/lib/rigor/analysis/check_rules/rule_walk.rb +1 -2
  28. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -2
  29. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +6 -12
  30. data/lib/rigor/analysis/check_rules.rb +383 -48
  31. data/lib/rigor/analysis/crash_signature.rb +188 -0
  32. data/lib/rigor/analysis/dependency_recorder.rb +18 -3
  33. data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
  34. data/lib/rigor/analysis/incremental.rb +28 -0
  35. data/lib/rigor/analysis/incremental_session.rb +63 -16
  36. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +1 -2
  37. data/lib/rigor/analysis/reachability/graph.rb +14 -5
  38. data/lib/rigor/analysis/reachability/scan.rb +7 -3
  39. data/lib/rigor/analysis/result.rb +32 -0
  40. data/lib/rigor/analysis/run_cache_key.rb +41 -1
  41. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +233 -16
  42. data/lib/rigor/analysis/runner/pool_coordinator.rb +266 -47
  43. data/lib/rigor/analysis/runner/project_pre_passes.rb +15 -11
  44. data/lib/rigor/analysis/runner/run_snapshots.rb +12 -2
  45. data/lib/rigor/analysis/runner.rb +212 -16
  46. data/lib/rigor/analysis/worker_session.rb +37 -3
  47. data/lib/rigor/bleeding_edge.rb +7 -6
  48. data/lib/rigor/builtins/hkt_builtins.rb +45 -6
  49. data/lib/rigor/builtins/imported_refinements.rb +11 -9
  50. data/lib/rigor/builtins/predefined_constant_refinements.rb +108 -59
  51. data/lib/rigor/builtins/static_return_refinements.rb +20 -1
  52. data/lib/rigor/cache/annotation_location.rb +72 -0
  53. data/lib/rigor/cache/descriptor.rb +42 -2
  54. data/lib/rigor/cache/incremental_snapshot.rb +40 -3
  55. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +70 -8
  56. data/lib/rigor/cache/store.rb +17 -1
  57. data/lib/rigor/cli/coverage_command.rb +23 -18
  58. data/lib/rigor/cli/coverage_mutation.rb +13 -17
  59. data/lib/rigor/cli/coverage_scan.rb +47 -6
  60. data/lib/rigor/cli/fused_protection_renderer.rb +19 -2
  61. data/lib/rigor/cli/fused_protection_report.rb +23 -2
  62. data/lib/rigor/cli/measurement_integrity_warning.rb +57 -0
  63. data/lib/rigor/cli/mutation_protection_renderer.rb +22 -2
  64. data/lib/rigor/cli/mutation_protection_report.rb +31 -2
  65. data/lib/rigor/cli/sig_gen_command.rb +47 -0
  66. data/lib/rigor/cli/type_of_command.rb +188 -41
  67. data/lib/rigor/cli/type_of_renderer.rb +72 -3
  68. data/lib/rigor/cli/type_scan_command.rb +10 -1
  69. data/lib/rigor/cli.rb +1 -1
  70. data/lib/rigor/configuration.rb +7 -4
  71. data/lib/rigor/effects/plugin_facts.rb +1 -1
  72. data/lib/rigor/effects/scanner.rb +2 -3
  73. data/lib/rigor/environment/bundle_sig_discovery.rb +64 -13
  74. data/lib/rigor/environment/failure_slot.rb +28 -0
  75. data/lib/rigor/environment/lockfile_resolver.rb +15 -5
  76. data/lib/rigor/environment/missing_gem_constant_index.rb +45 -4
  77. data/lib/rigor/environment/rbs_hierarchy.rb +16 -22
  78. data/lib/rigor/environment/rbs_loader.rb +743 -93
  79. data/lib/rigor/environment.rb +125 -25
  80. data/lib/rigor/inference/acceptance.rb +172 -20
  81. data/lib/rigor/inference/captured_locals.rb +62 -0
  82. data/lib/rigor/inference/content_join.rb +347 -0
  83. data/lib/rigor/inference/def_handle.rb +15 -3
  84. data/lib/rigor/inference/def_node_resolver.rb +62 -3
  85. data/lib/rigor/inference/expression_typer.rb +1020 -141
  86. data/lib/rigor/inference/fork_map.rb +6 -1
  87. data/lib/rigor/inference/hkt_reducer.rb +12 -1
  88. data/lib/rigor/inference/hkt_registry.rb +61 -3
  89. data/lib/rigor/inference/hkt_sugar_translator.rb +87 -0
  90. data/lib/rigor/inference/index_write_widening.rb +48 -0
  91. data/lib/rigor/inference/indexed_narrowing.rb +14 -0
  92. data/lib/rigor/inference/method_dispatcher/block_folding.rb +7 -5
  93. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +10 -0
  94. data/lib/rigor/inference/method_dispatcher/json_folding.rb +58 -0
  95. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +81 -38
  96. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +54 -17
  97. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -2
  98. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +63 -0
  99. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +63 -10
  100. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +104 -0
  101. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +81 -0
  102. data/lib/rigor/inference/method_dispatcher.rb +155 -14
  103. data/lib/rigor/inference/method_parameter_binder.rb +3 -3
  104. data/lib/rigor/inference/mutation_widening.rb +244 -174
  105. data/lib/rigor/inference/narrowing.rb +331 -27
  106. data/lib/rigor/inference/parameter_inference_collector.rb +1 -0
  107. data/lib/rigor/inference/pre_eval_constants.rb +15 -0
  108. data/lib/rigor/inference/precision_scanner.rb +5 -2
  109. data/lib/rigor/inference/project_patched_scanner.rb +2 -1
  110. data/lib/rigor/inference/rbs_type_translator.rb +89 -39
  111. data/lib/rigor/inference/scope_indexer.rb +1493 -140
  112. data/lib/rigor/inference/statement_evaluator.rb +276 -91
  113. data/lib/rigor/inference/struct_fold_safety.rb +186 -27
  114. data/lib/rigor/inference/version_guard.rb +229 -0
  115. data/lib/rigor/language_server/selection_range_provider.rb +1 -1
  116. data/lib/rigor/plugin/base.rb +3 -2
  117. data/lib/rigor/plugin/inflector.rb +14 -5
  118. data/lib/rigor/plugin/io_boundary.rb +107 -4
  119. data/lib/rigor/plugin/loader.rb +14 -0
  120. data/lib/rigor/plugin/registry.rb +25 -2
  121. data/lib/rigor/protection/analysis_guard.rb +60 -0
  122. data/lib/rigor/protection/closure_kill_oracle.rb +43 -38
  123. data/lib/rigor/protection/diagnostic_oracle.rb +12 -4
  124. data/lib/rigor/protection/discovery_seed.rb +1 -1
  125. data/lib/rigor/protection/measurement_integrity.rb +36 -0
  126. data/lib/rigor/protection/mutation_scanner.rb +61 -6
  127. data/lib/rigor/protection/mutator.rb +121 -18
  128. data/lib/rigor/rbs_extended/envelope_scanner.rb +8 -7
  129. data/lib/rigor/rbs_extended/hkt_directives.rb +16 -1
  130. data/lib/rigor/rbs_extended/reporter.rb +93 -13
  131. data/lib/rigor/rbs_extended.rb +6 -1
  132. data/lib/rigor/reflection.rb +182 -30
  133. data/lib/rigor/scope/discovery_index.rb +39 -1
  134. data/lib/rigor/scope.rb +385 -4
  135. data/lib/rigor/sig_gen/classification.rb +12 -1
  136. data/lib/rigor/sig_gen/generator.rb +320 -85
  137. data/lib/rigor/sig_gen/renderer.rb +14 -12
  138. data/lib/rigor/sig_gen/writer.rb +3 -0
  139. data/lib/rigor/source/constant_path.rb +79 -2
  140. data/lib/rigor/triage/catalogue.rb +1 -1
  141. data/lib/rigor/type/combinator.rb +10 -0
  142. data/lib/rigor/type/maybe.rb +47 -0
  143. data/lib/rigor/type/refined.rb +1 -2
  144. data/lib/rigor/type/result.rb +53 -0
  145. data/lib/rigor/type.rb +2 -0
  146. data/lib/rigor/version.rb +1 -1
  147. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +3 -1
  148. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +58 -8
  149. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +15 -2
  150. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +4 -1
  151. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +3 -1
  152. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +39 -14
  153. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +36 -3
  154. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -2
  155. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +21 -5
  156. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +47 -8
  157. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +13 -3
  158. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +242 -4
  159. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +3 -1
  160. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +7 -3
  161. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +62 -11
  162. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +15 -2
  163. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +3 -1
  164. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +5 -1
  165. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -2
  166. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +798 -22
  167. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +156 -13
  168. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +140 -40
  169. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +2 -2
  170. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +20 -6
  171. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +14 -4
  172. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +6 -3
  173. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +23 -6
  174. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +264 -9
  175. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +876 -23
  176. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +99 -0
  177. data/plugins/rigor-dry-monads/lib/rigor-dry-monads.rb +5 -0
  178. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +34 -0
  179. data/plugins/rigor-ethon/lib/rigor-ethon.rb +3 -0
  180. data/plugins/rigor-ethon/sig/ethon.rbs +27 -0
  181. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -2
  182. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +198 -0
  183. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/binding_recognizer.rb +75 -0
  184. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/catalog.rb +64 -0
  185. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +173 -0
  186. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +46 -0
  187. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +161 -0
  188. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +121 -0
  189. data/plugins/rigor-ffi/lib/rigor-ffi.rb +3 -0
  190. data/plugins/rigor-ffi/sig/ffi.rbs +67 -0
  191. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +19 -0
  192. data/plugins/rigor-ffi-rzmq/lib/rigor-ffi-rzmq.rb +3 -0
  193. data/plugins/rigor-ffi-rzmq/sig/ffi_rzmq.rbs +29 -0
  194. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +2 -4
  195. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -1
  196. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  197. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +2 -1
  198. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +2 -1
  199. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +2 -1
  200. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +156 -11
  201. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +30 -0
  202. data/plugins/rigor-rbnacl/lib/rigor-rbnacl.rb +3 -0
  203. data/plugins/rigor-rbnacl/sig/rbnacl.rbs +22 -0
  204. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +55 -0
  205. data/plugins/rigor-sassc/lib/rigor-sassc.rb +3 -0
  206. data/plugins/rigor-sassc/sig/sassc.rbs +24 -0
  207. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +32 -11
  208. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +3 -1
  209. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +4 -1
  210. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +63 -10
  211. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +15 -2
  212. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +61 -2
  213. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +2 -2
  214. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +4 -2
  215. data/sig/rigor/analysis/baseline.rbs +1 -1
  216. data/sig/rigor/environment.rbs +9 -0
  217. data/sig/rigor/inference.rbs +4 -1
  218. data/sig/rigor/plugin/io_boundary.rbs +2 -0
  219. data/sig/rigor/reflection.rbs +3 -1
  220. data/sig/rigor/scope.rbs +25 -1
  221. data/sig/rigor/type.rbs +95 -1
  222. data/sig/rigor.rbs +16 -0
  223. metadata +71 -3
@@ -115,17 +115,79 @@ module Rigor
115
115
  # sources. The env stays a LOCAL variable (not an ivar) so it goes GC-eligible when the method
116
116
  # returns — holding it as long-lived state added memory pressure that surfaced as a Bus Error
117
117
  # during the spec suite under Ruby 4.0 + rbs 4.0.2.
118
- def analyze_files(files, environment: nil)
119
- return [] if files.empty?
120
- return dispatch_pool(files) if pool_mode?
121
-
122
- analyze_files_sequentially(files, environment || resolve_sequential_environment(source_files: files))
118
+ # @param project_files [Array<String>, nil] issue #784 — the WHOLE project's analyzed file set
119
+ # (`expansion.fetch(:files)`), independent of any `analyze_only` narrowing of `files`. Read only
120
+ # when `files` is empty, to decide whether anyone could have demanded the HKT registry at all.
121
+ def analyze_files(files, environment: nil, project_files: nil)
122
+ if files.empty?
123
+ # Issue #784 — an EMPTY analyze set still owes the run its HKT-scan row: the per-file cache never
124
+ # holds it (`IncrementalSession` caches only `Runner#per_file_diagnostics`, so every run-level row
125
+ # is regenerated every run), so returning here without recording flips a red project green — and
126
+ # the shipping `--incremental` path reaches this branch with NO environment in hand on every
127
+ # warm recheck that changed nothing (`CheckCommand#run_incremental_check` builds its session
128
+ # without one). So: an environment already in hand is consulted; otherwise one is resolved over
129
+ # the project's OWN file list — not `[]`, which would drop every plugin-synthesized virtual RBS
130
+ # (`Environment.collect_virtual_rbs` short-circuits on an empty list) and scan a different type
131
+ # universe from the one a full run analyses — and only when the project HAS files: with none,
132
+ # nobody could have demanded a registry, and an empty project keeps paying no env build. Keyed
133
+ # on the project's files rather than on `analyze_only`, because a recheck over an EMPTY project
134
+ # narrows to `Set[]`, which is non-nil.
135
+ env = environment || @environment_override
136
+ if project_files && !project_files.empty?
137
+ env ||= resolve_sequential_environment(source_files: project_files)
138
+ end
139
+ # #788 rounds 6 and 9 — everything the run owes from its environment that no per-file analysis
140
+ # produces is taken here, the way `analyze_files_sequentially` takes it: the project-signature
141
+ # state (`synthesized-namespace`, `quarantined-signature`, `environment-build-failed`, the
142
+ # conformance results), the effect-annotation carrier the residual pass reads, and the HKT-scan
143
+ # outcome. Now that run-level rows are never served from the per-file cache, this branch is the
144
+ # only producer on a warm recheck that changed nothing — leaving any of them out turned a red
145
+ # project green on its second `--incremental` run (the inline-only `effect.annotations-unchecked`
146
+ # went 1 → 0; a quarantined `signature_paths:` file went 1 → 0; a `conforms-to` class whose
147
+ # definition build fails went 1 → 0). Same ORDER as the sequential path, because the order is
148
+ # the contract: the conformance scan inside the signature-state snapshot demands the definition
149
+ # of every `rigor:v1:conforms-to` class — a user-authored, invocation-independent demand that
150
+ # #696 counts — so the definition-build failures are read AFTER it and BEFORE the HKT demand,
151
+ # exactly where `analyze_files_sequentially` reads them relative to its own. What this branch
152
+ # cannot regenerate is the part of that set the per-file ANALYSIS demanded (#796). This also
153
+ # retires the #441 "`.rbs` lane only when the run analyses nothing" boundary: its cost premise
154
+ # (no environment on this path) stopped holding the moment the branch above resolved one.
155
+ snapshot_project_signature_state(env)
156
+ snapshot_effect_annotation_carrier(env&.rbs_loader)
157
+ record_definition_build_failures(env&.rbs_loader&.definition_build_failures)
158
+ record_hkt_scan_failure(hkt_scan_outcome(env))
159
+ return []
160
+ end
161
+ # Issue #784 / #793 — `files` is what this run ANALYSES; `source_files` is what its environment
162
+ # is BUILT over, and the two are the same only on a full run. A narrowed run (`analyze_only`: an
163
+ # incremental closure, a `--verify-incremental` partition) used to build its environment over the
164
+ # subset, so plugin-synthesized virtual RBS from every excluded file was missing and the run
165
+ # scanned a different type universe than the full run it is compared against — a scan failure
166
+ # whose trigger lived in an excluded file's synthesized RBS fired on the full run and vanished on
167
+ # the subset. Every environment and worker this run builds now takes the whole project.
168
+ source_files = project_files || files
169
+ return dispatch_pool(files, source_files: source_files) if pool_mode?
170
+
171
+ analyze_files_sequentially(
172
+ files, environment || resolve_sequential_environment(source_files: source_files)
173
+ )
123
174
  end
124
175
 
125
176
  def analyze_files_sequentially(files, environment)
126
177
  snapshot_project_signature_state(environment)
127
178
  snapshot_effect_annotation_carrier(environment&.rbs_loader)
128
179
  result = files.flat_map { |path| @analyze_file.call(path, environment) }
180
+ # Issue #696 — AFTER the loop, deliberately. Definition builds are lazy (ADR-54 WD1: per class, on
181
+ # first demand), so a snapshot taken beside the ones above — which run BEFORE `files.flat_map` —
182
+ # would read an empty list on every run, including the ones this diagnostic exists for.
183
+ record_definition_build_failures(environment&.rbs_loader&.definition_build_failures)
184
+ # Issue #784 — same timing contract, same reason: the HKT scan is first demanded from inside a
185
+ # file's analysis (the dispatcher's Singleton-receiver tier), so a snapshot taken any earlier
186
+ # would read nil on every run. And demanded HERE once more by the run itself, because a subset
187
+ # run (`--verify-incremental`'s partition, an incremental recheck's closure) may contain no file
188
+ # that demands it — the row must not depend on which files were analysed. See {#hkt_scan_outcome}
189
+ # for why a Rigor-owned demand is sound here where #696 forbids it.
190
+ record_hkt_scan_failure(hkt_scan_outcome(environment))
129
191
  if @collect_stats
130
192
  loader = environment.rbs_loader
131
193
  @snapshots.class_decl_paths = loader&.class_decl_paths || {}.freeze
@@ -152,7 +214,8 @@ module Rigor
152
214
  return
153
215
  end
154
216
 
155
- loader = environment.rbs_loader
217
+ # nil-safe: the empty-closure path reaches here with no environment when the project has no files.
218
+ loader = environment&.rbs_loader
156
219
  @snapshots.synthesized_namespaces = loader&.synthesized_namespaces || []
157
220
  @snapshots.quarantined_signatures = loader&.quarantined_signatures || []
158
221
  @snapshots.env_build_failure = loader&.env_build_failure
@@ -216,20 +279,24 @@ module Rigor
216
279
  # An effects run (ADR-103 WD13) is pinned for exactly the same reason — the Ractor messages carry
217
280
  # no side-table channel — and degrades the same way. The degrade is sound rather than merely safe:
218
281
  # the sequential fallback still collects, so the effect graph is complete either way.
219
- def dispatch_pool(files)
282
+ # @param source_files [Array<String>] the file list every worker's / the fallback's environment is
283
+ # built over — the whole project (issue #793), defaulting to `files` for direct callers.
284
+ def dispatch_pool(files, source_files: files)
220
285
  if @record_dependencies || @record_effects
221
- return analyze_files_in_fork_pool(files) if Process.respond_to?(:fork)
286
+ return analyze_files_in_fork_pool(files, source_files: source_files) if Process.respond_to?(:fork)
222
287
 
223
288
  return analyze_files_sequentially_fallback(
224
- files, reason: "incremental parallelism requires fork; recording sequentially"
289
+ files, reason: "incremental parallelism requires fork; recording sequentially",
290
+ source_files: source_files
225
291
  )
226
292
  end
227
293
  case pool_backend
228
- when :ractor then analyze_files_in_pool(files)
229
- when :fork then analyze_files_in_fork_pool(files)
294
+ when :ractor then analyze_files_in_pool(files, source_files: source_files)
295
+ when :fork then analyze_files_in_fork_pool(files, source_files: source_files)
230
296
  else
231
297
  analyze_files_sequentially_fallback(
232
- files, reason: "fork-based parallelism is unavailable on this platform"
298
+ files, reason: "fork-based parallelism is unavailable on this platform",
299
+ source_files: source_files
233
300
  )
234
301
  end
235
302
  end
@@ -295,7 +362,7 @@ module Rigor
295
362
  # worker touches comes back as an internal analyzer error. What the code below fixes is the
296
363
  # failure MODE, not the backend: the run used to hang forever instead of saying anything. Reviving
297
364
  # the backend needs an upstream change, which is why `pool_backend` keeps `fork` as the default.
298
- def analyze_files_in_pool(files) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
365
+ def analyze_files_in_pool(files, source_files: files) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
299
366
  # Pre-warm class-level lazy memos on the MAIN Ractor. `Environment::ClassRegistry.default` is the
300
367
  # default kwarg threaded through `Environment.new` inside each worker session; lazy-initialising
301
368
  # it from a non-main Ractor would trip `Ractor::IsolationError`. Touching it here forces the
@@ -310,10 +377,28 @@ module Rigor
310
377
  # constants.
311
378
  if @cache_store.nil?
312
379
  return analyze_files_sequentially_fallback(
313
- files, reason: "pool mode requires a cache_store (--no-cache disables pool)"
380
+ files, reason: "pool mode requires a cache_store (--no-cache disables pool)",
381
+ source_files: source_files
314
382
  )
315
383
  end
316
- prewarm_rbs_cache_for_pool
384
+ # Issue #798 — the SAME gap the fork pool had: this coordinator never analyses a file itself, so
385
+ # without an explicit read the project-signature state (`synthesized-namespace`,
386
+ # `quarantined-signature`, `environment-build-failed`, the conformance results) has no producer.
387
+ # Unlike the fork pool's copy-on-write children, a Ractor worker builds its OWN Environment
388
+ # inside its own isolated Ractor, so nothing here inherits what the coordinator's environment
389
+ # finds. `#prewarm_rbs_cache_for_pool` already builds and fully loads exactly this
390
+ # coordinator-side environment (to warm the cache before any worker spawns); it now hands it
391
+ # back so its RBS state can be read the same way the fork pool reads its pre-fork session
392
+ # environment. The conformance scan inside the snapshot demands the definition of every
393
+ # `rigor:v1:conforms-to` class — a demand no Ractor worker shares memory with — so a resulting
394
+ # definition-build failure is recorded explicitly right after, exactly as the empty-closure
395
+ # branch reads its own environment's demand (#788). No matching explicit call for the HKT-scan
396
+ # outcome: every worker already demands it from its OWN environment in `#drain_reporters`, and
397
+ # the scan has one outcome whoever demands it, so a worker's report already says what the
398
+ # coordinator's own demand would.
399
+ warm_env = prewarm_rbs_cache_for_pool
400
+ snapshot_project_signature_state(warm_env)
401
+ record_definition_build_failures(warm_env&.rbs_loader&.definition_build_failures)
317
402
 
318
403
  configuration = @configuration
319
404
  cache_root = @cache_store&.root
@@ -321,8 +406,9 @@ module Rigor
321
406
  explain = @explain
322
407
  # ADR-32 WD4 — the full project file list travels into every Ractor worker so each worker's
323
408
  # WorkerSession can invoke loaded plugins' source_rbs_synthesizers at env-build time. The list is
324
- # a frozen Array<String>; cheaply shareable.
325
- shareable_source_files = files.map { |path| path.to_s.dup.freeze }.freeze
409
+ # a frozen Array<String>; cheaply shareable. Issue #793 — it IS the full project now
410
+ # (`source_files`), not the analyzed subset this comment always described.
411
+ shareable_source_files = source_files.map { |path| path.to_s.dup.freeze }.freeze
326
412
 
327
413
  pool = Array.new(@workers) do
328
414
  Ractor.new(configuration, cache_root, blueprints, explain, shareable_source_files) do |configuration, cache_root, blueprints, explain, shareable_source_files| # rubocop:disable Layout/LineLength
@@ -393,15 +479,28 @@ module Rigor
393
479
  # The files no worker reported — every file of a worker that died, and any file in flight when
394
480
  # it did. Re-analysed in process, exactly as the fork backend re-analyses a dead child's slice.
395
481
  degraded = files.reject { |path| results_by_path.key?(path) }
396
- unless degraded.empty?
397
- environment = build_runner_environment(source_files: files)
398
- degraded.each { |path| results_by_path[path] = @analyze_file.call(path, environment) }
399
- end
482
+ reanalyze_degraded_in_process(degraded, results_by_path, source_files: source_files)
400
483
 
401
484
  diagnostics = Array(prepare_diagnostics) + files.flat_map { |path| results_by_path.fetch(path, []) }
402
485
  degraded.empty? ? diagnostics : diagnostics.unshift(pool_degraded_diagnostic(degraded.size, "ractor"))
403
486
  end
404
487
 
488
+ # The Ractor backend's degrade: the files of a worker that died are re-analysed on a LOCAL
489
+ # environment built over the whole project. That worker never sent `:done`, so nothing drains its
490
+ # reporters — and the local environment has no session to drain either — so the run-level state
491
+ # this analysis produced is taken here, exactly as the sequential fallback takes it: the
492
+ # definition-build failures the re-analysis demanded (#696) and the HKT-scan outcome, demanded
493
+ # once more by the run itself (#784 — a rescued scan failure otherwise vanished with the worker).
494
+ # The fork backend needs none of this: it re-analyses on the parent {WorkerSession} and drains it.
495
+ def reanalyze_degraded_in_process(degraded, results_by_path, source_files:)
496
+ return if degraded.empty?
497
+
498
+ environment = build_runner_environment(source_files: source_files)
499
+ degraded.each { |path| results_by_path[path] = @analyze_file.call(path, environment) }
500
+ record_definition_build_failures(environment.rbs_loader&.definition_build_failures)
501
+ record_hkt_scan_failure(hkt_scan_outcome(environment))
502
+ end
503
+
405
504
  # ADR-15 Amendment (2026-05-20) — fork-based worker pool, the active backend for `workers > 0`.
406
505
  # Builds ONE {WorkerSession} on the parent, then `fork`s N children that copy-on-write inherit it.
407
506
  # Each child analyses a contiguous slice of `files` and writes a Marshal'd `{results:, reporters:}`
@@ -414,7 +513,11 @@ module Rigor
414
513
  #
415
514
  # A child that exits non-zero (crash / unmarshalable payload) is degraded: the parent re-analyses
416
515
  # that slice in-process and prepends a `pool-degraded` warning.
417
- def analyze_files_in_fork_pool(files) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
516
+ #
517
+ # Also snapshots the project-signature state and the effect-annotation carrier off the pre-fork
518
+ # `session.environment` before any child spawns (#798) — the only environment this backend's
519
+ # coordinator ever holds, and so the only place those diagnostic rows can be read from.
520
+ def analyze_files_in_fork_pool(files, source_files: files) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
418
521
  Environment::ClassRegistry.default
419
522
 
420
523
  session = WorkerSession.new(
@@ -425,12 +528,27 @@ module Rigor
425
528
  synthetic_method_index: synthetic_method_index,
426
529
  project_patched_methods: project_patched_methods,
427
530
  project_scope_seed: project_scope_seed,
428
- source_files: files,
531
+ source_files: source_files,
429
532
  record_dependencies: @record_dependencies
430
533
  )
431
534
  # Force the full RBS load on the parent so children copy-on-write inherit a warm Environment
432
535
  # rather than each rebuilding it after the fork.
433
536
  session.environment.rbs_loader&.prewarm
537
+ # Issue #798 — same set, same ORDER as the sequential path (docs/type-specification/
538
+ # diagnostic-policy.md § `rbs.coverage.*`): the project-signature state
539
+ # (`synthesized-namespace`, `quarantined-signature`, `environment-build-failed`, the
540
+ # conformance results) has no per-file producer, so this coordinator-side environment is the
541
+ # only place a pooled run can take it from — exactly as the empty-closure branch above is the
542
+ # only place an empty run can (#788). Taken BEFORE the fork below: the conformance scan this
543
+ # runs demands the definition of every `rigor:v1:conforms-to` class, and doing that HERE means
544
+ # a resulting definition-build failure is already sitting in the parent's loader at the moment
545
+ # each child copy-on-write-inherits it — every child's own `#drain_reporters` then reports it
546
+ # right alongside whatever its own slice demanded, with no separate plumbing needed. Left
547
+ # unconditional, unlike `#snapshot_fork_pool_stats` below: these are diagnostic rows, not
548
+ # `RunStats` telemetry, and the stats gate must not decide which diagnostics a run reports (a
549
+ # `--workers N --no-stats` run used to say strictly LESS than the sequential path over the
550
+ # same project).
551
+ snapshot_project_signature_state(session.environment)
434
552
  snapshot_effect_annotation_carrier(session.environment.rbs_loader)
435
553
  snapshot_fork_pool_stats(session) if @collect_stats
436
554
 
@@ -484,18 +602,16 @@ module Rigor
484
602
  exit!(1)
485
603
  end
486
604
 
487
- # Snapshots `class_decl_paths` from the parent session's loader so end-of-run {RunStats} can
488
- # attribute the RBS class universe.
605
+ # Issue #798 — `RunStats` telemetry ONLY, now that `#snapshot_project_signature_state` (called
606
+ # unconditionally above) owns every diagnostic-bearing slot this method used to ALSO write
607
+ # (`quarantined_signatures`, `env_build_failure`): those must not be gated on `@collect_stats`, and
608
+ # this method's own name says what is left — `class_decl_paths` / `signature_paths`, read off the
609
+ # parent session's loader so end-of-run {RunStats} can attribute the RBS class universe, which a
610
+ # `--no-stats` run legitimately skips.
489
611
  def snapshot_fork_pool_stats(session)
490
612
  loader = session.environment.rbs_loader
491
613
  @snapshots.class_decl_paths = loader&.class_decl_paths || {}.freeze
492
614
  @snapshots.signature_paths = loader&.signature_paths || [].freeze
493
- # The workers each quarantine the same broken file, but they report no diagnostics for it — the row is
494
- # a whole-run one. Read it off the parent session's loader so a pooled run says exactly what a
495
- # sequential one says. The same reasoning holds for a total env-build failure.
496
- @snapshots.quarantined_signatures =
497
- project_signature_paths? ? (loader&.quarantined_signatures || []) : []
498
- @snapshots.env_build_failure = project_signature_paths? ? loader&.env_build_failure : nil
499
615
  end
500
616
 
501
617
  # Waits for every forked child, merges each successful payload into `results_by_path`, and returns
@@ -541,9 +657,10 @@ module Rigor
541
657
 
542
658
  # ADR-15 Phase 4b.x — drives every cached RBS producer on the main Ractor so each worker can serve
543
659
  # all reflection queries from disk (Marshal-load only). Builds a single coordinator-side
544
- # {Environment} for this purpose; the env object is discarded immediately after the cache is warm
545
- # workers build their own `Environment.for_project` inside the Ractor body, which then routes
546
- # through `cached_env` instead of `RBS::EnvironmentLoader.new`.
660
+ # {Environment} for this purpose and returns it fully loaded issue #798: the caller also reads
661
+ # the project-signature state off it, since it is the only environment this backend's coordinator
662
+ # ever holds. Workers still build their OWN `Environment.for_project` inside the Ractor body, which
663
+ # then routes through `cached_env` instead of `RBS::EnvironmentLoader.new`.
547
664
  def prewarm_rbs_cache_for_pool
548
665
  warm_env = Environment.for_project(
549
666
  libraries: @configuration.libraries,
@@ -556,17 +673,30 @@ module Rigor
556
673
  rbs_collection_auto_detect: @configuration.rbs_collection_auto_detect
557
674
  )
558
675
  warm_env.rbs_loader&.prewarm
676
+ warm_env
559
677
  end
560
678
 
561
679
  # ADR-15 Phase 4b.x — pool-mode safety net. When pool mode is configured but a precondition fails
562
680
  # (currently: `--no-cache` would force workers through `EnvironmentLoader.new`), degrade to
563
681
  # sequential analysis with a `:warning` `pool-degraded` diagnostic at run start. The actual
564
682
  # per-file analysis runs on the coordinator, identical to the default sequential path.
565
- def analyze_files_sequentially_fallback(files, reason:)
566
- environment = build_runner_environment
683
+ # @param source_files [Array<String>] issue #793 — the whole project, so this path's environment
684
+ # carries the same plugin-synthesized RBS the pool workers' would. It used to build over `[]`,
685
+ # i.e. with no synthesized RBS at all, even on a full run.
686
+ def analyze_files_sequentially_fallback(files, reason:, source_files: files)
687
+ environment = build_runner_environment(source_files: source_files)
567
688
  snapshot_effect_annotation_carrier(environment.rbs_loader)
568
689
  diagnostics = files.flat_map { |path| @analyze_file.call(path, environment) }
569
690
  loader = environment.rbs_loader
691
+ # Issue #696 — this path analyses on the coordinator, so its loader IS the one that reached the
692
+ # failing build, and it must snapshot the same slot the two real paths do. Reached whenever
693
+ # `fork` is unavailable (Windows) and on `--incremental` / effects runs without it: a run that
694
+ # degraded to sequential must not also report less than a sequential run would.
695
+ record_definition_build_failures(loader&.definition_build_failures)
696
+ # Issue #784 — same reasoning: this path's Environment IS the one that reached the scan, so it
697
+ # must snapshot the slot too, or a run that degraded to sequential would report less than a
698
+ # sequential run would. Demanded once by the run as well, for the reason at the sequential site.
699
+ record_hkt_scan_failure(hkt_scan_outcome(environment))
570
700
  @snapshots.class_decl_paths = loader&.class_decl_paths || {}.freeze
571
701
  @snapshots.signature_paths = loader&.signature_paths || [].freeze
572
702
  @snapshots.quarantined_signatures =
@@ -582,17 +712,7 @@ module Rigor
582
712
  end
583
713
 
584
714
  def merge_worker_reporters(drained)
585
- rbs = drained.fetch(:rbs_extended)
586
- rbs.fetch(:unresolved_payloads).each do |entry|
587
- @rbs_extended_reporter.record_unresolved(
588
- payload: entry.payload, source_location: entry.source_location
589
- )
590
- end
591
- rbs.fetch(:lossy_projections).each do |entry|
592
- @rbs_extended_reporter.record_lossy_projection(
593
- head: entry.head, source_location: entry.source_location
594
- )
595
- end
715
+ merge_rbs_extended_reporter(drained.fetch(:rbs_extended))
596
716
  drained.fetch(:boundary_cross).each do |entry|
597
717
  @boundary_cross_reporter.record(
598
718
  class_name: entry.class_name,
@@ -608,10 +728,109 @@ module Rigor
608
728
  plugin_id: entry.plugin_id, path: entry.path, message: entry.message
609
729
  )
610
730
  end
731
+ # Issue #696. Fetched with a default so an older drain stays compatible, exactly as the line above.
732
+ record_definition_build_failures(drained[:definition_build_failures])
733
+ # Issue #784. `Hash#[]` is already a nil default, exactly as `env_build_failure` is snapshotted
734
+ # elsewhere — an older drain shape simply has no key and records nothing.
735
+ record_hkt_scan_failure(drained[:hkt_scan_failure])
611
736
  end
612
737
 
613
738
  private
614
739
 
740
+ # Replays one worker's three {RbsExtended::Reporter} streams into the run's own reporter.
741
+ #
742
+ # `hkt_directive_errors` (issue #785) is read with `Hash#[]`'s nil default rather than `fetch`,
743
+ # exactly as `source_rbs_synthesis` is, so an older drain shape simply records nothing.
744
+ #
745
+ # All three streams replay by their `(path, line, column)` primitives (#785 for the hkt stream, #805
746
+ # for its two elders). Every worker reads the same `.rbs`, so each hands over the same entries, and
747
+ # only a primitive-keyed dedup collapses them back to the single row a `--workers=0` run prints — an
748
+ # `RBS::Location` compares equal only against a location over the same `RBS::Buffer` object, so a
749
+ # location-keyed one would print N copies at `--workers=N`. The entries could not reach here carrying
750
+ # a location anyway: the drain Marshals its payload and an `RBS::Location` has no `_dump`.
751
+ def merge_rbs_extended_reporter(rbs)
752
+ rbs.fetch(:unresolved_payloads).each do |entry|
753
+ @rbs_extended_reporter.record_unresolved(
754
+ payload: entry.payload, path: entry.path, line: entry.line, column: entry.column
755
+ )
756
+ end
757
+ rbs.fetch(:lossy_projections).each do |entry|
758
+ @rbs_extended_reporter.record_lossy_projection(
759
+ head: entry.head, path: entry.path, line: entry.line, column: entry.column
760
+ )
761
+ end
762
+ Array(rbs[:hkt_directive_errors]).each do |entry|
763
+ @rbs_extended_reporter.record_hkt_error(
764
+ message: entry.message, path: entry.path, line: entry.line, column: entry.column
765
+ )
766
+ end
767
+ end
768
+
769
+ # Issue #696 — accumulate the per-class `RBS::DefinitionBuilder` failures this run observed, deduped
770
+ # by class name and kept in first-seen order.
771
+ #
772
+ # Accumulating rather than assigning is what makes a pooled run say what a sequential one says: each
773
+ # worker holds its OWN loader and its own memo (the `@state` that also gates the stderr banner, which
774
+ # is why the banner repeats per worker — #295), so a class that fails in two workers arrives twice
775
+ # and a class only one worker touched arrives once. The union over the workers is the set the RUN
776
+ # hit; neither worker alone is.
777
+ #
778
+ # NOT gated on `project_signature_paths?`, unlike {#snapshot_project_signature_state}. That gate
779
+ # exists because each condition there forces the otherwise-lazy env build, which would warm the cache
780
+ # on a project with no sig set of its own. Nothing is forced here — this reads what the analysis
781
+ # already demanded — and the failure is not a project-sig-only condition anyway: two BUNDLED sources
782
+ # declaring the same method collapse a class with no `signature_paths:` in sight (the
783
+ # `bigdecimal` / `BigMath` shape, #299). Gating it would suppress exactly the variant the user cannot
784
+ # fix by editing their own `sig/`.
785
+ def record_definition_build_failures(failures)
786
+ return if failures.nil? || failures.empty?
787
+
788
+ @snapshots.definition_build_failures =
789
+ (@snapshots.definition_build_failures + failures).uniq(&:first).freeze
790
+ end
791
+
792
+ # Issue #784 — first-wins, unlike {#record_definition_build_failures}'s accumulate-and-dedup. That
793
+ # method accumulates because each pool WORKER owns its own loader and its own per-class memo, so a
794
+ # collapsed class can genuinely be observed by only some workers and the run's set is the union.
795
+ # The HKT scan has no such per-worker variation: `Environment#hkt_registry` builds from the SAME
796
+ # `signature_paths:` overlay every worker was handed, so every worker that demands it either all
797
+ # raise identically or all succeed — there is only ever one tuple to record, and `||=` is correct
798
+ # (and cheap) rather than an accumulate-and-dedup this slot never needs.
799
+ def record_hkt_scan_failure(tuple)
800
+ @snapshots.hkt_scan_failure ||= tuple
801
+ end
802
+
803
+ # Issue #784 — demand the registry once on behalf of the run, then read the slot. The seam in
804
+ # {Environment#hkt_registry} is demand-driven, and nothing guarantees any analysed file demands it:
805
+ # a `--verify-incremental` partition or an incremental closure can miss every `Klass.method` call,
806
+ # and then the slot is nil after the loop and the run says nothing — while `--incremental` has also
807
+ # dropped the row from its per-file cache. Demanding here makes the outcome a property of the RUN,
808
+ # not of which files happened to be in it.
809
+ #
810
+ # This is sound where #696's "a demand that is Rigor's own MUST NOT contribute" is not, and the
811
+ # difference is the shape of what is recorded. #696 reports a per-class LIST whose membership is
812
+ # "the classes the analysis demanded"; a Rigor-internal demand adds classes the user never asked
813
+ # about and makes that list vary with configuration. The HKT scan is ONE build with ONE outcome —
814
+ # the same tuple whoever demands it — so an extra demand cannot change what is reported, only
815
+ # guarantee it is observed.
816
+ #
817
+ # What it costs, stated plainly because {#record_definition_build_failures}'s comment promises
818
+ # "nothing is forced here": the demand DOES force the RBS env build when the environment has not
819
+ # built it yet (the scan reads the loader). On every path that reaches this method that build is
820
+ # either already done (the loop demanded it) or the same load one analysed file would have paid:
821
+ # a Marshal load when a cache store exists, the parse a full run pays under `--no-cache`. Never a
822
+ # build on a project with no files (see {#analyze_files}). Memoised, so on a reused Environment
823
+ # this is a hash read.
824
+ #
825
+ # @param environment [Rigor::Environment, nil]
826
+ # @return [Array, nil] the recorded tuple, or nil (no environment, or the scan built)
827
+ def hkt_scan_outcome(environment)
828
+ return nil if environment.nil?
829
+
830
+ environment.hkt_registry
831
+ environment.hkt_scan_failure
832
+ end
833
+
615
834
  # True when the project declares its own `signature_paths:` (the only place the
616
835
  # qualified-name-without-namespace mistake lives).
617
836
  def project_signature_paths?
@@ -53,17 +53,11 @@ module Rigor
53
53
  # consumes them and so never pays the double parse (see {#discover} + `Runner#ensure_project_discovery`).
54
54
  # The slot names mirror the discovery half of {Result} exactly.
55
55
  Discovery = Data.define(
56
- :discovered_classes,
57
- :discovered_def_nodes,
58
- :discovered_singleton_def_nodes,
59
- :discovered_def_sources,
60
- :discovered_singleton_def_sources,
61
- :discovered_superclasses,
62
- :discovered_includes,
63
- :discovered_class_sources,
64
- :discovered_method_visibilities,
65
- :discovered_methods,
66
- :data_member_layouts,
56
+ :discovered_classes, :discovered_def_nodes, :discovered_def_nestings,
57
+ :discovered_singleton_def_nodes, :discovered_def_sources, :discovered_singleton_def_sources,
58
+ :discovered_superclasses, :discovered_header_nestings, :discovered_includes,
59
+ :discovered_class_sources, :constant_values, :constant_sources, :constant_writes,
60
+ :discovered_method_visibilities, :discovered_methods, :data_member_layouts,
67
61
  :struct_member_layouts
68
62
  )
69
63
 
@@ -156,12 +150,22 @@ module Rigor
156
150
  Discovery.new(
157
151
  discovered_classes: index.fetch(:classes),
158
152
  discovered_def_nodes: def_index.fetch(:def_nodes),
153
+ # Issue #681 — the recorded `Module.nesting` per declared `def`, so a callee's body re-walk
154
+ # resolves its constants the way the declaration that owns it does.
155
+ discovered_def_nestings: def_index.fetch(:def_nestings),
159
156
  discovered_singleton_def_nodes: def_index.fetch(:singleton_def_nodes),
160
157
  discovered_def_sources: def_index.fetch(:def_sources),
161
158
  discovered_singleton_def_sources: def_index.fetch(:singleton_def_sources),
162
159
  discovered_superclasses: def_index.fetch(:superclasses),
160
+ # Issue #682 — the nesting each declaration header is written in, so an ancestor name resolves in
161
+ # the cref Ruby resolves it in rather than by peeling the subclass's own qualified name.
162
+ discovered_header_nestings: def_index.fetch(:header_nestings),
163
163
  discovered_includes: def_index.fetch(:includes),
164
164
  discovered_class_sources: def_index.fetch(:class_sources),
165
+ # Issue #644 — the cross-file value-constant publication table and its write attribution.
166
+ constant_values: def_index.fetch(:constant_values),
167
+ constant_sources: def_index.fetch(:constant_sources),
168
+ constant_writes: def_index.fetch(:constant_writes),
165
169
  discovered_method_visibilities: def_index.fetch(:method_visibilities),
166
170
  discovered_methods: def_index.fetch(:methods),
167
171
  data_member_layouts: def_index.fetch(:data_member_layouts),
@@ -17,12 +17,18 @@ module Rigor
17
17
  class RunSnapshots
18
18
  attr_accessor :class_decl_paths, :signature_paths,
19
19
  :synthesized_namespaces, :quarantined_signatures, :conformance_results,
20
- :env_build_failure, :effect_annotation_carrier
20
+ :env_build_failure, :definition_build_failures, :hkt_scan_failure,
21
+ :effect_annotation_carrier
21
22
 
22
23
  # Constructor defaults match the {Runner} constructor: the pre-seed values `build_run_stats` /
23
24
  # `pre_file_diagnostics` read before the first analysis path runs are frozen empties. The
24
25
  # `env_build_failure` slot is nil (no failure) rather than an empty collection — it holds a single
25
- # `[error_class, first_line, buffer_names]` tuple or nothing.
26
+ # `[error_class, first_line, buffer_names]` tuple or nothing. Its per-class sibling
27
+ # `definition_build_failures` (#696) holds a LIST, because a collapsed universe fails many classes,
28
+ # and is accumulated ACROSS pool workers rather than assigned once — see
29
+ # {PoolCoordinator#merge_worker_reporters}. `hkt_scan_failure` (#784) is nil-or-tuple like
30
+ # `env_build_failure`, not a list like `definition_build_failures`: the scan is ONE build over the
31
+ # whole `signature_paths:` overlay, not a per-class one, so it has exactly one outcome per run.
26
32
  def initialize
27
33
  @class_decl_paths = {}.freeze
28
34
  @signature_paths = [].freeze
@@ -30,6 +36,8 @@ module Rigor
30
36
  @quarantined_signatures = [].freeze
31
37
  @conformance_results = [].freeze
32
38
  @env_build_failure = nil
39
+ @definition_build_failures = [].freeze
40
+ @hkt_scan_failure = nil
33
41
  @effect_annotation_carrier = [].freeze
34
42
  end
35
43
 
@@ -42,6 +50,8 @@ module Rigor
42
50
  @quarantined_signatures = []
43
51
  @conformance_results = []
44
52
  @env_build_failure = nil
53
+ @definition_build_failures = []
54
+ @hkt_scan_failure = nil
45
55
  @effect_annotation_carrier = [].freeze
46
56
  end
47
57
  end