rigortype 0.4.6 → 0.4.7

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 (417) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
  4. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  5. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  6. data/docs/handbook/01-getting-started.md +5 -2
  7. data/docs/handbook/11-sig-gen.md +50 -3
  8. data/docs/handbook/appendix-phpstan.md +4 -4
  9. data/docs/manual/02-cli-reference.md +152 -26
  10. data/docs/manual/03-configuration.md +36 -3
  11. data/docs/manual/06-baseline.md +6 -4
  12. data/docs/manual/09-editor-integration.md +13 -2
  13. data/docs/manual/10-mcp-server.md +38 -2
  14. data/docs/manual/12-caching.md +4 -3
  15. data/docs/manual/plugins/rigor-actionpack.md +19 -11
  16. data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
  17. data/lib/rigor/analysis/baseline.rb +24 -27
  18. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  19. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  20. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
  21. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  22. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
  23. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
  24. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +0 -1
  25. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  26. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
  27. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
  28. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  29. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  30. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  31. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  32. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  33. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  34. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
  35. data/lib/rigor/analysis/check_rules.rb +124 -44
  36. data/lib/rigor/analysis/crash_signature.rb +2 -2
  37. data/lib/rigor/analysis/dependency_recorder.rb +4 -4
  38. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  39. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  40. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  41. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  42. data/lib/rigor/analysis/edit_replay.rb +93 -6
  43. data/lib/rigor/analysis/fact_store.rb +3 -3
  44. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
  45. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  46. data/lib/rigor/analysis/incremental_session.rb +27 -16
  47. data/lib/rigor/analysis/rule_catalog.rb +2 -2
  48. data/lib/rigor/analysis/run_stats.rb +2 -2
  49. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +2 -2
  50. data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
  51. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +193 -93
  52. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
  53. data/lib/rigor/analysis/runner/pool_coordinator.rb +147 -323
  54. data/lib/rigor/analysis/runner/project_pre_passes.rb +3 -3
  55. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  56. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  57. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  58. data/lib/rigor/analysis/runner.rb +77 -71
  59. data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
  60. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  61. data/lib/rigor/analysis/template_units.rb +9 -1
  62. data/lib/rigor/analysis/worker_session.rb +24 -31
  63. data/lib/rigor/bleeding_edge.rb +3 -6
  64. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  65. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  66. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  67. data/lib/rigor/cache/descriptor.rb +6 -5
  68. data/lib/rigor/cache/incremental_snapshot.rb +31 -12
  69. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  70. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  71. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  72. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  73. data/lib/rigor/cache/rbs_descriptor.rb +54 -3
  74. data/lib/rigor/cache/rbs_environment.rb +4 -1
  75. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  76. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  77. data/lib/rigor/cache/store.rb +11 -10
  78. data/lib/rigor/ci_detector.rb +1 -1
  79. data/lib/rigor/cli/annotate_command.rb +24 -19
  80. data/lib/rigor/cli/baseline_command.rb +145 -55
  81. data/lib/rigor/cli/catalogue.rb +256 -0
  82. data/lib/rigor/cli/check_command.rb +108 -102
  83. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  84. data/lib/rigor/cli/command.rb +15 -1
  85. data/lib/rigor/cli/coverage_command.rb +21 -8
  86. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  87. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  88. data/lib/rigor/cli/coverage_scan.rb +3 -5
  89. data/lib/rigor/cli/diff_command.rb +25 -20
  90. data/lib/rigor/cli/docs_command.rb +1 -2
  91. data/lib/rigor/cli/doctor_command.rb +12 -12
  92. data/lib/rigor/cli/effects_command.rb +50 -40
  93. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  94. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  95. data/lib/rigor/cli/effects_renderer.rb +2 -1
  96. data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
  97. data/lib/rigor/cli/explain_command.rb +18 -8
  98. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  99. data/lib/rigor/cli/json_document.rb +41 -0
  100. data/lib/rigor/cli/lens_command.rb +12 -8
  101. data/lib/rigor/cli/lsp_command.rb +250 -23
  102. data/lib/rigor/cli/mcp_command.rb +12 -7
  103. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  104. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  105. data/lib/rigor/cli/options.rb +40 -1
  106. data/lib/rigor/cli/plugin_command.rb +93 -46
  107. data/lib/rigor/cli/plugins_command.rb +17 -18
  108. data/lib/rigor/cli/plugins_renderer.rb +10 -6
  109. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  110. data/lib/rigor/cli/protection_renderer.rb +2 -1
  111. data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
  112. data/lib/rigor/cli/sig_gen_command.rb +84 -27
  113. data/lib/rigor/cli/skill_command.rb +32 -3
  114. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  115. data/lib/rigor/cli/skill_describe.rb +1 -1
  116. data/lib/rigor/cli/trace_command.rb +14 -9
  117. data/lib/rigor/cli/triage_command.rb +10 -11
  118. data/lib/rigor/cli/triage_renderer.rb +2 -1
  119. data/lib/rigor/cli/type_of_command.rb +16 -11
  120. data/lib/rigor/cli/type_of_project_scope.rb +1 -4
  121. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  122. data/lib/rigor/cli/type_scan_command.rb +14 -9
  123. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  124. data/lib/rigor/cli/unused_command.rb +27 -17
  125. data/lib/rigor/cli/upgrade_command.rb +5 -0
  126. data/lib/rigor/cli.rb +209 -54
  127. data/lib/rigor/config_audit.rb +1 -1
  128. data/lib/rigor/configuration/severity_profile.rb +5 -5
  129. data/lib/rigor/configuration.rb +72 -30
  130. data/lib/rigor/effects/definition_context.rb +0 -2
  131. data/lib/rigor/effects/envelope.rb +1 -1
  132. data/lib/rigor/effects/framework_units.rb +1 -1
  133. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  134. data/lib/rigor/effects/propagator.rb +1 -1
  135. data/lib/rigor/effects/registry.rb +2 -2
  136. data/lib/rigor/effects/snapshot.rb +1 -1
  137. data/lib/rigor/effects/taint_cause.rb +0 -2
  138. data/lib/rigor/effects/unit_scan.rb +9 -13
  139. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
  140. data/lib/rigor/environment/class_registry.rb +3 -3
  141. data/lib/rigor/environment/core_root.rb +77 -0
  142. data/lib/rigor/environment/default_libraries.rb +1 -1
  143. data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
  144. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  145. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  146. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  147. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  148. data/lib/rigor/environment/member_consistency.rb +56 -7
  149. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  150. data/lib/rigor/environment/project_path_gems.rb +124 -0
  151. data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
  152. data/lib/rigor/environment/rbs_loader.rb +480 -67
  153. data/lib/rigor/environment/reflection.rb +7 -7
  154. data/lib/rigor/environment/required_features.rb +0 -3
  155. data/lib/rigor/environment/ruby_library_roots.rb +1 -1
  156. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  157. data/lib/rigor/environment.rb +41 -10
  158. data/lib/rigor/flow_contribution/merger.rb +4 -4
  159. data/lib/rigor/inference/acceptance.rb +93 -32
  160. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  161. data/lib/rigor/inference/block_run_count.rb +178 -0
  162. data/lib/rigor/inference/budget_trace.rb +8 -2
  163. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  164. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  165. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  166. data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
  167. data/lib/rigor/inference/callee_write_census.rb +815 -0
  168. data/lib/rigor/inference/construction_escape.rb +217 -0
  169. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  170. data/lib/rigor/inference/deep_erasure.rb +39 -7
  171. data/lib/rigor/inference/def_handle.rb +1 -1
  172. data/lib/rigor/inference/def_node_resolver.rb +1 -1
  173. data/lib/rigor/inference/def_return_typer.rb +46 -4
  174. data/lib/rigor/inference/definer_resolution.rb +4 -2
  175. data/lib/rigor/inference/definers_index.rb +106 -0
  176. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  177. data/lib/rigor/inference/element_read_widening.rb +49 -3
  178. data/lib/rigor/inference/expression_typer.rb +116 -49
  179. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  180. data/lib/rigor/inference/global_write_census.rb +76 -2
  181. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  182. data/lib/rigor/inference/hkt_body.rb +1 -2
  183. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  184. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  185. data/lib/rigor/inference/hkt_registry.rb +3 -5
  186. data/lib/rigor/inference/indexed_narrowing.rb +4 -7
  187. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  188. data/lib/rigor/inference/ivar_writer_census.rb +51 -0
  189. data/lib/rigor/inference/key_presence_guard.rb +2 -1
  190. data/lib/rigor/inference/loop_value_record.rb +8 -4
  191. data/lib/rigor/inference/match_rebinding/frame.rb +46 -0
  192. data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
  193. data/lib/rigor/inference/match_rebinding.rb +11 -1
  194. data/lib/rigor/inference/memo_audit.rb +55 -0
  195. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  197. data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
  198. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
  199. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  200. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  201. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
  202. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  203. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
  204. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  205. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
  206. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  208. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
  209. data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
  210. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
  211. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  212. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  213. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  214. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  215. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher.rb +64 -14
  217. data/lib/rigor/inference/module_function_state.rb +11 -1
  218. data/lib/rigor/inference/multi_target_binder.rb +1 -1
  219. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  220. data/lib/rigor/inference/mutation_widening.rb +157 -27
  221. data/lib/rigor/inference/narrowing.rb +47 -33
  222. data/lib/rigor/inference/optimistic_origin.rb +1 -1
  223. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  224. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  225. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  226. data/lib/rigor/inference/project_method_ownership.rb +6 -3
  227. data/lib/rigor/inference/project_yielder.rb +11 -17
  228. data/lib/rigor/inference/receiver_alias.rb +20 -0
  229. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
  230. data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
  231. data/lib/rigor/inference/remover_holder_scan.rb +233 -0
  232. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  233. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  234. data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
  235. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  236. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +58 -15
  237. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  238. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  239. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
  240. data/lib/rigor/inference/scope_indexer.rb +137 -46
  241. data/lib/rigor/inference/self_call_ivar_writes.rb +84 -12
  242. data/lib/rigor/inference/set_mutation.rb +189 -0
  243. data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
  244. data/lib/rigor/inference/statement_evaluator.rb +2328 -138
  245. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  246. data/lib/rigor/inference/synthetic_method_scanner.rb +5 -6
  247. data/lib/rigor/inference/toplevel_def_census.rb +59 -0
  248. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  249. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  250. data/lib/rigor/inference/void_tail_summary.rb +2 -1
  251. data/lib/rigor/language_server/buffer_table.rb +3 -3
  252. data/lib/rigor/language_server/completion_provider.rb +26 -20
  253. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  254. data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
  255. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  256. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  257. data/lib/rigor/language_server/hover_provider.rb +1 -7
  258. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  259. data/lib/rigor/language_server/project_context.rb +39 -1
  260. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  261. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  262. data/lib/rigor/language_server/server.rb +7 -3
  263. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  264. data/lib/rigor/lens/plugins.rb +1 -1
  265. data/lib/rigor/mcp/server.rb +73 -2
  266. data/lib/rigor/mcp.rb +1 -1
  267. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  268. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  269. data/lib/rigor/plugin/io_boundary.rb +1 -1
  270. data/lib/rigor/plugin/loader.rb +25 -8
  271. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  272. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  273. data/lib/rigor/plugin/manifest.rb +9 -13
  274. data/lib/rigor/plugin/registry.rb +3 -3
  275. data/lib/rigor/plugin/services.rb +2 -3
  276. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  277. data/lib/rigor/plugin/trust_policy.rb +2 -2
  278. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  279. data/lib/rigor/plugin.rb +6 -8
  280. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  281. data/lib/rigor/project_environment.rb +16 -2
  282. data/lib/rigor/protection/analysis_guard.rb +1 -1
  283. data/lib/rigor/protection/discovery_seed.rb +44 -5
  284. data/lib/rigor/protection/mutator.rb +1 -1
  285. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  286. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  287. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  288. data/lib/rigor/rbs_extended.rb +2 -3
  289. data/lib/rigor/reflection/constant_ancestors.rb +5 -3
  290. data/lib/rigor/rooted_glob.rb +15 -1
  291. data/lib/rigor/run_scoped_slots.rb +38 -0
  292. data/lib/rigor/runtime/jit.rb +1 -1
  293. data/lib/rigor/scope/discovery_index.rb +19 -6
  294. data/lib/rigor/scope/resolution_chain.rb +3 -1
  295. data/lib/rigor/scope.rb +365 -31
  296. data/lib/rigor/sig_gen/classification.rb +4 -0
  297. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  298. data/lib/rigor/sig_gen/generator.rb +371 -58
  299. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  300. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  301. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  302. data/lib/rigor/sig_gen/method_candidate.rb +23 -4
  303. data/lib/rigor/sig_gen/observation_collector.rb +3 -5
  304. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  305. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  306. data/lib/rigor/sig_gen/renderer.rb +15 -9
  307. data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
  308. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  309. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  310. data/lib/rigor/sig_gen/write_result.rb +23 -12
  311. data/lib/rigor/sig_gen/writer.rb +217 -37
  312. data/lib/rigor/type/acceptance_router.rb +2 -2
  313. data/lib/rigor/type/accepts_result.rb +1 -1
  314. data/lib/rigor/type/app.rb +4 -6
  315. data/lib/rigor/type/combinator.rb +9 -13
  316. data/lib/rigor/type/constant.rb +4 -5
  317. data/lib/rigor/type/hash_shape.rb +21 -40
  318. data/lib/rigor/type/union.rb +1 -1
  319. data/lib/rigor/type_node/identifier.rb +1 -2
  320. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  321. data/lib/rigor/type_node/string_literal.rb +1 -2
  322. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  323. data/lib/rigor/type_node/union.rb +3 -6
  324. data/lib/rigor/value_semantics.rb +1 -10
  325. data/lib/rigor/version.rb +1 -1
  326. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  327. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  329. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
  330. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  331. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
  332. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  333. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  334. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  335. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
  337. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +66 -19
  338. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  339. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
  340. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  341. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  342. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  343. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  344. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
  345. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
  346. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
  347. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  348. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  349. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
  350. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  351. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  352. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  353. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
  354. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
  355. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  356. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  357. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  358. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  359. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  360. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  361. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  362. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  363. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  364. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  365. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  366. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  367. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
  368. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  369. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  370. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  371. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  372. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
  373. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  374. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  375. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  376. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  377. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  378. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
  379. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  380. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  381. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  382. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
  383. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  384. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
  385. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  386. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  387. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  388. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
  389. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  390. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  391. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
  392. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  393. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
  394. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  395. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
  396. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  397. data/sig/rigor/analysis/baseline.rbs +0 -3
  398. data/sig/rigor/analysis/fact_store.rbs +1 -0
  399. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  400. data/sig/rigor/cli/diff_command.rbs +1 -1
  401. data/sig/rigor/cli/explain_command.rbs +1 -1
  402. data/sig/rigor/effects/registry.rbs +2 -2
  403. data/sig/rigor/inference.rbs +4 -1
  404. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  405. data/sig/rigor/plugin/base.rbs +1 -0
  406. data/sig/rigor/plugin/effect_attribution.rbs +3 -3
  407. data/sig/rigor/plugin/effect_edge.rbs +1 -1
  408. data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
  409. data/sig/rigor/plugin/io_boundary.rbs +2 -1
  410. data/sig/rigor/plugin/manifest.rbs +9 -9
  411. data/sig/rigor/plugin/protocol_contract.rbs +3 -7
  412. data/sig/rigor/scope.rbs +23 -3
  413. data/skills/rigor-plugin-author/SKILL.md +4 -4
  414. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  415. data/skills/rigor-plugin-review/SKILL.md +3 -3
  416. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
  417. metadata +36 -3
@@ -378,8 +378,8 @@ module Rigor
378
378
  # Builds the {Rigor::Plugin::TrustPolicy} for this run. Trusted gems are the gem-name half of every
379
379
  # entry in `Configuration#plugins`. Allowed read roots default to the project root (CWD), the
380
380
  # project's signature_paths, and each trusted gem's `Gem::Specification#full_gem_path`, plus any
381
- # extras the user listed under `plugins_io.allowed_paths`. Slice 2 keeps `network_policy` `:disabled`
382
- # — the only value the configuration accepts today.
381
+ # extras the user listed under `plugins_io.allowed_paths`. `network_policy` is `plugins_io.network`
382
+ # (`:disabled` or `:allowlist`).
383
383
  def build_trust_policy
384
384
  trusted_gems = @configuration.plugins.map { |entry| trusted_gem_name(entry) }.uniq
385
385
  roots = [Dir.pwd]
@@ -409,7 +409,7 @@ module Rigor
409
409
  return nil if gem_name.nil? || gem_name.empty?
410
410
 
411
411
  spec = Gem.loaded_specs[gem_name]
412
- spec&.full_gem_path # rigor:disable undefined-method
412
+ spec&.full_gem_path
413
413
  rescue StandardError
414
414
  nil
415
415
  end
@@ -0,0 +1,304 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ module Analysis
5
+ class Runner
6
+ # ADR-116 WD3 (#1195) — the run-level fact registry. A run-level fact is a fact about the RUN rather than
7
+ # about one file: the project-signature state, the definition-build failures, the HKT-scan outcome, the
8
+ # `RunStats` RBS tables, the effect-annotation carrier. Each is declared ONCE below, and every path that
9
+ # used to hand-list the slots — the {RunSnapshots} store, the {PoolCoordinator} backends, the
10
+ # {WorkerSession} drain, the {DiagnosticAggregator} rows and the incremental snapshot's
11
+ # `RunLevelRows` — reads the declarations instead.
12
+ #
13
+ # A {Fact} carries:
14
+ #
15
+ # - `name` and `empty` — the store key and its value before anything is recorded;
16
+ # - `merge` — how a recorded value folds into the slot: {Merge.assign}, {Merge.first_wins} (#784) or
17
+ # {Merge.union_by_class} (#696);
18
+ # - `source` — `:environment` (read off a coordinator-side environment before the per-file loop, so a
19
+ # fork child inherits the demand it makes), `:analysis` (read after the loop, and drained out of each
20
+ # pool worker, because the per-file analysis is what demands it) or `:stats` (`RunStats` telemetry,
21
+ # read only when stats are collected);
22
+ # - `gate` — for an `:environment` fact, which environment it is read from; an empty-closure run
23
+ # resolves one per gate, lazily, and every other path hands the same environment to both;
24
+ # - `demand` — for an `:analysis` fact, whether collecting it makes a demand of its own (the HKT
25
+ # registry build, #784) rather than reading what the analysis already demanded. A path that must not
26
+ # pay for a demand (the Ractor prewarm, an empty closure with a replay in hand) drains only the others;
27
+ # - `replay` — whether a narrowed incremental run replays the previous full run's value
28
+ # ({Cache::IncrementalSnapshot::RunLevelRows});
29
+ # - `row` / `position` — the {DiagnosticAggregator} method that turns the slot into diagnostics, and
30
+ # where that row sits in the post-analysis stream (docs/type-specification/diagnostic-policy.md fixes
31
+ # the order). Nil for a fact no diagnostic reads.
32
+ #
33
+ # The declaration ORDER is the collection order: {.harvest} reads the `:environment` facts in it, and
34
+ # that order is load-bearing (the stand-downs are read before the env-forcing reads, the conformance
35
+ # scan — which demands definitions — last). The row order is `position`, independently.
36
+ #
37
+ # Rows whose producer is not a slot here — the reporter streams, which the reporters snapshot and
38
+ # absorb themselves, and the two plugin tables the coordinator collects — are declared as {Row}s so
39
+ # the whole post-analysis order lives in one place.
40
+ #
41
+ # Everything here is frozen data plus module functions, never a closure: {WorkerSession#drain_reporters}
42
+ # reads these declarations inside a Ractor worker, and a drained value has to stay Marshal-clean (#805)
43
+ # for the fork backend.
44
+ module RunLevelFacts
45
+ Fact = Data.define(:name, :empty, :merge, :source, :gate, :demand, :replay, :row, :position)
46
+ Row = Data.define(:builder, :position)
47
+
48
+ # The predicates an `:environment` collector consults, taken by the coordinator when it harvests.
49
+ Context = Data.define(:project_signature_paths, :plugin_signature_paths, :record_effects)
50
+
51
+ # How a recorded value folds into its slot.
52
+ module Merge
53
+ module_function
54
+
55
+ def assign(_current, incoming)
56
+ incoming
57
+ end
58
+
59
+ # Issue #784 — the HKT scan is one build over the overlay every worker shares, so the first tuple
60
+ # recorded is the run's outcome.
61
+ def first_wins(current, incoming)
62
+ current.nil? ? incoming : current
63
+ end
64
+
65
+ # Issue #696 / #1902 — each pool worker holds its own loader, so the run's set is the union, one
66
+ # tuple per class chosen by {Environment::RbsLoader.definition_build_detail_order} rather than by
67
+ # arrival.
68
+ def union_by_class(current, incoming)
69
+ return current if incoming.nil? || incoming.empty?
70
+
71
+ (current + incoming)
72
+ .group_by(&:first)
73
+ .map { |_, candidates| candidates.min_by { |c| Environment::RbsLoader.definition_build_detail_order(c) } }
74
+ .freeze
75
+ end
76
+ end
77
+
78
+ # One collector per fact, named after it: `(environment, context) -> value`.
79
+ module Collect
80
+ module_function
81
+
82
+ # Issue #610 — gated on a loaded plugin contributing signatures, not on `signature_paths:`: the
83
+ # source a plugin's `sig/` stands down against is typically an `rbs collection install`.
84
+ def signature_standdowns(environment, context)
85
+ return [] unless context.plugin_signature_paths
86
+
87
+ loader = environment&.rbs_loader
88
+ return [] if loader.nil? || !loader.respond_to?(:deferred_signature_paths)
89
+ return [] if loader.deferred_signature_paths.empty?
90
+
91
+ loader.signature_standdowns
92
+ end
93
+
94
+ # Issue #1713 — not behind the `signature_paths:` gate either; the loader answers without building
95
+ # its env when no gated directory is active.
96
+ def vendored_standdowns(environment, _context)
97
+ loader = environment&.rbs_loader
98
+ loader.respond_to?(:vendored_standdowns) ? loader.vendored_standdowns : []
99
+ end
100
+
101
+ # The four below read the project's OWN `signature_paths:` state, and each forces the otherwise-lazy
102
+ # env build, so they are gated on the project declaring `signature_paths:`.
103
+ def synthesized_namespaces(environment, context)
104
+ return [] unless context.project_signature_paths
105
+
106
+ environment&.rbs_loader&.synthesized_namespaces || []
107
+ end
108
+
109
+ # Read after the env-forcing read above: the collision derivation reads the BUILT env.
110
+ def quarantined_signatures(environment, context)
111
+ return [] unless context.project_signature_paths
112
+
113
+ environment&.rbs_loader&.quarantined_signatures || []
114
+ end
115
+
116
+ def env_build_failure(environment, context)
117
+ return nil unless context.project_signature_paths
118
+
119
+ environment&.rbs_loader&.env_build_failure
120
+ end
121
+
122
+ # Read LAST of the signature state: the scan demands the definition of every `rigor:v1:conforms-to`
123
+ # class, and a resulting definition-build failure has to be in the loader before the `:analysis`
124
+ # read of {.definition_build_failures}.
125
+ def conformance_results(environment, context)
126
+ return [] unless context.project_signature_paths
127
+
128
+ RbsExtended::ConformanceChecker.scan(environment&.rbs_loader)
129
+ end
130
+
131
+ # #441 — the inline stratum of `effect.annotations-unchecked`. Skipped when effect collection is on:
132
+ # {EffectEnvelopePass} reads the loader directly then.
133
+ def effect_annotation_carrier(environment, context)
134
+ virtual = context.record_effects ? nil : environment&.rbs_loader&.virtual_rbs
135
+ Effects::SignatureSources.annotated_carrier(virtual)
136
+ rescue StandardError
137
+ [].freeze
138
+ end
139
+
140
+ # Issue #696 — read after the loop: definition builds are lazy, per class, on first demand. NOT
141
+ # gated on `signature_paths:` like the signature state: nothing is forced here (this reads what the
142
+ # analysis already demanded), and two BUNDLED sources can collapse a class with no project sig set
143
+ # in sight (the `bigdecimal` / `BigMath` shape, #299).
144
+ def definition_build_failures(environment, _context)
145
+ environment&.rbs_loader&.definition_build_failures || []
146
+ end
147
+
148
+ # Issue #784 — the run demands the registry once itself, so the outcome does not depend on which
149
+ # files were analysed. One build, one outcome whoever demands it, so a Rigor-owned demand cannot
150
+ # change what is reported (unlike #696's per-class list).
151
+ def hkt_scan_failure(environment, _context)
152
+ return nil if environment.nil?
153
+
154
+ environment.hkt_registry
155
+ environment.hkt_scan_failure
156
+ end
157
+
158
+ def class_decl_paths(environment, _context)
159
+ environment&.rbs_loader&.class_decl_paths || {}.freeze
160
+ end
161
+
162
+ def signature_paths(environment, _context)
163
+ environment&.rbs_loader&.signature_paths || [].freeze
164
+ end
165
+ end
166
+
167
+ EMPTY_LIST = [].freeze
168
+ EMPTY_TABLE = {}.freeze
169
+
170
+ FACTS = [
171
+ Fact.new(name: :signature_standdowns, empty: EMPTY_LIST, merge: :assign, source: :environment,
172
+ gate: :signature_state, demand: false, replay: false,
173
+ row: :rbs_plugin_signature_stood_down_diagnostics, position: 60),
174
+ Fact.new(name: :vendored_standdowns, empty: EMPTY_LIST, merge: :assign, source: :environment,
175
+ gate: :signature_state, demand: false, replay: false,
176
+ row: :rbs_vendored_signature_stood_down_diagnostics, position: 70),
177
+ Fact.new(name: :synthesized_namespaces, empty: EMPTY_LIST, merge: :assign, source: :environment,
178
+ gate: :signature_state, demand: false, replay: false,
179
+ row: :rbs_synthesized_namespace_diagnostics, position: 50),
180
+ Fact.new(name: :quarantined_signatures, empty: EMPTY_LIST, merge: :assign, source: :environment,
181
+ gate: :signature_state, demand: false, replay: false,
182
+ row: :rbs_quarantined_signature_diagnostics, position: 10),
183
+ Fact.new(name: :env_build_failure, empty: nil, merge: :assign, source: :environment,
184
+ gate: :signature_state, demand: false, replay: false,
185
+ row: :rbs_environment_build_failed_diagnostics, position: 20),
186
+ Fact.new(name: :conformance_results, empty: EMPTY_LIST, merge: :assign, source: :environment,
187
+ gate: :signature_state, demand: false, replay: false,
188
+ row: :conforms_to_diagnostics, position: 80),
189
+ # Read by {EffectAnnotationResidualPass}, outside this stream.
190
+ Fact.new(name: :effect_annotation_carrier, empty: EMPTY_LIST, merge: :assign, source: :environment,
191
+ gate: :annotation_carrier, demand: false, replay: false, row: nil, position: nil),
192
+ Fact.new(name: :definition_build_failures, empty: EMPTY_LIST, merge: :union_by_class, source: :analysis,
193
+ gate: nil, demand: false, replay: true,
194
+ row: :rbs_definition_build_failed_diagnostics, position: 30),
195
+ Fact.new(name: :hkt_scan_failure, empty: nil, merge: :first_wins, source: :analysis,
196
+ gate: nil, demand: true, replay: true,
197
+ row: :rbs_hkt_scan_failed_diagnostics, position: 40),
198
+ Fact.new(name: :class_decl_paths, empty: EMPTY_TABLE, merge: :assign, source: :stats,
199
+ gate: nil, demand: false, replay: false, row: nil, position: nil),
200
+ Fact.new(name: :signature_paths, empty: EMPTY_LIST, merge: :assign, source: :stats,
201
+ gate: nil, demand: false, replay: false, row: nil, position: nil)
202
+ ].freeze
203
+
204
+ # The rows no slot above produces. The reporter streams follow the `rbs.coverage.*` ladder and the
205
+ # conformance rows; the plugin-authored run disclosures (#1051) come before the engine-authored trust
206
+ # refusal envelope (#959), which is read last so every per-file plugin call has recorded its refusals.
207
+ ROWS = [
208
+ Row.new(builder: :unresolved_payload_diagnostics, position: 90),
209
+ Row.new(builder: :lossy_projection_diagnostics, position: 100),
210
+ Row.new(builder: :hkt_directive_diagnostics, position: 110),
211
+ Row.new(builder: :deprecated_form_diagnostics, position: 120),
212
+ Row.new(builder: :boundary_cross_diagnostics, position: 130),
213
+ Row.new(builder: :source_rbs_synthesis_diagnostics, position: 140),
214
+ Row.new(builder: :plugin_run_disclosure_diagnostics, position: 150),
215
+ Row.new(builder: :plugin_trust_refusal_diagnostics, position: 160)
216
+ ].freeze
217
+
218
+ # The per-run reporter accumulators, by drain key. Each reporter snapshots and absorbs its own
219
+ # streams (`#snapshot` / `#absorb`), so a new stream inside one touches that reporter and its row.
220
+ REPORTERS = %i[rbs_extended boundary_cross source_rbs_synthesis].freeze
221
+
222
+ BY_NAME = FACTS.to_h { |fact| [fact.name, fact] }.freeze
223
+
224
+ # Read inside a Ractor worker's drain, so every constant here is deeply shareable.
225
+ Ractor.make_shareable(FACTS)
226
+ Ractor.make_shareable(ROWS)
227
+ Ractor.make_shareable(BY_NAME)
228
+
229
+ module_function
230
+
231
+ def facts
232
+ FACTS
233
+ end
234
+
235
+ def fact(name)
236
+ BY_NAME.fetch(name)
237
+ end
238
+
239
+ def names(source: nil)
240
+ (source ? FACTS.select { |fact| fact.source == source } : FACTS).map(&:name)
241
+ end
242
+
243
+ def replayable_names
244
+ FACTS.select(&:replay).map(&:name)
245
+ end
246
+
247
+ def empty_values
248
+ FACTS.to_h { |fact| [fact.name, fact.empty] }
249
+ end
250
+
251
+ # The post-analysis diagnostic builders, in their contract order.
252
+ def row_builders
253
+ (FACTS.select(&:row).map { |fact| Row.new(builder: fact.row, position: fact.position) } + ROWS)
254
+ .sort_by(&:position)
255
+ .map(&:builder)
256
+ end
257
+
258
+ # Collects every fact of `source`, in declaration order, from the environment the block yields for
259
+ # its gate (one call per gate, on first need). `:analysis` and `:stats` facts take the block's
260
+ # environment with a nil gate.
261
+ def harvest(source, context)
262
+ environments = {}
263
+ FACTS.each_with_object({}) do |fact, collected|
264
+ next unless fact.source == source
265
+
266
+ environment = environments.fetch(fact.gate) { environments[fact.gate] = yield(fact.gate) }
267
+ collected[fact.name] = Collect.public_send(fact.name, environment, context)
268
+ end
269
+ end
270
+
271
+ # The `:analysis` facts as an analysing process reports them: the {WorkerSession} drain, and the
272
+ # coordinator's own environment on the paths that analyse on it. Plain tuples — Marshal-clean and
273
+ # shareable.
274
+ #
275
+ # @param demanding — nil drains every `:analysis` fact; false only those that read what was already
276
+ # demanded; true only those that make a demand of their own.
277
+ def drain(environment, demanding: nil)
278
+ FACTS.each_with_object({}) do |fact, drained|
279
+ next unless fact.source == :analysis
280
+ next unless demanding.nil? || fact.demand == demanding
281
+
282
+ drained[fact.name] = Collect.public_send(fact.name, environment, nil)
283
+ end
284
+ end
285
+
286
+ # The `:analysis` facts out of a drained hash, for {RunSnapshots#absorb}.
287
+ def drained_facts(drained)
288
+ names(source: :analysis).each_with_object({}) do |name, facts|
289
+ facts[name] = drained[name] if drained.key?(name)
290
+ end
291
+ end
292
+
293
+ def snapshot_reporters(reporters)
294
+ REPORTERS.to_h { |name| [name, reporters.fetch(name).snapshot] }
295
+ end
296
+
297
+ # A drain shape that lacks a reporter's key records nothing for it.
298
+ def absorb_reporters(reporters, drained)
299
+ REPORTERS.each { |name| reporters.fetch(name).absorb(drained[name]) }
300
+ end
301
+ end
302
+ end
303
+ end
304
+ end
@@ -1,68 +1,58 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "run_level_facts"
4
+
3
5
  module Rigor
4
6
  module Analysis
5
7
  class Runner
6
- # Mutable per-run holder for the end-of-pass snapshots that the analysis paths compute as a side
7
- # effect and the rest of the run reads back: the RBS `class_decl_paths` / `signature_paths` tables
8
- # (consumed by {RunStats}), the synthesized-namespace name list, the `conforms-to` scan results
9
- # (consumed by the diagnostic aggregator), and the effect-annotation carrier (#441 — consumed by
10
- # {EffectAnnotationResidualPass} on the runs that never store the environment).
8
+ # Mutable per-run store for the run-level facts ({RunLevelFacts}): one slot per declared fact, keyed by
9
+ # its name. The analysis paths write it ({PoolCoordinator} — sequential, fork-pool, Ractor-pool,
10
+ # fallback, empty closure) and the rest of the run reads it back: the {DiagnosticAggregator} rows,
11
+ # {RunStats}, {EffectAnnotationResidualPass} and the incremental snapshot's `RunLevelRows`.
12
+ #
13
+ # A shared mutable holder keeps the write/read timing of the original inline ivars while letting the
14
+ # writer and the readers live in separate objects without a back-reference cycle.
11
15
  #
12
- # The snapshots are written by whichever analysis path ran ({PoolCoordinator} sequential / fork-pool /
13
- # fallback) and read by the {Runner} and {DiagnosticAggregator}. A shared mutable holder keeps the
14
- # write/read timing bit-for-bit with the original inline ivars (the same pattern the reporter
15
- # accumulators use) while letting the writer and readers live in separate objects without a
16
- # back-reference cycle.
16
+ # The analysis paths write through {#absorb}, which folds each value in by its fact's declared merge
17
+ # rule. `[]=` and the per-fact `name=` writers (defined from the declarations, like the readers) are a
18
+ # plain assignment that bypasses the merge rule: they exist for specs that seed a slot, and no
19
+ # production path calls them.
17
20
  class RunSnapshots
18
- attr_accessor :class_decl_paths, :signature_paths,
19
- :synthesized_namespaces, :quarantined_signatures, :signature_standdowns, :vendored_standdowns,
20
- :conformance_results,
21
- :env_build_failure, :definition_build_failures, :hkt_scan_failure,
22
- :effect_annotation_carrier
23
-
24
- # Constructor defaults match the {Runner} constructor: the pre-seed values `build_run_stats` /
25
- # `pre_file_diagnostics` read before the first analysis path runs are frozen empties. The
26
- # `env_build_failure` slot is nil (no failure) rather than an empty collection — it holds a single
27
- # `[error_class, first_line, buffer_names]` tuple or nothing. Its per-class sibling
28
- # `definition_build_failures` (#696) holds a LIST, because a collapsed universe fails many classes,
29
- # and is accumulated ACROSS pool workers rather than assigned once — see
30
- # {PoolCoordinator#merge_worker_reporters}. `hkt_scan_failure` (#784) is nil-or-tuple like
31
- # `env_build_failure`, not a list like `definition_build_failures`: the scan is ONE build over the
32
- # whole `signature_paths:` overlay, not a per-class one, so it has exactly one outcome per run.
33
- # `signature_standdowns` (#610) is the quarantine slot's twin for the plugin-contributed files that
34
- # stood down against a colliding generic arity: a list, assigned once from the loader as
35
- # `quarantined_signatures` is, and derived from the final env so a cache HIT carries it too.
36
- # `vendored_standdowns` (#1713) is the same shape of slot for what a required-feature-gated vendored
37
- # directory left out because another signature source declares it.
38
21
  def initialize
39
- @class_decl_paths = {}.freeze
40
- @signature_paths = [].freeze
41
- @synthesized_namespaces = [].freeze
42
- @quarantined_signatures = [].freeze
43
- @signature_standdowns = [].freeze
44
- @vendored_standdowns = [].freeze
45
- @conformance_results = [].freeze
46
- @env_build_failure = nil
47
- @definition_build_failures = [].freeze
48
- @hkt_scan_failure = nil
49
- @effect_annotation_carrier = [].freeze
22
+ @values = RunLevelFacts.empty_values
50
23
  end
51
24
 
52
- # Per-`#run` reset. Mirrors the original `#run` body, which reset these to NON-frozen empties (distinct
53
- # from the frozen constructor defaults) at the top of each run.
25
+ # Per-`#run` reset to every fact's declared empty value.
54
26
  def reset_for_run
55
- @class_decl_paths = {}.freeze
56
- @signature_paths = []
57
- @synthesized_namespaces = []
58
- @quarantined_signatures = []
59
- @signature_standdowns = []
60
- @vendored_standdowns = []
61
- @conformance_results = []
62
- @env_build_failure = nil
63
- @definition_build_failures = []
64
- @hkt_scan_failure = nil
65
- @effect_annotation_carrier = [].freeze
27
+ @values = RunLevelFacts.empty_values
28
+ end
29
+
30
+ def [](name)
31
+ RunLevelFacts.fact(name)
32
+ @values[name]
33
+ end
34
+
35
+ def []=(name, value)
36
+ RunLevelFacts.fact(name)
37
+ @values[name] = value
38
+ end
39
+
40
+ # Folds `facts` (`{name => value}`) into the store, each by its fact's merge rule.
41
+ def absorb(facts)
42
+ facts.each do |name, value|
43
+ merge = RunLevelFacts.fact(name).merge
44
+ @values[name] = RunLevelFacts::Merge.public_send(merge, @values[name], value)
45
+ end
46
+ self
47
+ end
48
+
49
+ def to_h
50
+ @values.dup
51
+ end
52
+
53
+ RunLevelFacts.facts.each do |fact|
54
+ define_method(fact.name) { @values[fact.name] }
55
+ define_method(:"#{fact.name}=") { |value| @values[fact.name] = value }
66
56
  end
67
57
  end
68
58
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ module Analysis
5
+ class Runner
6
+ # ADR-116 WD3 (#1195) — what one analysing {WorkerSession} hands back to the {PoolCoordinator}: its
7
+ # per-file results and every drain, taken in one place ({.harvest}) and folded in by one
8
+ # ({PoolCoordinator#absorb_worker_payload}). A fork child marshals it to its payload file, the Ractor
9
+ # backend sends it with `:done`, and the fork backend's degrade path harvests the parent session the
10
+ # same way.
11
+ #
12
+ # Every field is Marshal-clean (#805) and, on the Ractor backend, copyable: the drains are frozen
13
+ # `Data` entries and Hashes of Strings. `effects` is nil unless the run collects effects (ADR-103 WD13).
14
+ #
15
+ # The refusals are carried but not absorbed: their merge order is the file order of the whole run, so
16
+ # each backend merges them itself (#1900).
17
+ WorkerPayload = Data.define(:results, :reporters, :dependencies, :analysis_reads, :analysis_disclosures,
18
+ :disclosures, :refusals, :effects) do
19
+ # Drains `session` in the order the fork child always has: the reporters (which demand the HKT
20
+ # registry) before the refusal delta, so a refusal that demand makes is reported.
21
+ def self.harvest(session, results: {}, record_effects: false)
22
+ new(
23
+ results: results,
24
+ reporters: session.drain_reporters,
25
+ dependencies: session.drain_dependencies,
26
+ analysis_reads: session.drain_analysis_reads,
27
+ analysis_disclosures: session.drain_analysis_disclosures,
28
+ disclosures: session.drain_run_disclosures,
29
+ refusals: session.drain_trust_refusals,
30
+ effects: record_effects ? session.drain_effects : nil
31
+ )
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end