rigortype 0.4.5 → 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 (452) 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 +61 -5
  8. data/docs/handbook/appendix-phpstan.md +4 -4
  9. data/docs/manual/02-cli-reference.md +192 -27
  10. data/docs/manual/03-configuration.md +38 -4
  11. data/docs/manual/04-diagnostics.md +1 -1
  12. data/docs/manual/06-baseline.md +6 -4
  13. data/docs/manual/09-editor-integration.md +13 -2
  14. data/docs/manual/10-mcp-server.md +47 -3
  15. data/docs/manual/12-caching.md +9 -1
  16. data/docs/manual/plugins/rigor-actionmailer.md +30 -0
  17. data/docs/manual/plugins/rigor-actionpack.md +49 -11
  18. data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
  19. data/docs/manual/plugins/rigor-devise.md +15 -0
  20. data/docs/manual/plugins/rigor-rspec.md +2 -1
  21. data/lib/rigor/analysis/baseline.rb +24 -27
  22. data/lib/rigor/analysis/buffer_binding.rb +14 -0
  23. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  24. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  25. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
  26. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  27. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +105 -12
  28. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +143 -15
  29. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +4 -1
  30. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  31. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
  32. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +66 -3
  33. data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
  34. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
  35. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  36. data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
  37. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  38. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  39. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  40. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  41. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +53 -2
  42. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
  43. data/lib/rigor/analysis/check_rules.rb +217 -52
  44. data/lib/rigor/analysis/crash_signature.rb +2 -2
  45. data/lib/rigor/analysis/dependency_recorder.rb +15 -4
  46. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  47. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  48. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  49. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  50. data/lib/rigor/analysis/edit_replay.rb +154 -0
  51. data/lib/rigor/analysis/fact_store.rb +3 -3
  52. data/lib/rigor/analysis/incremental.rb +37 -0
  53. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
  54. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  55. data/lib/rigor/analysis/incremental_session.rb +202 -43
  56. data/lib/rigor/analysis/path_expansion.rb +41 -0
  57. data/lib/rigor/analysis/require_relative_closure.rb +100 -0
  58. data/lib/rigor/analysis/rule_catalog.rb +7 -3
  59. data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
  60. data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
  61. data/lib/rigor/analysis/run_stats.rb +2 -2
  62. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +8 -4
  63. data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
  64. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +204 -96
  65. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
  66. data/lib/rigor/analysis/runner/pool_coordinator.rb +157 -320
  67. data/lib/rigor/analysis/runner/project_pre_passes.rb +48 -6
  68. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  69. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  70. data/lib/rigor/analysis/runner/scan_environment.rb +51 -0
  71. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  72. data/lib/rigor/analysis/runner.rb +233 -120
  73. data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
  74. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  75. data/lib/rigor/analysis/template_units.rb +9 -1
  76. data/lib/rigor/analysis/worker_session.rb +38 -33
  77. data/lib/rigor/bleeding_edge.rb +3 -6
  78. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  79. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  80. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  81. data/lib/rigor/cache/descriptor.rb +6 -5
  82. data/lib/rigor/cache/incremental_snapshot.rb +60 -18
  83. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  84. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  85. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  86. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  87. data/lib/rigor/cache/rbs_descriptor.rb +54 -3
  88. data/lib/rigor/cache/rbs_environment.rb +4 -1
  89. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  90. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  91. data/lib/rigor/cache/store.rb +11 -10
  92. data/lib/rigor/ci_detector.rb +1 -1
  93. data/lib/rigor/cli/annotate_command.rb +24 -19
  94. data/lib/rigor/cli/baseline_command.rb +145 -55
  95. data/lib/rigor/cli/catalogue.rb +256 -0
  96. data/lib/rigor/cli/check_command.rb +188 -93
  97. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  98. data/lib/rigor/cli/command.rb +15 -1
  99. data/lib/rigor/cli/coverage_command.rb +21 -8
  100. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  101. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  102. data/lib/rigor/cli/coverage_scan.rb +3 -5
  103. data/lib/rigor/cli/diff_command.rb +27 -21
  104. data/lib/rigor/cli/docs_command.rb +1 -2
  105. data/lib/rigor/cli/doctor_command.rb +12 -12
  106. data/lib/rigor/cli/effects_command.rb +50 -40
  107. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  108. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  109. data/lib/rigor/cli/effects_renderer.rb +2 -1
  110. data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
  111. data/lib/rigor/cli/explain_command.rb +18 -8
  112. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  113. data/lib/rigor/cli/json_document.rb +41 -0
  114. data/lib/rigor/cli/lens_command.rb +17 -13
  115. data/lib/rigor/cli/lsp_command.rb +250 -23
  116. data/lib/rigor/cli/mcp_command.rb +14 -8
  117. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  118. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  119. data/lib/rigor/cli/options.rb +60 -4
  120. data/lib/rigor/cli/plugin_command.rb +93 -46
  121. data/lib/rigor/cli/plugins_command.rb +22 -18
  122. data/lib/rigor/cli/plugins_renderer.rb +34 -8
  123. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  124. data/lib/rigor/cli/protection_renderer.rb +2 -1
  125. data/lib/rigor/cli/show_bleedingedge_command.rb +23 -16
  126. data/lib/rigor/cli/sig_gen_command.rb +130 -34
  127. data/lib/rigor/cli/skill_command.rb +32 -3
  128. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  129. data/lib/rigor/cli/skill_describe.rb +1 -1
  130. data/lib/rigor/cli/trace_command.rb +14 -9
  131. data/lib/rigor/cli/triage_command.rb +10 -11
  132. data/lib/rigor/cli/triage_renderer.rb +2 -1
  133. data/lib/rigor/cli/type_of_command.rb +18 -12
  134. data/lib/rigor/cli/type_of_project_scope.rb +24 -0
  135. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  136. data/lib/rigor/cli/type_scan_command.rb +14 -9
  137. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  138. data/lib/rigor/cli/unused_command.rb +27 -17
  139. data/lib/rigor/cli/upgrade_command.rb +5 -0
  140. data/lib/rigor/cli.rb +209 -54
  141. data/lib/rigor/config_audit.rb +17 -6
  142. data/lib/rigor/configuration/severity_profile.rb +5 -5
  143. data/lib/rigor/configuration.rb +86 -34
  144. data/lib/rigor/effects/definition_context.rb +0 -2
  145. data/lib/rigor/effects/envelope.rb +1 -1
  146. data/lib/rigor/effects/framework_units.rb +1 -1
  147. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  148. data/lib/rigor/effects/propagator.rb +1 -1
  149. data/lib/rigor/effects/registry.rb +2 -2
  150. data/lib/rigor/effects/snapshot.rb +1 -1
  151. data/lib/rigor/effects/taint_cause.rb +0 -2
  152. data/lib/rigor/effects/unit_scan.rb +9 -13
  153. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
  154. data/lib/rigor/environment/class_registry.rb +3 -3
  155. data/lib/rigor/environment/core_root.rb +77 -0
  156. data/lib/rigor/environment/default_libraries.rb +1 -1
  157. data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
  158. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  159. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  160. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  161. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  162. data/lib/rigor/environment/member_consistency.rb +56 -7
  163. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  164. data/lib/rigor/environment/project_path_gems.rb +124 -0
  165. data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
  166. data/lib/rigor/environment/rbs_loader.rb +592 -70
  167. data/lib/rigor/environment/reflection.rb +7 -7
  168. data/lib/rigor/environment/required_features.rb +136 -32
  169. data/lib/rigor/environment/ruby_library_roots.rb +122 -0
  170. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  171. data/lib/rigor/environment.rb +44 -11
  172. data/lib/rigor/flow_contribution/merger.rb +4 -4
  173. data/lib/rigor/inference/acceptance.rb +93 -32
  174. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  175. data/lib/rigor/inference/block_run_count.rb +178 -0
  176. data/lib/rigor/inference/budget_trace.rb +8 -2
  177. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  178. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  179. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  180. data/lib/rigor/inference/callee_mutation_chain.rb +126 -0
  181. data/lib/rigor/inference/callee_write_census.rb +815 -0
  182. data/lib/rigor/inference/construction_escape.rb +217 -0
  183. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  184. data/lib/rigor/inference/deep_erasure.rb +170 -0
  185. data/lib/rigor/inference/def_handle.rb +1 -1
  186. data/lib/rigor/inference/def_node_resolver.rb +1 -1
  187. data/lib/rigor/inference/def_return_typer.rb +204 -12
  188. data/lib/rigor/inference/definer_resolution.rb +21 -5
  189. data/lib/rigor/inference/definers_index.rb +106 -0
  190. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  191. data/lib/rigor/inference/element_read_widening.rb +49 -3
  192. data/lib/rigor/inference/error_info.rb +17 -3
  193. data/lib/rigor/inference/expression_typer.rb +173 -57
  194. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  195. data/lib/rigor/inference/global_write_census.rb +110 -1
  196. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  197. data/lib/rigor/inference/hkt_body.rb +1 -2
  198. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  199. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  200. data/lib/rigor/inference/hkt_registry.rb +3 -5
  201. data/lib/rigor/inference/indexed_narrowing.rb +4 -7
  202. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  203. data/lib/rigor/inference/ivar_writer_census.rb +246 -0
  204. data/lib/rigor/inference/key_presence_guard.rb +2 -1
  205. data/lib/rigor/inference/loop_value_record.rb +57 -0
  206. data/lib/rigor/inference/match_rebinding/frame.rb +65 -0
  207. data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
  208. data/lib/rigor/inference/match_rebinding.rb +11 -1
  209. data/lib/rigor/inference/memo_audit.rb +55 -0
  210. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  211. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  212. data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
  213. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +113 -12
  214. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  215. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -3
  217. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  218. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
  219. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  220. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
  221. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  222. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  223. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
  224. data/lib/rigor/inference/method_dispatcher/set_folding.rb +59 -24
  225. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
  226. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  227. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  228. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  229. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  230. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  231. data/lib/rigor/inference/method_dispatcher.rb +109 -29
  232. data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
  233. data/lib/rigor/inference/module_function_state.rb +23 -1
  234. data/lib/rigor/inference/multi_target_binder.rb +1 -1
  235. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  236. data/lib/rigor/inference/mutation_widening.rb +162 -29
  237. data/lib/rigor/inference/narrowing.rb +113 -38
  238. data/lib/rigor/inference/object_mixins.rb +13 -1
  239. data/lib/rigor/inference/optimistic_origin.rb +1 -1
  240. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  241. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  242. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  243. data/lib/rigor/inference/project_method_ownership.rb +6 -3
  244. data/lib/rigor/inference/project_seed_tables.rb +2 -1
  245. data/lib/rigor/inference/project_yielder.rb +122 -0
  246. data/lib/rigor/inference/receiver_alias.rb +20 -0
  247. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
  248. data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
  249. data/lib/rigor/inference/remover_holder_scan.rb +233 -0
  250. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  251. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  252. data/lib/rigor/inference/scope_indexer/certainty.rb +9 -1
  253. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  254. data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
  255. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +205 -0
  256. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  257. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  258. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
  259. data/lib/rigor/inference/scope_indexer/unseen_ivar_writers.rb +133 -0
  260. data/lib/rigor/inference/scope_indexer.rb +614 -146
  261. data/lib/rigor/inference/self_call_ivar_writes.rb +101 -17
  262. data/lib/rigor/inference/set_mutation.rb +189 -0
  263. data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
  264. data/lib/rigor/inference/statement_evaluator.rb +3036 -278
  265. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  266. data/lib/rigor/inference/synthetic_method_index.rb +23 -0
  267. data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
  268. data/lib/rigor/inference/synthetic_method_scanner.rb +73 -14
  269. data/lib/rigor/inference/toplevel_def_census.rb +59 -0
  270. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  271. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  272. data/lib/rigor/inference/void_tail_summary.rb +2 -1
  273. data/lib/rigor/language_server/buffer_table.rb +9 -3
  274. data/lib/rigor/language_server/completion_provider.rb +26 -20
  275. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  276. data/lib/rigor/language_server/diagnostic_publisher.rb +28 -9
  277. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  278. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  279. data/lib/rigor/language_server/hover_provider.rb +1 -7
  280. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  281. data/lib/rigor/language_server/project_context.rb +98 -4
  282. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  283. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  284. data/lib/rigor/language_server/server.rb +12 -6
  285. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  286. data/lib/rigor/lens/plugins.rb +1 -1
  287. data/lib/rigor/mcp/server.rb +82 -3
  288. data/lib/rigor/mcp.rb +1 -1
  289. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  290. data/lib/rigor/plugin/base.rb +26 -11
  291. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  292. data/lib/rigor/plugin/io_boundary.rb +1 -1
  293. data/lib/rigor/plugin/loader.rb +25 -8
  294. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  295. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  296. data/lib/rigor/plugin/manifest.rb +38 -16
  297. data/lib/rigor/plugin/registry.rb +41 -3
  298. data/lib/rigor/plugin/services.rb +2 -3
  299. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  300. data/lib/rigor/plugin/trust_policy.rb +2 -2
  301. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  302. data/lib/rigor/plugin.rb +6 -8
  303. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  304. data/lib/rigor/project_environment.rb +16 -2
  305. data/lib/rigor/protection/analysis_guard.rb +1 -1
  306. data/lib/rigor/protection/discovery_seed.rb +96 -2
  307. data/lib/rigor/protection/mutator.rb +1 -1
  308. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  309. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  310. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  311. data/lib/rigor/rbs_extended.rb +2 -3
  312. data/lib/rigor/reflection/constant_ancestors.rb +5 -3
  313. data/lib/rigor/rooted_glob.rb +25 -2
  314. data/lib/rigor/run_scoped_slots.rb +38 -0
  315. data/lib/rigor/runtime/jit.rb +1 -1
  316. data/lib/rigor/scope/discovery_index.rb +19 -6
  317. data/lib/rigor/scope/resolution_chain.rb +3 -1
  318. data/lib/rigor/scope.rb +379 -36
  319. data/lib/rigor/sig_gen/classification.rb +4 -0
  320. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  321. data/lib/rigor/sig_gen/generator.rb +668 -84
  322. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  323. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  324. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  325. data/lib/rigor/sig_gen/method_candidate.rb +23 -4
  326. data/lib/rigor/sig_gen/observation_collector.rb +7 -6
  327. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  328. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  329. data/lib/rigor/sig_gen/rbs_validity.rb +1 -1
  330. data/lib/rigor/sig_gen/renderer.rb +21 -12
  331. data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
  332. data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
  333. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  334. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  335. data/lib/rigor/sig_gen/write_result.rb +23 -12
  336. data/lib/rigor/sig_gen/writer.rb +217 -37
  337. data/lib/rigor/type/acceptance_router.rb +2 -2
  338. data/lib/rigor/type/accepts_result.rb +1 -1
  339. data/lib/rigor/type/app.rb +4 -6
  340. data/lib/rigor/type/combinator.rb +13 -16
  341. data/lib/rigor/type/constant.rb +48 -7
  342. data/lib/rigor/type/hash_shape.rb +26 -40
  343. data/lib/rigor/type/struct_instance.rb +3 -2
  344. data/lib/rigor/type/union.rb +1 -1
  345. data/lib/rigor/type_node/identifier.rb +1 -2
  346. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  347. data/lib/rigor/type_node/string_literal.rb +1 -2
  348. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  349. data/lib/rigor/type_node/union.rb +3 -6
  350. data/lib/rigor/value_semantics.rb +1 -10
  351. data/lib/rigor/version.rb +1 -1
  352. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  353. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  354. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  355. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +89 -70
  356. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  357. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +25 -4
  358. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  359. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  360. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  361. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  362. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
  363. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +79 -19
  364. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  365. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +6 -3
  366. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  367. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  368. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  369. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  370. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
  371. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
  372. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
  373. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  374. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  375. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
  376. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  377. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  378. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  379. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
  380. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
  381. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  382. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +5 -3
  383. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  384. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  385. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  386. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +7 -6
  387. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  388. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  389. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  390. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  391. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  392. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  393. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  394. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
  395. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  396. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  397. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  398. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  399. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
  400. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  401. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  402. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  403. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  404. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  405. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
  406. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  407. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  408. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  409. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
  410. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  411. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +102 -11
  412. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  413. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  414. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +5 -2
  416. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  417. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
  421. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  422. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
  423. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  424. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
  425. data/sig/rigor/analysis/baseline.rbs +0 -2
  426. data/sig/rigor/analysis/fact_store.rbs +1 -0
  427. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  428. data/sig/rigor/cli/diff_command.rbs +1 -1
  429. data/sig/rigor/cli/explain_command.rbs +1 -1
  430. data/sig/rigor/effects/file_collection.rbs +5 -6
  431. data/sig/rigor/effects/registry.rbs +8 -6
  432. data/sig/rigor/effects/summary.rbs +6 -5
  433. data/sig/rigor/environment.rbs +5 -3
  434. data/sig/rigor/inference/optimistic_origin.rbs +11 -0
  435. data/sig/rigor/inference.rbs +10 -1
  436. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  437. data/sig/rigor/plugin/base.rbs +1 -0
  438. data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
  439. data/sig/rigor/plugin/effect_attribution.rbs +25 -10
  440. data/sig/rigor/plugin/effect_edge.rbs +12 -5
  441. data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
  442. data/sig/rigor/plugin/io_boundary.rbs +6 -5
  443. data/sig/rigor/plugin/manifest.rbs +16 -18
  444. data/sig/rigor/plugin/protocol_contract.rbs +6 -9
  445. data/sig/rigor/plugin/registry.rbs +1 -1
  446. data/sig/rigor/scope.rbs +51 -50
  447. data/sig/rigor/type.rbs +0 -6
  448. data/skills/rigor-plugin-author/SKILL.md +4 -4
  449. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  450. data/skills/rigor-plugin-review/SKILL.md +3 -3
  451. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
  452. metadata +55 -3
@@ -155,6 +155,10 @@ module Rigor
155
155
  ].freeze
156
156
  Ractor.make_shareable(TOOLS)
157
157
 
158
+ # The tools that read a configuration — the ones taking a `config` argument — and so report its warnings.
159
+ CONFIG_TOOLS = TOOLS.filter_map { |tool| tool[:name] if tool.dig(:inputSchema, :properties, :config) }.freeze
160
+ Ractor.make_shareable(CONFIG_TOOLS)
161
+
158
162
  def initialize(config_path: nil, err: $stderr)
159
163
  @config_path = config_path
160
164
  @err = err
@@ -165,7 +169,7 @@ module Rigor
165
169
  id = request["id"]
166
170
  method_name = request["method"]
167
171
 
168
- # Notifications carry no `id` and require no response.
172
+ # A notification carries no `id` and requires no response.
169
173
  return nil if id.nil?
170
174
 
171
175
  case method_name
@@ -181,8 +185,38 @@ module Rigor
181
185
  end
182
186
  end
183
187
 
188
+ # ADR-126 WD4 — `rigor_type_of` and `rigor_explain` print documents whose top-level shape changes in the next
189
+ # minor, and their CLI says so on stderr, which `tool_result` drops once there is a document. The notice is
190
+ # carried to the agent as its own `text` content block, the same text the CLI printed, so the document block
191
+ # stays byte-for-byte what the CLI wrote. When stdout is empty the error text already is stderr.
192
+ SHAPE_NOTICE_TOOLS = %w[rigor_type_of rigor_explain].freeze
193
+
184
194
  private
185
195
 
196
+ def finish_tool_result(name, args, out, err, exit_code)
197
+ result = with_config_warnings(tool_result(out, err, exit_code), name, args, out)
198
+ result = with_shape_notice(result, name, out, err)
199
+ with_path_warnings(result, name, out, err)
200
+ end
201
+
202
+ # #2206 — `rigor coverage` writes its path warnings (a missing path skipped beside files it measures) to stderr,
203
+ # which a successful run drops. The tool carries the CLI's whole stderr (path warnings and any other warnings)
204
+ # as a second text block, unfiltered, so an agent sees what was skipped.
205
+ def with_path_warnings(result, name, out, err)
206
+ return result unless name == "rigor_coverage" && !out.empty? && !err.strip.empty?
207
+
208
+ result.merge(content: result.fetch(:content) + [{ type: "text", text: err }])
209
+ end
210
+
211
+ def with_shape_notice(result, name, out, err)
212
+ return result unless SHAPE_NOTICE_TOOLS.include?(name) && !out.empty?
213
+
214
+ notice = err[CLI::JsonDocument::NOTICE_PATTERN]
215
+ return result unless notice
216
+
217
+ result.merge(content: result.fetch(:content) + [{ type: "text", text: notice }])
218
+ end
219
+
186
220
  def handle_initialize(id)
187
221
  require_relative "../version"
188
222
  success(id, {
@@ -201,7 +235,7 @@ module Rigor
201
235
  require_relative "../cli"
202
236
  exit_code = with_captured_streams(out_io, err_io) { CLI.new(argv, out: out_io, err: err_io).run }
203
237
 
204
- success(id, tool_result(out_io.string, err_io.string, exit_code))
238
+ success(id, finish_tool_result(name, args, out_io.string, err_io.string, exit_code))
205
239
  rescue SystemExit => e
206
240
  # Defence against any command that exits: it must not take the server down with it.
207
241
  success(id, { content: [{ type: "text", text: "Command exited (status #{e.status})" }], isError: true })
@@ -221,6 +255,43 @@ module Rigor
221
255
  { content: [{ type: "text", text: out.empty? ? err : out }], isError: is_error }
222
256
  end
223
257
 
258
+ # Issue #2231 — `rigor check` puts {ConfigAudit} warnings in its JSON `config_warnings`; the other tools read the
259
+ # same configuration and said nothing about it, so an agent saw `call.undefined-method` on a project whose
260
+ # `.rigor.yml` misspelt the key meant to silence it and got no hint why. Every tool that takes a `config`
261
+ # argument now carries them: a second `text` content block holding `{"config_warnings": [...]}`, each entry the
262
+ # shape check's JSON uses. The tool's own document is left byte-for-byte as its CLI printed it, and an output
263
+ # that already carries the key (check's) gets no second copy. Nothing is added when there is nothing to say,
264
+ # nor when the configuration does not load — the CLI has already reported that.
265
+ def with_config_warnings(result, name, args, out)
266
+ return result unless CONFIG_TOOLS.include?(name)
267
+
268
+ warnings = config_warnings(present_config(args["config"]) || @config_path)
269
+ return result if warnings.empty? || carries_config_warnings?(out)
270
+
271
+ block = { type: "text", text: JSON.generate({ "config_warnings" => warnings.map(&:to_h) }) }
272
+ result.merge(content: result.fetch(:content) + [block])
273
+ end
274
+
275
+ # Only a configuration that does not load is "no warnings to show": the CLI has already reported that error.
276
+ # A failure in ConfigAudit itself is a bug and surfaces as an internal error rather than reading as clean.
277
+ def config_warnings(config_path)
278
+ require_relative "../configuration"
279
+ require_relative "../config_audit"
280
+ begin
281
+ configuration = Configuration.load(config_path)
282
+ rescue ConfigurationError
283
+ return []
284
+ end
285
+ ConfigAudit.warnings(configuration)
286
+ end
287
+
288
+ def carries_config_warnings?(out)
289
+ document = JSON.parse(out)
290
+ document.is_a?(Hash) && document.key?("config_warnings")
291
+ rescue JSON::ParserError
292
+ false
293
+ end
294
+
224
295
  # The JSON-RPC channel is the process's stdout, so nothing a command writes past its `out:` (an option
225
296
  # parser's `--help`, a stray `puts`) may reach it while the command runs.
226
297
  def with_captured_streams(out_io, err_io)
@@ -235,7 +306,7 @@ module Rigor
235
306
  end
236
307
 
237
308
  def build_argv(name, args) # rubocop:disable Metrics/MethodLength,Metrics/CyclomaticComplexity,Metrics/AbcSize,Metrics/PerceivedComplexity
238
- effective_config = args["config"] || @config_path
309
+ effective_config = present_config(args["config"]) || @config_path
239
310
 
240
311
  case name
241
312
  when "rigor_check"
@@ -290,6 +361,14 @@ module Rigor
290
361
  end
291
362
  end
292
363
 
364
+ # A per-call `config` that is blank names no file, so the session default applies rather than `--config=`
365
+ # reaching the CLI as an empty path (which the CLI refuses).
366
+ def present_config(value)
367
+ return value unless value.is_a?(String) && value.strip.empty?
368
+
369
+ nil
370
+ end
371
+
293
372
  def success(id, result)
294
373
  { "jsonrpc" => "2.0", "id" => id, "result" => result }
295
374
  end
data/lib/rigor/mcp.rb CHANGED
@@ -4,7 +4,7 @@ module Rigor
4
4
  # The MCP (Model Context Protocol) server subsystem. See `docs/adr/33-mcp-server.md` for the design.
5
5
  #
6
6
  # Entry point: `rigor mcp --transport stdio`. The server exposes Rigor's analysis tools (check, type-of, triage,
7
- # annotate, sig-gen, explain, coverage) as MCP tool calls over a newline-delimited JSON-RPC 2.0 stdio stream.
7
+ # lens, annotate, sig-gen, explain, coverage) as MCP tool calls over a newline-delimited JSON-RPC 2.0 stdio stream.
8
8
  module MCP
9
9
  end
10
10
  end
@@ -6,8 +6,7 @@ module Rigor
6
6
  # analyzer can convert the exception into a `:plugin_loader` diagnostic without crashing `rigor check`.
7
7
  #
8
8
  # The policy is documented in [ADR-2 § "Plugin Trust and I/O Policy"](../../../docs/adr/2-extension-api.md).
9
- # Slice 2's surface lists `:read_outside_scope` and `:network_disabled` as the two reason codes; future slices
10
- # may extend the set.
9
+ # `reason:` is one of `:read_outside_scope`, `:network_disabled`, `:url_fetch_failed` or `:url_body_too_large`.
11
10
  class AccessDeniedError < StandardError
12
11
  attr_reader :reason, :resource
13
12
 
@@ -8,6 +8,7 @@ require "prism"
8
8
  require_relative "manifest"
9
9
  require_relative "node_context"
10
10
  require_relative "../analysis/diagnostic"
11
+ require_relative "../analysis/run_disclosure_credit"
11
12
  require_relative "../cache/engine_source"
12
13
  # `producer generation_cap:` defaults to (and validates against) `Cache::Store::UNBOUNDED_GENERATIONS`, and a
13
14
  # plugin class body can be evaluated before anything else pulled the cache layer in.
@@ -780,11 +781,12 @@ module Rigor
780
781
  # that differs between workers.
781
782
  def disclose_once(key, message:, severity: :info, rule: "load-error")
782
783
  id = key.to_s
783
- return nil if run_registration_taken?(id, batch: false)
784
-
785
- @run_disclosures[id] = {
786
- key: id, message: message.to_s, severity: severity.to_sym, rule: rule.to_s
787
- }.freeze
784
+ unless run_registration_taken?(id, batch: false)
785
+ @run_disclosures[id] = {
786
+ key: id, message: message.to_s, severity: severity.to_sym, rule: rule.to_s
787
+ }.freeze
788
+ end
789
+ attribute_run_registration(id)
788
790
  nil
789
791
  end
790
792
 
@@ -816,14 +818,15 @@ module Rigor
816
818
  # raises `ArgumentError`, which the engine reports as this plugin's `runtime-error`.
817
819
  def emit_once(key, diagnostics)
818
820
  id = key.to_s
819
- return nil if run_registration_taken?(id, batch: true)
820
-
821
- batch = Array(diagnostics).map do |row|
822
- next row.dup.freeze if row.is_a?(Rigor::Analysis::Diagnostic)
821
+ unless run_registration_taken?(id, batch: true)
822
+ batch = Array(diagnostics).map do |row|
823
+ next row.dup.freeze if row.is_a?(Rigor::Analysis::Diagnostic)
823
824
 
824
- raise ArgumentError, "emit_once expects Rigor::Analysis::Diagnostic rows, got #{row.class}"
825
+ raise ArgumentError, "emit_once expects Rigor::Analysis::Diagnostic rows, got #{row.class}"
826
+ end
827
+ @run_disclosures[id] = { key: id, diagnostics: batch.freeze }.freeze
825
828
  end
826
- @run_disclosures[id] = { key: id, diagnostics: batch.freeze }.freeze
829
+ attribute_run_registration(id)
827
830
  nil
828
831
  end
829
832
 
@@ -847,6 +850,18 @@ module Rigor
847
850
  end
848
851
  private :run_registration_taken?
849
852
 
853
+ # Issue #2020 — hands every call, a repeat of a key already registered included, to the engine's per-file
854
+ # attribution ({Analysis::RunDisclosureCredit}), so an `--incremental` recheck can replay the row for a file
855
+ # it serves from cache. A repeat counts because a discloser memoises the input its notice is about and
856
+ # re-registers the notice from each file's hook. A batch registered empty adds nothing to replay.
857
+ def attribute_run_registration(id)
858
+ record = @run_disclosures[id]
859
+ return if record.nil? || record[:diagnostics]&.empty?
860
+
861
+ Analysis::RunDisclosureCredit.attribute(self, record)
862
+ end
863
+ private :attribute_run_registration
864
+
850
865
  # Boilerplate-reduction helper (review §1.3): the "did you mean …?" suggestion every
851
866
  # diagnostic-emitting plugin otherwise hand-rolls. Returns the closest of `candidates` to `name` via
852
867
  # `DidYouMean::SpellChecker` (the same engine Ruby's own `NoMethodError` hints use), or `nil` when
@@ -127,7 +127,7 @@ module Rigor
127
127
  def require_bundled_plugins
128
128
  return {} unless File.directory?(BUNDLED_PLUGINS_ROOT)
129
129
 
130
- add_bundled_lib_dirs
130
+ Loader.add_bundled_lib_dirs
131
131
  Dir.children(BUNDLED_PLUGINS_ROOT).sort.each_with_object({}) do |gem_name, resolved|
132
132
  path = Loader.bundled_plugin_path(gem_name)
133
133
  next if path.nil?
@@ -137,17 +137,6 @@ module Rigor
137
137
  end
138
138
  end
139
139
 
140
- # A bundled plugin's entry file requires its own tree by bare feature name (`require
141
- # "rigor/plugin/activestorage"`), which resolves only with that plugin's `lib` on the load path — the
142
- # loader gets it from the bundle, and this catalogue has to arrange it itself. Prepending the engine's
143
- # own copies matches ADR-93 WD5's anti-skew rule, where the bundled copy always wins.
144
- def add_bundled_lib_dirs
145
- Dir.children(BUNDLED_PLUGINS_ROOT).sort.each do |gem_name|
146
- dir = File.join(BUNDLED_PLUGINS_ROOT, gem_name, "lib")
147
- $LOAD_PATH.unshift(dir) if File.directory?(dir) && !$LOAD_PATH.include?(dir)
148
- end
149
- end
150
-
151
140
  # A plugin gem that fails to load is skipped rather than fatal: the catalogue exists to *advise*, and
152
141
  # a broken bundled copy is already reported by `rigor doctor`'s plugin checks for the plugins the
153
142
  # project actually enabled. The failure is recorded in {load_failures} so a missing entry stays
@@ -17,7 +17,7 @@ module Rigor
17
17
  # scope and feed the cache descriptor; contributions built on top of out-of-scope reads will not
18
18
  # invalidate correctly.
19
19
  #
20
- # Slice 2 ships a minimal surface:
20
+ # The surface:
21
21
  #
22
22
  # - `#read_file(path)` — validates against the policy, returns the file's contents, and adds a
23
23
  # digest-keyed {Cache::Descriptor::FileEntry} to the boundary's accumulated descriptor. A read that
@@ -90,6 +90,20 @@ module Rigor
90
90
  File.directory?(path) ? path : nil
91
91
  end
92
92
 
93
+ # Puts every bundled plugin's `lib` on `$LOAD_PATH`, so a bundled plugin that requires another by gem
94
+ # name (`require "rigor-ffi"` from `rigor-ethon`, `rigor-sassc`, `rigor-ffi-rzmq` and `rigor-rbnacl`)
95
+ # resolves without a bundle. The engine's own copies go first, which matches ADR-93 WD5's anti-skew
96
+ # rule: the bundled copy always wins. Idempotent; a trimmed packaging with no `plugins/` tree adds none.
97
+ def self.add_bundled_lib_dirs
98
+ root = File.join(ENGINE_ROOT, "plugins")
99
+ return unless File.directory?(root)
100
+
101
+ Dir.children(root).sort.each do |gem_name|
102
+ dir = File.join(root, gem_name, "lib")
103
+ $LOAD_PATH.unshift(dir) if File.directory?(dir) && !$LOAD_PATH.include?(dir)
104
+ end
105
+ end
106
+
93
107
  # @param entries — the raw `plugins:` list from the configuration.
94
108
  def load(entries)
95
109
  plugins = []
@@ -233,7 +247,13 @@ module Rigor
233
247
  # `feature_resolver` is unaffected: an absolute-path require's `$LOADED_FEATURES` entry still ends in
234
248
  # `/<gem>.rb`, the suffix {FEATURE_RESOLVER} matches.
235
249
  def require_gem!(entry)
236
- @requirer.call(self.class.bundled_plugin_path(entry[:gem]) || entry[:gem])
250
+ path = self.class.bundled_plugin_path(entry[:gem])
251
+ if path
252
+ self.class.add_bundled_lib_dirs
253
+ @requirer.call(path)
254
+ else
255
+ @requirer.call(entry[:gem])
256
+ end
237
257
  rescue ::LoadError => e
238
258
  raise LoadError.new(
239
259
  "could not load plugin gem #{entry[:gem].inspect}: #{e.message}",
@@ -272,13 +292,10 @@ module Rigor
272
292
  end
273
293
  end
274
294
 
275
- # A gem that registers more than one plugin is a meta-gem only when NONE of the ids is its own. The
276
- # FFI family (`rigor-sassc`, `rigor-ethon`, `rigor-ffi-rzmq`, `rigor-rbnacl`) each `require "rigor-ffi"`
277
- # for the shared binding analyzer, so listing one of them ALONE registered two plugins — its own and
278
- # `ffi` — and the meta-gem branch rejected the entry, telling the user to list `rigor-ffi` instead,
279
- # which activates the wrong plugin. The failure was load-order dependent (listing `rigor-ffi` first
280
- # made the nested require a no-op and the entry loaded), so it never showed up in the suite, where
281
- # some earlier example has always loaded `rigor-ffi`.
295
+ # A gem that registers several plugins is a meta-gem only when NONE of the ids is its own. The FFI family
296
+ # (`rigor-sassc`, `rigor-ethon`, `rigor-ffi-rzmq`, `rigor-rbnacl`) each `require "rigor-ffi"`, so listing
297
+ # one alone registered `ffi` too and was wrongly rejected as a meta-gem. The bug was load-order dependent
298
+ # and hid in the suite, where an earlier example always loaded `rigor-ffi`.
282
299
  #
283
300
  # `rigor-rails` stays a meta-gem under this rule: it registers eight ids and none of them is `rails`.
284
301
  def own_id_among(gem_name, ids)
@@ -48,11 +48,11 @@ module Rigor
48
48
  #
49
49
  # Slice 2 ships at the **floor**: each emit row's `name:` is the source of truth for the synthetic
50
50
  # method's name (a single `"\#{name}"` placeholder gets interpolated with the literal symbol argument at
51
- # `symbol_arg_position`). The `returns:` strings are **recorded in the manifest but not resolved**; the
52
- # engine emits synthetic methods with `Dynamic[T]` returns plus a `macro.tier_c.unresolved-return`
53
- # provenance marker. Precise return-type resolution via ADR-13's `Plugin::TypeNodeResolver` is the
54
- # **ceiling**, deferred to a later slice — the `returns:` declarations cost nothing to write today and
55
- # unlock precision then.
51
+ # `symbol_arg_position`). The synthetic method's return type is the `returns_from_arg:` lookup when it
52
+ # resolves, else a non-empty `returns:` string (`SyntheticMethodScanner#resolve_emit_return_type`); the
53
+ # dispatcher turns it into a nominal type through `MethodDispatcher#promote_via_return_type` when the
54
+ # environment knows the class. With neither, an `"untyped"` / `"void"` placeholder, or an unknown class, the
55
+ # method answers `Dynamic[Top]`. Resolution through ADR-13's `Plugin::TypeNodeResolver` is not wired.
56
56
  #
57
57
  # Engine wiring: `Inference::SyntheticMethodScanner` (slice 2b, `synthetic_method_scanner.rb`) consumes
58
58
  # `manifest.heredoc_templates`. Worked consumers: `plugins/rigor-dry-struct/` and
@@ -98,7 +98,7 @@ module Rigor
98
98
  # `\#{name}` with the call-site literal symbol) and its declared return type. The return type can be
99
99
  # a static String (resolved via `Environment#nominal_for_name` per ADR-16 slice 6b) or a
100
100
  # per-call-site lookup ({ReturnsFromArg}) — see
101
- # [ADR-18](../../../../../docs/adr/18-substrate-per-call-site-return-type.md). When both are nil, the
101
+ # [ADR-18](../../../../docs/adr/18-substrate-per-call-site-return-type.md). When both are nil, the
102
102
  # synthesised method's return type falls back to `Dynamic[Top]`.
103
103
  class Emit
104
104
  attr_reader :name, :returns, :returns_from_arg
@@ -147,8 +147,7 @@ module Rigor
147
147
  # lookup_via: { plugin_id: "dry-types", fact: :dry_type_aliases }
148
148
  # }
149
149
  #
150
- # Slice 1 (this file) ships the value class + validation only. The scanner-side arg-position
151
- # extraction + fact-store lookup land in slice 2 / 3.
150
+ # The scanner's `resolve_emit_return_type` does the arg-position extraction and fact-store lookup.
152
151
  class ReturnsFromArg
153
152
  attr_reader :position, :plugin_id, :fact
154
153
 
@@ -39,12 +39,10 @@ module Rigor
39
39
  # lexical receiver class equals or inherits from this constraint.
40
40
  # - `method_name` — Symbol naming the DSL method (e.g. `:devise`).
41
41
  # - `symbol_arg_position` — `:rest` (all positional Symbol args are traits, slice 3a's only supported
42
- # form) or a non-negative Integer (the index of a single trait symbol — reserved for future shapes;
43
- # not yet honoured by the scanner).
44
- # - `modules_by_symbol` — Hash<Symbol, String>. Maps each recognised trait symbol to a fully-qualified
45
- # module name. Symbols not in the table fall through (silent skip; the scanner emits a
46
- # `macro.tier_b.unknown-trait` `:info` provenance marker per WD9 / WD13).
47
- # - `always_included` — Array<String>. Fully-qualified module names that are added to every call site
42
+ # form) or a non-negative Integer (the index of a single trait symbol).
43
+ # - `modules_by_symbol` — Maps each recognised trait symbol to a fully-qualified module name. Symbols not
44
+ # in the table are silently skipped.
45
+ # - `always_included` — Fully-qualified module names that are added to every call site
48
46
  # (even when no symbols match). Mirrors Devise's `always_include` modules.
49
47
  #
50
48
  # ## Floor / ceiling per ADR-16 WD13
@@ -53,7 +51,7 @@ module Rigor
53
51
  # methods into the existing `SyntheticMethodIndex` (slice 2b primitive). The synthesised methods adopt
54
52
  # the module's authored RBS return types — Tier B is NOT subject to the Tier C `Dynamic[T]` floor
55
53
  # because the source-of-truth (the module's authored RBS) is not a manifest-declared string. Per ADR-5
56
- # robustness, the substrate does not fabricate precision; it simply replays the modules's signatures.
54
+ # robustness, the substrate does not fabricate precision; it simply replays the module's signatures.
57
55
  #
58
56
  # **Out of scope for slice 3** (deferred follow-ups):
59
57
  # - `class_methods_module:` per-trait (Devise's `ClassMethods` extend-pattern); slice 3 covers instance
@@ -106,8 +104,7 @@ module Rigor
106
104
  end
107
105
 
108
106
  # @return fully-qualified module name for the given trait symbol, or nil when the
109
- # registry doesn't know the symbol (caller emits a tier_b.unknown-trait provenance marker and falls
110
- # through).
107
+ # registry doesn't know the symbol (the caller skips it).
111
108
  def module_for(symbol)
112
109
  modules_by_symbol[symbol.to_sym]
113
110
  end
@@ -56,7 +56,7 @@ module Rigor
56
56
  :hkt_registrations, :hkt_definitions, :signature_paths, :protocol_contracts,
57
57
  :source_rbs_synthesizer, :additional_initializers,
58
58
  :effect_root, :effect_labels, :effect_attributions, :effect_edges, :effect_entry_points,
59
- :effect_ancestry
59
+ :effect_ancestry, :per_invocation_classes, :per_invocation_non_action_macros
60
60
 
61
61
  # #392 — the project-relative globs whose files this plugin compiles into {TemplateUnit}s. Purely a
62
62
  # CLAIM: the engine globs these, reads each match's bytes and offers them to
@@ -74,7 +74,8 @@ module Rigor
74
74
  hkt_registrations: [], hkt_definitions: [], signature_paths: [], protocol_contracts: [],
75
75
  source_rbs_synthesizer: nil, additional_initializers: [],
76
76
  effect_root: nil, effect_labels: [], effect_attributions: [], effect_edges: [],
77
- effect_entry_points: [], effect_ancestry: [], template_globs: []
77
+ effect_entry_points: [], effect_ancestry: [], template_globs: [], per_invocation_classes: [],
78
+ per_invocation_non_action_macros: []
78
79
  )
79
80
  validate_id!(id)
80
81
  validate_version!(version)
@@ -109,6 +110,7 @@ module Rigor
109
110
  assign_additional_initializers(additional_initializers)
110
111
  assign_effect_fields(effect_root, effect_labels, effect_attributions, effect_edges,
111
112
  effect_entry_points, effect_ancestry, template_globs)
113
+ assign_per_invocation_classes(per_invocation_classes, per_invocation_non_action_macros)
112
114
  freeze
113
115
  end
114
116
 
@@ -166,9 +168,8 @@ module Rigor
166
168
  end
167
169
  private :assign_additional_initializers
168
170
 
169
- # ADR-103 WD2 / WD6 / WD10 / WD14 — the effect contract's five fields, assigned together and outside
170
- # `assign_fields` (which already carries the maximum positional arity).
171
- # The six `effect_*` fields validate together, so {#initialize} carries one call rather than six.
171
+ # ADR-103 WD2 / WD6 / WD10 / WD14 — the six `effect_*` fields validate together here, so {#initialize}
172
+ # carries one call rather than six.
172
173
  def validate_effect_fields!(effect_root, effect_labels, effect_attributions, effect_edges,
173
174
  effect_entry_points, effect_ancestry)
174
175
  validate_effect_root!(effect_root)
@@ -202,6 +203,28 @@ module Rigor
202
203
  @template_globs = globs.map { |glob| glob.to_s.dup.freeze }.uniq.freeze
203
204
  end
204
205
  private :assign_template_globs
206
+
207
+ # ADR-125 — `per_invocation_classes:` names the framework classes whose every instance serves exactly one
208
+ # entry point, so ivar writes in two distinct entry points of a descendant are never live together.
209
+ # `per_invocation_non_action_macros:` names the class-body macros whose Symbol, String or inline-`def`
210
+ # arguments name a public method that is NOT an entry point (`before_action :load`, `helper_method
211
+ # :current_user`); they apply to this manifest's own `per_invocation_classes:`, so a list without classes
212
+ # is refused. Validated and assigned in one call, because `#initialize` is at its line budget.
213
+ def assign_per_invocation_classes(names, macros)
214
+ validate_array_of!("per_invocation_classes", names, "non-empty String") do |name|
215
+ name.is_a?(String) && !name.empty?
216
+ end
217
+ validate_array_of!("per_invocation_non_action_macros", macros, "Symbol/non-empty String") do |macro|
218
+ macro.is_a?(Symbol) || (macro.is_a?(String) && !macro.empty?)
219
+ end
220
+ if names.empty? && !macros.empty?
221
+ raise ArgumentError, "per_invocation_non_action_macros needs per_invocation_classes to apply to"
222
+ end
223
+
224
+ @per_invocation_classes = names.map { |name| name.to_s.delete_prefix("::").dup.freeze }.uniq.freeze
225
+ @per_invocation_non_action_macros = macros.map(&:to_sym).uniq.freeze
226
+ end
227
+ private :assign_per_invocation_classes
205
228
  # rubocop:enable Metrics/ParameterLists, Metrics/AbcSize
206
229
 
207
230
  public
@@ -258,7 +281,9 @@ module Rigor
258
281
  "effect_edges" => effect_edges.map(&:to_h),
259
282
  "effect_entry_points" => effect_entry_points.map(&:to_h),
260
283
  "effect_ancestry" => effect_ancestry.map(&:to_h),
261
- "template_globs" => template_globs
284
+ "template_globs" => template_globs,
285
+ "per_invocation_classes" => per_invocation_classes,
286
+ "per_invocation_non_action_macros" => per_invocation_non_action_macros.map(&:to_s)
262
287
  }
263
288
  end
264
289
 
@@ -449,10 +474,9 @@ module Rigor
449
474
  end
450
475
  end
451
476
 
452
- # ADR-13 slice 2 — `type_node_resolvers:` declares the plugin-supplied `TypeNodeResolver` instances the
453
- # parser consults (in slice 3) when an RBS::Extended payload's named- or generic-type head misses the
454
- # built-in registry. Slice 2 carries the declarations on the manifest and the registry exposes them in
455
- # registration order; the parser integration that actually drives the chain lands in slice 3.
477
+ # ADR-13 — `type_node_resolvers:` declares the plugin-supplied `TypeNodeResolver` instances the parser
478
+ # consults when an RBS::Extended payload's named- or generic-type head misses the built-in registry. The
479
+ # registry exposes them in registration order, and `Environment#build_name_scope` builds the chain.
456
480
  def validate_type_node_resolvers!(resolvers)
457
481
  validate_array_of!("type_node_resolvers", resolvers, "Rigor::Plugin::TypeNodeResolver instances") do |r|
458
482
  r.is_a?(TypeNodeResolver)
@@ -460,8 +484,7 @@ module Rigor
460
484
  end
461
485
 
462
486
  # ADR-16 slice 1a — `block_as_methods:` declares the Tier A substrate entries the plugin contributes.
463
- # Slice 1a carries the declarations on the manifest; the engine hook that actually narrows
464
- # `Scope#self_type` for matching blocks arrives in a subsequent slice.
487
+ # The engine hook that narrows `Scope#self_type` for matching blocks is `Inference::MacroBlockSelfType`.
465
488
  def validate_block_as_methods!(entries)
466
489
  validate_array_of!("block_as_methods", entries, "Rigor::Plugin::Macro::BlockAsMethod instances") do |e|
467
490
  e.is_a?(Macro::BlockAsMethod)
@@ -469,8 +492,7 @@ module Rigor
469
492
  end
470
493
 
471
494
  # ADR-16 slice 2a — `heredoc_templates:` declares the Tier C substrate entries (heredoc-template
472
- # synthesis on class-level DSL calls). Slice 2a carries the declarations on the manifest; the pre-pass +
473
- # `SyntheticMethodIndex` that actually emit synthetic methods arrive in slice 2b.
495
+ # synthesis on class-level DSL calls). `Inference::SyntheticMethodScanner` emits the synthetic methods.
474
496
  def validate_heredoc_templates!(entries)
475
497
  validate_array_of!("heredoc_templates", entries, "Rigor::Plugin::Macro::HeredocTemplate instances") do |e|
476
498
  e.is_a?(Macro::HeredocTemplate)
@@ -489,8 +511,8 @@ module Rigor
489
511
  end
490
512
 
491
513
  # ADR-16 slice 3a — `trait_registries:` declares the Tier B substrate entries (trait-inlining via
492
- # bundled module registry). Slice 3a carries the declarations on the manifest; the scanner + per-method
493
- # explosion through `SyntheticMethodIndex` (slice 2b primitive) arrives in slice 3b.
514
+ # bundled module registry). `Inference::SyntheticMethodScanner#collect_trait_registries` explodes them
515
+ # per method through `SyntheticMethodIndex`.
494
516
  def validate_trait_registries!(entries)
495
517
  validate_array_of!("trait_registries", entries, "Rigor::Plugin::Macro::TraitRegistry instances") do |e|
496
518
  e.is_a?(Macro::TraitRegistry)
@@ -214,7 +214,7 @@ module Rigor
214
214
  # tie-breaker.
215
215
  #
216
216
  # ADR-15 Phase 3 — alongside the instantiated `plugins`, the registry carries `blueprints`: a frozen,
217
- # Ractor-shareable `Array<Blueprint>` that records how to re-instantiate the same plugin set in a worker
217
+ # Ractor-shareable list of {Blueprint} that records how to re-instantiate the same plugin set in a worker
218
218
  # Ractor. The eventual Phase 4 pool ships `blueprints` across the boundary and calls {.materialize}
219
219
  # per-Ractor; the live `plugins` carriage on the coordinator registry stays unchanged.
220
220
  class Registry
@@ -397,6 +397,28 @@ module Rigor
397
397
  @rbs_complete_extends_set.include?(class_name.to_s)
398
398
  end
399
399
 
400
+ # ADR-125 — the aggregate class names declared across loaded plugins as per-invocation (manifest
401
+ # `per_invocation_classes:`): each instance of a descendant serves exactly one public-method entry point.
402
+ # `per_invocation_class?` is the exact-name membership predicate; `def.ivar-write-mismatch` walks a
403
+ # class's ancestry and asks it of each ancestor.
404
+ attr_reader :per_invocation_classes
405
+
406
+ def per_invocation_class?(class_name)
407
+ return false if class_name.nil?
408
+
409
+ @per_invocation_classes_set.include?(class_name.to_s.delete_prefix("::"))
410
+ end
411
+
412
+ # ADR-125 — the macros whose arguments name a non-entry-point method on a descendant of the declared
413
+ # per-invocation class `class_name`: the union of `per_invocation_non_action_macros:` over the manifests
414
+ # that declare that class. An empty Set for a class no manifest declares.
415
+ def per_invocation_non_action_macros(class_name)
416
+ @per_invocation_macros.fetch(class_name.to_s.delete_prefix("::"), EMPTY_MACROS)
417
+ end
418
+
419
+ EMPTY_MACROS = Set.new.freeze
420
+ private_constant :EMPTY_MACROS
421
+
400
422
  # ADR-28 — flat, ordered list of every loaded plugin's path-scoped method-protocol contracts, in
401
423
  # plugin registration order. Read from each plugin's `#protocol_contracts` (which the manifest backs
402
424
  # by default but a plugin MAY override to fold in per-project config). Consumed by
@@ -465,12 +487,28 @@ module Rigor
465
487
  @rbs_complete_ancestors_set = @rbs_complete_ancestors.to_set.freeze
466
488
  @rbs_complete_extends = aggregate_class_names(manifests, :rbs_complete_extends)
467
489
  @rbs_complete_extends_set = @rbs_complete_extends.to_set.freeze
490
+ @per_invocation_classes = aggregate_class_names(manifests, :per_invocation_classes)
491
+ @per_invocation_classes_set = @per_invocation_classes.to_set.freeze
492
+ @per_invocation_macros = compile_per_invocation_macros(manifests)
468
493
  @type_node_resolvers = manifests.flat_map { |m| m&.type_node_resolvers || [] }.freeze
469
494
  @protocol_contracts = @plugins.flat_map { |p| safe_protocol_contracts(p) }.freeze
470
495
  @contracts_by_path = {}
471
496
  @effect_memo = {}
472
497
  end
473
498
 
499
+ # ADR-125 — declared class name => frozen Set of the non-action macros its declaring manifests list.
500
+ def compile_per_invocation_macros(manifests)
501
+ table = Hash.new { |hash, key| hash[key] = Set.new }
502
+ manifests.each do |manifest|
503
+ next if manifest.nil?
504
+
505
+ manifest.per_invocation_classes.each do |name|
506
+ table[name].merge(manifest.per_invocation_non_action_macros)
507
+ end
508
+ end
509
+ table.to_h { |name, macros| [name, macros.freeze] }.freeze
510
+ end
511
+
474
512
  # The shared shape of `open_receivers:` / `rbs_complete_ancestors:` — a flat uniq'd class-name list
475
513
  # aggregated across manifests, each entry normalised to String.
476
514
  def aggregate_class_names(manifests, field)
@@ -518,8 +556,8 @@ module Rigor
518
556
  # Issue #806 — {#safe_manifest} with a receipt. The construction-time aggregates degraded silently
519
557
  # when a manifest read raised, so a plugin that contributed nothing looked identical to a plugin that
520
558
  # was never asked; the load-error channel is where "a plugin could not be loaded" already lives, and
521
- # `Analysis::Runner` turns each entry into one `plugin_loader.load-error` row naming the plugin. The
522
- # plugin ref is the CLASS, not `manifest.id`: the manifest is the surface that just failed.
559
+ # `Analysis::Runner` turns each entry into one `load-error` row (source family `plugin_loader`) naming
560
+ # the plugin. The plugin ref is the CLASS, not `manifest.id`: the manifest is the surface that just failed.
523
561
  def guarded_manifest(plugin, errors)
524
562
  plugin.manifest
525
563
  rescue StandardError => e
@@ -25,9 +25,8 @@ module Rigor
25
25
  # documents the trust model the boundary enforces.
26
26
  #
27
27
  # ADR-9 slice 2 adds `fact_store`: the per-run cross-plugin `Plugin::FactStore`. Producer plugins publish
28
- # their facts in `#prepare(services)` (slice 3); consumer plugins read in `#diagnostics_for_file` via
29
- # `services.fact_store.read(...)`. A fresh `FactStore` instance is constructed per Services when none is
30
- # supplied — the runner threads its own instance in once slice 3 wires `#prepare` invocation.
28
+ # their facts in `#prepare(services)`; consumer plugins read in `#diagnostics_for_file` via
29
+ # `services.fact_store.read(...)`. A fresh `FactStore` is constructed when none is supplied.
31
30
  class Services
32
31
  attr_reader :reflection, :type, :configuration, :cache_store, :trust_policy, :fact_store
33
32
 
@@ -61,6 +61,17 @@ module Rigor
61
61
  def empty?
62
62
  @entries.empty?
63
63
  end
64
+
65
+ # The pool drain ({Rigor::Analysis::WorkerSession#drain_reporters}): the frozen entries.
66
+ def snapshot
67
+ entries
68
+ end
69
+
70
+ # Replays one worker's {#snapshot}, `kind:` (#824) and `line:` (#1075) included; the dedup above
71
+ # collapses every worker's copy. Nil records nothing.
72
+ def absorb(snapshot)
73
+ Array(snapshot).each { |entry| record(**entry.to_h) }
74
+ end
64
75
  end
65
76
  end
66
77
  end
@@ -19,8 +19,8 @@ module Rigor
19
19
  # - `trusted_gems`: gem names the user has authorised. Derived from the `plugins:` section of `.rigor.yml`
20
20
  # plus any gems they reach transitively. Used today for documentation and future trust diagnostics.
21
21
  # - `allowed_read_roots`: absolute paths plugin code may read from through the {IoBoundary}. The default
22
- # set covers the project root, the project's `signature_paths`, the active `Gemfile.lock`, and each
23
- # trusted gem's `Gem::Specification#full_gem_path`. The user extends this with `.rigor.yml`'s
22
+ # set covers the project root, the project's `signature_paths`, and each trusted
23
+ # gem's `Gem::Specification#full_gem_path`. The user extends this with `.rigor.yml`'s
24
24
  # `plugins_io.allowed_paths:`.
25
25
  # - `network_policy`: one of {VALID_NETWORK_POLICIES}. `:disabled` (default) makes
26
26
  # {IoBoundary#open_url} always raise. `:allowlist` (v0.1.2) consults `allowed_url_hosts` on every fetch —