rigortype 0.4.6 → 0.4.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 (550) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/data/core_overlay/bigdecimal.rbs +33 -0
  4. data/data/core_overlay/resolv.rbs +30 -0
  5. data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
  6. data/data/gem_overlay/addressable/addressable.rbs +40 -0
  7. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  8. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  9. data/docs/handbook/01-getting-started.md +6 -3
  10. data/docs/handbook/11-sig-gen.md +85 -3
  11. data/docs/handbook/appendix-phpstan.md +4 -4
  12. data/docs/install.md +20 -0
  13. data/docs/manual/01-installation.md +67 -26
  14. data/docs/manual/02-cli-reference.md +455 -113
  15. data/docs/manual/03-configuration.md +76 -5
  16. data/docs/manual/04-diagnostics.md +5 -4
  17. data/docs/manual/06-baseline.md +6 -4
  18. data/docs/manual/08-skills.md +11 -0
  19. data/docs/manual/09-editor-integration.md +21 -2
  20. data/docs/manual/10-mcp-server.md +38 -2
  21. data/docs/manual/12-caching.md +4 -3
  22. data/docs/manual/14-rails-quickstart.md +1 -1
  23. data/docs/manual/19-effect-labels.md +3 -4
  24. data/docs/manual/plugins/rigor-actionpack.md +32 -15
  25. data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
  26. data/docs/manual/plugins/rigor-sorbet.md +17 -3
  27. data/lib/rigor/analysis/baseline.rb +24 -27
  28. data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
  29. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  30. data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
  31. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  32. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -4
  33. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  34. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
  35. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +16 -4
  36. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
  37. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  38. data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
  40. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
  41. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
  42. data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
  43. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  44. data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
  45. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  46. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  47. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  48. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  49. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +2 -2
  50. data/lib/rigor/analysis/check_rules.rb +720 -141
  51. data/lib/rigor/analysis/crash_signature.rb +45 -3
  52. data/lib/rigor/analysis/dependency_recorder.rb +237 -80
  53. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  54. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  55. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  56. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  57. data/lib/rigor/analysis/edit_replay.rb +93 -6
  58. data/lib/rigor/analysis/fact_store.rb +3 -3
  59. data/lib/rigor/analysis/incremental.rb +11 -1
  60. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
  61. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  62. data/lib/rigor/analysis/incremental_session.rb +306 -53
  63. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
  64. data/lib/rigor/analysis/rule_catalog.rb +39 -3
  65. data/lib/rigor/analysis/run_cache_key.rb +27 -3
  66. data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
  67. data/lib/rigor/analysis/run_stats.rb +2 -2
  68. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
  69. data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
  70. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
  71. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
  72. data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
  73. data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
  74. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  75. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  76. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  77. data/lib/rigor/analysis/runner.rb +298 -109
  78. data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
  79. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  80. data/lib/rigor/analysis/template_units.rb +9 -1
  81. data/lib/rigor/analysis/worker_session.rb +65 -71
  82. data/lib/rigor/bleeding_edge.rb +22 -10
  83. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  84. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  85. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  86. data/lib/rigor/cache/descriptor.rb +6 -5
  87. data/lib/rigor/cache/file_digest.rb +39 -31
  88. data/lib/rigor/cache/incremental_snapshot.rb +95 -17
  89. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  90. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  91. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  92. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  93. data/lib/rigor/cache/rbs_descriptor.rb +90 -4
  94. data/lib/rigor/cache/rbs_environment.rb +8 -1
  95. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  96. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  97. data/lib/rigor/cache/store.rb +11 -10
  98. data/lib/rigor/ci_detector.rb +1 -1
  99. data/lib/rigor/cli/annotate_command.rb +24 -19
  100. data/lib/rigor/cli/baseline_command.rb +160 -57
  101. data/lib/rigor/cli/catalogue.rb +256 -0
  102. data/lib/rigor/cli/check_command.rb +112 -104
  103. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  104. data/lib/rigor/cli/command.rb +15 -1
  105. data/lib/rigor/cli/coverage_command.rb +21 -8
  106. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  107. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  108. data/lib/rigor/cli/coverage_scan.rb +3 -5
  109. data/lib/rigor/cli/diff_command.rb +25 -20
  110. data/lib/rigor/cli/docs_command.rb +1 -2
  111. data/lib/rigor/cli/doctor_command.rb +12 -12
  112. data/lib/rigor/cli/effects_command.rb +50 -40
  113. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  114. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  115. data/lib/rigor/cli/effects_renderer.rb +2 -1
  116. data/lib/rigor/cli/effects_snapshot_command.rb +26 -18
  117. data/lib/rigor/cli/explain_command.rb +18 -8
  118. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  119. data/lib/rigor/cli/json_document.rb +41 -0
  120. data/lib/rigor/cli/lens_command.rb +29 -10
  121. data/lib/rigor/cli/lsp_command.rb +250 -23
  122. data/lib/rigor/cli/mcp_command.rb +12 -7
  123. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  124. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  125. data/lib/rigor/cli/options.rb +40 -1
  126. data/lib/rigor/cli/plugin_command.rb +93 -46
  127. data/lib/rigor/cli/plugins_command.rb +17 -18
  128. data/lib/rigor/cli/plugins_renderer.rb +10 -6
  129. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  130. data/lib/rigor/cli/protection_renderer.rb +2 -1
  131. data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
  132. data/lib/rigor/cli/sig_gen_command.rb +92 -27
  133. data/lib/rigor/cli/skill_command.rb +32 -3
  134. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  135. data/lib/rigor/cli/skill_describe.rb +2 -1
  136. data/lib/rigor/cli/trace_command.rb +14 -9
  137. data/lib/rigor/cli/triage_command.rb +10 -11
  138. data/lib/rigor/cli/triage_renderer.rb +2 -1
  139. data/lib/rigor/cli/type_of_command.rb +16 -11
  140. data/lib/rigor/cli/type_of_project_scope.rb +1 -4
  141. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  142. data/lib/rigor/cli/type_scan_command.rb +14 -9
  143. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  144. data/lib/rigor/cli/unused_command.rb +27 -17
  145. data/lib/rigor/cli/upgrade_command.rb +5 -0
  146. data/lib/rigor/cli.rb +214 -56
  147. data/lib/rigor/config_audit.rb +33 -19
  148. data/lib/rigor/configuration/severity_profile.rb +14 -5
  149. data/lib/rigor/configuration.rb +104 -35
  150. data/lib/rigor/effects/collector.rb +53 -21
  151. data/lib/rigor/effects/definition_context.rb +0 -2
  152. data/lib/rigor/effects/envelope.rb +1 -1
  153. data/lib/rigor/effects/framework_units.rb +1 -1
  154. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  155. data/lib/rigor/effects/propagator.rb +1 -1
  156. data/lib/rigor/effects/registry.rb +2 -2
  157. data/lib/rigor/effects/snapshot.rb +1 -1
  158. data/lib/rigor/effects/taint_cause.rb +0 -2
  159. data/lib/rigor/effects/unit_scan.rb +9 -13
  160. data/lib/rigor/environment/bundle_sig_discovery.rb +64 -33
  161. data/lib/rigor/environment/class_registry.rb +3 -3
  162. data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
  163. data/lib/rigor/environment/core_root.rb +77 -0
  164. data/lib/rigor/environment/default_libraries.rb +18 -1
  165. data/lib/rigor/environment/deferred_signature_plan.rb +381 -0
  166. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  167. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  168. data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
  169. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  170. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  171. data/lib/rigor/environment/member_consistency.rb +56 -7
  172. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  173. data/lib/rigor/environment/project_path_gems.rb +124 -0
  174. data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
  175. data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
  176. data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
  177. data/lib/rigor/environment/rbs_loader.rb +772 -110
  178. data/lib/rigor/environment/reflection.rb +7 -7
  179. data/lib/rigor/environment/required_features.rb +0 -3
  180. data/lib/rigor/environment/ruby_library_roots.rb +1 -1
  181. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  182. data/lib/rigor/environment.rb +169 -42
  183. data/lib/rigor/flow_contribution/merger.rb +4 -4
  184. data/lib/rigor/inference/acceptance.rb +136 -40
  185. data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
  186. data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
  187. data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
  188. data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
  189. data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
  190. data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
  191. data/lib/rigor/inference/block_auto_splat.rb +1 -1
  192. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  193. data/lib/rigor/inference/block_run_count.rb +185 -0
  194. data/lib/rigor/inference/budget_trace.rb +8 -2
  195. data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
  196. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  197. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  198. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  199. data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
  200. data/lib/rigor/inference/callee_write_census.rb +815 -0
  201. data/lib/rigor/inference/captured_locals.rb +2 -0
  202. data/lib/rigor/inference/census_foreign_block.rb +354 -0
  203. data/lib/rigor/inference/clause_widening.rb +1 -1
  204. data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
  205. data/lib/rigor/inference/construction_escape.rb +258 -0
  206. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  207. data/lib/rigor/inference/declaration_walk.rb +4 -0
  208. data/lib/rigor/inference/deep_erasure.rb +39 -7
  209. data/lib/rigor/inference/def_handle.rb +1 -1
  210. data/lib/rigor/inference/def_node_resolver.rb +33 -15
  211. data/lib/rigor/inference/def_return_typer.rb +47 -5
  212. data/lib/rigor/inference/definer_resolution.rb +19 -9
  213. data/lib/rigor/inference/definers_index.rb +162 -0
  214. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  215. data/lib/rigor/inference/element_read_widening.rb +49 -3
  216. data/lib/rigor/inference/error_info.rb +16 -0
  217. data/lib/rigor/inference/expression_typer.rb +1559 -320
  218. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  219. data/lib/rigor/inference/flow_tracer.rb +31 -14
  220. data/lib/rigor/inference/fork_map.rb +3 -0
  221. data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
  222. data/lib/rigor/inference/global_write_census.rb +81 -5
  223. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  224. data/lib/rigor/inference/hkt_body.rb +1 -2
  225. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  226. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  227. data/lib/rigor/inference/hkt_registry.rb +3 -5
  228. data/lib/rigor/inference/in_effect_refinements.rb +7 -0
  229. data/lib/rigor/inference/indexed_narrowing.rb +61 -15
  230. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  231. data/lib/rigor/inference/ivar_writer_census.rb +154 -8
  232. data/lib/rigor/inference/key_presence_guard.rb +51 -24
  233. data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
  234. data/lib/rigor/inference/last_line.rb +5 -2
  235. data/lib/rigor/inference/loop_value_record.rb +66 -14
  236. data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
  237. data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
  238. data/lib/rigor/inference/match_rebinding.rb +28 -5
  239. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  240. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  241. data/lib/rigor/inference/method_dispatcher/block_folding.rb +44 -30
  242. data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
  243. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
  244. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  245. data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
  246. data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
  247. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
  248. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
  249. data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
  250. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  251. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
  252. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  253. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +51 -27
  254. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  255. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
  256. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  257. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  258. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +3 -3
  259. data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
  260. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
  261. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  262. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  263. data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
  264. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  265. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  266. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  267. data/lib/rigor/inference/method_dispatcher.rb +123 -25
  268. data/lib/rigor/inference/method_parameter_binder.rb +136 -56
  269. data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
  270. data/lib/rigor/inference/module_function_state.rb +24 -3
  271. data/lib/rigor/inference/multi_target_binder.rb +2 -2
  272. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  273. data/lib/rigor/inference/mutation_widening.rb +199 -29
  274. data/lib/rigor/inference/narrowing.rb +238 -50
  275. data/lib/rigor/inference/operand_effects.rb +43 -2
  276. data/lib/rigor/inference/optimistic_origin.rb +134 -9
  277. data/lib/rigor/inference/origin_lookup.rb +1 -1
  278. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  279. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  280. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  281. data/lib/rigor/inference/precision_scanner.rb +1 -1
  282. data/lib/rigor/inference/project_method_ownership.rb +30 -9
  283. data/lib/rigor/inference/project_patched_methods.rb +65 -13
  284. data/lib/rigor/inference/project_patched_scanner.rb +120 -25
  285. data/lib/rigor/inference/project_patched_visibility.rb +39 -3
  286. data/lib/rigor/inference/project_yielder.rb +11 -17
  287. data/lib/rigor/inference/rbs_type_translator.rb +68 -15
  288. data/lib/rigor/inference/receiver_alias.rb +20 -0
  289. data/lib/rigor/inference/receiver_writer_call.rb +67 -0
  290. data/lib/rigor/inference/recursion_rebinding.rb +157 -0
  291. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
  292. data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
  293. data/lib/rigor/inference/refined_dispatch.rb +13 -0
  294. data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
  295. data/lib/rigor/inference/remover_holder_scan.rb +287 -0
  296. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  297. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  298. data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
  299. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
  300. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  301. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
  302. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  303. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  304. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +295 -0
  305. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
  306. data/lib/rigor/inference/scope_indexer.rb +2213 -226
  307. data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
  308. data/lib/rigor/inference/self_preserving_block.rb +87 -0
  309. data/lib/rigor/inference/set_mutation.rb +189 -0
  310. data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
  311. data/lib/rigor/inference/singleton_object_constant.rb +32 -2
  312. data/lib/rigor/inference/statement_evaluator.rb +3268 -358
  313. data/lib/rigor/inference/string_mutation.rb +14 -0
  314. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  315. data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
  316. data/lib/rigor/inference/toplevel_def_census.rb +67 -0
  317. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  318. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  319. data/lib/rigor/inference/untyped_rejoin.rb +1 -1
  320. data/lib/rigor/inference/void_tail_summary.rb +29 -9
  321. data/lib/rigor/language_server/buffer_table.rb +3 -3
  322. data/lib/rigor/language_server/completion_provider.rb +26 -20
  323. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  324. data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
  325. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  326. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  327. data/lib/rigor/language_server/hover_provider.rb +1 -7
  328. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  329. data/lib/rigor/language_server/project_context.rb +39 -1
  330. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  331. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  332. data/lib/rigor/language_server/server.rb +7 -3
  333. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  334. data/lib/rigor/lens/claims/comparison.rb +78 -0
  335. data/lib/rigor/lens/claims/request.rb +131 -0
  336. data/lib/rigor/lens/claims/type_reader.rb +216 -0
  337. data/lib/rigor/lens/claims.rb +208 -0
  338. data/lib/rigor/lens/contract_reader.rb +5 -0
  339. data/lib/rigor/lens/document.rb +1 -1
  340. data/lib/rigor/lens/file_reader.rb +21 -8
  341. data/lib/rigor/lens/plugins.rb +1 -1
  342. data/lib/rigor/lens/run.rb +8 -0
  343. data/lib/rigor/lens.rb +1 -1
  344. data/lib/rigor/library_probe.rb +68 -0
  345. data/lib/rigor/mcp/server.rb +73 -2
  346. data/lib/rigor/mcp.rb +1 -1
  347. data/lib/rigor/memo_audit.rb +282 -0
  348. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  349. data/lib/rigor/plugin/base.rb +15 -2
  350. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  351. data/lib/rigor/plugin/io_boundary.rb +26 -4
  352. data/lib/rigor/plugin/loader.rb +25 -8
  353. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  354. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  355. data/lib/rigor/plugin/manifest.rb +16 -16
  356. data/lib/rigor/plugin/registry.rb +16 -5
  357. data/lib/rigor/plugin/services.rb +2 -3
  358. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  359. data/lib/rigor/plugin/trust_policy.rb +48 -2
  360. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  361. data/lib/rigor/plugin.rb +6 -8
  362. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  363. data/lib/rigor/project_environment.rb +16 -2
  364. data/lib/rigor/protection/analysis_guard.rb +1 -1
  365. data/lib/rigor/protection/discovery_seed.rb +44 -5
  366. data/lib/rigor/protection/mutation_cache.rb +4 -1
  367. data/lib/rigor/protection/mutator.rb +1 -1
  368. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  369. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  370. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  371. data/lib/rigor/rbs_extended.rb +2 -3
  372. data/lib/rigor/reflection/constant_ancestors.rb +81 -22
  373. data/lib/rigor/reflection/declaration_collectors.rb +55 -0
  374. data/lib/rigor/reflection/declarations.rb +58 -0
  375. data/lib/rigor/reflection/project_method.rb +177 -0
  376. data/lib/rigor/reflection.rb +2 -0
  377. data/lib/rigor/rooted_glob.rb +32 -1
  378. data/lib/rigor/run_scoped_slots.rb +38 -0
  379. data/lib/rigor/runtime/jit.rb +1 -1
  380. data/lib/rigor/scope/discovery_index.rb +168 -8
  381. data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
  382. data/lib/rigor/scope/resolution_chain.rb +901 -54
  383. data/lib/rigor/scope.rb +644 -37
  384. data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
  385. data/lib/rigor/sig_gen/classification.rb +18 -1
  386. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  387. data/lib/rigor/sig_gen/generator.rb +588 -66
  388. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  389. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  390. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  391. data/lib/rigor/sig_gen/method_candidate.rb +37 -4
  392. data/lib/rigor/sig_gen/observation_collector.rb +14 -7
  393. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  394. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  395. data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
  396. data/lib/rigor/sig_gen/renderer.rb +15 -9
  397. data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -2
  398. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  399. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  400. data/lib/rigor/sig_gen/write_result.rb +23 -12
  401. data/lib/rigor/sig_gen/writer.rb +238 -37
  402. data/lib/rigor/sig_gen.rb +1 -0
  403. data/lib/rigor/source/forwardable_delegators.rb +122 -0
  404. data/lib/rigor/source/node_children.rb +13 -0
  405. data/lib/rigor/type/acceptance_router.rb +2 -2
  406. data/lib/rigor/type/accepts_result.rb +1 -1
  407. data/lib/rigor/type/app.rb +4 -6
  408. data/lib/rigor/type/combinator.rb +173 -21
  409. data/lib/rigor/type/constant.rb +4 -5
  410. data/lib/rigor/type/hash_shape.rb +21 -40
  411. data/lib/rigor/type/interface.rb +60 -0
  412. data/lib/rigor/type/union.rb +1 -1
  413. data/lib/rigor/type.rb +1 -0
  414. data/lib/rigor/type_node/identifier.rb +1 -2
  415. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  416. data/lib/rigor/type_node/string_literal.rb +1 -2
  417. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  418. data/lib/rigor/type_node/union.rb +3 -6
  419. data/lib/rigor/type_of_memo/serving.rb +99 -0
  420. data/lib/rigor/type_of_memo/stats.rb +43 -0
  421. data/lib/rigor/type_of_memo.rb +337 -0
  422. data/lib/rigor/typing_state.rb +1094 -0
  423. data/lib/rigor/value_semantics.rb +1 -10
  424. data/lib/rigor/version.rb +1 -1
  425. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  426. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  427. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  428. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
  429. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  430. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
  431. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  432. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  433. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  434. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  435. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +3 -3
  436. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
  437. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
  438. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
  439. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  440. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
  441. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
  442. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
  443. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
  444. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  445. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  446. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  447. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  448. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +132 -33
  449. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
  450. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
  451. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
  452. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
  453. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  454. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  455. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +11 -14
  456. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  457. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  458. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  459. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +30 -9
  460. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
  461. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
  462. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
  463. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  464. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  465. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  466. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  467. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  468. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  469. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  470. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  471. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  472. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  473. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  474. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  475. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +8 -7
  476. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  477. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  478. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
  479. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  480. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  481. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +8 -2
  482. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  483. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  484. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  485. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  486. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  487. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +200 -4
  488. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  489. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  490. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  491. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +4 -3
  492. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  493. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
  494. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  495. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  496. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  497. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
  498. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  499. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  500. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +210 -3
  501. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
  502. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  503. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
  504. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
  505. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  506. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
  507. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
  508. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  509. data/sig/rigor/analysis/baseline.rbs +0 -3
  510. data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
  511. data/sig/rigor/analysis/fact_store.rbs +2 -2
  512. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  513. data/sig/rigor/cache/descriptor.rbs +14 -0
  514. data/sig/rigor/cli/diff_command.rbs +1 -1
  515. data/sig/rigor/cli/explain_command.rbs +1 -1
  516. data/sig/rigor/effects/registry.rbs +2 -2
  517. data/sig/rigor/environment.rbs +4 -1
  518. data/sig/rigor/inference.rbs +4 -1
  519. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  520. data/sig/rigor/plugin/base.rbs +53 -18
  521. data/sig/rigor/plugin/effect_attribution.rbs +3 -3
  522. data/sig/rigor/plugin/effect_edge.rbs +1 -1
  523. data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
  524. data/sig/rigor/plugin/interfaces.rbs +51 -0
  525. data/sig/rigor/plugin/io_boundary.rbs +3 -3
  526. data/sig/rigor/plugin/manifest.rbs +9 -9
  527. data/sig/rigor/plugin/node_context.rbs +21 -0
  528. data/sig/rigor/plugin/protocol_contract.rbs +3 -7
  529. data/sig/rigor/reflection.rbs +37 -0
  530. data/sig/rigor/scope.rbs +43 -4
  531. data/sig/rigor/trinary.rbs +1 -0
  532. data/sig/rigor/type.rbs +9 -3
  533. data/sig/rigor.rbs +3 -2
  534. data/skills/rigor-ask/SKILL.md +1 -0
  535. data/skills/rigor-plugin-author/SKILL.md +4 -4
  536. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  537. data/skills/rigor-plugin-review/SKILL.md +3 -3
  538. data/skills/rigor-project-init/references/01-detect.md +1 -0
  539. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
  540. data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
  541. data/skills/rigor-rbs-contract/SKILL.md +147 -0
  542. data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
  543. data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
  544. data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
  545. data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
  546. data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
  547. data/skills/rigor-rbs-setup/SKILL.md +3 -1
  548. data/skills/rigor-type-oracle/SKILL.md +26 -1
  549. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
  550. metadata +82 -3
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../typing_state"
4
+
3
5
  module Rigor
4
6
  module Analysis
5
7
  # ADR-24 slice 4a — records every *implicit-self* method call the inference engine could not resolve to any
@@ -18,15 +20,32 @@ module Rigor
18
20
  # recompute" lesson applied to self-calls.
19
21
  #
20
22
  # ADR-24 slice 4 (`call.self-undefined-method`) consumes the recorded misses behind a confidently-closed-class
21
- # gate (see `CheckRules` L775). The rule ships `:off` by default — {active?} is false on a normal run, so
23
+ # gate (see `CheckRules`). The rule ships `:off` by default — {active?} is false on a normal run, so
22
24
  # the instrumented choke-point pays a single integer read and records nothing. Recording is purely
23
25
  # observational; it never changes a diagnostic.
24
26
  #
25
27
  # Modelled on {DependencyRecorder}: process-thread-local accumulator, a cheap disabled fast path, and a
26
28
  # frozen snapshot for consumers.
27
29
  module SelfCallResolutionRecorder
28
- KEY = :__rigor_self_call_resolution_recorder__
29
- private_constant :KEY
30
+ # ADR-129 WD1 — the installed accumulator is a Frame; its contents are the bound Table {CALLS}. The activation
31
+ # count below is a process switch, outside the typing state (WD5).
32
+ RECORDER = TypingState.frame(
33
+ :self_call_resolution_recorder,
34
+ contents: :table, reason: "the accumulator a recording installs; each unresolved call records into it",
35
+ # G1′ witness: the memo journals and replays the records, so it is not a shut switch (unlike the tracers).
36
+ witness: "spec/fixtures/typing_state_witnesses/self_call_resolution_recorder.rb"
37
+ )
38
+ private_constant :RECORDER
39
+
40
+ # ADR-129 WD1 — the recorded misses are a bound Table: each installed accumulator is one of its stores, keyed by
41
+ # the call tuple (deduplicated, so the first record of a tuple is its only write), journaled while a capture is
42
+ # open so a later `type_of` memo can validate the reads and replay the writes in order.
43
+ CALLS = TypingState.table(
44
+ :self_call_misses,
45
+ bound: true,
46
+ reason: "UnresolvedSelfCall => true per consumer accumulator, appended in first-record order"
47
+ )
48
+ private_constant :CALLS
30
49
 
31
50
  # One unresolved implicit-self call. `class_name` is the receiver's statically known class (the
32
51
  # enclosing `self` type); `method_name` the called name; `node` the Prism `CallNode` (held in-memory so
@@ -45,10 +64,28 @@ module Rigor
45
64
  @seen = Set.new
46
65
  end
47
66
 
48
- def add(call)
49
- return unless @seen.add?(call)
67
+ def add?(call)
68
+ return false unless @seen.add?(call)
50
69
 
51
70
  @calls << call
71
+ true
72
+ end
73
+
74
+ # Whether `call` is already recorded.
75
+ def recorded?(call) = @seen.include?(call)
76
+
77
+ # The bound Table's store protocol ({CALLS}): `true` for a recorded call, else `default`.
78
+ def fetch(call, default) = @seen.include?(call) || default
79
+
80
+ # The bound Table's store protocol: an undone write (G1's stage-4 form) forgets `call`.
81
+ def delete(call)
82
+ @calls.delete(call) if @seen.delete?(call)
83
+ end
84
+
85
+ # The bound Table's store protocol: a replayed write records `call` as {#add?} does, so the replay order of a
86
+ # write slice is the order of {#calls}.
87
+ def []=(call, _value)
88
+ add?(call)
52
89
  end
53
90
 
54
91
  def snapshot
@@ -69,15 +106,16 @@ module Rigor
69
106
  # Activates recording for `consumer` (the path being analyzed) for the duration of the block and returns
70
107
  # the frozen {Record}. Nests safely; restores the previous recorder on exit.
71
108
  def record_for(consumer)
72
- previous = Thread.current[KEY]
73
109
  accumulator = Accumulator.new(consumer.to_s)
74
- Thread.current[KEY] = accumulator
75
- @mutex.synchronize { @active_count += 1 }
76
- yield
77
- accumulator.snapshot
78
- ensure
79
- Thread.current[KEY] = previous
80
- @mutex.synchronize { @active_count -= 1 }
110
+ TypingState.current.with(RECORDER, accumulator) do
111
+ @mutex.synchronize { @active_count += 1 }
112
+ begin
113
+ yield
114
+ accumulator.snapshot
115
+ ensure
116
+ @mutex.synchronize { @active_count -= 1 }
117
+ end
118
+ end
81
119
  end
82
120
 
83
121
  # Plain integer read (GVL-atomic) — no `Thread.current` lookup on the disabled fast path.
@@ -88,19 +126,20 @@ module Rigor
88
126
  # Records one unresolved implicit-self call. No-op when no consumer is active on this thread (another
89
127
  # thread may have flipped {active?}).
90
128
  def record(class_name:, method_name:, node:, path:, line:, column:)
91
- accumulator = Thread.current[KEY]
129
+ state = TypingState.current
130
+ accumulator = state[RECORDER]
92
131
  return if accumulator.nil?
93
132
 
94
- accumulator.add(
95
- UnresolvedSelfCall.new(
96
- class_name: class_name.to_s,
97
- method_name: method_name.to_sym,
98
- node: node,
99
- path: path,
100
- line: line,
101
- column: column
102
- )
133
+ call = UnresolvedSelfCall.new(
134
+ class_name: class_name.to_s,
135
+ method_name: method_name.to_sym,
136
+ node: node,
137
+ path: path,
138
+ line: line,
139
+ column: column
103
140
  )
141
+ state.log_bound_read(CALLS, accumulator, call, accumulator.fetch(call, TypingState::ABSENT)) if state.capturing?
142
+ state.log_bound_write(CALLS, accumulator, call, true, TypingState::ABSENT) if accumulator.add?(call)
104
143
  end
105
144
  end
106
145
  end
@@ -173,9 +173,8 @@ module Rigor
173
173
  # copies one tag body once, and the second `@author` is the same copy seen from the other end, not a
174
174
  # second reading. Only a node OUTSIDE the winning run's own compiled span counts, which is what keeps
175
175
  # the busy lines of a real view answerable while the cross-tag repeat (`<%= v %> <%= v.to_s %>`, or
176
- # `<%= v %><%= "v" %>` probed at the STRING's `v`) still declines — the family a plugin-exported tag
177
- # span would answer instead
178
- # (see the PR), and the follow-up this rule is deliberately conservative ahead of.
176
+ # `<%= v %><%= "v" %>` probed at the STRING's `v`) still declines. That family is what a plugin-exported
177
+ # tag span would answer instead, and it is the follow-up this rule is deliberately conservative ahead of.
179
178
  #
180
179
  # `spill` is the nearest NON-BLANK compiled line above this template line's own, where a hoisted
181
180
  # leading-text literal lives. Only a string literal the run does NOT contain counts there. A code
@@ -189,6 +189,15 @@ module Rigor
189
189
  @entries.keys.sort
190
190
  end
191
191
 
192
+ # Issue #2233 — whether a plugin claims `path` by one of its `template_globs:`, in any spelling a caller holds it
193
+ # (absolute, `./`-prefixed, relative). A claimed template is analysed as a unit, never as a plain `.rb` file, so
194
+ # the runner keeps the buffer's logical path out of the `.rb` targets, so it is not analysed twice.
195
+ def claims?(path)
196
+ return false if @claimed_globs.empty?
197
+
198
+ TemplateUnitPaths.claims?(@claimed_globs, TemplateUnitPaths.relative(path, @root))
199
+ end
200
+
192
201
  def [](path)
193
202
  entry_for(path)
194
203
  end
@@ -293,7 +302,6 @@ module Rigor
293
302
  [entry.locals.keys.map(&:to_sym)]
294
303
  end
295
304
 
296
- # The `view:<logical_name>` effect-unit key for a path, or nil.
297
305
  # The file whose bytes a unit's path was READ from: the editor's buffer when one is bound to it, the
298
306
  # project file otherwise. Read by the run's dependency descriptor, so it digests what the run read.
299
307
  def physical_path(path, buffer)
@@ -25,6 +25,7 @@ require_relative "dependency_source_inference"
25
25
  require_relative "diagnostic"
26
26
  require_relative "erb_template_detector"
27
27
  require_relative "template_units"
28
+ require_relative "runner/run_level_facts"
28
29
 
29
30
  module Rigor
30
31
  module Analysis
@@ -39,8 +40,8 @@ module Rigor
39
40
  # - `configuration` — Phase 2a ({Rigor::Configuration} is `Ractor.shareable?`).
40
41
  # - `cache_store` — the fork backend passes the parent runner's pre-built Store (`cache_store:
41
42
  # @cache_store` in PoolCoordinator); workers share it rather than building their own at `cache_root`.
42
- # - `plugin_blueprints` — Phase 3a (`Array<Plugin::Blueprint>` is `Ractor.shareable?`).
43
- # - `explain` — Boolean.
43
+ # - `plugin_blueprints` — Phase 3a (a list of `Plugin::Blueprint` is `Ractor.shareable?`).
44
+ # - `explain` — a flag.
44
45
  # - `synthetic_method_index` / `project_patched_methods` / `project_scope_seed` — optional (default `nil`
45
46
  # / `{}`). NOT `Ractor.shareable?` (the seed tables carry Prism def nodes), so the Ractor pool path
46
47
  # leaves them unset; the fork backend (ADR-15 Amendment), which builds the session pre-fork on the
@@ -109,7 +110,7 @@ module Rigor
109
110
  # above: a worker and the parent agree without a flag to keep in sync.
110
111
  @effect_attribution = Effects::Attribution.build(configuration.effects_attribution)
111
112
  @file_effects = {}
112
- # ADR-32 WD4 — full project file list (frozen Array<String>) for env-build-time invocation of any
113
+ # ADR-32 WD4 — full project file list (frozen) for env-build-time invocation of any
113
114
  # loaded plugin's `source_rbs_synthesizer` callable.
114
115
  @source_files = source_files
115
116
  # #392 — the run's template units, synthesised on the PARENT before the fork and inherited here as
@@ -162,12 +163,20 @@ module Rigor
162
163
  end
163
164
 
164
165
  # Equivalent of {Rigor::Analysis::Runner#analyze_file} + `plugin_emitted_diagnostics` +
165
- # `explain_diagnostics`. Returns a flat `Array<Diagnostic>` for the file. Severity profile re-stamping
166
+ # `explain_diagnostics`. Returns a flat list of diagnostics for the file. Severity profile re-stamping
166
167
  # is intentionally NOT applied — that is a per-run aggregate concern handled by the caller. When
167
168
  # `record_dependencies:` was set, wraps the analysis in a {DependencyRecorder} window (ADR-46) so the
168
169
  # worker captures the file's cross-file reads for {#drain_dependencies}; the recorder's own disabled
169
170
  # fast path makes the unwrapped case free.
170
171
  def analyze(path)
172
+ diagnostics = analyze_attributed(path)
173
+ return diagnostics unless Inference::MemoAudit.enabled?
174
+
175
+ # ADR-129 WD5 (#2785) — the audit's mismatches this file raised, as `Runner#analyze_file` reports them.
176
+ CrashSignature.with_swallowed(path, diagnostics, Inference::MemoAudit.drain)
177
+ end
178
+
179
+ def analyze_attributed(path)
171
180
  return analyze_with_effects(path) unless @record_dependencies
172
181
 
173
182
  diagnostics = nil
@@ -203,6 +212,7 @@ module Rigor
203
212
  diagnostics
204
213
  end
205
214
  private :analyze_with_effects
215
+ private :analyze_attributed
206
216
 
207
217
  # ADR-103 WD6 / #386 — the worker-side mirror of `Runner#effect_envelope_index`. Derived from the
208
218
  # same configuration and the session's own environment, so a worker's `≤` lane is the one the
@@ -226,6 +236,20 @@ module Rigor
226
236
  end
227
237
  private :effect_envelope_index
228
238
 
239
+ # The scope a file's analysis starts from: the project seed, with its template units.
240
+ def seeded_scope(path)
241
+ @template_units.seed(seed_project_scope(Scope.empty(environment: @environment, source_path: path)), path)
242
+ end
243
+
244
+ # The main pass over `root`, which the ADR-129 stage-3 shadow ({TypeOfMemo}) counts apart from the rule walk
245
+ # that follows it.
246
+ def index_main_pass(root, scope)
247
+ TypeOfMemo.begin_file
248
+ index = Inference::ScopeIndexer.index(root, default_scope: scope, widen_clauses: true)
249
+ TypeOfMemo.phase!(:rule)
250
+ index
251
+ end
252
+
229
253
  def analyze_body(path)
230
254
  parse_result = parse_source(path)
231
255
  unless parse_result.errors.empty?
@@ -236,12 +260,10 @@ module Rigor
236
260
 
237
261
  root = parse_result.value
238
262
  Effects::Collector.record_root(root)
239
- scope = @template_units.seed(
240
- seed_project_scope(Scope.empty(environment: @environment, source_path: path)), path
241
- )
242
- index = Inference::ScopeIndexer.index(root, default_scope: scope, widen_clauses: true)
263
+ scope = seeded_scope(path)
264
+ index = index_main_pass(root, scope)
243
265
  # ADR-53 B4 — built-in collectors + plugin node rules share one walk.
244
- node_collectors = CheckRules.build_node_collectors(path, index, root)
266
+ node_collectors = built_in_collectors(path, index, root)
245
267
  node_results = node_rule_results_by_plugin(path, root, scope, node_collectors, index)
246
268
  diagnostics = CheckRules.diagnose(
247
269
  path: path,
@@ -257,11 +279,21 @@ module Rigor
257
279
  diagnostics + explain_diagnostics(path, parse_result.value, scope)
258
280
  rescue Errno::ENOENT => e
259
281
  [analyzer_error(path, e.message)]
260
- rescue StandardError => e
261
- [analyzer_error(path, CrashSignature.check_rule_message(e))]
282
+ rescue StandardError, SystemStackError => e
283
+ [analyzer_error(path, CrashSignature.contained_message!(e, parse_result))]
284
+ ensure
285
+ TypeOfMemo.end_file
262
286
  end
263
287
  private :analyze_body
264
288
 
289
+ # The built-in collectors for one file; `plugin.api-surface`'s joins only when the rule can report.
290
+ def built_in_collectors(path, index, root)
291
+ CheckRules.build_node_collectors(
292
+ path, index, root, plugin_api_surface: CheckRules::PluginApiSurface.active?(@configuration)
293
+ )
294
+ end
295
+ private :built_in_collectors
296
+
265
297
  # Read-once snapshot of the per-worker reporters so the caller (or the eventual Phase 4b pool
266
298
  # aggregator) can merge into a single coordinator-side reporter. Both reporters dedupe at write time,
267
299
  # so a post-hoc concat + de-dup at the entry-key level is sound.
@@ -271,7 +303,7 @@ module Rigor
271
303
  # definition and never enters the failing build; a diagnostic wired off the parent's loader would
272
304
  # appear at `--workers=0` and vanish at `--workers=N`, which is the same "reports less depending on
273
305
  # how you ran it" defect the diagnostic exists to end. Draining it out of the workers is what makes
274
- # the two paths say the same thing. The payload is `[String, String, String, Array<String>]` tuples —
306
+ # the two paths say the same thing. The payload is a list of plain tuples —
275
307
  # Marshal-clean for the fork backend and shareable for the Ractor one.
276
308
  #
277
309
  # Issue #784 — `hkt_scan_failure` rides the same channel for the same reason: the PARENT's own
@@ -284,33 +316,25 @@ module Rigor
284
316
  # project whose `sig/` produced a single such event killed each worker HERE — after its files were
285
317
  # analysed — and the run degraded to in-process re-analysis with a `pool-degraded` row. All three
286
318
  # streams now carry `(path, line, column)` primitives.
319
+ #
320
+ # ADR-116 WD3 (#1195) — both halves are declared rather than listed here: each reporter snapshots its
321
+ # own streams ({Runner::RunLevelFacts::REPORTERS}), and the `:analysis` facts come from
322
+ # {Runner::RunLevelFacts.drain}, the same read the coordinator makes of an environment it analysed on.
323
+ # The facts are read first: their HKT demand (#784, made once per worker so a worker whose share of
324
+ # files demanded nothing still reports the run's outcome) is what fills the hkt-directive stream (#785).
325
+ # On a worker that analysed a file the demand is a memoised read; the fork backend never hands a worker
326
+ # an empty slice, so no idle worker pays the env load.
287
327
  def drain_reporters
288
- # Issue #784 — demand the registry once per worker before reading the slot, so a worker whose share
289
- # of files happened to contain no `Klass.method` call still reports the run's outcome (the same
290
- # reason `PoolCoordinator#hkt_scan_outcome` demands it on the sequential path). The Environment
291
- # object is built in the constructor, but its RBS env is lazy: on a worker that analysed a file this
292
- # is a memoised read; on one that demanded nothing it is the same cache-served env load that file
293
- # would have paid. The fork backend never hands a worker an empty slice, so no idle worker pays it.
294
- @environment&.hkt_registry
295
- {
296
- rbs_extended: {
297
- unresolved_payloads: @rbs_extended_reporter.unresolved_payloads,
298
- lossy_projections: @rbs_extended_reporter.lossy_projections,
299
- # Issue #785 — the HKT directive stream rides the same channel for the same reason
300
- # `hkt_scan_failure` does: the directives are read by the registry scan, and under the pool the
301
- # PARENT never demands that scan, so a diagnostic wired off the parent's reporter would appear
302
- # at `--workers=0` and vanish at `--workers=N`. The demand above is what fills this.
303
- hkt_directive_errors: @rbs_extended_reporter.hkt_directive_errors,
304
- # ADR-109 WD3 — the deprecated-form stream rides the same channel: the payload is read where
305
- # the annotated method is dispatched, which under the pool is only ever inside a worker.
306
- deprecated_forms: @rbs_extended_reporter.deprecated_forms
307
- },
308
- boundary_cross: @boundary_cross_reporter.entries,
309
- source_rbs_synthesis: @source_rbs_synthesis_reporter.entries,
310
- definition_build_failures: @environment&.rbs_loader&.definition_build_failures || [],
311
- hkt_scan_failure: @environment&.hkt_scan_failure
312
- }
328
+ facts = Runner::RunLevelFacts.drain(@environment)
329
+ Runner::RunLevelFacts.snapshot_reporters(reporters).merge(facts)
330
+ end
331
+
332
+ # The per-worker reporter accumulators, by {Runner::RunLevelFacts::REPORTERS} key.
333
+ def reporters
334
+ { rbs_extended: @rbs_extended_reporter, boundary_cross: @boundary_cross_reporter,
335
+ source_rbs_synthesis: @source_rbs_synthesis_reporter }
313
336
  end
337
+ private :reporters
314
338
 
315
339
  # ADR-46 — the per-file {DependencyRecorder::Record}s captured by this session's `#analyze` calls (empty
316
340
  # unless `record_dependencies:` was set). Marshal-clean (Data of frozen Sets / String Hashes), so the
@@ -426,40 +450,10 @@ module Rigor
426
450
  Prism.parse(File.read(physical), filepath: path, version: @configuration.target_ruby)
427
451
  end
428
452
 
429
- # Mirrors {Runner#build_trust_policy}. Deriving trust inside the session keeps the substrate decoupled
430
- # from the coordinator; configuration is already Ractor-shareable.
453
+ # {Rigor::Plugin::TrustPolicy.for_configuration} is the single derivation `rigor check`, pooled
454
+ # workers and `rigor unused`'s plugin roots share (#2586); configuration is already Ractor-shareable.
431
455
  def build_trust_policy
432
- trusted_gems = @configuration.plugins.map { |entry| trusted_gem_name(entry) }.uniq
433
- roots = [Dir.pwd]
434
- Array(@configuration.signature_paths).each { |sp| roots << File.absolute_path(sp) }
435
- trusted_gems.each do |gem_name|
436
- path = trusted_gem_root(gem_name)
437
- roots << path if path
438
- end
439
- @configuration.plugins_io_allowed_paths.each { |p| roots << File.absolute_path(p) }
440
-
441
- Plugin::TrustPolicy.new(
442
- trusted_gems: trusted_gems,
443
- allowed_read_roots: roots,
444
- network_policy: @configuration.plugins_io_network,
445
- allowed_url_hosts: @configuration.plugins_io_allowed_url_hosts
446
- )
447
- end
448
-
449
- def trusted_gem_name(entry)
450
- case entry
451
- when String then entry
452
- when Hash then entry["gem"] || entry["id"]
453
- end
454
- end
455
-
456
- def trusted_gem_root(gem_name)
457
- return nil if gem_name.nil? || gem_name.empty?
458
-
459
- spec = Gem.loaded_specs[gem_name]
460
- spec&.full_gem_path # rigor:disable undefined-method
461
- rescue StandardError
462
- nil
456
+ Plugin::TrustPolicy.for_configuration(@configuration)
463
457
  end
464
458
 
465
459
  def run_plugin_prepare
@@ -480,7 +474,7 @@ module Rigor
480
474
  def plugin_prepare_error_diagnostic(plugin, error)
481
475
  plugin_id = safe_plugin_id(plugin)
482
476
  Diagnostic.new(
483
- path: ".rigor.yml",
477
+ path: @configuration.config_display_path,
484
478
  line: 1,
485
479
  column: 1,
486
480
  message: "plugin #{plugin_id.inspect} raised during prepare: " \
@@ -9,11 +9,9 @@ module Rigor
9
9
  # *today's* rules are) and is versioned with the gem, NOT a user-supplied file: the
10
10
  # inspectable counterpart to PHPStan's `bleedingEdge` include.
11
11
  #
12
- # The WD2 foundation slice wired the surface end-to-end (`bleeding_edge:` config, the
13
- # `rigor show-bleedingedge` command, the severity-composition hook in
14
- # {Configuration::SeverityProfile.resolve}), so a *severity* discipline lands as a single
15
- # {FEATURES} entry with no engine plumbing — as the first one,
16
- # `reject-unparseable-signatures`, does.
12
+ # The surface is wired end-to-end (`bleeding_edge:` config, `rigor show-bleedingedge`, the
13
+ # severity-composition hook in {Configuration::SeverityProfile.resolve}), so a *severity*
14
+ # discipline is a single {FEATURES} entry with no engine plumbing.
17
15
  #
18
16
  # A queued change whose effect is not a severity move — a measurement, an algorithm, or a
19
17
  # default that changes while every rule keeps its severity — is a `:behaviour` feature
@@ -40,7 +38,6 @@ module Rigor
40
38
  # {Configuration#bleeding_edge_active?}.
41
39
  KINDS = %i[severity behaviour].freeze
42
40
 
43
- # The severity map a `:behaviour` feature carries: none.
44
41
  NO_SEVERITY_OVERRIDES = {}.freeze
45
42
 
46
43
  # One queued change.
@@ -129,6 +126,20 @@ module Rigor
129
126
  "static.value-use.void" => :warning
130
127
  }.freeze
131
128
  ),
129
+ # ADR-128 WD6 / ADR-130 slice 2 (#2686). Off by default because a new required diagnostic is an ADR-50
130
+ # Decision 3 new discipline; it graduates at the next hard break.
131
+ Feature.new(
132
+ id: "plugin-api-surface",
133
+ kind: :severity,
134
+ summary: "In plugin source (code inside a class that inherits `Rigor::Plugin::Base`), a call on a value " \
135
+ "typed by a `Rigor::Plugin` interface (`_Scope`, `_Environment`, `_Services`, ...) to a member " \
136
+ "the interface does not declare becomes a `:warning` (`plugin.api-surface`). The engine object " \
137
+ "answers such a call today, so it is not `call.undefined-method`; it is outside the plugin API " \
138
+ "Rigor promises to keep (ADR-128).",
139
+ severity_overrides: {
140
+ "plugin.api-surface" => :warning
141
+ }.freeze
142
+ ),
132
143
  Feature.new(
133
144
  id: "discovery-seeded-mutation-sites",
134
145
  kind: :behaviour,
@@ -159,7 +170,7 @@ module Rigor
159
170
  "kills — every surviving breakage there is one the analyzer reports nowhere at all, not one " \
160
171
  "it reports in a caller."
161
172
  ),
162
- # ADR-103 WD15 — the owner-ruled preview of the v0.4.0 default. `:behaviour` because it moves no rule's
173
+ # ADR-103 WD15 — the owner-ruled preview of the v0.5.0 default. `:behaviour` because it moves no rule's
163
174
  # severity; safe under the header comment's cache-identity rule for a reason specific to this feature
164
175
  # rather than the generic WD13 argument, so it is spelled out here: `effects:` collection is
165
176
  # OBSERVATIONAL (ADR-103 WD13) and the `effects:` block itself is deliberately absent from the
@@ -177,9 +188,10 @@ module Rigor
177
188
  "it had written `effects: {}` — effect collection, the `rigor effects` verbs' cache sharing, " \
178
189
  "and `effects.check` all turn on with every sub-key at its default. Writing `effects: false` " \
179
190
  "explicitly still opts out, and a project that writes its own `effects:` block keeps that " \
180
- "block's shape untouched either way. Graduates to default-on at v0.4.0 (owner ruling, " \
181
- "2026-08-17): ADR-50 § WD7 reads \"at a major\" as \"at v0.4.0\" for this feature on the 0.x " \
182
- "evaluation line. See ADR-103 § WD15 for the preconditions that gate the flip."
191
+ "block's shape untouched either way. Graduates to default-on at v0.5.0 (owner ruling, " \
192
+ "2026-10-26, moving the v0.4.0 date of 2026-08-17): ADR-50 § WD7 reads \"at a major\" as the " \
193
+ "version that ruling names, for this feature on the 0.x evaluation line. See ADR-103 § WD15 " \
194
+ "for the preconditions that gate the flip and § WD18 for the move."
183
195
  )
184
196
  ].freeze
185
197
 
@@ -113,9 +113,8 @@ module Rigor
113
113
  },
114
114
  # ADR-13 § "Canonical type-function additions" — five shape-projection type functions
115
115
  # that the `rigor-typescript-utility-types` plugin (and any other plugin that ships a
116
- # shape-projection vocabulary) maps onto. Phase A handles `HashShape` carriers;
117
- # non-shape inputs return the input unchanged (the lossy-projection diagnostic lands
118
- # in slice 5).
116
+ # shape-projection vocabulary) maps onto. Non-shape inputs return the input unchanged;
117
+ # the Resolver records `dynamic.shape.lossy-projection` for them.
119
118
  "pick_of" => lambda { |args|
120
119
  return nil unless args.size == 2
121
120
 
@@ -148,7 +147,7 @@ module Rigor
148
147
  # `Class[range]` — a numeric class head bounded by a Ruby range literal (ADR-109). The
149
148
  # builder receives the `Range` value and returns the carrier, or `nil` when the range is
150
149
  # empty or its endpoints are not literals of the head class; `nil` surfaces as the ordinary
151
- # unresolved-payload diagnostic. `Float` joins this table with the `FloatRange` carrier.
150
+ # unresolved-payload diagnostic. `Float` builds a `FloatRange` carrier.
152
151
  RANGE_HEAD_BUILDERS = {
153
152
  "Integer" => lambda { |range|
154
153
  lo = range.begin
@@ -186,8 +185,7 @@ module Rigor
186
185
  # `Array<Integer>` and returns a `Rigor::Type` (or `nil`). Bounds are signed integer
187
186
  # literals. A reversed pair (`min` > `max`) is declined as `nil` like every other shape
188
187
  # mismatch, so the caller surfaces `dynamic.rbs-extended.unresolved` at the annotation;
189
- # `Type::IntegerRange` raises on it instead, and that `ArgumentError` used to escape as an
190
- # `internal analyzer error` that abandoned the whole Ruby file.
188
+ # `Type::IntegerRange` would raise on it instead, which must not escape the analyzer.
191
189
  PARAMETERISED_INT_BUILDERS = {
192
190
  "int" => lambda { |bounds|
193
191
  return nil unless bounds.size == 2
@@ -276,14 +274,14 @@ module Rigor
276
274
  # grammar:
277
275
  #
278
276
  # type := simple_name | parametric
279
- # simple_name := /[a-z][a-z0-9-]*/
277
+ # simple_name := /[a-z][a-z0-9_-]*/
280
278
  # parametric := simple_name '[' type_arg_list ']'
281
279
  # | simple_name '<' int_bound_list '>'
282
280
  # type_arg_list := type_arg (',' type_arg)*
283
281
  # type_arg := range_literal | type | class_name
284
282
  # class_name := /[A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*/
285
283
  # range_literal := endpoint? ('..' | '...') endpoint? (at least one side written)
286
- # endpoint := signed_int | 'nil'
284
+ # endpoint := signed_int | float_literal | 'nil' | 'Float::INFINITY' | 'Float::MAX'
287
285
  # int_bound_list := signed_int (',' signed_int)*
288
286
  # signed_int := /-?\d+/
289
287
  #
@@ -62,7 +62,7 @@ module Rigor
62
62
  # `RBS::VERSION` while Rigor checks itself, rubygems and Bundler internals while it checks
63
63
  # CRuby's `lib/` — and that is worse than no answer: identical source would type differently
64
64
  # depending on how the analyzer was installed. Removing an ad hoc `Object.const_get` from an
65
- # analysis path is the same move ADR-4 § 330 already made for predicate narrowing.
65
+ # analysis path is the same move ADR-4 already made for predicate narrowing.
66
66
  #
67
67
  # This module is consulted by `Environment#constant_for_name` BEFORE the RBS
68
68
  # constant-type table (widest types) but AFTER in-source constant writes (the user's own
@@ -8,13 +8,11 @@ module Rigor
8
8
  # carriers Rigor already ships (`decimal-int-string`, `hex-int-string`,
9
9
  # `octal-int-string`, `lowercase-string`, `uppercase-string`, `numeric-string`). See
10
10
  # `docs/type-specification/imported-built-in-types.md` for the registry the refinements
11
- # come from and `docs/ROADMAP.md` § "v0.1.1 — Planned" Track 1 slice 1 for the binding
12
- # scope of this recogniser.
11
+ # come from.
13
12
  #
14
- # The intended consumer is `Inference::Narrowing.analyse_match_write`: given `if
15
- # /(?<year>\d+)/ =~ str; year; end`, the v0.1.0 baseline narrows `year` to plain `String`;
16
- # v0.1.1 introspects the regex source and narrows further to `decimal-int-string`
17
- # whenever the named-capture body matches one of the rows in {RULES}.
13
+ # The consumer is `Inference::Narrowing.analyse_match_write`: given `if /(?<year>\d+)/ =~ str;
14
+ # year; end`, `year` narrows to `decimal-int-string` when the named-capture body matches a row
15
+ # in {RULES}, and stays plain `String` otherwise.
18
16
  #
19
17
  # Recognised body shapes (each row admits the `+` quantifier and the bounded `{n}` /
20
18
  # `{n,m}` forms with `n >= 1`):
@@ -10,7 +10,8 @@ module Rigor
10
10
  module Cache
11
11
  # Cache invalidation descriptor — the typed-slot schema fixed by
12
12
  # [`docs/design/20260505-cache-slice-taxonomy.md`](../../../docs/design/20260505-cache-slice-taxonomy.md).
13
- # Pure value object: no I/O, no global state, fully immutable after construction. The storage layer
13
+ # Value object, fully immutable after construction. Filesystem reads happen only in the `FileEntry.stat`,
14
+ # `FileEntry.kind` and `GlobEntry.compute` builders and in {#fresh?}. The storage layer
14
15
  # ([`Rigor::Cache::Store`](store.rb), v0.0.8 slice 2) consumes descriptors but does not extend them.
15
16
  #
16
17
  # The descriptor has six slots (`files`, `gems`, `plugins`, `configs`, `dependencies`, `globs`); every slot is
@@ -462,10 +463,10 @@ module Rigor
462
463
 
463
464
  # ADR-45 — re-validates this descriptor's recorded {FileEntry}s against the current filesystem. Used by
464
465
  # the record-and-validate run-result cache: a value cached alongside its dependency descriptor is fresh
465
- # iff every recorded file still matches. Only `files` are checked — non-file inputs (config / gems /
466
+ # iff every recorded file still matches. Only `files` and `globs` are checked — non-file inputs (config / gems /
466
467
  # version) belong in the cache *key*, not the validated dependency set — so a descriptor carrying any
467
468
  # non-file slot is never considered fresh (it was built wrong for this use). ADR-60 WD3 adds `globs`
468
- # alongside `files` as a re-validatable slot: a {GlobEntry} is fresh when re-globbing + re-digesting
469
+ # alongside `files` as a re-validatable slot: a {GlobEntry} is fresh when re-globbing
469
470
  # reproduces its recorded value. ADR-45 WD1 (#577): an absence row ({FileEntry.absent}) is fresh
470
471
  # while its path is still missing — the negative half of the dependency set.
471
472
  def fresh?
@@ -581,8 +582,8 @@ module Rigor
581
582
  false
582
583
  end
583
584
 
584
- # ADR-60 WD3 / ADR-87 WD2 — stat-validates the entry's per-file table against the live tree, re-hashing
585
- # only stat-moved files. Any failure reads as stale (recompute), never a crash.
585
+ # ADR-60 WD3 / ADR-87 WD2 — recomputes the entry's signature over the live tree and compares it. Any
586
+ # failure reads as stale (recompute), never a crash.
586
587
  def glob_entry_fresh?(entry)
587
588
  GlobEntry.fresh?(entry)
588
589
  end