rigortype 0.3.8 → 0.3.9

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 (447) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
  5. data/docs/handbook/02-everyday-types.md +17 -14
  6. data/docs/handbook/03-narrowing.md +37 -4
  7. data/docs/handbook/05-methods-and-blocks.md +1 -1
  8. data/docs/handbook/06-classes.md +2 -2
  9. data/docs/handbook/07-rbs-and-extended.md +14 -1
  10. data/docs/handbook/11-sig-gen.md +72 -1
  11. data/docs/handbook/README.md +1 -1
  12. data/docs/handbook/appendix-elixir.md +2 -2
  13. data/docs/handbook/appendix-go.md +2 -2
  14. data/docs/handbook/appendix-java-csharp.md +2 -2
  15. data/docs/handbook/appendix-mypy.md +2 -2
  16. data/docs/handbook/appendix-phpstan.md +1 -1
  17. data/docs/handbook/appendix-rust.md +1 -1
  18. data/docs/handbook/appendix-type-theory.md +4 -4
  19. data/docs/handbook/appendix-typescript.md +1 -1
  20. data/docs/llms.txt +2 -0
  21. data/docs/manual/02-cli-reference.md +32 -4
  22. data/docs/manual/03-configuration.md +11 -0
  23. data/docs/manual/04-diagnostics.md +11 -0
  24. data/docs/manual/07-plugins.md +15 -5
  25. data/docs/manual/08-skills.md +23 -2
  26. data/docs/manual/11-ci.md +9 -0
  27. data/docs/manual/15-type-protection-coverage.md +8 -0
  28. data/docs/manual/16-rbs-extended-annotations.md +6 -1
  29. data/docs/manual/18-removing-dead-code.md +10 -8
  30. data/docs/manual/plugins/rigor-actionpack.md +18 -0
  31. data/docs/manual/plugins/rigor-activerecord.md +28 -0
  32. data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
  33. data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
  34. data/lib/rigor/analysis/baseline.rb +2 -2
  35. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  36. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  37. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  38. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  40. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
  42. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  43. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  44. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  45. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  46. data/lib/rigor/analysis/check_rules.rb +200 -32
  47. data/lib/rigor/analysis/crash_signature.rb +8 -24
  48. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  49. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  50. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  51. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  52. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  53. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  54. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  55. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  56. data/lib/rigor/analysis/fact_store.rb +6 -1
  57. data/lib/rigor/analysis/incremental.rb +16 -0
  58. data/lib/rigor/analysis/incremental_session.rb +81 -22
  59. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  60. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  61. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
  62. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  63. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  64. data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
  65. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  66. data/lib/rigor/analysis/result.rb +1 -3
  67. data/lib/rigor/analysis/rule_catalog.rb +4 -1
  68. data/lib/rigor/analysis/run_cache_key.rb +1 -1
  69. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  70. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  71. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  72. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +177 -26
  73. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  74. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
  75. data/lib/rigor/analysis/runner/pool_coordinator.rb +256 -88
  76. data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
  77. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  78. data/lib/rigor/analysis/runner.rb +133 -35
  79. data/lib/rigor/analysis/worker_session.rb +9 -10
  80. data/lib/rigor/bleeding_edge.rb +12 -24
  81. data/lib/rigor/builtins/hkt_builtins.rb +2 -2
  82. data/lib/rigor/builtins/imported_refinements.rb +155 -21
  83. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  84. data/lib/rigor/builtins/regex_refinement.rb +4 -4
  85. data/lib/rigor/builtins/static_return_refinements.rb +5 -6
  86. data/lib/rigor/cache/annotation_location.rb +2 -4
  87. data/lib/rigor/cache/descriptor.rb +58 -20
  88. data/lib/rigor/cache/engine_source.rb +4 -4
  89. data/lib/rigor/cache/file_digest.rb +9 -1
  90. data/lib/rigor/cache/incremental_snapshot.rb +40 -6
  91. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  92. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  93. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  94. data/lib/rigor/cache/rbs_descriptor.rb +79 -11
  95. data/lib/rigor/cache/rbs_environment.rb +8 -4
  96. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  97. data/lib/rigor/cache/store.rb +77 -32
  98. data/lib/rigor/cli/annotate_command.rb +5 -6
  99. data/lib/rigor/cli/check_command.rb +57 -10
  100. data/lib/rigor/cli/check_invocation.rb +6 -11
  101. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  102. data/lib/rigor/cli/coverage_command.rb +1 -1
  103. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  104. data/lib/rigor/cli/coverage_scan.rb +1 -5
  105. data/lib/rigor/cli/diff_command.rb +1 -1
  106. data/lib/rigor/cli/doc_links.rb +3 -3
  107. data/lib/rigor/cli/docs_command.rb +2 -2
  108. data/lib/rigor/cli/doctor_command.rb +85 -38
  109. data/lib/rigor/cli/effects_command.rb +2 -2
  110. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  111. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  112. data/lib/rigor/cli/explain_command.rb +34 -1
  113. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  114. data/lib/rigor/cli/lsp_command.rb +1 -1
  115. data/lib/rigor/cli/mcp_command.rb +1 -1
  116. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  117. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  118. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  119. data/lib/rigor/cli/plugin_command.rb +1 -1
  120. data/lib/rigor/cli/plugins_command.rb +2 -1
  121. data/lib/rigor/cli/prism_colorizer.rb +2 -2
  122. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  123. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  124. data/lib/rigor/cli/sig_gen_command.rb +2 -2
  125. data/lib/rigor/cli/skill_command.rb +1 -1
  126. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  127. data/lib/rigor/cli/skill_describe.rb +36 -35
  128. data/lib/rigor/cli/trace_command.rb +4 -4
  129. data/lib/rigor/cli/trace_renderer.rb +5 -6
  130. data/lib/rigor/cli/triage_command.rb +1 -1
  131. data/lib/rigor/cli/type_of_command.rb +10 -6
  132. data/lib/rigor/cli/type_scan_command.rb +4 -4
  133. data/lib/rigor/cli/unused_command.rb +11 -3
  134. data/lib/rigor/cli/upgrade_command.rb +1 -1
  135. data/lib/rigor/cli.rb +68 -6
  136. data/lib/rigor/config_audit.rb +30 -5
  137. data/lib/rigor/configuration/severity_profile.rb +6 -6
  138. data/lib/rigor/configuration.rb +43 -4
  139. data/lib/rigor/effects/attribution.rb +1 -3
  140. data/lib/rigor/effects/config_envelopes.rb +6 -8
  141. data/lib/rigor/effects/definition_lines.rb +25 -6
  142. data/lib/rigor/effects/effect_table.rb +0 -1
  143. data/lib/rigor/effects/entry_points.rb +0 -2
  144. data/lib/rigor/effects/envelope_check.rb +8 -8
  145. data/lib/rigor/effects/envelope_index.rb +5 -8
  146. data/lib/rigor/effects/framework_units.rb +4 -6
  147. data/lib/rigor/effects/identity.rb +4 -6
  148. data/lib/rigor/effects/inline_anchor.rb +7 -7
  149. data/lib/rigor/effects/label_intent.rb +3 -4
  150. data/lib/rigor/effects/liskov_check.rb +8 -8
  151. data/lib/rigor/effects/method_key.rb +1 -1
  152. data/lib/rigor/effects/plugin_facts.rb +4 -6
  153. data/lib/rigor/effects/propagator.rb +2 -3
  154. data/lib/rigor/effects/scanner.rb +1 -1
  155. data/lib/rigor/effects/signature_sources.rb +3 -5
  156. data/lib/rigor/effects/snapshot.rb +10 -10
  157. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  158. data/lib/rigor/effects/unit_scan.rb +10 -10
  159. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  160. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  161. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  162. data/lib/rigor/environment/class_registry.rb +3 -1
  163. data/lib/rigor/environment/failure_slot.rb +2 -2
  164. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  165. data/lib/rigor/environment/lockfile_resolver.rb +51 -4
  166. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  167. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  168. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  169. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  170. data/lib/rigor/environment/rbs_loader.rb +388 -59
  171. data/lib/rigor/environment.rb +105 -20
  172. data/lib/rigor/flow_contribution/merger.rb +0 -2
  173. data/lib/rigor/flow_contribution.rb +11 -13
  174. data/lib/rigor/inference/acceptance.rb +153 -8
  175. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  176. data/lib/rigor/inference/body_fixpoint.rb +5 -5
  177. data/lib/rigor/inference/budget_trace.rb +1 -5
  178. data/lib/rigor/inference/captured_locals.rb +2 -3
  179. data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
  180. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  181. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  182. data/lib/rigor/inference/element_read_widening.rb +168 -0
  183. data/lib/rigor/inference/expression_typer.rb +763 -143
  184. data/lib/rigor/inference/fork_map.rb +5 -7
  185. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  186. data/lib/rigor/inference/hkt_registry.rb +4 -7
  187. data/lib/rigor/inference/index_write_widening.rb +1 -5
  188. data/lib/rigor/inference/macro_block_self_type.rb +1 -4
  189. data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
  190. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  191. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  192. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  193. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  194. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
  195. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  197. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  198. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  199. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
  200. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  201. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
  202. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  203. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  204. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  205. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  206. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  208. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  209. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
  210. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  211. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  212. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  213. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  214. data/lib/rigor/inference/method_dispatcher.rb +12 -13
  215. data/lib/rigor/inference/method_parameter_binder.rb +4 -6
  216. data/lib/rigor/inference/multi_target_binder.rb +1 -3
  217. data/lib/rigor/inference/mutation_rejoin.rb +168 -0
  218. data/lib/rigor/inference/mutation_widening.rb +100 -51
  219. data/lib/rigor/inference/narrowing.rb +299 -46
  220. data/lib/rigor/inference/optimistic_origin.rb +1 -7
  221. data/lib/rigor/inference/origin_lookup.rb +2 -3
  222. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  223. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  224. data/lib/rigor/inference/precision_scanner.rb +3 -4
  225. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  226. data/lib/rigor/inference/project_patched_scanner.rb +3 -3
  227. data/lib/rigor/inference/protection_scanner.rb +1 -2
  228. data/lib/rigor/inference/range_constant.rb +57 -0
  229. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  230. data/lib/rigor/inference/receiver_alias.rb +3 -3
  231. data/lib/rigor/inference/refinement_mutation.rb +72 -0
  232. data/lib/rigor/inference/scope_indexer.rb +688 -151
  233. data/lib/rigor/inference/statement_evaluator.rb +221 -50
  234. data/lib/rigor/inference/string_mutation.rb +60 -0
  235. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  236. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  237. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  238. data/lib/rigor/inference/version_guard.rb +28 -21
  239. data/lib/rigor/inference/void_origin.rb +3 -3
  240. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  241. data/lib/rigor/language_server/buffer_table.rb +4 -4
  242. data/lib/rigor/language_server/completion_provider.rb +1 -1
  243. data/lib/rigor/language_server/debouncer.rb +1 -1
  244. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  245. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  246. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  247. data/lib/rigor/language_server/hover_provider.rb +1 -1
  248. data/lib/rigor/language_server/hover_renderer.rb +2 -2
  249. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  250. data/lib/rigor/language_server/project_context.rb +2 -5
  251. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  252. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  253. data/lib/rigor/language_server/server.rb +9 -9
  254. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  255. data/lib/rigor/language_server/uri.rb +1 -1
  256. data/lib/rigor/plugin/base.rb +90 -21
  257. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  258. data/lib/rigor/plugin/effect_attribution.rb +7 -7
  259. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  260. data/lib/rigor/plugin/fact_store.rb +6 -6
  261. data/lib/rigor/plugin/io_boundary.rb +93 -8
  262. data/lib/rigor/plugin/isolation.rb +29 -6
  263. data/lib/rigor/plugin/loader.rb +31 -12
  264. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  265. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  266. data/lib/rigor/plugin/manifest.rb +23 -1
  267. data/lib/rigor/plugin/registry.rb +42 -12
  268. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
  269. data/lib/rigor/plugin/trust_policy.rb +2 -4
  270. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  271. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  272. data/lib/rigor/project_environment.rb +138 -0
  273. data/lib/rigor/protection/analysis_guard.rb +93 -14
  274. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  275. data/lib/rigor/protection/dependency_closure.rb +5 -8
  276. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  277. data/lib/rigor/protection/discovery_seed.rb +11 -11
  278. data/lib/rigor/protection/kill_signature.rb +2 -4
  279. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  280. data/lib/rigor/protection/mutation_cache.rb +6 -9
  281. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  282. data/lib/rigor/protection/mutator.rb +2 -1
  283. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  284. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  285. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  286. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  287. data/lib/rigor/rbs_extended.rb +61 -21
  288. data/lib/rigor/reflection/constant_path.rb +126 -0
  289. data/lib/rigor/reflection.rb +26 -10
  290. data/lib/rigor/runtime/jit.rb +6 -8
  291. data/lib/rigor/scope/discovery_index.rb +30 -0
  292. data/lib/rigor/scope.rb +203 -4
  293. data/lib/rigor/sig_gen/generator.rb +115 -22
  294. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  295. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  296. data/lib/rigor/sig_gen/observation_collector.rb +17 -15
  297. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  298. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  299. data/lib/rigor/sig_gen/renderer.rb +4 -5
  300. data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
  301. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  302. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  303. data/lib/rigor/sig_gen/writer.rb +23 -6
  304. data/lib/rigor/signature_path_audit.rb +153 -6
  305. data/lib/rigor/source/literals.rb +0 -23
  306. data/lib/rigor/source/node_children.rb +0 -2
  307. data/lib/rigor/source/node_locator.rb +5 -11
  308. data/lib/rigor/source/node_walker.rb +2 -5
  309. data/lib/rigor/triage/catalogue.rb +1 -3
  310. data/lib/rigor/triage.rb +3 -5
  311. data/lib/rigor/type/accepts_result.rb +24 -6
  312. data/lib/rigor/type/combinator.rb +68 -5
  313. data/lib/rigor/type/data_class.rb +2 -2
  314. data/lib/rigor/type/data_instance.rb +4 -4
  315. data/lib/rigor/type/float_range.rb +128 -0
  316. data/lib/rigor/type/hash_shape.rb +5 -5
  317. data/lib/rigor/type/integer_range.rb +13 -8
  318. data/lib/rigor/type/nominal.rb +8 -2
  319. data/lib/rigor/type/refined.rb +3 -3
  320. data/lib/rigor/type/struct_class.rb +3 -3
  321. data/lib/rigor/type/struct_instance.rb +4 -4
  322. data/lib/rigor/type.rb +1 -0
  323. data/lib/rigor/type_node/generic.rb +1 -1
  324. data/lib/rigor/type_node/range_literal.rb +25 -0
  325. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  326. data/lib/rigor/type_node.rb +1 -0
  327. data/lib/rigor/version.rb +1 -1
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  329. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  330. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  331. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
  332. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  333. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  334. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  335. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
  337. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  338. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  339. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
  340. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  341. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  342. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  343. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  344. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  345. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  346. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
  347. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
  348. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  349. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  350. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
  351. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  352. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
  353. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  354. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
  355. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  356. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
  357. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  358. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  359. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  360. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  361. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  362. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  363. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  364. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  365. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  366. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  367. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  368. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  369. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  370. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  371. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  372. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
  373. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  374. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  375. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
  376. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  377. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  378. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  379. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  380. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  381. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
  382. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  383. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  384. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  385. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
  386. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  387. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
  388. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
  389. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
  390. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
  395. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  396. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  397. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
  398. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  399. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  400. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  403. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  404. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  405. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  406. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  407. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  408. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  409. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  410. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
  411. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
  414. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  415. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  416. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  417. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
  423. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  424. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  425. data/sig/rigor/cache.rbs +2 -3
  426. data/sig/rigor/environment.rbs +5 -0
  427. data/sig/rigor/inference.rbs +3 -10
  428. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  429. data/sig/rigor/plugin/manifest.rbs +2 -1
  430. data/sig/rigor/reflection.rbs +3 -1
  431. data/sig/rigor/scope.rbs +24 -3
  432. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
  433. data/sig/rigor/type.rbs +27 -1
  434. data/sig/rigor.rbs +1 -0
  435. data/skills/rigor-ask/SKILL.md +8 -6
  436. data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
  437. data/skills/rigor-project-init/SKILL.md +6 -3
  438. data/skills/rigor-project-init/references/02-configure.md +10 -6
  439. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  440. data/skills/rigor-type-oracle/SKILL.md +228 -0
  441. data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
  442. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  443. data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
  444. data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
  445. metadata +26 -3
  446. data/lib/rigor/cli/probe_environment.rb +0 -85
  447. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -84,7 +84,29 @@ module Rigor
84
84
  # `[]`, deserialises cleanly, and would send every unchanged file's top-level helper back to the peel
85
85
  # on a warm run while a cold run resolved at the top level — the `--verify-incremental` divergence 14
86
86
  # was bumped for, in the same table.
87
- SCHEMA = 17
87
+ # 18: issue #728 re-keys each seed bundle's `header_nestings` VALUE from one chain per class to one
88
+ # chain per ancestor NAME the class's sites wrote, plus an unkeyed chain holding the old union. A
89
+ # pre-18 blob's Array value is not a Hash and the resolver's per-name lookup would raise on it, so the
90
+ # gate has to reject it — and even a reader that tolerated the old shape would serve the per-class
91
+ # union for every unchanged file, which is the answer this issue exists to stop giving.
92
+ # 19: issue #668 re-keys a constant write through a base no name reaches — `k::LIMIT = 7` — from the
93
+ # bare last segment to the wildcard `*::LIMIT`, in `constant_decls` and in each seed bundle's
94
+ # `constant_writes` census alike. A pre-19 blob's `LIMIT` key deserialises cleanly and reads as an
95
+ # ordinary top-level write, so every unchanged file would keep publishing the `Foo::LIMIT` the write
96
+ # may already have replaced while a cold run made it gradual — the `--verify-incremental` divergence
97
+ # 14 was bumped for.
98
+ # 20: issue #722 residue 2 adds each seed bundle's compact-header re-anchor census, without which a
99
+ # warm fold sees no candidate for an unchanged file and keeps serving the pre-fix `Wrap::Outer::Leaf`
100
+ # for a `class Outer::Leaf` that reopens `::Outer::Leaf`.
101
+ # 21: issue #915 widens the seed bundle's `extends` table to a singleton-body `include` / `prepend`;
102
+ # a pre-21 bundle for an unchanged file would keep the narrower record and still report the
103
+ # `case Widget when M` arm unreachable.
104
+ # 22: issues #796 / #794 add `run_level_rows` — the cold run's `definition-build-failed` set and its
105
+ # HKT-scan outcome, the two rows a narrowed run cannot re-derive from the files it analyses. A pre-22
106
+ # blob has no such section and would deserialise it as nil, which reads as "nothing cached" and is
107
+ # therefore not misread — but a snapshot written by an engine that did not yet REPLAY them was also
108
+ # written by one whose narrowed runs under-reported, so the gate rejects it and the next run is cold.
109
+ SCHEMA = 22
88
110
 
89
111
  # The persisted per-file state.
90
112
  # `cache` maps an analyzed file to its diagnostics.
@@ -136,11 +158,21 @@ module Rigor
136
158
  # collection was off). It is a SEPARATE gate from the global `fingerprint`: the `effects:` block is
137
159
  # deliberately absent from `Configuration#to_h`, so turning collection on invalidates no diagnostics,
138
160
  # and a vocabulary / catalogue / `effects:` change must invalidate the summaries alone.
161
+ # Issues #796 / #794 — the run-level rows a narrowed run replays instead of re-deriving; see
162
+ # docs/internal-spec/cache.md § "Run-level rows the snapshot carries" for why exactly these two are
163
+ # fresh under the fingerprint and why no other `.rigor.yml`-level row may join them.
139
164
  Payload = Data.define(:cache, :sources, :digests, :analyzed,
140
165
  :symbol_sources, :ancestry_sources, :symbol_fingerprints,
141
166
  :missing, :class_decls, :constant_decls, :seed_bundles, :plugin_fact_digest,
142
167
  :return_summaries, :param_table,
143
- :effect_collections, :effects_identity)
168
+ :effect_collections, :effects_identity, :run_level_rows)
169
+
170
+ # Issues #796 / #794 — the two run-level rows the snapshot carries. `definition_build_failures` is the
171
+ # reported per-class detail list ({Environment::RbsLoader#definition_build_failures}); `hkt_scan_failure`
172
+ # is the `[error_class, first_line, frame, stage]` tuple ({Rigor::Environment#hkt_scan_failure}), nil
173
+ # when the scan succeeded — which is why the presence of this object, not the tuple, is what says a
174
+ # replay is available.
175
+ RunLevelRows = Data.define(:definition_build_failures, :hkt_scan_failure)
144
176
 
145
177
  # The global fingerprint that gates a snapshot load: a digest of the inputs whose change requires a full
146
178
  # rebuild — the engine version + schema, the engine's own SOURCE when the version does not pin it, the
@@ -222,8 +254,8 @@ module Rigor
222
254
  # The fingerprint that matched is returned alongside the payload so the caller can mix it into ITS own
223
255
  # cache key — the snapshot's identity is exactly what "these dependency edges came from that world" means.
224
256
  #
225
- # @param fingerprints [Array<String>] candidates, most-specific first.
226
- # @return [Array(String, Payload), nil] `[matched fingerprint, payload]`, or nil on any miss.
257
+ # @param fingerprints candidates, most-specific first.
258
+ # @return `[matched fingerprint, payload]`, or nil on any miss.
227
259
  def load_any(fingerprints:)
228
260
  data = read_data
229
261
  return nil if data.nil?
@@ -259,7 +291,8 @@ module Rigor
259
291
  return_summaries: data[:return_summaries] || {},
260
292
  param_table: data[:param_table] || {},
261
293
  effect_collections: data[:effect_collections] || {},
262
- effects_identity: data[:effects_identity]
294
+ effects_identity: data[:effects_identity],
295
+ run_level_rows: data[:run_level_rows]
263
296
  )
264
297
  end
265
298
  private :payload_from
@@ -283,7 +316,8 @@ module Rigor
283
316
  return_summaries: payload.return_summaries,
284
317
  param_table: payload.param_table,
285
318
  effect_collections: payload.effect_collections,
286
- effects_identity: payload.effects_identity
319
+ effects_identity: payload.effects_identity,
320
+ run_level_rows: payload.run_level_rows
287
321
  )
288
322
  blob = Zlib::Deflate.deflate(raw)
289
323
  tmp = "#{@path}.#{Process.pid}.tmp"
@@ -20,9 +20,6 @@ module Rigor
20
20
  class RbsClassAncestorTable < RbsCacheProducer
21
21
  PRODUCER_ID = "rbs.class_ancestor_table"
22
22
 
23
- # @param loader [Rigor::Environment::RbsLoader]
24
- # @param store [Rigor::Cache::Store]
25
- # @return [Hash{String => Array<String>}]
26
23
  def self.compute(loader)
27
24
  table = {}
28
25
  loader.each_known_class_name do |name|
@@ -20,9 +20,6 @@ module Rigor
20
20
  class RbsClassTypeParamNames < RbsCacheProducer
21
21
  PRODUCER_ID = "rbs.class_type_param_names"
22
22
 
23
- # @param loader [Rigor::Environment::RbsLoader]
24
- # @param store [Rigor::Cache::Store]
25
- # @return [Hash{String => Array<Symbol>}]
26
23
  def self.compute(loader)
27
24
  table = {}
28
25
  loader.each_known_class_name do |name|
@@ -15,9 +15,6 @@ module Rigor
15
15
  class RbsConstantTable < RbsCacheProducer
16
16
  PRODUCER_ID = "rbs.constant_type_table"
17
17
 
18
- # @param loader [Rigor::Environment::RbsLoader]
19
- # @param store [Rigor::Cache::Store]
20
- # @return [Hash{String => Rigor::Type}]
21
18
  def self.compute(loader)
22
19
  table = {}
23
20
  loader.each_constant_decl do |name, entry|
@@ -12,13 +12,11 @@ module Rigor
12
12
  # slots, so factoring the construction here keeps the producers small and ensures invalidation behaves
13
13
  # identically across them.
14
14
  module RbsDescriptor
15
- # @param loader [Rigor::Environment::RbsLoader]
16
- # @return [Rigor::Cache::Descriptor]
17
15
  def self.build(loader)
18
16
  Descriptor.new(
19
17
  gems: [rbs_gem_entry],
20
18
  files: file_entries(loader),
21
- configs: config_entries(loader)
19
+ configs: config_entries(loader) + env_only_config_entries(loader)
22
20
  )
23
21
  end
24
22
 
@@ -26,8 +24,9 @@ module Rigor
26
24
  # `gems` + `configs` ({Runner#run_key_descriptor}); the RBS signature-tree `files` are read solely on a
27
25
  # MISS, by the dependency descriptor ({Runner#run_dependency_descriptor}). So a warm HIT never digests
28
26
  # the (large, vendored) RBS tree. {RunDescriptor} is NOT a {Descriptor} — it is never composed, hashed,
29
- # or `==`'d, only its three readers are consulted — so deferring `files` costs no soundness, and `gems`
30
- # / `configs` are byte-identical to {.build} (the key is unchanged).
27
+ # or `==`'d, only its four readers are consulted — so deferring `files` costs no soundness, and `gems`
28
+ # / `configs` match {.build}'s shared slots (the key is unchanged; the one slot the env key adds on
29
+ # top, {.env_only_config_entries}, is never read here).
31
30
  def self.build_run(loader)
32
31
  RunDescriptor.new(loader: loader, gems: [rbs_gem_entry], configs: config_entries(loader))
33
32
  end
@@ -38,19 +37,47 @@ module Rigor
38
37
  [libraries_entry(loader.libraries), virtual_rbs_entry(loader)].compact
39
38
  end
40
39
 
40
+ # Issue #610 — the slots only the env-cache KEY reads, on top of {.config_entries}. Which of the
41
+ # loader's `signature_paths:` are DEFERRED (a bundled plugin's `sig/`, loaded last and allowed to
42
+ # stand down against a colliding generic arity) changes the env built from byte-identical files, so
43
+ # the partition belongs in the key: moving a plugin's `sig/` between `plugins:` and an explicit
44
+ # `signature_paths:` entry changes no file, and an env cached before the producer honoured the
45
+ # partition must read as a miss once. Kept OUT of {.config_entries} deliberately — the run-result
46
+ # key already digests the whole resolved configuration (`RunCacheKey`), and its boot-slimming probe
47
+ # reconstructs {.config_entries} without loading a plugin, so an entry there that it cannot rebuild
48
+ # would turn the warm fast lane into a permanent miss on every project that enables a bundled plugin.
49
+ def self.env_only_config_entries(loader)
50
+ [deferred_signature_paths_entry(loader)].compact
51
+ end
52
+
53
+ def self.deferred_signature_paths_entry(loader)
54
+ return nil unless loader.respond_to?(:deferred_signature_paths)
55
+
56
+ deferred = Array(loader.deferred_signature_paths).map(&:to_s).sort
57
+ return nil if deferred.empty?
58
+
59
+ Descriptor::ConfigEntry.new(
60
+ key: "rbs.deferred_signature_paths",
61
+ value_hash: Digest::SHA256.hexdigest(deferred.join("\n"))
62
+ )
63
+ end
64
+
65
+ private_class_method :deferred_signature_paths_entry
66
+
41
67
  # Public (ADR-87 WD4) so the boot-slimming run-cache probe reconstructs the identical `gems` +
42
68
  # `rbs.libraries` key slots the runner writes, from the config-derived library list, without a loader.
43
69
  def self.rbs_gem_entry
44
70
  Descriptor::GemEntry.new(name: "rbs", requirement: ">= 0", locked: ::RBS::VERSION.to_s)
45
71
  end
46
72
 
47
- # @param comparator [Symbol] `:digest` (default) for the env-cache KEY descriptor ({.build}), where the
73
+ # @param comparator `:digest` (default) for the env-cache KEY descriptor ({.build}), where the
48
74
  # value must be deterministic; `:stat` (ADR-87 WD1) for the validation-only run-dependency descriptor
49
75
  # ({RunDescriptor#files}), where the stat tier short-circuits the SHA-256 on an unmoved file.
50
76
  def self.file_entries(loader, comparator: :digest)
51
77
  roots = loader.signature_paths +
52
78
  Rigor::Environment::RbsLoader.vendored_gem_sig_paths +
53
- Rigor::Environment::RbsLoader.core_overlay_sig_paths
79
+ Rigor::Environment::RbsLoader.core_overlay_sig_paths +
80
+ Rigor::Environment::RbsLoader.capability_role_sig_paths
54
81
  roots.flat_map do |root|
55
82
  next [] unless root.directory?
56
83
 
@@ -65,7 +92,40 @@ module Rigor
65
92
  end
66
93
  end
67
94
 
68
- # @param library_names [Array<String, Symbol>] the loader's merged library list (or, on the WD4 probe
95
+ # Issue #979 the directory-LISTING rows for the signature roots, one {Descriptor::GlobEntry} per
96
+ # root over `**/*.rbs`. {.file_entries} answers "did any signature file I read change", which is a
97
+ # question only about files that existed when the run recorded its dependencies; a `sig/roles.rbs`
98
+ # written afterwards is in no row, so the ADR-45 run-result slot validated fresh and the warm run kept
99
+ # serving diagnostics computed without it. A glob row is the same edge {Plugin::IoBoundary} records
100
+ # for a listed directory (#954): re-globbing on the next run sees the appearance, and one row per root
101
+ # (not per file) keeps the cost a single `Dir.glob`.
102
+ #
103
+ # The rows are `:names` mode, not the default `:stat`: the question they add is which signature files
104
+ # EXIST, and every edit to one of them is already carried by that file's own ADR-87 WD1 `:stat` row —
105
+ # which survives a moved stat tuple by falling back to its recorded content digest. A stat-mode glob
106
+ # would not, so a `git checkout`, a `bundle install`, or a CI run with a restored cache over a fresh
107
+ # checkout — none of which changes a byte of RBS — would turn every warm run into a full re-analysis.
108
+ # Names mode also reads no stat, so it neither pays for nor perturbs the per-run validation-stat memo.
109
+ #
110
+ # Scoped to the loader's own roots — the project's `signature_paths:` (including the auto-detected
111
+ # `sig/`), the bundled-plugin `sig/` trees (ADR-25), the bundle walk, `rbs collection`, the ADR-72
112
+ # overlays. Rigor's own `data/` trees ({RbsLoader.vendored_gem_sig_paths}, `.core_overlay_sig_paths`,
113
+ # `.capability_role_sig_paths`) are engine-owned: a file appears under one only when the engine tree is
114
+ # edited, which normally arrives with the `lib/` change that reads it and so re-keys every
115
+ # computed-value slot through {EngineSource}. They get file rows and no glob — a glob there would cost
116
+ # every project a stat walk of the vendored gem-signature tree per warm run to catch an
117
+ # engine-development edit.
118
+ def self.glob_entries(loader)
119
+ # Deduplicated by path: a root reachable twice (an explicit `signature_paths:` entry that is also a
120
+ # bundled plugin's `sig/`, #610) would otherwise contribute two rows whose stat signatures race.
121
+ loader.signature_paths.uniq(&:to_s).filter_map do |root|
122
+ next unless root.directory?
123
+
124
+ Descriptor::GlobEntry.compute(root: root.to_s, pattern: File.join("**", "*.rbs"), mode: :names)
125
+ end
126
+ end
127
+
128
+ # @param library_names — the loader's merged library list (or, on the WD4 probe
69
129
  # path, `Environment::DEFAULT_LIBRARIES + config.libraries` reconstructed without a loader).
70
130
  def self.libraries_entry(library_names)
71
131
  sorted = library_names.map(&:to_s).sort
@@ -93,10 +153,11 @@ module Rigor
93
153
 
94
154
  private_class_method :virtual_rbs_entry
95
155
 
96
- # The lazy-files run descriptor {RbsDescriptor.build_run} returns. Exposes the three readers the
156
+ # The lazy-files run descriptor {RbsDescriptor.build_run} returns. Exposes the four readers the
97
157
  # run-diagnostics cache consults — `gems` + `configs` are supplied eagerly (they feed the cache KEY,
98
- # and are cheap); `files` (the RBS signature-tree digests, read only on a MISS) is computed once on
99
- # first access and memoised, so a warm HIT never pays for it.
158
+ # and are cheap); `files` (the RBS signature-tree digests) and `globs` (#979, the per-root listing
159
+ # rows) are read only on a MISS, computed once on first access and memoised, so a warm HIT pays for
160
+ # neither.
100
161
  class RunDescriptor
101
162
  attr_reader :gems, :configs
102
163
 
@@ -106,6 +167,13 @@ module Rigor
106
167
  @configs = configs
107
168
  end
108
169
 
170
+ # Issue #979 — one directory-listing row per signature root, so a `.rbs` file APPEARING under one
171
+ # (or vanishing from it) invalidates the run-result slot. Memoised for the same reason `files` is:
172
+ # a collecting run asks twice about the same post-run world.
173
+ def globs
174
+ @globs ||= RbsDescriptor.glob_entries(@loader)
175
+ end
176
+
109
177
  def files
110
178
  # ADR-87 WD1 — this descriptor is validated (never a cache key), so the RBS signature tree rides the
111
179
  # stat-then-digest `:stat` tier: a warm run stat-checks the (large, vendored) tree instead of
@@ -22,14 +22,18 @@ module Rigor
22
22
  class RbsEnvironment < RbsCacheProducer
23
23
  PRODUCER_ID = "rbs.environment"
24
24
 
25
- # @param loader [Rigor::Environment::RbsLoader]
26
- # @param store [Rigor::Cache::Store]
27
- # @return [::RBS::Environment]
25
+ # Issue #610 — the deferred (plugin-contributed) subset rides through too. This is the build path every
26
+ # cached run takes: a cache store is the CLI default and only `--no-cache` reaches the loader's own
27
+ # `build_env`, so a list threaded through the loader but not through here ran the arity stand-down on
28
+ # no real `rigor check` at all — 0.3.8 shipped exactly that, behind a green gate built over a
29
+ # store-less loader. Any input `build_env_for` takes MUST be passed from here as well.
28
30
  def self.compute(loader)
29
31
  Rigor::Environment::RbsLoader.build_env_for(
30
32
  libraries: loader.libraries,
31
33
  signature_paths: loader.signature_paths,
32
- virtual_rbs: loader.respond_to?(:virtual_rbs) ? loader.virtual_rbs : []
34
+ virtual_rbs: loader.respond_to?(:virtual_rbs) ? loader.virtual_rbs : [],
35
+ deferred_signature_paths:
36
+ loader.respond_to?(:deferred_signature_paths) ? loader.deferred_signature_paths : []
33
37
  )
34
38
  end
35
39
 
@@ -18,9 +18,6 @@ module Rigor
18
18
  class RbsKnownClassNames < RbsCacheProducer
19
19
  PRODUCER_ID = "rbs.known_class_names"
20
20
 
21
- # @param loader [Rigor::Environment::RbsLoader]
22
- # @param store [Rigor::Cache::Store]
23
- # @return [Set<String>]
24
21
  def self.compute(loader)
25
22
  names = Set.new
26
23
  loader.each_known_class_name { |name| names << name }
@@ -65,8 +65,8 @@ module Rigor
65
65
 
66
66
  VALID_PRODUCER_ID = /\A[a-z][a-z0-9._-]*\z/
67
67
 
68
- # @param root [String] cache root directory.
69
- # @param read_only [Boolean] when true, every disk-side side-effect is suppressed: `fetch_or_compute`
68
+ # @param root cache root directory.
69
+ # @param read_only when true, every disk-side side-effect is suppressed: `fetch_or_compute`
70
70
  # still reads existing entries (hits, gated on a current `schema_version.txt` marker — see
71
71
  # {#ensure_schema_version!}) and still runs the producer block on miss, but it does NOT write the
72
72
  # produced value to disk, does NOT update the marker, and does NOT touch the on-disk root directory.
@@ -107,7 +107,7 @@ module Rigor
107
107
 
108
108
  attr_reader :root
109
109
 
110
- # @return [Boolean] whether this Store suppresses disk writes (`schema_version.txt`, entry creation).
110
+ # @return whether this Store suppresses disk writes (`schema_version.txt`, entry creation).
111
111
  # Reads are unaffected.
112
112
  def read_only?
113
113
  @read_only
@@ -118,7 +118,7 @@ module Rigor
118
118
  # specific instance rather than the on-disk cache state. Disk-level state is reported separately by
119
119
  # {.disk_inventory}.
120
120
  #
121
- # @return [Hash] `{ hits:, misses:, writes:, by_producer: { id => { hits:, misses:, writes: } } }`
121
+ # @return `{ hits:, misses:, writes:, by_producer: { id => { hits:, misses:, writes: } } }`
122
122
  def stats
123
123
  @monitor.synchronize do
124
124
  per_producer = @by_producer.transform_values { |counts| counts.dup.freeze }.freeze
@@ -130,7 +130,7 @@ module Rigor
130
130
  # `rigor check --cache-stats` to surface cache size and per-producer entry counts without depending on
131
131
  # in-process counters (which only reflect the current run).
132
132
  #
133
- # @return [Hash] `{ root:, schema_version:, total_entries:, total_bytes:, producers: [{ id:, entries:,
133
+ # @return `{ root:, schema_version:, total_entries:, total_bytes:, producers: [{ id:, entries:,
134
134
  # bytes: }, ...] }`. When the root does not exist or has no schema-version marker, `schema_version` is
135
135
  # nil and the producer list is empty.
136
136
  #
@@ -176,21 +176,21 @@ module Rigor
176
176
  end
177
177
  private_class_method :collect_producers
178
178
 
179
- # @param producer_id [String] stable cache namespace; only `[a-z][a-z0-9._-]*` is accepted.
180
- # @param generation_cap [Integer, Symbol] how many generations of this producer survive a compaction
179
+ # @param producer_id stable cache namespace; only `[a-z][a-z0-9._-]*` is accepted.
180
+ # @param generation_cap how many generations of this producer survive a compaction
181
181
  # pass — a positive `Integer` for a whole-project producer (one live entry, older ones unreachable),
182
182
  # or {UNBOUNDED_GENERATIONS} for a producer with many simultaneously-live entries. REQUIRED, and
183
183
  # sourced from the producer's own declaration (`RbsCacheProducer.generation_cap`,
184
184
  # `RunCacheKey::GENERATION_CAP`, `Plugin::Base.producer generation_cap:`) rather than invented at the
185
185
  # call site. See {#evict!}.
186
- # @param params [Hash] producer inputs; mixed into the cache key via {Descriptor#cache_key_for}.
187
- # @param descriptor [Rigor::Cache::Descriptor] the invalidation descriptor for the value being cached.
188
- # @param serialize [#call, nil] optional callable that turns the producer's return value into a binary
186
+ # @param params producer inputs; mixed into the cache key via {Descriptor#cache_key_for}.
187
+ # @param descriptor the invalidation descriptor for the value being cached.
188
+ # @param serialize optional callable that turns the producer's return value into a binary
189
189
  # `String`. Defaults to `Marshal.dump(value).b`. Producers whose return values are not
190
190
  # `Marshal`-clean (RBS-native objects with `RBS::Location` members, raw `IO`, …) MUST provide a
191
191
  # serialiser. The pair `(serialize, deserialize)` MUST round-trip — a producer that reads with one
192
192
  # strategy and writes with another corrupts its own cache slice.
193
- # @param deserialize [#call, nil] optional callable that turns bytes back into the producer's value.
193
+ # @param deserialize optional callable that turns bytes back into the producer's value.
194
194
  # Defaults to `Marshal.load`. Any exception (`StandardError`) raised by the deserialiser is treated as
195
195
  # a cache miss — the entry is considered corrupt, the producer block reruns, and the next write
196
196
  # overwrites it. This is consistent with the fault-tolerance contract for the default `Marshal.load`
@@ -394,10 +394,22 @@ module Rigor
394
394
 
395
395
  # Reads and validates one entry file. Any failure (missing, short, bad magic, bad version, bad
396
396
  # checksum, unmarshal-able) returns nil so the caller treats it as a cache miss.
397
+ #
398
+ # The `ENOENT` rescue covers the gap between the existence check and the open: a concurrent process
399
+ # compacting or clearing the root can unlink the file in between, and a file that is gone reads as the
400
+ # same miss the existence check above already reports for one that was never there (issue #807). It is
401
+ # deliberately narrow — every other `SystemCallError` (a permission error, an I/O error) still raises,
402
+ # so a genuinely broken filesystem stays visible instead of degrading into an endless recompute. A
403
+ # present-but-corrupt file is not touched by this either: it still goes through the envelope and
404
+ # payload checks below.
397
405
  def read_entry(path, deserialize: nil)
398
406
  return nil unless File.file?(path)
399
407
 
400
- bytes = File.binread(path)
408
+ begin
409
+ bytes = File.binread(path)
410
+ rescue Errno::ENOENT
411
+ return nil
412
+ end
401
413
  return nil unless envelope_valid?(bytes)
402
414
 
403
415
  body = bytes.byteslice(HEADER.bytesize, bytes.bytesize - HEADER.bytesize - 32)
@@ -486,22 +498,34 @@ module Rigor
486
498
  def atomically_replace(path, body)
487
499
  File.open(path, File::RDWR | File::CREAT, 0o644) do |lock_fd|
488
500
  lock_fd.flock(File::LOCK_EX)
489
- tmp = "#{path}.tmp.#{Process.pid}.#{SecureRandom.hex(4)}"
490
- begin
491
- File.open(tmp, "wb") do |f|
492
- f.write(body)
493
- f.fsync
494
- end
495
- File.rename(tmp, path)
496
- ensure
497
- # A failed write/rename must not leak its temp file — rely on the 1-hour `cleanup_stale_temp_files`
498
- # sweep only as a backstop for crashes that skip this ensure entirely.
499
- unlink_entry(tmp) if File.exist?(tmp)
500
- end
501
+ publish_by_rename(path, body)
501
502
  fsync_directory(File.dirname(path))
502
503
  end
503
504
  end
504
505
 
506
+ # The rename-into-place half of {#atomically_replace}, without the destination lock: writes `body` to a
507
+ # sibling temp file, fsyncs it, and renames it over `path`. POSIX makes the rename atomic within one
508
+ # filesystem, so an unlocked concurrent reader sees either the previous contents in full or the new
509
+ # ones in full — never a truncated or half-written file.
510
+ #
511
+ # {#repair_writable_marker!} uses this half alone. It needs the atomicity (its reader is another
512
+ # process's constructor, which does not lock) but not the ordering a lock would buy: every writer of
513
+ # `schema_version.txt` writes the same value, so there is no last-writer-wins question to settle.
514
+ def publish_by_rename(path, body)
515
+ tmp = "#{path}.tmp.#{Process.pid}.#{SecureRandom.hex(4)}"
516
+ begin
517
+ File.open(tmp, "wb") do |f|
518
+ f.write(body)
519
+ f.fsync
520
+ end
521
+ File.rename(tmp, path)
522
+ ensure
523
+ # A failed write/rename must not leak its temp file — rely on the 1-hour `cleanup_stale_temp_files`
524
+ # sweep only as a backstop for crashes that skip this ensure entirely.
525
+ unlink_entry(tmp) if File.exist?(tmp)
526
+ end
527
+ end
528
+
505
529
  # Checks (and, for a writable store, repairs) the `schema_version.txt` marker, and reports whether the
506
530
  # disk tier is usable for the rest of this Store's lifetime. The result is memoized in `@disk_available`
507
531
  # — one check per Store is enough; a benign double-check under a thread race would just repeat
@@ -515,8 +539,6 @@ module Rigor
515
539
  # (permission, disk full, deleted root) also reports unavailable, degrading this Store instance to
516
540
  # in-memory-memo-only for the rest of its lifetime — the producer block still runs, its result still
517
541
  # lands in `@memo`, and disk reads/writes are simply skipped.
518
- #
519
- # @return [Boolean]
520
542
  def ensure_schema_version!
521
543
  return @disk_available unless @disk_available.nil?
522
544
 
@@ -532,25 +554,48 @@ module Rigor
532
554
  false
533
555
  end
534
556
 
557
+ # The marker is PUBLISHED BY RENAME rather than written in place. Its reader is another process's
558
+ # constructor, which takes no lock, so an `O_TRUNC`-then-write would expose a window in which the file
559
+ # exists and is empty; a reader landing there concludes the marker disagrees and clears the whole cache
560
+ # root — under, among others, a sibling that is between its own `File.file?` and `File.binread`, which
561
+ # is the `Errno::ENOENT` of issue #807. Two `rigor check` processes over one fresh `.rigor/cache` are
562
+ # enough to hit it.
563
+ #
564
+ # An EMPTY marker is treated as an ABSENT one — rewritten, never cleared on. Emptiness carries no
565
+ # information: it is what a torn write by an older Rigor looks like, what a crash between the create
566
+ # and the write leaves, and what this method's own clear-then-rewrite passes through. Answering an
567
+ # ambiguous signal by destroying a cache root is the wrong trade, and declining grants the entries
568
+ # below no trust they did not already have — a marker that is simply MISSING over existing entries is
569
+ # already kept and served (see the spec's writable-store rules), and `clear_cache_root!` removes the
570
+ # marker before rewriting it, so it produces that state itself.
535
571
  def repair_writable_marker!
536
572
  FileUtils.mkdir_p(@root)
537
573
  marker = File.join(@root, "schema_version.txt")
538
574
  current = self.class.schema_marker_value
539
575
 
540
- if File.file?(marker)
541
- on_disk = File.read(marker).strip
542
- return true if on_disk == current
576
+ on_disk = read_marker(marker)
577
+ return true if on_disk == current
543
578
 
544
- clear_cache_root!
545
- end
579
+ clear_cache_root! unless on_disk.nil? || on_disk.empty?
546
580
 
547
581
  FileUtils.mkdir_p(@root)
548
- File.write(marker, "#{current}\n")
582
+ publish_by_rename(marker, "#{current}\n")
583
+ fsync_directory(@root)
549
584
  true
550
585
  rescue StandardError
551
586
  false
552
587
  end
553
588
 
589
+ # @return the marker's content, or nil when there is none to read — including the case of a concurrent
590
+ # {#clear_cache_root!} removing it between the check and the open.
591
+ def read_marker(path)
592
+ return nil unless File.file?(path)
593
+
594
+ File.read(path).strip
595
+ rescue Errno::ENOENT
596
+ nil
597
+ end
598
+
554
599
  def clear_cache_root!
555
600
  Dir.children(@root).each do |entry|
556
601
  FileUtils.rm_rf(File.join(@root, entry))
@@ -15,7 +15,7 @@ require_relative "../inference/scope_indexer"
15
15
  require_relative "../inference/statement_evaluator"
16
16
  require_relative "prism_colorizer"
17
17
  require_relative "command"
18
- require_relative "probe_environment"
18
+ require_relative "../project_environment"
19
19
 
20
20
  module Rigor
21
21
  class CLI
@@ -43,7 +43,7 @@ module Rigor
43
43
  # paging stays off because the CLI may itself sit in a pipeline.
44
44
  BAT_ARGS = %w[--language=ruby --style=plain --paging=never --color=always].freeze
45
45
 
46
- # @return [Integer] CLI exit status.
46
+ # @return CLI exit status.
47
47
  def run
48
48
  options = parse_options
49
49
  file = @argv.shift
@@ -132,10 +132,10 @@ module Rigor
132
132
 
133
133
  # The plugin-aware environment for the annotated file, so a `#=> <type>` matches what `rigor check`
134
134
  # infers on that line — including types synthesized from the file's own inline RBS annotations (the file
135
- # is threaded as the synthesizer's `source_files:`; see {ProbeEnvironment}).
135
+ # is threaded as the synthesizer's `source_files:`; see {ProjectEnvironment}).
136
136
  def base_scope(configuration, file)
137
137
  Scope.empty(
138
- environment: ProbeEnvironment.build(configuration: configuration, source_files: [file])
138
+ environment: ProjectEnvironment.build(configuration: configuration, source_files: [file])
139
139
  )
140
140
  end
141
141
 
@@ -220,8 +220,7 @@ module Rigor
220
220
  @scope_index = scope_index
221
221
  end
222
222
 
223
- # @param program [Prism::ProgramNode]
224
- # @return [Hash{Integer => Rigor::Type}] 1-indexed line => type.
223
+ # @return 1-indexed line => type.
225
224
  def collect(program)
226
225
  by_line = {}
227
226
  each_statement(program) do |statement|