rigortype 0.4.6 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (550) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/data/core_overlay/bigdecimal.rbs +33 -0
  4. data/data/core_overlay/resolv.rbs +30 -0
  5. data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
  6. data/data/gem_overlay/addressable/addressable.rbs +40 -0
  7. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  8. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  9. data/docs/handbook/01-getting-started.md +6 -3
  10. data/docs/handbook/11-sig-gen.md +85 -3
  11. data/docs/handbook/appendix-phpstan.md +4 -4
  12. data/docs/install.md +20 -0
  13. data/docs/manual/01-installation.md +67 -26
  14. data/docs/manual/02-cli-reference.md +455 -113
  15. data/docs/manual/03-configuration.md +76 -5
  16. data/docs/manual/04-diagnostics.md +5 -4
  17. data/docs/manual/06-baseline.md +6 -4
  18. data/docs/manual/08-skills.md +11 -0
  19. data/docs/manual/09-editor-integration.md +21 -2
  20. data/docs/manual/10-mcp-server.md +38 -2
  21. data/docs/manual/12-caching.md +4 -3
  22. data/docs/manual/14-rails-quickstart.md +1 -1
  23. data/docs/manual/19-effect-labels.md +3 -4
  24. data/docs/manual/plugins/rigor-actionpack.md +32 -15
  25. data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
  26. data/docs/manual/plugins/rigor-sorbet.md +17 -3
  27. data/lib/rigor/analysis/baseline.rb +24 -27
  28. data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
  29. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  30. data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
  31. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  32. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -4
  33. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  34. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
  35. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +16 -4
  36. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
  37. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  38. data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
  40. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
  41. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
  42. data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
  43. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  44. data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
  45. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  46. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  47. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  48. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  49. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +2 -2
  50. data/lib/rigor/analysis/check_rules.rb +720 -141
  51. data/lib/rigor/analysis/crash_signature.rb +45 -3
  52. data/lib/rigor/analysis/dependency_recorder.rb +237 -80
  53. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  54. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  55. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  56. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  57. data/lib/rigor/analysis/edit_replay.rb +93 -6
  58. data/lib/rigor/analysis/fact_store.rb +3 -3
  59. data/lib/rigor/analysis/incremental.rb +11 -1
  60. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
  61. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  62. data/lib/rigor/analysis/incremental_session.rb +306 -53
  63. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
  64. data/lib/rigor/analysis/rule_catalog.rb +39 -3
  65. data/lib/rigor/analysis/run_cache_key.rb +27 -3
  66. data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
  67. data/lib/rigor/analysis/run_stats.rb +2 -2
  68. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
  69. data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
  70. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
  71. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
  72. data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
  73. data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
  74. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  75. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  76. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  77. data/lib/rigor/analysis/runner.rb +298 -109
  78. data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
  79. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  80. data/lib/rigor/analysis/template_units.rb +9 -1
  81. data/lib/rigor/analysis/worker_session.rb +65 -71
  82. data/lib/rigor/bleeding_edge.rb +22 -10
  83. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  84. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  85. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  86. data/lib/rigor/cache/descriptor.rb +6 -5
  87. data/lib/rigor/cache/file_digest.rb +39 -31
  88. data/lib/rigor/cache/incremental_snapshot.rb +95 -17
  89. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  90. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  91. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  92. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  93. data/lib/rigor/cache/rbs_descriptor.rb +90 -4
  94. data/lib/rigor/cache/rbs_environment.rb +8 -1
  95. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  96. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  97. data/lib/rigor/cache/store.rb +11 -10
  98. data/lib/rigor/ci_detector.rb +1 -1
  99. data/lib/rigor/cli/annotate_command.rb +24 -19
  100. data/lib/rigor/cli/baseline_command.rb +160 -57
  101. data/lib/rigor/cli/catalogue.rb +256 -0
  102. data/lib/rigor/cli/check_command.rb +112 -104
  103. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  104. data/lib/rigor/cli/command.rb +15 -1
  105. data/lib/rigor/cli/coverage_command.rb +21 -8
  106. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  107. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  108. data/lib/rigor/cli/coverage_scan.rb +3 -5
  109. data/lib/rigor/cli/diff_command.rb +25 -20
  110. data/lib/rigor/cli/docs_command.rb +1 -2
  111. data/lib/rigor/cli/doctor_command.rb +12 -12
  112. data/lib/rigor/cli/effects_command.rb +50 -40
  113. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  114. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  115. data/lib/rigor/cli/effects_renderer.rb +2 -1
  116. data/lib/rigor/cli/effects_snapshot_command.rb +26 -18
  117. data/lib/rigor/cli/explain_command.rb +18 -8
  118. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  119. data/lib/rigor/cli/json_document.rb +41 -0
  120. data/lib/rigor/cli/lens_command.rb +29 -10
  121. data/lib/rigor/cli/lsp_command.rb +250 -23
  122. data/lib/rigor/cli/mcp_command.rb +12 -7
  123. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  124. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  125. data/lib/rigor/cli/options.rb +40 -1
  126. data/lib/rigor/cli/plugin_command.rb +93 -46
  127. data/lib/rigor/cli/plugins_command.rb +17 -18
  128. data/lib/rigor/cli/plugins_renderer.rb +10 -6
  129. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  130. data/lib/rigor/cli/protection_renderer.rb +2 -1
  131. data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
  132. data/lib/rigor/cli/sig_gen_command.rb +92 -27
  133. data/lib/rigor/cli/skill_command.rb +32 -3
  134. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  135. data/lib/rigor/cli/skill_describe.rb +2 -1
  136. data/lib/rigor/cli/trace_command.rb +14 -9
  137. data/lib/rigor/cli/triage_command.rb +10 -11
  138. data/lib/rigor/cli/triage_renderer.rb +2 -1
  139. data/lib/rigor/cli/type_of_command.rb +16 -11
  140. data/lib/rigor/cli/type_of_project_scope.rb +1 -4
  141. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  142. data/lib/rigor/cli/type_scan_command.rb +14 -9
  143. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  144. data/lib/rigor/cli/unused_command.rb +27 -17
  145. data/lib/rigor/cli/upgrade_command.rb +5 -0
  146. data/lib/rigor/cli.rb +214 -56
  147. data/lib/rigor/config_audit.rb +33 -19
  148. data/lib/rigor/configuration/severity_profile.rb +14 -5
  149. data/lib/rigor/configuration.rb +104 -35
  150. data/lib/rigor/effects/collector.rb +53 -21
  151. data/lib/rigor/effects/definition_context.rb +0 -2
  152. data/lib/rigor/effects/envelope.rb +1 -1
  153. data/lib/rigor/effects/framework_units.rb +1 -1
  154. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  155. data/lib/rigor/effects/propagator.rb +1 -1
  156. data/lib/rigor/effects/registry.rb +2 -2
  157. data/lib/rigor/effects/snapshot.rb +1 -1
  158. data/lib/rigor/effects/taint_cause.rb +0 -2
  159. data/lib/rigor/effects/unit_scan.rb +9 -13
  160. data/lib/rigor/environment/bundle_sig_discovery.rb +64 -33
  161. data/lib/rigor/environment/class_registry.rb +3 -3
  162. data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
  163. data/lib/rigor/environment/core_root.rb +77 -0
  164. data/lib/rigor/environment/default_libraries.rb +18 -1
  165. data/lib/rigor/environment/deferred_signature_plan.rb +381 -0
  166. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  167. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  168. data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
  169. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  170. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  171. data/lib/rigor/environment/member_consistency.rb +56 -7
  172. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  173. data/lib/rigor/environment/project_path_gems.rb +124 -0
  174. data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
  175. data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
  176. data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
  177. data/lib/rigor/environment/rbs_loader.rb +772 -110
  178. data/lib/rigor/environment/reflection.rb +7 -7
  179. data/lib/rigor/environment/required_features.rb +0 -3
  180. data/lib/rigor/environment/ruby_library_roots.rb +1 -1
  181. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  182. data/lib/rigor/environment.rb +169 -42
  183. data/lib/rigor/flow_contribution/merger.rb +4 -4
  184. data/lib/rigor/inference/acceptance.rb +136 -40
  185. data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
  186. data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
  187. data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
  188. data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
  189. data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
  190. data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
  191. data/lib/rigor/inference/block_auto_splat.rb +1 -1
  192. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  193. data/lib/rigor/inference/block_run_count.rb +185 -0
  194. data/lib/rigor/inference/budget_trace.rb +8 -2
  195. data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
  196. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  197. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  198. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  199. data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
  200. data/lib/rigor/inference/callee_write_census.rb +815 -0
  201. data/lib/rigor/inference/captured_locals.rb +2 -0
  202. data/lib/rigor/inference/census_foreign_block.rb +354 -0
  203. data/lib/rigor/inference/clause_widening.rb +1 -1
  204. data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
  205. data/lib/rigor/inference/construction_escape.rb +258 -0
  206. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  207. data/lib/rigor/inference/declaration_walk.rb +4 -0
  208. data/lib/rigor/inference/deep_erasure.rb +39 -7
  209. data/lib/rigor/inference/def_handle.rb +1 -1
  210. data/lib/rigor/inference/def_node_resolver.rb +33 -15
  211. data/lib/rigor/inference/def_return_typer.rb +47 -5
  212. data/lib/rigor/inference/definer_resolution.rb +19 -9
  213. data/lib/rigor/inference/definers_index.rb +162 -0
  214. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  215. data/lib/rigor/inference/element_read_widening.rb +49 -3
  216. data/lib/rigor/inference/error_info.rb +16 -0
  217. data/lib/rigor/inference/expression_typer.rb +1559 -320
  218. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  219. data/lib/rigor/inference/flow_tracer.rb +31 -14
  220. data/lib/rigor/inference/fork_map.rb +3 -0
  221. data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
  222. data/lib/rigor/inference/global_write_census.rb +81 -5
  223. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  224. data/lib/rigor/inference/hkt_body.rb +1 -2
  225. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  226. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  227. data/lib/rigor/inference/hkt_registry.rb +3 -5
  228. data/lib/rigor/inference/in_effect_refinements.rb +7 -0
  229. data/lib/rigor/inference/indexed_narrowing.rb +61 -15
  230. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  231. data/lib/rigor/inference/ivar_writer_census.rb +154 -8
  232. data/lib/rigor/inference/key_presence_guard.rb +51 -24
  233. data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
  234. data/lib/rigor/inference/last_line.rb +5 -2
  235. data/lib/rigor/inference/loop_value_record.rb +66 -14
  236. data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
  237. data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
  238. data/lib/rigor/inference/match_rebinding.rb +28 -5
  239. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  240. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  241. data/lib/rigor/inference/method_dispatcher/block_folding.rb +44 -30
  242. data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
  243. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
  244. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  245. data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
  246. data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
  247. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
  248. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
  249. data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
  250. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  251. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
  252. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  253. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +51 -27
  254. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  255. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
  256. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  257. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  258. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +3 -3
  259. data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
  260. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
  261. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  262. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  263. data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
  264. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  265. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  266. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  267. data/lib/rigor/inference/method_dispatcher.rb +123 -25
  268. data/lib/rigor/inference/method_parameter_binder.rb +136 -56
  269. data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
  270. data/lib/rigor/inference/module_function_state.rb +24 -3
  271. data/lib/rigor/inference/multi_target_binder.rb +2 -2
  272. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  273. data/lib/rigor/inference/mutation_widening.rb +199 -29
  274. data/lib/rigor/inference/narrowing.rb +238 -50
  275. data/lib/rigor/inference/operand_effects.rb +43 -2
  276. data/lib/rigor/inference/optimistic_origin.rb +134 -9
  277. data/lib/rigor/inference/origin_lookup.rb +1 -1
  278. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  279. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  280. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  281. data/lib/rigor/inference/precision_scanner.rb +1 -1
  282. data/lib/rigor/inference/project_method_ownership.rb +30 -9
  283. data/lib/rigor/inference/project_patched_methods.rb +65 -13
  284. data/lib/rigor/inference/project_patched_scanner.rb +120 -25
  285. data/lib/rigor/inference/project_patched_visibility.rb +39 -3
  286. data/lib/rigor/inference/project_yielder.rb +11 -17
  287. data/lib/rigor/inference/rbs_type_translator.rb +68 -15
  288. data/lib/rigor/inference/receiver_alias.rb +20 -0
  289. data/lib/rigor/inference/receiver_writer_call.rb +67 -0
  290. data/lib/rigor/inference/recursion_rebinding.rb +157 -0
  291. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
  292. data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
  293. data/lib/rigor/inference/refined_dispatch.rb +13 -0
  294. data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
  295. data/lib/rigor/inference/remover_holder_scan.rb +287 -0
  296. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  297. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  298. data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
  299. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
  300. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  301. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
  302. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  303. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  304. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +295 -0
  305. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
  306. data/lib/rigor/inference/scope_indexer.rb +2213 -226
  307. data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
  308. data/lib/rigor/inference/self_preserving_block.rb +87 -0
  309. data/lib/rigor/inference/set_mutation.rb +189 -0
  310. data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
  311. data/lib/rigor/inference/singleton_object_constant.rb +32 -2
  312. data/lib/rigor/inference/statement_evaluator.rb +3268 -358
  313. data/lib/rigor/inference/string_mutation.rb +14 -0
  314. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  315. data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
  316. data/lib/rigor/inference/toplevel_def_census.rb +67 -0
  317. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  318. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  319. data/lib/rigor/inference/untyped_rejoin.rb +1 -1
  320. data/lib/rigor/inference/void_tail_summary.rb +29 -9
  321. data/lib/rigor/language_server/buffer_table.rb +3 -3
  322. data/lib/rigor/language_server/completion_provider.rb +26 -20
  323. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  324. data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
  325. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  326. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  327. data/lib/rigor/language_server/hover_provider.rb +1 -7
  328. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  329. data/lib/rigor/language_server/project_context.rb +39 -1
  330. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  331. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  332. data/lib/rigor/language_server/server.rb +7 -3
  333. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  334. data/lib/rigor/lens/claims/comparison.rb +78 -0
  335. data/lib/rigor/lens/claims/request.rb +131 -0
  336. data/lib/rigor/lens/claims/type_reader.rb +216 -0
  337. data/lib/rigor/lens/claims.rb +208 -0
  338. data/lib/rigor/lens/contract_reader.rb +5 -0
  339. data/lib/rigor/lens/document.rb +1 -1
  340. data/lib/rigor/lens/file_reader.rb +21 -8
  341. data/lib/rigor/lens/plugins.rb +1 -1
  342. data/lib/rigor/lens/run.rb +8 -0
  343. data/lib/rigor/lens.rb +1 -1
  344. data/lib/rigor/library_probe.rb +68 -0
  345. data/lib/rigor/mcp/server.rb +73 -2
  346. data/lib/rigor/mcp.rb +1 -1
  347. data/lib/rigor/memo_audit.rb +282 -0
  348. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  349. data/lib/rigor/plugin/base.rb +15 -2
  350. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  351. data/lib/rigor/plugin/io_boundary.rb +26 -4
  352. data/lib/rigor/plugin/loader.rb +25 -8
  353. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  354. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  355. data/lib/rigor/plugin/manifest.rb +16 -16
  356. data/lib/rigor/plugin/registry.rb +16 -5
  357. data/lib/rigor/plugin/services.rb +2 -3
  358. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  359. data/lib/rigor/plugin/trust_policy.rb +48 -2
  360. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  361. data/lib/rigor/plugin.rb +6 -8
  362. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  363. data/lib/rigor/project_environment.rb +16 -2
  364. data/lib/rigor/protection/analysis_guard.rb +1 -1
  365. data/lib/rigor/protection/discovery_seed.rb +44 -5
  366. data/lib/rigor/protection/mutation_cache.rb +4 -1
  367. data/lib/rigor/protection/mutator.rb +1 -1
  368. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  369. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  370. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  371. data/lib/rigor/rbs_extended.rb +2 -3
  372. data/lib/rigor/reflection/constant_ancestors.rb +81 -22
  373. data/lib/rigor/reflection/declaration_collectors.rb +55 -0
  374. data/lib/rigor/reflection/declarations.rb +58 -0
  375. data/lib/rigor/reflection/project_method.rb +177 -0
  376. data/lib/rigor/reflection.rb +2 -0
  377. data/lib/rigor/rooted_glob.rb +32 -1
  378. data/lib/rigor/run_scoped_slots.rb +38 -0
  379. data/lib/rigor/runtime/jit.rb +1 -1
  380. data/lib/rigor/scope/discovery_index.rb +168 -8
  381. data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
  382. data/lib/rigor/scope/resolution_chain.rb +901 -54
  383. data/lib/rigor/scope.rb +644 -37
  384. data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
  385. data/lib/rigor/sig_gen/classification.rb +18 -1
  386. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  387. data/lib/rigor/sig_gen/generator.rb +588 -66
  388. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  389. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  390. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  391. data/lib/rigor/sig_gen/method_candidate.rb +37 -4
  392. data/lib/rigor/sig_gen/observation_collector.rb +14 -7
  393. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  394. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  395. data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
  396. data/lib/rigor/sig_gen/renderer.rb +15 -9
  397. data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -2
  398. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  399. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  400. data/lib/rigor/sig_gen/write_result.rb +23 -12
  401. data/lib/rigor/sig_gen/writer.rb +238 -37
  402. data/lib/rigor/sig_gen.rb +1 -0
  403. data/lib/rigor/source/forwardable_delegators.rb +122 -0
  404. data/lib/rigor/source/node_children.rb +13 -0
  405. data/lib/rigor/type/acceptance_router.rb +2 -2
  406. data/lib/rigor/type/accepts_result.rb +1 -1
  407. data/lib/rigor/type/app.rb +4 -6
  408. data/lib/rigor/type/combinator.rb +173 -21
  409. data/lib/rigor/type/constant.rb +4 -5
  410. data/lib/rigor/type/hash_shape.rb +21 -40
  411. data/lib/rigor/type/interface.rb +60 -0
  412. data/lib/rigor/type/union.rb +1 -1
  413. data/lib/rigor/type.rb +1 -0
  414. data/lib/rigor/type_node/identifier.rb +1 -2
  415. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  416. data/lib/rigor/type_node/string_literal.rb +1 -2
  417. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  418. data/lib/rigor/type_node/union.rb +3 -6
  419. data/lib/rigor/type_of_memo/serving.rb +99 -0
  420. data/lib/rigor/type_of_memo/stats.rb +43 -0
  421. data/lib/rigor/type_of_memo.rb +337 -0
  422. data/lib/rigor/typing_state.rb +1094 -0
  423. data/lib/rigor/value_semantics.rb +1 -10
  424. data/lib/rigor/version.rb +1 -1
  425. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  426. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  427. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  428. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
  429. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  430. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
  431. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  432. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  433. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  434. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  435. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +3 -3
  436. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
  437. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
  438. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
  439. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  440. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
  441. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
  442. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
  443. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
  444. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  445. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  446. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  447. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  448. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +132 -33
  449. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
  450. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
  451. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
  452. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
  453. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  454. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  455. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +11 -14
  456. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  457. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  458. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  459. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +30 -9
  460. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
  461. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
  462. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
  463. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  464. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  465. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  466. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  467. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  468. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  469. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  470. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  471. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  472. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  473. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  474. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  475. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +8 -7
  476. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  477. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  478. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
  479. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  480. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  481. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +8 -2
  482. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  483. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  484. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  485. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  486. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  487. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +200 -4
  488. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  489. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  490. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  491. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +4 -3
  492. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  493. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
  494. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  495. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  496. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  497. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
  498. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  499. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  500. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +210 -3
  501. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
  502. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  503. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
  504. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
  505. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  506. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
  507. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
  508. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  509. data/sig/rigor/analysis/baseline.rbs +0 -3
  510. data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
  511. data/sig/rigor/analysis/fact_store.rbs +2 -2
  512. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  513. data/sig/rigor/cache/descriptor.rbs +14 -0
  514. data/sig/rigor/cli/diff_command.rbs +1 -1
  515. data/sig/rigor/cli/explain_command.rbs +1 -1
  516. data/sig/rigor/effects/registry.rbs +2 -2
  517. data/sig/rigor/environment.rbs +4 -1
  518. data/sig/rigor/inference.rbs +4 -1
  519. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  520. data/sig/rigor/plugin/base.rbs +53 -18
  521. data/sig/rigor/plugin/effect_attribution.rbs +3 -3
  522. data/sig/rigor/plugin/effect_edge.rbs +1 -1
  523. data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
  524. data/sig/rigor/plugin/interfaces.rbs +51 -0
  525. data/sig/rigor/plugin/io_boundary.rbs +3 -3
  526. data/sig/rigor/plugin/manifest.rbs +9 -9
  527. data/sig/rigor/plugin/node_context.rbs +21 -0
  528. data/sig/rigor/plugin/protocol_contract.rbs +3 -7
  529. data/sig/rigor/reflection.rbs +37 -0
  530. data/sig/rigor/scope.rbs +43 -4
  531. data/sig/rigor/trinary.rbs +1 -0
  532. data/sig/rigor/type.rbs +9 -3
  533. data/sig/rigor.rbs +3 -2
  534. data/skills/rigor-ask/SKILL.md +1 -0
  535. data/skills/rigor-plugin-author/SKILL.md +4 -4
  536. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  537. data/skills/rigor-plugin-review/SKILL.md +3 -3
  538. data/skills/rigor-project-init/references/01-detect.md +1 -0
  539. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
  540. data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
  541. data/skills/rigor-rbs-contract/SKILL.md +147 -0
  542. data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
  543. data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
  544. data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
  545. data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
  546. data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
  547. data/skills/rigor-rbs-setup/SKILL.md +3 -1
  548. data/skills/rigor-type-oracle/SKILL.md +26 -1
  549. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
  550. metadata +82 -3
@@ -0,0 +1,147 @@
1
+ ---
2
+ name: rigor-rbs-contract
3
+ description: >-
4
+ Audit, repair, and extend RBS declarations as contracts — a gem's own `sig/`, an application's `sig/`,
5
+ or a gem_rbs_collection entry — by checking each one against Rigor's inference and the library's
6
+ documented behaviour, then proving and explaining the change. Use when correcting or contributing RBS;
7
+ not for sourcing one type mid-task (`rigor-type-oracle`) or raising `coverage --protection` numbers.
8
+ license: MPL-2.0
9
+ metadata:
10
+ version: 0.1.0
11
+ homepage: https://github.com/rigortype/rigor
12
+ ---
13
+
14
+ # Rigor RBS Contract
15
+
16
+ An RBS declaration is not a record of what a method happens to return. It is a promise about what
17
+ callers may rely on. Ruby returns every method's last expression, so inference always finds *some*
18
+ type; whether callers may depend on it is a choice only a declaration makes. This skill turns that into
19
+ a repeatable loop: find where declarations and code disagree, decide which side is wrong on cited
20
+ evidence, change the declaration, prove the change, and explain it so a reviewer can re-check it.
21
+
22
+ ## First: load the version-current copy
23
+
24
+ Commands, flags, rule ids and skip-reason ids drift between Rigor releases, so follow the copy that
25
+ ships with the **installed** Rigor rather than a vendored or frozen copy of this file:
26
+
27
+ ```sh
28
+ rigor skill --full rigor-rbs-contract # this body + all its references/, inline
29
+ ```
30
+
31
+ If you already loaded it via `rigor skill`, proceed. If `rigor` is not on `PATH`, run
32
+ **`rigor-next-steps`** first.
33
+
34
+ ## When to use
35
+
36
+ - A gem author wants their shipped `sig/` checked, corrected or extended.
37
+ - An application's quality owner wants the RBS their `rigor check` relies on — the project's `sig/`, or
38
+ a dependency's community RBS — audited, or a wrong dependency signature fixed.
39
+ - Someone is contributing to `ruby/gem_rbs_collection`: adding methods, fixing a declaration, or acting
40
+ on a discrepancy Rigor surfaced.
41
+ - A diagnostic or a review comment says "the RBS says X but the code does Y", and the task is to settle
42
+ which one is right.
43
+
44
+ ## When NOT to use
45
+
46
+ - **Writing one type in the middle of another task** → `rigor-type-oracle`. It owns where a single type
47
+ comes from; this skill owns what a set of declarations should promise.
48
+ - **Raising `rigor coverage --protection` numbers** on the project's own code → `rigor-protection-uplift`.
49
+ - **Installing community RBS** that exists but is not set up → `rigor-rbs-setup`.
50
+ - **A Rigor diagnostic you believe is wrong about correct code** → `rigor-ask`; that is an engine
51
+ question, not a contract one.
52
+
53
+ ## The two rules
54
+
55
+ **Contract rule.** Inference proves what a body does. It may *refute* a declaration, or fill a slot
56
+ nobody declared, but it never chooses the abstraction a declaration makes. A contract the body does not
57
+ prove is written only on the word of the party entitled to make it, and cited: the library's own
58
+ documentation for code you do not own, the owner's explicit answer for code they do.
59
+
60
+ **Direction rule.** Making a declaration *say less* — a return that admits `nil`, a parameter that
61
+ accepts more — needs a **witness** that the case is real: a call shown, by RBS's own runtime type checker,
62
+ to return or to need what the old declaration excludes; or the library's documentation. Rigor's findings
63
+ locate these cases and name the exit to aim at, but they have false positives, so on their own they are
64
+ a lead, not a witness. **A witness counts only once its negative control has failed**: break the
65
+ declaration on purpose and confirm the same run reports a type error. RBS's checker silently skips
66
+ some declarations (one inherited from a module or superclass by a class that defines the method itself
67
+ is never checked), so a clean run means nothing until the broken one fails. Making it *say more* — a narrower return, a narrower parameter, `void` replaced by
68
+ a value — needs a contract source. A `sig-gen` proposal counts only where the slot was undeclared or
69
+ `untyped`.
70
+
71
+ Five consequences come up on almost every run:
72
+
73
+ - **`void`, `boolish` and a deliberately wide nominal stand** unless the library documents a value.
74
+ The body returning something is not documentation.
75
+ - **A new declaration is where that choice is made.** A `sig-gen` proposal for an undeclared method
76
+ that is a literal (`-> 0`, `-> false`), an empty collection (`-> {}`), `nil`, or the incidental value of
77
+ a procedure's last statement becomes `void` (`bool` for a predicate) unless the documentation names
78
+ the value. Adopting it would create the contract the `void` rule protects.
79
+ - **Reading the implementation finds candidates; it does not decide them.** The one exception is an
80
+ undeclared or `untyped` parameter's structural need — an argument used only through `to_s` accepts
81
+ `_ToS`. A *declared* parameter is a promise too; today's body is no reason to widen it.
82
+ - **A wholly wrong declaration is treated as missing.** When the declared type and the body share
83
+ nothing (declared a connection pool, returns an `Integer`), it is neither saying less nor more: type
84
+ the slot as if it were undeclared, and witness that the declared type never comes back.
85
+ - **One alias that is both input and output carries two contracts.** When they differ, split it rather
86
+ than weaken it until both fit.
87
+
88
+ The full decision table, with the evidence each change needs and the shapes that look like findings but
89
+ are not: [`references/02-contract-rules.md`](references/02-contract-rules.md).
90
+
91
+ ## Step 0 — Pick the target; it decides who has authority
92
+
93
+ | Target | Who decides a contract | Mode | Reference |
94
+ | --- | --- | --- | --- |
95
+ | The project's own code — a gem's `sig/`, an application's `sig/` | the owner | **interactive**: batch the open contract questions and ask | [`03-target-own-code.md`](references/03-target-own-code.md) |
96
+ | A gem you do not own — a gem_rbs_collection entry, or a local override of one | the gem's documentation | **autonomous** up to a finished branch and description; undocumented contracts are left alone and listed; pushing and opening the pull request wait for the user's go-ahead | [`04-target-gem-rbs-collection.md`](references/04-target-gem-rbs-collection.md) |
97
+
98
+ An application whose wrong signature comes from a dependency's community RBS is the second row: fix it
99
+ upstream, and keep a local override only until the fix is released.
100
+
101
+ Either target may be a **new declaration set** rather than an audit: a gem with no RBS anywhere, or a
102
+ new collection entry. Then there is nothing to compare against, the skeleton (mixins, attributes,
103
+ aliases, metaprogrammed methods) does not come from `sig-gen`, and most decisions are about
104
+ undocumented proven slots and blocks. The reference for the target has a "New declaration set" section.
105
+
106
+ ## The loop
107
+
108
+ 1. **Find.** Collect candidates from Rigor and from the documentation, not from a read-through. How,
109
+ per signal, and what each one can and cannot see:
110
+ [`references/01-finding-candidates.md`](references/01-finding-candidates.md).
111
+ 2. **Triage.** Drop the shapes that are not contract findings — abstract methods, mutable registries
112
+ proposed as `[]`, platform-guarded files, a `Dynamic` inference, a memoised value Rigor reads as
113
+ nilable — before spending evidence on them.
114
+ 3. **Decide.** For each remaining candidate, gather the evidence the direction rule asks for and cite
115
+ it: the witness call and the error it raised, the command and its output, the quote with its
116
+ `file:line` in the version under review, or the owner's answer. A candidate without the evidence it
117
+ needs is not changed; it is reported.
118
+ 4. **Write.** Change the declaration. Prefer `rigor sig-gen` output verbatim where it applies; write by
119
+ hand only what the evidence supports, and nothing beyond it.
120
+ 5. **Prove.** Run the target's gate. For every change that says less, keep the runtime witness: the
121
+ call fails RBS's type checker under the old declaration and passes under the new one. Before trusting
122
+ any clean run — one witness or a whole test suite — run the declaration-mutation pass: break each
123
+ method's return in turn and list the ones no run notices. Those returns are unchecked, whatever the
124
+ suite says (parameters and attributes are not covered by the pass). How: [`references/05-declaration-mutants.md`](references/05-declaration-mutants.md). Add
125
+ a use-site test that exercises the new contract — a `nil` check on a newly optional return, a call
126
+ with the newly accepted argument.
127
+ 6. **Explain.** Every change carries its evidence. Every Rigor finding and every candidate you
128
+ deliberately left alone — above all a `void` whose body returns a value, and a Rigor finding no
129
+ witness confirmed — carries its reason, so a reviewer can tell restraint from oversight.
130
+
131
+ ## Done when
132
+
133
+ Every candidate from step 1, including every row Rigor reported, is changed with its cited evidence,
134
+ left alone with its reason, or reported as an open question; the target's gate is green; every
135
+ declaration the mutation pass left surviving is named in the write-up as unchecked at runtime; and the
136
+ write-up lists all three groups.
137
+
138
+ When the owner does not answer an open question, or the documentation cannot be found, stop there: leave
139
+ those declarations as they are, finish and prove the changes the evidence did settle, and hand back the
140
+ open list. Never resolve an open question by picking the option the body happens to match.
141
+
142
+ ## Gaps are findings here too
143
+
144
+ When Rigor answers `Dynamic[top]`, `untyped`, or skips a method, that is not permission to fill the
145
+ slot from your reading — `rigor-type-oracle`'s gap protocol applies unchanged. What this skill adds is
146
+ the other source: where the library documents the contract, the documentation fills the slot, with the
147
+ quote. Where neither speaks, the slot stays as declared.
@@ -0,0 +1,212 @@
1
+ # 01 — Finding candidates
2
+
3
+ A candidate is a declaration that may promise the wrong thing, or a public method that promises nothing
4
+ because it is not declared. Collect them from the signals below, record the command that produced each,
5
+ and only then decide anything. Every signal sees part of the picture; the table at the end says which
6
+ part.
7
+
8
+ All commands run with the RBS under review on Rigor's signature path and the library's source as the
9
+ analysed paths. How to arrange that for each target: [`03-target-own-code.md`](03-target-own-code.md),
10
+ [`04-target-gem-rbs-collection.md`](04-target-gem-rbs-collection.md).
11
+
12
+ ## Signal 1 — `rigor check` over the library's own source
13
+
14
+ ```sh
15
+ rigor check --no-baseline --format=json lib
16
+ ```
17
+
18
+ The rule to look for is **`def.return-type-mismatch`**: the body's last expression has a type the
19
+ declared return does not accept. Its message names both sides — `declared String, inferred String?`, or
20
+ a literal such as `inferred "a"?`. It compares a union member by member, so a typed member beside
21
+ `Dynamic` still counts: sidekiq 7.0's `Client#push` against its old `-> String` reports
22
+ `declared String, inferred Dynamic[top]?`. `--no-baseline` matters in an application: a baseline may
23
+ already be hiding exactly these.
24
+
25
+ A mismatch is a **lead**, not a witness. The rule has false positives: a memoised value re-read after a
26
+ `yield` (`@c ||= Chain.new(self); yield @c if block_given?; @c`) reads as `Chain?` although it can
27
+ never be `nil` — sidekiq's `Config#client_middleware` is that shape. A mismatch becomes a change only
28
+ with a runtime witness (Signal 6) or documentation.
29
+
30
+ Also worth reading here: `def.override-return-widened` (a subclass's declaration widens what its parent
31
+ promised). `static.value-use.void` (code uses a value a declaration says is `void`) is off in every
32
+ profile and fires only under the `use-of-void-value` bleeding-edge feature, so its silence proves
33
+ nothing.
34
+
35
+ What it cannot see: only the **last expression** is compared. An explicit `return nil` earlier in the
36
+ body, or a falsy arm behind an untyped value (`mime && new(mime[0])` where `mime` is untyped reads as
37
+ `Dynamic[top] | Marcel::Magic`), produces no diagnostic. The rule is also silent when the declared return
38
+ is `untyped` or `void`, and when the whole inferred type is `Dynamic`.
39
+
40
+ ## Signal 2 — `rigor sig-gen --diff` against the declarations
41
+
42
+ ```sh
43
+ rigor sig-gen --diff --format=json lib
44
+ ```
45
+
46
+ Each entry in `candidates` has a `classification`:
47
+
48
+ | `classification` | Meaning | What it is evidence for |
49
+ | --- | --- | --- |
50
+ | `new_method` | a public method with no declaration; `rbs` is the proposal | filling an undeclared slot — adopt the proposal verbatim, or leave the method out |
51
+ | `tighter_return` | Rigor proved a return narrower than `declared_return_rbs` | over `untyped`: an adoptable proposal. Over anything else: a "say more" change, which needs a contract source — the proposal alone is not enough |
52
+ | `skipped` | no proposal; `skip_reason` says why | nothing — see triage below |
53
+
54
+ `sig-gen` never proposes over a declared `void` and never tightens a declared nominal to a literal. It
55
+ also compares a declaration that is *narrower* than the body over every exit — an early `return nil`
56
+ included — but classifies the result `equivalent`, which `--diff` does not print (#3062). So an empty
57
+ `--diff` does not mean the declarations are sound; Signal 1, the leads below and Signal 6 cover that
58
+ half.
59
+
60
+ Private and protected methods are skipped unless you pass `--include-private`; a collection entry rarely
61
+ wants them.
62
+
63
+ `sig-gen` writes method signatures and nothing else: no `include` / `extend`, no `attr_*`, no `alias`, no
64
+ constants, and no method that only exists through metaprogramming (`define_method`, `class_eval` strings).
65
+ Readers come back as `untyped-return` skips rather than `attr_reader` lines. Some methods are also
66
+ absent without a skip row (#3068). So for a new declaration set its output is the types, not the
67
+ skeleton: see the target reference's "New declaration set" section.
68
+
69
+ ## Signal 3 — `rigor check` over code that uses the library
70
+
71
+ Use-site code is where an input contract shows up: the project's own callers, the gem's `spec/` or
72
+ `test/`, or a collection entry's `_test/`.
73
+
74
+ ```sh
75
+ rigor check --no-baseline --format=json <use-site paths>
76
+ ```
77
+
78
+ Read each finding against the question "is this call correct Ruby that the library accepts?":
79
+
80
+ - **`call.argument-type-mismatch` on a call the library accepts** — the declared parameter is too
81
+ narrow. retryable's `{ sleep: 3.2 }` failed because one record type required all ten keys.
82
+ - **`call.undefined-method` on a method the library defines** — a missing declaration.
83
+ - **`call.possible-nil-receiver`** — usually the declaration is right and the caller is wrong; check
84
+ before "fixing" the RBS to silence it.
85
+
86
+ A use-site diagnostic can also be a Rigor false positive. If the call is correct and the declaration is
87
+ correct too, reproduce it minimally and route it through `rigor-ask`; do not bend the RBS around it.
88
+
89
+ ## Signal 4 — `rigor type-of` on a returned value
90
+
91
+ ```sh
92
+ rigor type-of lib/foo.rb:LINE:COL
93
+ ```
94
+
95
+ Point it at the returned *value*, not the `return` keyword: in `return nil unless ok`, the column of
96
+ `nil` answers `nil`, while the column of `return` answers `bot` (the statement itself never produces a
97
+ value). Use it to confirm or rule out a lead. `String?` is proof. `Dynamic[top] | Foo` means one arm is unknown —
98
+ the declaration may or may not be right, and the decision needs documentation or a run.
99
+
100
+ ## Leads from reading — locate only
101
+
102
+ These are worth searching for because no command reports them, but each is a lead, not evidence. A
103
+ lead becomes a change only with documentation or a witness (Signal 6).
104
+
105
+ - an explicit `return` or `return nil` before the last expression;
106
+ - `x && y`, `x&.y`, or `find`/`detect`/`Hash#[]` whose `nil` result is returned as is;
107
+ - `rescue … nil` or a `rescue` clause whose value is returned;
108
+ - a method whose declaration is `void` but whose doc comment describes a return value (the reverse
109
+ case: the declaration may be too wide, and the documentation is the evidence).
110
+
111
+ ## Signal 5 — the documentation
112
+
113
+ Read the documentation that ships with the version under review — doc comments and the README in that
114
+ version's source — as its own sweep, not only to confirm a lead, and note each quote's `file:line`. A
115
+ website or wiki describes some other release; see `02-contract-rules.md` for what it may support.
116
+
117
+ - doc comments containing "Returns", "nil", "or", "raises", "yields";
118
+ - the README's examples, which show which calls the authors consider the public API;
119
+ - the API reference, for public methods the RBS does not declare.
120
+
121
+ A documented return that the declaration contradicts is a candidate even when every Rigor signal is
122
+ silent. For sidekiq's `Client#push`, Signal 1 found the candidate and the doc comment — "If middleware
123
+ stops the job, nil will be returned instead" — settled it.
124
+
125
+ ## Signal 6 — a runtime witness
126
+
127
+ RBS ships a runtime type checker. It wraps the methods of the classes you name, runs your Ruby, and
128
+ reports every argument or return value that does not match the loaded declarations. A call it rejects
129
+ is a **witness**: the evidence a "say less" change needs, and the way to tell a real Rigor finding from
130
+ a false positive. This **executes the library's code with the user's privileges**. For the project's
131
+ own code or a gem already in the user's bundle that is the same trust as running its tests. For any
132
+ other gem, run it in a container or VM, or ask the user before executing it.
133
+
134
+ Write a short script that calls the method with an input aimed at the exit Rigor or your reading
135
+ pointed to — for `return nil unless carrier_accepts?(parcel)`, a parcel the carrier rejects:
136
+
137
+ ```ruby
138
+ # witness.rb
139
+ require "courier"
140
+ Courier::Client.new.deliver(Courier::Parcel.new(2, 50))
141
+ ```
142
+
143
+ ```sh
144
+ RBS_TEST_TARGET='Courier::*' RBS_TEST_OPT='-I sig' ruby -I lib -rrbs/test/setup witness.rb
145
+ # [Courier::Client#deliver] ReturnTypeError: expected `::String` but returns `nil`
146
+ ```
147
+
148
+ Under Bundler, run the same line as `bundle exec ruby -I lib -rrbs/test/setup witness.rb`. Do not add
149
+ `-rbundler/setup` instead: when Bundler switches versions it re-executes Ruby without `-I` and `-r`, the
150
+ script fails to load, and the non-zero exit looks like a witness. When the target's bundle does not
151
+ contain `rbs` (a gem's own `Gemfile` usually does not), run plain `ruby` with `GEM_PATH` naming both the
152
+ directory where the target's gems are installed and one where `rbs` is installed. Add `-r <library>` to
153
+ `RBS_TEST_OPT` for each standard library the declarations use.
154
+
155
+ A witness is the `ReturnTypeError` (or `ArgumentTypeError`) naming the method, not a non-zero exit: read
156
+ the error. Run it again after the change: it must exit 0. The same check
157
+ fits a test suite as `RBS::UnitTest`'s `assert_send_type "(untyped) -> String?", receiver, :deliver,
158
+ arg`, which also checks the call against the declaration loaded for the class, so it fails before the
159
+ fix and passes after it.
160
+
161
+ **Run the negative control first.** Run every witness once under a declaration you have broken on
162
+ purpose and confirm it reports the type error; only then does a clean run under the real declaration
163
+ mean anything. The checker silently skips some declarations. It checks a method only against a
164
+ declaration written on the same class or module that defines the method in Ruby. A declaration
165
+ inherited from a module or superclass, applied to a method an including class or subclass defines or
166
+ overrides, is skipped unless it carries `%a{rbs:test:target}`. So are methods of classes created
167
+ without `class … end` (`Struct.new`, `Class.new`, `Data.define`) and of classes loaded before
168
+ `rbs/test/setup`. A witness that calls a skipped method exits 0 whatever the declaration says. On rgl,
169
+ `remove_edge` declared `-> bool` on the module `MutableGraph` (which defines an abstract version) and
170
+ overridden in the class `DirectedAdjacencyGraph` returned a `Set` and the run passed; redeclared on the
171
+ class, the same run raised `expected bool but returns Set[5]`.
172
+
173
+ A run that raises nothing is not a witness the other way: it shows only that the inputs you tried
174
+ behave. When no input you can justify produces the excluded value, the finding is "left unchanged — no
175
+ witness", not a change. A whole test suite run under `rbs/test/setup` checks only the declarations its
176
+ calls reach and the checker does not skip; the declaration-mutation pass in
177
+ [`05-declaration-mutants.md`](05-declaration-mutants.md) lists the ones it does not, which is the
178
+ negative control for every declaration at once.
179
+
180
+ `rbs prototype runtime` is a different tool: it prints the methods, arities, mixins and constants of the
181
+ loaded classes, with every type `untyped`. It is useful for the skeleton of a new declaration set and
182
+ for finding undeclared methods — including ones `define_method` creates — and parameter shapes, never
183
+ for a return type. It loads the library, so the witness safety rule applies to it too.
184
+
185
+ ## Triage before deciding
186
+
187
+ Drop these before spending evidence on them:
188
+
189
+ | Shape | Why it is not a contract finding |
190
+ | --- | --- |
191
+ | `skip_reason` `sig.skipped.abstract-method`, `template-method`, `overridden-bot`, `overridden-by-unsigned-subclass` | abstract or template methods; the declaration states what overriders return, which the base body cannot show |
192
+ | `sig.skipped.platform-guarded` | the definition loads only under a `RUBY_ENGINE` / `RUBY_PLATFORM` guard; the declaration describes the other platform's implementation |
193
+ | `sig.skipped.untyped-return` | Rigor has no answer; the gap protocol in `rigor-type-oracle` applies |
194
+ | a proposal of `[]` or `{}` | usually a collection the method fills later, or a registry callers append to; the element type is the contract, not "empty" |
195
+ | a proposal of `bot` or `top` | `bot` is almost always an abstract or raising method; `top` says nothing a declaration should adopt |
196
+ | a `tighter_return` that drops an arm the code clearly has (an `else`, a `rescue`) | an inference gap; report it, do not adopt it |
197
+ | a `def.return-type-mismatch` adding `nil` to a memoised value (`@x ||= …`, possibly re-read after a `yield`) | a known false-positive shape; it needs a witness, and usually none exists |
198
+ | a mismatch whose `nil` comes only from a lookup (`Hash#[]`, `find`, `detect`) on a key or element the library always supplies | worst-case reading; it needs a witness with an input the library allows |
199
+
200
+ `rigor explain <skip-reason>` documents each skip reason in full.
201
+
202
+ ## What each signal sees
203
+
204
+ | Question | Signal |
205
+ | --- | --- |
206
+ | Is a declared return narrower than the body's last expression? | 1, confirmed by 6 |
207
+ | Is a declared return narrower than an early `return` or an untyped falsy arm? | leads, confirmed by 6 or 5 |
208
+ | Is a Rigor finding real or a false positive? | 6 |
209
+ | Is a public method undeclared? | 2 (`new_method`), 5, `rbs prototype runtime` |
210
+ | Could an `untyped` return be precise? | 2 (`tighter_return` over `untyped`) |
211
+ | Is a parameter too narrow? | 3 |
212
+ | Is a `void` hiding a documented value? | 5 |
@@ -0,0 +1,157 @@
1
+ # 02 — Deciding a candidate
2
+
3
+ Every change to a declaration either makes it say less, makes it say more, or replaces a declaration
4
+ that is wholly wrong. The kind decides the evidence it needs. Cite that evidence with the change;
5
+ without it, the candidate is reported, not changed.
6
+
7
+ ## What counts as evidence
8
+
9
+ | Evidence | How to cite it | Good for |
10
+ | --- | --- | --- |
11
+ | The library's documentation **for the version under review**: a doc comment on the method, the README shipped in that gem | quote it with its `file:line` in that version's source | any contract on code you do not own |
12
+ | The owner's explicit answer, for the project's own code | the question and the answer; better, a doc comment written from it | any contract on that code |
13
+ | A runtime witness: a call that RBS's runtime type checker (`rbs/test/setup` or `RBS::UnitTest`) rejects under the old declaration and accepts under the new one | the call, and the error it raised under the old declaration | saying less; showing a wholly wrong declaration is never returned |
14
+ | Rigor's findings: `def.return-type-mismatch`, a `sig-gen` proposal, a `type-of` answer | the command and its output | locating a candidate and the exit to aim a witness at; filling an undeclared or `untyped` slot |
15
+ | A correct call the old declaration rejects, found by `rigor check` on use-site code | the call and the diagnostic | widening a parameter or an input record |
16
+ | An undeclared or `untyped` parameter's structural need, read from the body | the line that uses it | typing that parameter as an interface (`_ToS`, `_Each[T]`) |
17
+ | Reading the implementation for anything else | — | nothing. It finds candidates; it does not decide them |
18
+
19
+ How to run a witness: [`01-finding-candidates.md`](01-finding-candidates.md) § "Signal 6".
20
+
21
+ **Documentation is pinned to the version.** Quote the doc comment or README from the unpacked source of
22
+ the version whose declaration you are changing. A website, wiki or default-branch README describes some
23
+ other release, so it may support a question in the write-up but is never the sole support for a change —
24
+ least of all for `void` → a value. Grade what you quote: a doc comment that states the return ("Returns
25
+ a unique Job ID. If middleware stops the job, nil will be returned instead") is strong; a README example
26
+ that happens to use the value is weak. Say which kind you are citing.
27
+
28
+ **The gem's own type annotations are documentation, and can be wrong.** A YARD `@return [Foo]` or
29
+ `@param` in the gem's source is the author's statement, so you may read and quote it; `rigor-type-oracle`'s
30
+ rule against bracketed YARD types governs what *you* write, not what you read. Grade a tag like any other
31
+ documentation, and expect it to drift: on rgl 0.6.7, `Graph#reverse` is tagged
32
+ `@return [DirectedAdjacencyGraph]` while its own prose says an undirected graph returns `self`, and the
33
+ README's `remove_edge … #=> true` is stale (the call returns a `Set`). **When two pieces of the same
34
+ version's documentation disagree, or a tag disagrees with Rigor's proof, a witness decides.** Quote
35
+ both and cite the witness.
36
+
37
+ ## Saying less
38
+
39
+ | Change | Needs | Example |
40
+ | --- | --- | --- |
41
+ | A return admits a value it excluded (`String` → `String?`) | a runtime witness or that version's documentation. On code you do not own, a Rigor finding alone is never enough | sidekiq `Client#push` (documented, and a halting middleware returns `nil`); marcel `Magic.by_magic` (`instance` → `instance?`, witnessed by a lookup on input that matches nothing) |
42
+ | A parameter accepts more (`String` → `String \| Pathname`) | documentation, or a correct call the old declaration rejects | — |
43
+ | An undeclared or `untyped` parameter is typed as an interface | the body's structural need | marcel `by_extension` takes `_ToS`, because the gem only calls `to_s` on it |
44
+ | A record key becomes optional on input | a correct call that omits it | retryable `{ sleep: 3.2 }` |
45
+ | A declaration is added for a public method, as `untyped` slots | nothing beyond the method existing | — |
46
+
47
+ **Rigor's findings have false positives, so they are leads.** sidekiq 7.0's `Config#client_middleware`
48
+ is `@client_chain ||= Chain.new(self); yield @client_chain if block_given?; @client_chain`, and
49
+ `rigor check` reports `declared Chain, inferred Chain?` although it never returns `nil`. An agent that
50
+ widened on that finding would push a `nil` check onto every caller of the gem. A witness is what tells
51
+ the two apart: no input makes `client_middleware` return `nil`, so none can be produced.
52
+
53
+ **A missing witness is not proof the other way.** A run that raises nothing shows only that the inputs
54
+ you tried behave; it never justifies a narrower declaration. When you cannot produce a witness and the
55
+ documentation is silent, leave the declaration and record the finding.
56
+
57
+ **A declared parameter is a promise both ways.** Widening it promises callers that the library accepts
58
+ more, and the next release may not keep that promise (a body that only calls `to_s` today may call
59
+ `downcase` tomorrow). So a declared parameter widens only on documentation or a correct call it rejects,
60
+ never on the body's current structural need alone.
61
+
62
+ Before widening a return, look at what callers lose. Every caller now has to handle the new case, so the
63
+ change is right only if the case is real. "It could be nil if someone passes nonsense" is not a witness;
64
+ an input the documentation allows, or a use the library's own callers make, is.
65
+
66
+ ## Saying more
67
+
68
+ | Change | Needs |
69
+ | --- | --- |
70
+ | A narrower return over a declared non-`untyped` type | a contract source. A `tighter_return` proposal alone is not enough |
71
+ | A narrower return over `untyped`, or a new method's return | Rigor's proof (`sig-gen`'s proposal, verbatim) — or a contract source for anything it did not propose |
72
+ | A narrower parameter | a contract source. Observed call sites (`sig-gen --params=observed`) show what callers pass today, not what the method accepts; widen literals before adopting them |
73
+ | `void` or `boolish` → a value type | that version's documentation of the value. The body returning something is not documentation |
74
+ | A deliberately wide nominal (`Numeric`, `Object`) → what the body returns | that version's documentation of the narrower value |
75
+ | A new method whose `sig-gen` proposal is a literal (`-> 0`, `-> false`), an empty collection (`-> {}`, `-> []`), `nil`, or the incidental value of a procedure's last statement | nothing adopts it as is: write `void` (`bool` for a predicate) unless the documentation names the value. A literal default that subclasses override (`directed? -> false`, overridden with `true`) is a predicate: `bool` |
76
+ | `instance` or `self` instead of the class `sig-gen` printed (a `new` in a singleton method, `self.class.new`, a method returning `self`) | the body's construction (`new` on the receiver's own class) plus documentation that subclasses get their own type. A witness cannot show it, because the checker accepts a subclass instance either way (#3069) |
77
+
78
+ ## Replacing a wholly wrong declaration
79
+
80
+ Sometimes the declared type and the body have nothing in common: sidekiq 7.0's `Config#total_concurrency`
81
+ is declared `ConnectionPool[RedisClientAdapter]`, and its body is `capsules.each_value.sum(&:concurrency)`
82
+ (`rigor check` reports `inferred Integer`). That is neither saying less nor saying more. Treat the slot
83
+ as undeclared:
84
+
85
+ 1. Take the type from that version's documentation, or from `rigor sig-gen --print` run on a copy of the
86
+ declarations with this member removed — the same source an undeclared method would get.
87
+ 2. Witness that the old declaration is wrong: one call whose result RBS's type checker rejects against
88
+ the old type.
89
+ 3. Say in the write-up that the declaration was replaced, not widened or narrowed.
90
+
91
+ ## Blocks
92
+
93
+ Most of a new declaration's work is in its parameters and blocks, and `sig-gen` does not always type
94
+ blocks (#3067). Decide them in this order:
95
+
96
+ - **Required or optional.** Optional (`?{ … }`) when every `yield` or `block.call` is guarded by
97
+ `block_given?` (or the block is passed on to a method whose declaration makes it optional); required
98
+ when one is unconditional. If the documentation says optional and the body uses the block
99
+ unconditionally, see "When the documented contract crashes" below.
100
+ - **Two overloads, not an optional block,** when the method returns something else without a block
101
+ (`return enum_for(__method__) unless block_given?`): `() { (T) -> void } -> self` and
102
+ `() -> Enumerator[T, self]`, rather than one optional block with a union return.
103
+ - **Block parameters** come from what is yielded, and from `:yields:` / `@yield` / `@yieldparam` in the
104
+ documentation.
105
+ - **Block return** is `void` unless the method uses the value; `boolish` when it is used only for
106
+ truthiness (a filter or predicate block); a precise type only when the method's documented result
107
+ depends on it.
108
+
109
+ ## When the documented contract crashes
110
+
111
+ Sometimes a witness shows the documentation promising more than the code delivers: rgl documents
112
+ `depth_first_search`'s block as optional, and calling it without one raises `NoMethodError`. Keep the
113
+ documented declaration — it is the contract — note the defect as an upstream bug for the user to
114
+ report (filing an issue on someone else's repository is outward-facing; do not do it unasked), and put
115
+ it under open questions in the write-up. Narrowing the declaration to what the code survives would be saying more
116
+ without a contract source.
117
+
118
+ ## Rules that come up on almost every run
119
+
120
+ **`void` and `boolish` stand.** (For the project's own code, the owner may decide otherwise; that is a
121
+ contract decision, so ask rather than infer.) Every Ruby method returns its last expression, so a body
122
+ nearly always has a value. `-> void` says that value is not part of the contract; `boolish` says only
123
+ its truthiness is. `static.value-use.void` reports a use of the first where a project enables it. Do not
124
+ tighten either to the body's value unless the library documents that value. In sidekiq 7.0,
125
+ `perform_async` and `Client.enqueue*` return `push`'s result from their bodies, but nothing documents
126
+ it, so their `void` stayed while `push` itself became `String?`.
127
+
128
+ **One alias, two contracts.** When a type alias, record or interface is both what callers pass in and
129
+ what the library hands back, check whether the two agree. Inputs usually accept partial or loose values;
130
+ outputs usually guarantee more. If they differ, split the alias — for example `option_overrides` (keys
131
+ optional) for input and `options` (keys required) for output — rather than making it loose enough for
132
+ both and weakening every return that uses it. Inputs stay lenient; outputs stay as precise as their
133
+ documented guarantee.
134
+
135
+ **A nilable wrapper on a value is not the same as an optional key.** `{ ?sleep: Numeric }` says the key may
136
+ be absent; `{ sleep: Numeric? }` says it may be present and `nil`. Use the second only where a present
137
+ `nil` is accepted and means something.
138
+
139
+ **Interfaces describe what the method uses.** When an undeclared or `untyped` parameter is only used
140
+ through one or two methods, an interface (`_ToS`, `_ToPath`, `_Each[T]`, or one you declare inside the
141
+ library's namespace) is the honest contract. Do not add top-level aliases or interfaces to a shared RBS
142
+ tree.
143
+
144
+ **Consistency across versions.** A collection entry often has several version directories. Apply the same
145
+ contract to each version whose code and documentation say the same thing — witnessed or quoted in that
146
+ version — and say in the write-up which versions you changed and which you did not and why.
147
+
148
+ ## Every finding gets a disposition
149
+
150
+ Each row Rigor reported (Signal 1 and Signal 2) and each lead you followed ends in exactly one list:
151
+
152
+ - **Changed** — with its evidence.
153
+ - **Deliberately left unchanged** — you looked, and the declaration is the right contract: a `void` over
154
+ a value; a wide nominal; a proposal that is an inference gap; a Rigor finding no witness confirmed
155
+ (say so — "`rigor check` reports `Chain?`; no input returns `nil`").
156
+ - **Open questions** — the evidence does not settle it. For your own code, ask the owner. Upstream, a
157
+ question in the pull request description is better than a guess in the diff.
@@ -0,0 +1,90 @@
1
+ # 03 — Target: the project's own code
2
+
3
+ A gem's shipped `sig/`, an application's `sig/`, or rbs-inline annotations (`#:`, `# @rbs`) in either.
4
+ The project's owner is the authority on every contract this code makes, so this target is
5
+ **interactive**: Rigor and the documentation narrow the questions, and the owner answers the ones they
6
+ leave open.
7
+
8
+ ## Setup
9
+
10
+ The project's Rigor configuration already analyses its source with its `sig/` on the signature path, so
11
+ the commands in [`01-finding-candidates.md`](01-finding-candidates.md) run as they are:
12
+
13
+ ```sh
14
+ rigor check --no-baseline --format=json lib # Signal 1
15
+ rigor sig-gen --diff --format=json lib # Signal 2
16
+ rigor check --no-baseline --format=json spec # Signal 3, or test/, or the application's callers
17
+ ```
18
+
19
+ If `rigor` is not configured yet, run `rigor-next-steps` first. Record the current `rigor check` result
20
+ (with the baseline, as the project normally runs it) before changing anything; the gate below compares
21
+ against it.
22
+
23
+ ## Ask, in one batch
24
+
25
+ After triage, sort what is left into three groups:
26
+
27
+ 1. **Settled by evidence** — a `def.return-type-mismatch`, a `sig-gen` proposal over `untyped`, a
28
+ documented value. Apply these, with their citations.
29
+ 2. **Settled as "leave it"** — a `void` over a value nobody documented, a wide nominal, a proposal that
30
+ is an inference gap. Do not ask about these one by one; list them.
31
+ 3. **Open contract questions** — everything that needs the owner. Ask them together, each with the
32
+ evidence and the concrete options:
33
+
34
+ > `Client#push` is declared `-> String`. Its body returns `nil` when a middleware halts the job
35
+ > (`lib/client.rb:88`, `return nil unless item`), and `rigor check` cannot see that early return.
36
+ > Should callers be told to expect `nil`?
37
+ > (a) `-> String?` — callers must handle a halted job; (b) keep `-> String` and raise instead of
38
+ > returning `nil`; (c) `-> void` — callers should not use the value.
39
+
40
+ Never ask an open question as "what type is this?". The owner should choose a contract, not reverse-
41
+ engineer one.
42
+
43
+ ## Write the answer where it can be cited
44
+
45
+ An answer that lives only in the conversation is lost to the next contributor and to anyone writing RBS
46
+ for this library elsewhere. Offer to record each answered contract as a doc comment in prose — "Returns
47
+ the job id, or nil when a middleware stops the job." — next to the method. The declaration then has a
48
+ documented source, which is what makes it checkable later.
49
+
50
+ If the project keeps types out of doc comments, prose describing behaviour is still fine; a bracketed
51
+ YARD type is not, and `rigor-type-oracle` covers why.
52
+
53
+ ## The gate
54
+
55
+ 1. `rigor check` on the project, run as the project normally runs it, gains no new diagnostic. A new one
56
+ means a caller relied on the old contract: show it to the owner. It is either a bug the new contract
57
+ exposed, or a sign the contract change is wrong.
58
+ 2. If the project ships its RBS (a gem's `sig/`), it still loads: `rbs -I sig validate`, or the
59
+ project's own RBS task.
60
+ 3. If the project also runs Steep or another RBS consumer, run it. Rigor is not the only reader of these
61
+ files.
62
+ 4. Where you widened a return, a witness shows the case is real (see `01-finding-candidates.md`
63
+ § "Signal 6") and a test or a use site now handles it; where you widened a parameter, a test now
64
+ passes the newly accepted argument. The owner may accept a change without a witness — say so in the
65
+ write-up — but a Rigor finding alone is never presented as proof.
66
+ 5. If the project has a test suite, use it as the command for the declaration-mutation pass
67
+ ([`05-declaration-mutants.md`](05-declaration-mutants.md)): it runs the suite under `rbs/test/setup`
68
+ once per broken declaration. A killed mutant is a declaration the suite checks against real values,
69
+ which no static signal does; a survivor is one it does not check, however green the suite is. Report
70
+ the survivors to the owner.
71
+ 6. For a gem with no RBS yet, the skeleton and new-method rules in `04-target-gem-rbs-collection.md`
72
+ § "New declaration set" apply to its `sig/` as well; ship it with the gem rather than through the
73
+ collection.
74
+
75
+ ## When the wrong signature belongs to a dependency
76
+
77
+ If a diagnostic in the project traces to a dependency's community RBS, the fix belongs upstream:
78
+ follow [`04-target-gem-rbs-collection.md`](04-target-gem-rbs-collection.md) for that gem. Until the fix
79
+ is released, a local corrected copy can stand in:
80
+
81
+ - in `rbs_collection.yaml`, set that gem to `ignore: true`, then run `rbs collection install` again so
82
+ `rbs_collection.lock.yaml` drops it (the old directory under `.gem_rbs_collection/` stays, but only
83
+ gems listed in the lockfile load) — otherwise the collection copy still loads next to yours and the duplicate declarations fail to build, which reads as a quieter run, not a
84
+ fixed one;
85
+ - copy its RBS into a project directory on `signature_paths`, apply the fix, and note the upstream pull
86
+ request next to it;
87
+ - remove the copy and the `ignore:` once the fix is in a collection release you have installed.
88
+
89
+ Do not patch the installed collection under `.gem_rbs_collection/`; the next `rbs collection install`
90
+ overwrites it.