rigortype 0.3.9 → 0.4.1
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.
- checksums.yaml +4 -4
- data/README.md +5 -4
- data/data/core_overlay/enumerable.rbs +51 -0
- data/data/core_overlay/enumerator.rbs +84 -0
- data/data/core_overlay/hash_rbs3.rbs +41 -0
- data/data/core_overlay/process.rbs +40 -0
- data/data/core_overlay/string_io.rbs +33 -0
- data/data/effects/core.yml +3 -3
- data/data/gem_overlay/activesupport/core_ext.rbs +127 -4
- data/docs/handbook/03-narrowing.md +95 -10
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- data/docs/handbook/07-rbs-and-extended.md +16 -11
- data/docs/handbook/08-understanding-errors.md +3 -2
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +454 -13
- data/docs/install.md +87 -15
- data/docs/manual/02-cli-reference.md +65 -4
- data/docs/manual/03-configuration.md +12 -0
- data/docs/manual/04-diagnostics.md +4 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/08-skills.md +4 -3
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/12-caching.md +43 -4
- data/docs/manual/14-rails-quickstart.md +19 -11
- data/docs/manual/16-rbs-extended-annotations.md +40 -7
- data/docs/manual/19-effect-labels.md +10 -0
- data/docs/manual/plugins/README.md +7 -0
- data/docs/manual/plugins/rigor-actioncable.md +8 -1
- data/docs/manual/plugins/rigor-actionmailer.md +7 -0
- data/docs/manual/plugins/rigor-actionpack.md +243 -0
- data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
- data/docs/manual/plugins/rigor-activejob.md +7 -0
- data/docs/manual/plugins/rigor-activerecord.md +110 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
- data/docs/manual/plugins/rigor-grape.md +106 -0
- data/docs/manual/plugins/rigor-graphql.md +23 -2
- data/docs/manual/plugins/rigor-pundit.md +8 -1
- data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
- data/docs/manual/plugins/rigor-rails-routes.md +7 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +212 -25
- data/docs/manual/plugins/rigor-sidekiq.md +8 -1
- data/docs/manual/plugins/rigor-sorbet.md +21 -5
- data/exe/rigor +19 -4
- data/lib/rigor/analysis/baseline.rb +1 -1
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +427 -0
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +36 -2
- data/lib/rigor/analysis/check_rules.rb +638 -136
- data/lib/rigor/analysis/dependency_recorder.rb +222 -17
- data/lib/rigor/analysis/fact_store.rb +9 -0
- data/lib/rigor/analysis/incremental.rb +26 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +266 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +171 -0
- data/lib/rigor/analysis/incremental_session.rb +309 -53
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
- data/lib/rigor/analysis/reachability/scan_cache.rb +3 -1
- data/lib/rigor/analysis/rule_catalog.rb +127 -0
- data/lib/rigor/analysis/run_cache_key.rb +46 -18
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +168 -12
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
- data/lib/rigor/analysis/runner/pool_coordinator.rb +109 -17
- data/lib/rigor/analysis/runner/project_pre_passes.rb +44 -17
- data/lib/rigor/analysis/runner.rb +603 -54
- data/lib/rigor/analysis/shadow_harness.rb +27 -0
- data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
- data/lib/rigor/analysis/template_unit_collector.rb +303 -0
- data/lib/rigor/analysis/template_unit_paths.rb +93 -0
- data/lib/rigor/analysis/template_unit_positions.rb +293 -0
- data/lib/rigor/analysis/template_units.rb +409 -0
- data/lib/rigor/analysis/worker_session.rb +57 -13
- data/lib/rigor/bleeding_edge.rb +0 -2
- data/lib/rigor/builtins/hkt_builtins.rb +1 -0
- data/lib/rigor/builtins/imported_refinements.rb +4 -0
- data/lib/rigor/builtins/regex_refinement.rb +17 -9
- data/lib/rigor/builtins/static_return_refinements.rb +2 -0
- data/lib/rigor/cache/descriptor.rb +45 -1
- data/lib/rigor/cache/engine_source.rb +29 -1
- data/lib/rigor/cache/file_digest.rb +28 -0
- data/lib/rigor/cache/incremental_snapshot.rb +102 -8
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- data/lib/rigor/cache/rbs_descriptor.rb +37 -7
- data/lib/rigor/cache/store.rb +63 -4
- data/lib/rigor/cache/value_digest.rb +325 -0
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/check_command.rb +83 -13
- data/lib/rigor/cli/doc_links.rb +1 -1
- data/lib/rigor/cli/docs_command.rb +4 -4
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/effects_report.rb +2 -2
- data/lib/rigor/cli/plugin_command.rb +3 -3
- data/lib/rigor/cli/prism_colorizer.rb +0 -1
- data/lib/rigor/cli/sig_gen_command.rb +210 -29
- data/lib/rigor/cli/skill_command.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +0 -1
- data/lib/rigor/cli/type_of_command.rb +19 -5
- data/lib/rigor/cli/type_of_renderer.rb +20 -9
- data/lib/rigor/cli/type_of_template_probe.rb +189 -0
- data/lib/rigor/cli/unused_command.rb +3 -3
- data/lib/rigor/cli.rb +21 -3
- data/lib/rigor/config_audit.rb +3 -2
- data/lib/rigor/config_path.rb +29 -0
- data/lib/rigor/configuration/severity_profile.rb +24 -3
- data/lib/rigor/configuration.rb +71 -7
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +11 -2
- data/lib/rigor/effects/callee_rule.rb +368 -0
- data/lib/rigor/effects/catalog.rb +7 -4
- data/lib/rigor/effects/collector.rb +11 -5
- data/lib/rigor/effects/config_envelopes.rb +9 -2
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/effect_table.rb +11 -3
- data/lib/rigor/effects/envelope_check.rb +1 -1
- data/lib/rigor/effects/envelope_index.rb +15 -0
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/identity.rb +16 -0
- data/lib/rigor/effects/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +21 -0
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +49 -37
- data/lib/rigor/effects/propagator.rb +295 -16
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +121 -72
- data/lib/rigor/effects/signature_sources.rb +1 -1
- data/lib/rigor/effects/snapshot.rb +2 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +385 -30
- data/lib/rigor/effects/visibility.rb +101 -0
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
- data/lib/rigor/environment/lockfile_resolver.rb +27 -1
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
- data/lib/rigor/environment/rbs_loader.rb +400 -134
- data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
- data/lib/rigor/environment.rb +104 -96
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +139 -9
- data/lib/rigor/inference/block_auto_splat.rb +216 -0
- data/lib/rigor/inference/block_call_timing.rb +338 -0
- data/lib/rigor/inference/block_parameter_binder.rb +88 -26
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +2 -1
- data/lib/rigor/inference/budget_trace.rb +2 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +393 -15
- data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/declaration_walk/context.rb +195 -0
- data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
- data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
- data/lib/rigor/inference/declaration_walk.rb +7 -0
- data/lib/rigor/inference/def_node_resolver.rb +3 -2
- data/lib/rigor/inference/def_return_typer.rb +11 -7
- data/lib/rigor/inference/define_method_block_self.rb +64 -0
- data/lib/rigor/inference/definer_resolution.rb +266 -0
- data/lib/rigor/inference/element_read_widening.rb +22 -10
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +1665 -538
- data/lib/rigor/inference/external_ancestor_resolution.rb +264 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
- data/lib/rigor/inference/global_write_census.rb +239 -0
- data/lib/rigor/inference/guard_rebinding.rb +488 -0
- data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
- data/lib/rigor/inference/index_write_widening.rb +16 -3
- data/lib/rigor/inference/indexed_narrowing.rb +61 -9
- data/lib/rigor/inference/jump_targets.rb +82 -0
- data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
- data/lib/rigor/inference/last_line.rb +340 -0
- data/lib/rigor/inference/last_status.rb +144 -0
- data/lib/rigor/inference/macro_block_self_type.rb +179 -17
- data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
- data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
- data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
- data/lib/rigor/inference/match_rebinding.rb +392 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +85 -24
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
- data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
- data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +901 -38
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +214 -13
- data/lib/rigor/inference/method_parameter_binder.rb +8 -3
- data/lib/rigor/inference/module_function_state.rb +213 -0
- data/lib/rigor/inference/multi_target_binder.rb +345 -52
- data/lib/rigor/inference/mutation_rejoin.rb +4 -1
- data/lib/rigor/inference/mutation_widening.rb +70 -48
- data/lib/rigor/inference/narrowing.rb +674 -124
- data/lib/rigor/inference/operand_effects.rb +167 -0
- data/lib/rigor/inference/operand_walk.rb +88 -0
- data/lib/rigor/inference/optimistic_origin.rb +152 -9
- data/lib/rigor/inference/parameter_inference_collector.rb +13 -11
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +7 -2
- data/lib/rigor/inference/project_patched_scanner.rb +7 -3
- data/lib/rigor/inference/receiver_alias.rb +90 -1
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +15 -11
- data/lib/rigor/inference/repeated_or_writes.rb +463 -0
- data/lib/rigor/inference/return_barrier.rb +54 -0
- data/lib/rigor/inference/rewrite_mutation.rb +120 -0
- data/lib/rigor/inference/scope_indexer/certainty.rb +176 -0
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
- data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
- data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
- data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +130 -0
- data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
- data/lib/rigor/inference/scope_indexer.rb +5913 -701
- data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
- data/lib/rigor/inference/statement_evaluator.rb +3249 -497
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +44 -7
- data/lib/rigor/inference/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- data/lib/rigor/language_server/debouncer.rb +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
- data/lib/rigor/language_server/hover_renderer.rb +3 -3
- data/lib/rigor/language_server/project_context.rb +5 -3
- data/lib/rigor/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +196 -13
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +1 -1
- data/lib/rigor/plugin/effect_attribution.rb +58 -4
- data/lib/rigor/plugin/io_boundary.rb +82 -4
- data/lib/rigor/plugin/isolation.rb +1 -1
- data/lib/rigor/plugin/loader.rb +2 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- data/lib/rigor/plugin/manifest.rb +71 -10
- data/lib/rigor/plugin/registry.rb +35 -1
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin/trust_policy.rb +3 -3
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/protection/discovery_seed.rb +20 -9
- data/lib/rigor/protection/kill_signature.rb +0 -1
- data/lib/rigor/protection/mutation_cache.rb +4 -5
- data/lib/rigor/rbs_extended.rb +27 -0
- data/lib/rigor/reflection/constant_ancestors.rb +146 -0
- data/lib/rigor/reflection/constant_path.rb +41 -13
- data/lib/rigor/reflection.rb +73 -107
- data/lib/rigor/scope/discovery_index.rb +351 -2
- data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
- data/lib/rigor/scope/resolution_chain.rb +884 -0
- data/lib/rigor/scope.rb +981 -198
- data/lib/rigor/sig_gen/alias_index.rb +289 -0
- data/lib/rigor/sig_gen/classification.rb +22 -5
- data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
- data/lib/rigor/sig_gen/generator.rb +580 -47
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- data/lib/rigor/sig_gen/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +124 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
- data/lib/rigor/sig_gen/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +166 -35
- data/lib/rigor/sig_gen.rb +3 -0
- data/lib/rigor/signature_path_audit.rb +1 -1
- data/lib/rigor/source/node_walker.rb +0 -3
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/combinator.rb +112 -12
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/hash_shape.rb +1 -1
- data/lib/rigor/type/refined.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +239 -4
- data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
- data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
- data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +380 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +10 -0
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
- data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
- data/plugins/rigor-grape/sig/grape.rbs +263 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +72 -2
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +217 -36
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
- data/sig/rigor/analysis/baseline.rbs +69 -7
- data/sig/rigor/analysis/fact_store.rbs +1 -1
- data/sig/rigor/analysis/project_scan.rbs +74 -0
- data/sig/rigor/effects/config_envelopes.rbs +100 -0
- data/sig/rigor/effects/effect_table.rbs +60 -0
- data/sig/rigor/effects/envelope.rbs +97 -0
- data/sig/rigor/effects/envelope_index.rbs +33 -0
- data/sig/rigor/effects/file_collection.rbs +81 -0
- data/sig/rigor/effects/label.rbs +26 -0
- data/sig/rigor/effects/label_set.rbs +46 -0
- data/sig/rigor/effects/method_key.rbs +24 -0
- data/sig/rigor/effects/origin.rbs +50 -0
- data/sig/rigor/effects/plugin_facts.rbs +141 -0
- data/sig/rigor/effects/registry.rbs +68 -0
- data/sig/rigor/effects/summary.rbs +50 -0
- data/sig/rigor/effects/taint_cause.rbs +12 -0
- data/sig/rigor/environment.rbs +14 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +6 -4
- data/sig/rigor/plugin/additional_initializer.rbs +36 -0
- data/sig/rigor/plugin/base.rbs +30 -7
- data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
- data/sig/rigor/plugin/effect_attribution.rbs +66 -0
- data/sig/rigor/plugin/effect_edge.rbs +33 -0
- data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
- data/sig/rigor/plugin/io_boundary.rbs +2 -1
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +50 -11
- data/sig/rigor/plugin/protocol_contract.rbs +68 -0
- data/sig/rigor/plugin/registry.rbs +62 -1
- data/sig/rigor/plugin.rbs +1 -1
- data/sig/rigor/rbs_extended.rbs +1 -1
- data/sig/rigor/reflection.rbs +7 -6
- data/sig/rigor/scope.rbs +177 -20
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +6 -0
- data/sig/rigor.rbs +38 -20
- data/skills/rigor-ask/SKILL.md +8 -5
- data/skills/rigor-baseline-reduce/SKILL.md +4 -6
- data/skills/rigor-ci-setup/SKILL.md +17 -21
- data/skills/rigor-doctor/SKILL.md +24 -22
- data/skills/rigor-doctor/references/01-checks.md +97 -33
- data/skills/rigor-editor-setup/SKILL.md +6 -4
- data/skills/rigor-mcp-setup/SKILL.md +5 -4
- data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
- data/skills/rigor-next-steps/SKILL.md +4 -2
- data/skills/rigor-plugin-author/SKILL.md +19 -23
- data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +12 -25
- data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
- data/skills/rigor-plugin-review/SKILL.md +6 -4
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
- data/skills/rigor-plugin-tune/SKILL.md +4 -2
- data/skills/rigor-project-init/SKILL.md +46 -18
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +40 -10
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
- data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
- data/skills/rigor-protection-uplift/SKILL.md +4 -6
- data/skills/rigor-rbs-setup/SKILL.md +4 -2
- data/skills/rigor-type-oracle/SKILL.md +4 -6
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
- data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +132 -1
|
@@ -93,6 +93,7 @@ module Rigor
|
|
|
93
93
|
["IO", :read, :singleton] => IO_READ_NO_LENGTH,
|
|
94
94
|
["File", :read, :singleton] => IO_READ_NO_LENGTH
|
|
95
95
|
}.freeze
|
|
96
|
+
Ractor.make_shareable(OVERRIDES)
|
|
96
97
|
private_constant :OVERRIDES
|
|
97
98
|
|
|
98
99
|
# Looks up a refined return type for the given call.
|
|
@@ -124,6 +125,7 @@ module Rigor
|
|
|
124
125
|
acc[mname] ||= []
|
|
125
126
|
acc[mname] << owner unless acc[mname].include?(owner)
|
|
126
127
|
end.freeze
|
|
128
|
+
Ractor.make_shareable(OWNERS_BY_METHOD)
|
|
127
129
|
private_constant :OWNERS_BY_METHOD
|
|
128
130
|
|
|
129
131
|
# @return the candidate owner class names for a bare method-name
|
|
@@ -53,7 +53,36 @@ module Rigor
|
|
|
53
53
|
# interface a `conforms-to` names) invalidates the warm entry. A pre-9 entry carries no signature-root
|
|
54
54
|
# row and would validate fresh across exactly that edit — the #577 reasoning, one slot over — so cached
|
|
55
55
|
# entries must read as misses once and rebuild carrying the rows.
|
|
56
|
-
|
|
56
|
+
# v10: #1123 — the def-index seed bundles gain the `:prepends` table, the instance-ancestor-ORDER half
|
|
57
|
+
# of the `include` table (`prepend M`, and `Recv.prepend(M)`), which `Scope#user_def_through_ancestors`
|
|
58
|
+
# searches ahead of a class's own `def`s. A pre-10 bundle would silently contribute no prepends for an
|
|
59
|
+
# unchanged file, so that file's consumers would keep answering with the pre-fix MRO — cached bundles
|
|
60
|
+
# must read as misses once and rebuild carrying the slot.
|
|
61
|
+
# v11: #1558 — a plugin producer served from its own cache now replays its entry's dependency rows into
|
|
62
|
+
# the plugin's IoBoundary, so the run-result entry and the entry of any producer computed after it carry
|
|
63
|
+
# the served value's inputs. A pre-11 entry written while a producer hit lacks those rows and would
|
|
64
|
+
# validate fresh across exactly the edit they exist to catch. A checkout's source digest already moved
|
|
65
|
+
# every such key, and a release clears the root through `Store::PAYLOAD_ABI_VERSION`; the bump closes
|
|
66
|
+
# the remaining window, a same-`VERSION` build installed as `gems/rigortype-<VERSION>`, which
|
|
67
|
+
# {EngineSource} treats as version-pinned (#285) and so keys on the version alone.
|
|
68
|
+
# v12: the def-index seed bundles gain the `:unpositioned_mixins` table, the mixin edges whose order is
|
|
69
|
+
# not a fact, which an ancestor-order reader declines on. A pre-12 bundle would silently contribute no
|
|
70
|
+
# such edge for an unchanged file, so cached bundles must read as misses once and rebuild carrying it.
|
|
71
|
+
# v13/v14: #1587 — the prepend table keeps every statement (a repeated `prepend` is no longer collapsed at
|
|
72
|
+
# the freeze) and a module written to both the include and the prepend table, or a repeated `extend`, is
|
|
73
|
+
# named unpositioned. A pre-14 bundle would hold the collapsed prepend list and miss the taint for an
|
|
74
|
+
# unchanged file, so cached bundles must read as misses once and rebuild.
|
|
75
|
+
# v15: #1592 — an `extend` (or a `class << self` include) written inside a block the walk used to skip is
|
|
76
|
+
# now recorded and named unpositioned. A pre-15 bundle would hold neither for an unchanged file.
|
|
77
|
+
# v16: ADR-119 WD1 — the def-index seed bundles gain the `:siblings` Hash (the `possible_*` / `contested_*`
|
|
78
|
+
# halves of the discovery pairs). A pre-16 bundle would contribute no sibling for an unchanged file, so cached
|
|
79
|
+
# bundles must read as misses once and rebuild carrying it.
|
|
80
|
+
# v17: ADR-119 WD3 — the def-contribution producers fill the siblings (a conditional `def`'s name possible, its
|
|
81
|
+
# def-node slot and envelope contested). A pre-17 bundle carries the empty siblings of the walk before them, so
|
|
82
|
+
# an unchanged file would fold as "no possible fact" where a cold walk records one.
|
|
83
|
+
# v18: ADR-119 WD3 — the visibility walk fills `contested_discovered_method_visibilities`; a pre-18 bundle
|
|
84
|
+
# carries it empty, so an unchanged file's conditional visibility would fold as certain.
|
|
85
|
+
SCHEMA_VERSION = 18
|
|
57
86
|
|
|
58
87
|
# Per-slot entry value objects. Constructors validate enums / required fields and freeze the resulting
|
|
59
88
|
# struct so no caller can mutate after the entry is in a Descriptor.
|
|
@@ -100,6 +129,21 @@ module Rigor
|
|
|
100
129
|
new(path: path, comparator: :stat, value: packed)
|
|
101
130
|
end
|
|
102
131
|
|
|
132
|
+
# A `:stat` row whose recording instant is no later than `limit_ns`: any file whose recorded mtime is at or
|
|
133
|
+
# after `limit_ns` is then racy, and validation re-hashes it instead of trusting its stat tuple. Any other
|
|
134
|
+
# row, and one whose instant is already earlier, is returned as is.
|
|
135
|
+
def with_recording_instant_at_most(limit_ns)
|
|
136
|
+
return self unless comparator == :stat
|
|
137
|
+
|
|
138
|
+
parts = value.split
|
|
139
|
+
return self unless parts.size == 6 && Integer(parts[5], 10) > limit_ns
|
|
140
|
+
|
|
141
|
+
parts[5] = limit_ns.to_s
|
|
142
|
+
self.class.new(path: path, comparator: comparator, value: parts.join(" "))
|
|
143
|
+
rescue ArgumentError
|
|
144
|
+
self
|
|
145
|
+
end
|
|
146
|
+
|
|
103
147
|
# ADR-45 WD1 (#577) — the absence row: fresh while `path` does not exist, stale the moment anything
|
|
104
148
|
# (a file, a directory, a symlink) comes into existence there. {Plugin::IoBoundary#read_file}
|
|
105
149
|
# records one when a read fails because the path is missing, so a value computed on "X is absent"
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "digest"
|
|
4
4
|
|
|
5
5
|
require_relative "../version"
|
|
6
|
+
require_relative "descriptor"
|
|
6
7
|
|
|
7
8
|
module Rigor
|
|
8
9
|
module Cache
|
|
@@ -63,6 +64,9 @@ module Rigor
|
|
|
63
64
|
# to whatever else happened to be there, which is exactly the weakening this module forbids.
|
|
64
65
|
REQUIRED_DIRECTORY = "lib"
|
|
65
66
|
|
|
67
|
+
# The config-row key every computed-value slot carries the identity under.
|
|
68
|
+
CONFIG_KEY = "engine-source"
|
|
69
|
+
|
|
66
70
|
module_function
|
|
67
71
|
|
|
68
72
|
# @return the gem root — the directory holding `lib/`, three levels above this file.
|
|
@@ -100,10 +104,34 @@ module Rigor
|
|
|
100
104
|
# exists at all. The same reading covers the long-running `rigor lsp` process.
|
|
101
105
|
#
|
|
102
106
|
# {Unavailable} propagates and is deliberately NOT memoised: the ivar is only assigned on success.
|
|
107
|
+
#
|
|
108
|
+
# The memo is FROZEN because a Ractor-pool worker reads it (#1055): the auto-wired ADR-93
|
|
109
|
+
# `rigor-rbs-inline` synthesizer reaches `.key_config_entries` from inside
|
|
110
|
+
# `Environment.for_project`, which is inside `WorkerSession#initialize`, which is inside the worker's
|
|
111
|
+
# own Ractor — and a non-main Ractor may read a class/module ivar only when its value is shareable.
|
|
112
|
+
# An unfrozen digest String therefore killed every worker in its constructor on any run with the
|
|
113
|
+
# synthesizer on, which is every real CLI run (the spec suite pins it off). The worker never WRITES
|
|
114
|
+
# it: `PoolCoordinator#analyze_files_in_pool` pre-warms this on the main Ractor first, which is where
|
|
115
|
+
# the argument above already says the value belongs.
|
|
103
116
|
def process_identity
|
|
104
117
|
return @process_identity if defined?(@process_identity)
|
|
105
118
|
|
|
106
|
-
@process_identity = identity
|
|
119
|
+
@process_identity = identity.freeze
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# The `engine-source` key row for a cache whose value this engine computed, built from
|
|
123
|
+
# {.process_identity}: empty for a version-pinned tree, so a released gem's keys stay byte-identical.
|
|
124
|
+
#
|
|
125
|
+
# Issue #1009 — a slot keyed only on its inputs and a plugin's manifest version outlives the engine
|
|
126
|
+
# that filled it. The per-file synthesizer slot and every plugin producer were keyed that way, so after
|
|
127
|
+
# an engine or bundled-plugin edit the run-result key moved and the run re-analysed, but through the
|
|
128
|
+
# previous build's synthesized RBS and producer values. {Unavailable} propagates, so a caller that
|
|
129
|
+
# cannot identify the engine bypasses its cache instead of falling back to the weaker key.
|
|
130
|
+
def key_config_entries
|
|
131
|
+
identity = process_identity
|
|
132
|
+
return [] if identity.nil?
|
|
133
|
+
|
|
134
|
+
[Descriptor::ConfigEntry.new(key: CONFIG_KEY, value_hash: Digest::SHA256.hexdigest(identity))]
|
|
107
135
|
end
|
|
108
136
|
|
|
109
137
|
# Discards the {.process_identity} memo; production code MUST NOT call this — a run that recomputed
|
|
@@ -112,6 +112,34 @@ module Rigor
|
|
|
112
112
|
hexdigest(path) == digest
|
|
113
113
|
end
|
|
114
114
|
|
|
115
|
+
# ADR-45 WD2 (#1507) — `packed` re-packed against `path`'s current stat, for a writer about to record it
|
|
116
|
+
# again: when the tuple moved (a `touch`, a checkout) or the entry is racy, but the file still hashes to the
|
|
117
|
+
# recorded digest, the new pack carries the current tuple and this run's recording instant, so the next
|
|
118
|
+
# validation is one `File::Stat` again rather than a re-hash every time.
|
|
119
|
+
#
|
|
120
|
+
# The hash is taken fresh, never from the per-run memo: a memoised digest describes the bytes as they were
|
|
121
|
+
# when change detection read them, and pairing it with a later stat would vouch for bytes written since. And
|
|
122
|
+
# the re-pack is kept only when the tuple it packed, taken after the hash, is the one taken before it: a
|
|
123
|
+
# write landing while the file is hashed moves the change time, and leaves `packed` as it was.
|
|
124
|
+
#
|
|
125
|
+
# `packed` comes back unchanged when its tuple still matches and it is not racy, and whenever the bytes no
|
|
126
|
+
# longer match the digest: a row that must stay stale is never refreshed into a fresh one. nil when `packed`
|
|
127
|
+
# is not a stat pack. A stat failure (the file is gone) returns `packed` unchanged, which then validates as
|
|
128
|
+
# stale.
|
|
129
|
+
def self.refresh_stat(path, packed)
|
|
130
|
+
parsed = parse_stat(packed)
|
|
131
|
+
return nil if parsed.nil?
|
|
132
|
+
|
|
133
|
+
before = File.stat(path)
|
|
134
|
+
return packed if !racy?(parsed) && tuple_matches?(before, parsed)
|
|
135
|
+
return packed unless Digest::SHA256.file(path).hexdigest == parsed[0]
|
|
136
|
+
|
|
137
|
+
repacked = pack_stat(path, parsed[0])
|
|
138
|
+
repacked && tuple_matches?(before, parse_stat(repacked)) ? repacked : packed
|
|
139
|
+
rescue SystemCallError
|
|
140
|
+
packed
|
|
141
|
+
end
|
|
142
|
+
|
|
115
143
|
# The content-digest field of a packed `:stat` entry ({.pack_stat}); nil when the entry is absent or not
|
|
116
144
|
# well-formed. Exposed for the caller that must compare the recorded digest against bytes it hashes
|
|
117
145
|
# itself rather than against the file on disk — the incremental session's editor-mode freshness check,
|
|
@@ -5,6 +5,10 @@ require "digest"
|
|
|
5
5
|
require "zlib"
|
|
6
6
|
|
|
7
7
|
require_relative "engine_source"
|
|
8
|
+
require_relative "store"
|
|
9
|
+
require_relative "../analysis/shadow_harness"
|
|
10
|
+
require_relative "../analysis/run_cache_key"
|
|
11
|
+
require_relative "../environment"
|
|
8
12
|
|
|
9
13
|
module Rigor
|
|
10
14
|
module Cache
|
|
@@ -106,7 +110,65 @@ module Rigor
|
|
|
106
110
|
# blob has no such section and would deserialise it as nil, which reads as "nothing cached" and is
|
|
107
111
|
# therefore not misread — but a snapshot written by an engine that did not yet REPLAY them was also
|
|
108
112
|
# written by one whose narrowed runs under-reported, so the gate rejects it and the next run is cold.
|
|
109
|
-
|
|
113
|
+
# 23: issue #992 gives each seed bundle a `parameter_envelopes` table — the joined positional envelope
|
|
114
|
+
# of every method a file records, plus its module / dynamic-surface marks — which `call.wrong-arity`
|
|
115
|
+
# reads for a method no signature declares. A pre-23 bundle folds as "no envelopes", which only ever
|
|
116
|
+
# withholds a check, but a warm run that withholds where a cold run fires is still the
|
|
117
|
+
# `--verify-incremental` divergence 14 was bumped for.
|
|
118
|
+
# 24: issue #1097 gives each seed bundle a `deferred_ranges` table — the per-file def / block /
|
|
119
|
+
# lambda body ranges `Scope#*_def_shadows_call?` orders a project-defined `sig` override
|
|
120
|
+
# against. A pre-24 bundle would fold as "no ranges", which the predicate reads as "cannot
|
|
121
|
+
# order → shadow" — a warm run declining the binding where a cold run binds.
|
|
122
|
+
# 25: issue #1097 extends each `deferred_ranges` row to `[start, end, name, kind, owner]` —
|
|
123
|
+
# owner-scoped ordering. A 24 bundle folds as "owner = nil", which the predicate reads as
|
|
124
|
+
# "cannot order", under-shadowing `module_function` defs whose only table evidence is the row
|
|
125
|
+
# itself.
|
|
126
|
+
# 26: issue #1123 gives each seed bundle a `prepends` table — the instance-ancestor ORDER of a
|
|
127
|
+
# class's `prepend`ed modules, which `Scope#user_def_through_ancestors` searches ahead of the
|
|
128
|
+
# class's own `def`s. A pre-26 bundle would fold as "prepends nothing": the warm run would answer
|
|
129
|
+
# the pre-fix MRO where a cold run answers the prepended module, exactly the `--verify-incremental`
|
|
130
|
+
# divergence 14 was bumped for (and a silently WRONG dispatch answer, not a withheld check).
|
|
131
|
+
# 27: issue #1173 re-MEANs each seed bundle's `includes` VALUES without changing their shape: the
|
|
132
|
+
# lists now store instance-ancestor SEARCH order (nearest first, prepends ahead of includes)
|
|
133
|
+
# instead of call order — the same flip 17 made for def-row `nesting`. A pre-27 blob deserialises
|
|
134
|
+
# cleanly and would serve the old call order on a warm run while a cold run searches nearer-first:
|
|
135
|
+
# `include A; include B` would resolve A's methods where a cold run resolves B's — the
|
|
136
|
+
# `--verify-incremental` divergence 14 was bumped for.
|
|
137
|
+
# 28: issue #1120 gives each seed bundle a `refinements` table — the `def`s of every `refine X do … end`
|
|
138
|
+
# body, which `call.undefined-method` reads where a `using` of the refining module is in effect. A
|
|
139
|
+
# pre-28 bundle would fold as "refines nothing", and a warm run would report a refined call a cold run
|
|
140
|
+
# declines.
|
|
141
|
+
# 29: issue #1367 gives each seed bundle a `global_write_census` — the globals its `alias` statements name and
|
|
142
|
+
# the `write` / `to_str` / `to_int` / hatch definitions and top-level mixins it holds, which the `global.*`
|
|
143
|
+
# write rules decline on project-wide. A pre-29 bundle would fold as "none of those", and a warm run would
|
|
144
|
+
# report a write a cold run exempts.
|
|
145
|
+
# 30: issue #1536 gives each seed bundle a `source_rbs_digest` — the digest of what every loaded source-RBS
|
|
146
|
+
# synthesizer contributes for the file — which ADR-89 WD1 now compares beside `declaration_signature`
|
|
147
|
+
# in place of switching the gate off whenever `rigor-rbs-inline` is configured. A pre-30 bundle
|
|
148
|
+
# carries none, which the session reads as "unknown" and so as moved: sound, but every first edit to
|
|
149
|
+
# each file would re-analyse the whole project, so the gate rejects the blob and the next run is cold.
|
|
150
|
+
# 31: issue #1574 digests `plugin_fact_digest` and a seed bundle's `source_rbs_digest` with
|
|
151
|
+
# {ValueDigest} instead of over `Marshal.dump` bytes, so the same value digests alike whether a producer
|
|
152
|
+
# computed it or its cache entry served it. A pre-31 digest is in the old form and would never match, so
|
|
153
|
+
# the next run would go cold anyway; the bump makes that a clean miss instead of a "plugin fact surface
|
|
154
|
+
# changed" note that nothing changed.
|
|
155
|
+
# 32: each seed bundle gains an `unpositioned_mixins` table — the file's mixin edges whose order is not a
|
|
156
|
+
# fact (`Scope::DiscoveryIndex#unpositioned_mixins`), which an ancestor-order reader declines on. A pre-32
|
|
157
|
+
# bundle would fold as "every edge positioned", and a warm run would answer an order a cold run declines.
|
|
158
|
+
# 33: the mixin producers change what a seed bundle holds (#1587 — the prepend table keeps every statement,
|
|
159
|
+
# and a both-kind module or a repeated `extend` is named unpositioned), so a pre-33 bundle folds as the
|
|
160
|
+
# collapsed, positioned table and a warm run would answer an order a cold run declines.
|
|
161
|
+
# 34: #1592 — an `extend` or a `class << self` include inside a block the extends walk used to skip is now
|
|
162
|
+
# recorded and named unpositioned, so a pre-34 bundle folds as if the edge did not exist.
|
|
163
|
+
# 35: ADR-119 WD1 — each seed bundle gains a `siblings` Hash, the `possible_*` / `contested_*` siblings of the
|
|
164
|
+
# `Scope::DiscoveryIndex` members that may admit a possible fact. Every sibling is empty until a producer
|
|
165
|
+
# fills one, so a pre-35 bundle folds as "no possible fact", which is what it would hold today; the bump keeps
|
|
166
|
+
# that true once one does.
|
|
167
|
+
# 36: ADR-119 WD3 — the def-contribution producers fill those siblings, so a pre-36 bundle folds an unchanged
|
|
168
|
+
# file's conditional `def` as certain and a warm run would answer a definer a cold run declines.
|
|
169
|
+
# 37: ADR-119 WD3 — the visibility walk fills `contested_discovered_method_visibilities`, which a pre-37 bundle
|
|
170
|
+
# carries empty.
|
|
171
|
+
SCHEMA = 37
|
|
110
172
|
|
|
111
173
|
# The persisted per-file state.
|
|
112
174
|
# `cache` maps an analyzed file to its diagnostics.
|
|
@@ -178,8 +240,13 @@ module Rigor
|
|
|
178
240
|
# rebuild — the engine version + schema, the engine's own SOURCE when the version does not pin it, the
|
|
179
241
|
# resolved configuration, the analysis **roots** (the path arguments, e.g. `["lib"]`, NOT the expanded
|
|
180
242
|
# file list — so a snapshot is keyed to an invocation's roots but adding / removing a file under them is
|
|
181
|
-
# handled incrementally by the session, not a full rebuild), the resolved gem set (
|
|
182
|
-
#
|
|
243
|
+
# handled incrementally by the session, not a full rebuild), the resolved gem set (the lockfiles
|
|
244
|
+
# {Analysis::RunCacheKey.resolved_lockfile_paths} resolves — `bundler.lockfile:` / `rbs_collection.lockfile:`,
|
|
245
|
+
# else `./Gemfile.lock` / `./rbs_collection.lock.yaml` — the same files the run-result key digests), the
|
|
246
|
+
# project's own RBS (the `sig/` roots the environment actually loads: the explicit
|
|
247
|
+
# `signature_paths`, or the auto-detected `<root>/sig` when `signature_paths` is nil), and the contents of
|
|
248
|
+
# every `pre_eval:` file (its PATH is in the configuration, but an edit to a file outside the analysed
|
|
249
|
+
# paths changes no analysed file, so it needs its own part). Built WITHOUT
|
|
183
250
|
# constructing the RBS environment so the warm path can gate the load cheaply, before the costly env
|
|
184
251
|
# build. The `--verify-incremental` gate is the safety net for any under-capture (it would surface as an
|
|
185
252
|
# incremental-vs-full mismatch). Returns nil on any error → the caller falls back to a non-persisted run.
|
|
@@ -193,16 +260,22 @@ module Rigor
|
|
|
193
260
|
# part, so a released gem's fingerprint is byte-identical to the pre-#289 one and its warm snapshots
|
|
194
261
|
# survive the upgrade untouched.
|
|
195
262
|
def self.fingerprint(configuration:, roots:)
|
|
263
|
+
bundler_lock, collection_lock = Analysis::RunCacheKey.resolved_lockfile_paths(configuration)
|
|
196
264
|
parts = [
|
|
197
265
|
"engine:#{Rigor::VERSION}:#{SCHEMA}",
|
|
198
266
|
"config:#{Digest::SHA256.hexdigest(Marshal.dump(configuration.to_h))}",
|
|
199
267
|
"roots:#{Array(roots).map(&:to_s).sort.join("\n")}",
|
|
200
|
-
"gems:#{digest_file_if_present(
|
|
201
|
-
"rbs_collection:#{digest_file_if_present(
|
|
202
|
-
"sig:#{digest_signature_paths(configuration
|
|
268
|
+
"gems:#{digest_file_if_present(bundler_lock)}",
|
|
269
|
+
"rbs_collection:#{digest_file_if_present(collection_lock)}",
|
|
270
|
+
"sig:#{digest_signature_paths(project_sig_roots(configuration))}",
|
|
271
|
+
"pre_eval:#{digest_pre_eval(configuration.pre_eval)}"
|
|
203
272
|
]
|
|
204
273
|
identity = EngineSource.process_identity
|
|
205
274
|
parts << "engine-source:#{identity}" if identity
|
|
275
|
+
# ADR-116 WD5 — the snapshot's per-file rows and seed bundles are only as checked as the run that
|
|
276
|
+
# wrote them ({Inference::DeclarationWalk::Shadow}); nil with the harness off adds no part.
|
|
277
|
+
shadow = Analysis::ShadowHarness.cache_identity
|
|
278
|
+
parts << "shadow-harness:#{shadow}" if shadow
|
|
206
279
|
Digest::SHA256.hexdigest(parts.join("\x00"))
|
|
207
280
|
rescue StandardError
|
|
208
281
|
# {EngineSource::Unavailable} lands here too, and nil is the answer it requires rather than one it
|
|
@@ -214,10 +287,29 @@ module Rigor
|
|
|
214
287
|
end
|
|
215
288
|
|
|
216
289
|
def self.digest_file_if_present(path)
|
|
217
|
-
|
|
290
|
+
path = path&.to_s
|
|
291
|
+
path && File.file?(path) ? Digest::SHA256.file(path).hexdigest : "absent"
|
|
218
292
|
end
|
|
219
293
|
private_class_method :digest_file_if_present
|
|
220
294
|
|
|
295
|
+
# Content-digest every `pre_eval:` file in configuration order, so an edit to one invalidates the snapshot
|
|
296
|
+
# even when it lies outside the analysed paths. A missing file digests as "absent", so creating it counts too.
|
|
297
|
+
def self.digest_pre_eval(paths)
|
|
298
|
+
digest = Digest::SHA256.new
|
|
299
|
+
Array(paths).each do |path|
|
|
300
|
+
digest << path.to_s << "\0" << digest_file_if_present(path.to_s) << "\0"
|
|
301
|
+
end
|
|
302
|
+
digest.hexdigest
|
|
303
|
+
end
|
|
304
|
+
private_class_method :digest_pre_eval
|
|
305
|
+
|
|
306
|
+
# The `sig/` roots the environment loads for this configuration: the configured `signature_paths`, or the
|
|
307
|
+
# auto-detected `<root>/sig` when those are nil ({Environment.project_signature_roots}).
|
|
308
|
+
def self.project_sig_roots(configuration)
|
|
309
|
+
Environment.project_signature_roots(configuration.signature_paths).map(&:to_s)
|
|
310
|
+
end
|
|
311
|
+
private_class_method :project_sig_roots
|
|
312
|
+
|
|
221
313
|
# Content-digest every `.rbs` under the configured signature paths (sorted for determinism) so a project
|
|
222
314
|
# RBS edit invalidates the snapshot. Sig trees are small; content (not mtime) keeps it stable across
|
|
223
315
|
# checkouts.
|
|
@@ -319,7 +411,9 @@ module Rigor
|
|
|
319
411
|
effects_identity: payload.effects_identity,
|
|
320
412
|
run_level_rows: payload.run_level_rows
|
|
321
413
|
)
|
|
322
|
-
blob
|
|
414
|
+
# #1507 — every `--incremental` run that analysed anything rewrites this whole blob, so its deflate is
|
|
415
|
+
# on the edit path; see {Store::DEFLATE_LEVEL}.
|
|
416
|
+
blob = Zlib::Deflate.deflate(raw, Store::DEFLATE_LEVEL)
|
|
323
417
|
tmp = "#{@path}.#{Process.pid}.tmp"
|
|
324
418
|
File.binwrite(tmp, blob)
|
|
325
419
|
File.rename(tmp, @path)
|
|
@@ -29,6 +29,10 @@ module Rigor
|
|
|
29
29
|
# files, same libraries), so the loader memoises one build per process instead of re-digesting every
|
|
30
30
|
# .rbs file once per producer.
|
|
31
31
|
descriptor = loader.rbs_cache_descriptor
|
|
32
|
+
# Issue #1014 — nil is an engine the descriptor could not identify ({EngineSource::Unavailable}).
|
|
33
|
+
# Compute uncached rather than keying the value without the engine that produced it.
|
|
34
|
+
return compute(loader) if descriptor.nil?
|
|
35
|
+
|
|
32
36
|
store.fetch_or_compute(producer_id: self::PRODUCER_ID, params: {}, descriptor: descriptor,
|
|
33
37
|
generation_cap: generation_cap) do
|
|
34
38
|
compute(loader)
|
|
@@ -3,20 +3,47 @@
|
|
|
3
3
|
require "digest"
|
|
4
4
|
|
|
5
5
|
require_relative "descriptor"
|
|
6
|
+
require_relative "engine_source"
|
|
6
7
|
require_relative "file_digest"
|
|
7
8
|
|
|
8
9
|
module Rigor
|
|
9
10
|
module Cache
|
|
10
11
|
# Shared descriptor builder for cache producers that depend on the RBS environment (constant table,
|
|
11
|
-
# known-class set, future Marshal-clean reflection artefacts). Every consumer attaches the same
|
|
12
|
-
#
|
|
13
|
-
#
|
|
12
|
+
# known-class set, future Marshal-clean reflection artefacts). Every consumer attaches the same slots —
|
|
13
|
+
# the `rbs` gem row, a digest row per signature file, and the `rbs.libraries` / `rbs.virtual_rbs` /
|
|
14
|
+
# `rbs.deferred_signature_paths` / `engine-source` config rows — so factoring the construction here keeps
|
|
15
|
+
# the producers small and ensures invalidation behaves identically across them.
|
|
14
16
|
module RbsDescriptor
|
|
17
|
+
# The key every `rbs.*` producer shares ({RbsCacheProducer#fetch}): `rbs.environment`,
|
|
18
|
+
# `rbs.constant_type_table`, `rbs.class_ancestor_table`, `rbs.known_class_names`,
|
|
19
|
+
# `rbs.class_type_param_names`.
|
|
20
|
+
#
|
|
21
|
+
# Issue #1014 — the engine-source row is here, not in {.config_entries}, because these are
|
|
22
|
+
# computed-value slots: what they hold is not the RBS the descriptor digests but what Rigor's own code
|
|
23
|
+
# made of it. `rbs.constant_type_table` stores `Inference::RbsTypeTranslator.translate` output and
|
|
24
|
+
# `rbs.class_ancestor_table` the chains `RbsClassAncestorTable.compute` walks out of
|
|
25
|
+
# `instance_definition`, so an engine edit to either moved the value while the key stood still: the
|
|
26
|
+
# run-result key moved (#285) and the run re-analysed, then read the previous build's table. Both were
|
|
27
|
+
# reproduced cross-build on a fixture — a true positive dropped, and a `flow.unreachable-clause` false
|
|
28
|
+
# positive served — before this row was added.
|
|
29
|
+
#
|
|
30
|
+
# {.config_entries} deliberately does NOT get it: that half is what the ADR-45 run cache key reads
|
|
31
|
+
# ({.build_run}), and `RunCacheKey` already contributes the identical row from
|
|
32
|
+
# `EngineSource.key_config_entries` itself. Adding it there would duplicate the row in the run key and
|
|
33
|
+
# would have to be reconstructible by the boot-slimming probe, which builds `config_entries` without a
|
|
34
|
+
# loader.
|
|
35
|
+
#
|
|
36
|
+
# @raise EngineSource::Unavailable — an engine that cannot be identified must not be keyed by its
|
|
37
|
+
# inputs alone; {RbsCacheProducer.fetch} turns this into an uncached compute.
|
|
15
38
|
def self.build(loader)
|
|
39
|
+
# FIRST, before {.file_entries} digests the whole signature tree: the {EngineSource::Unavailable}
|
|
40
|
+
# path discards this descriptor entirely, and a SHA-256 of every `.rbs` under every signature root is
|
|
41
|
+
# the expensive way to reach that conclusion.
|
|
42
|
+
engine = EngineSource.key_config_entries
|
|
16
43
|
Descriptor.new(
|
|
17
44
|
gems: [rbs_gem_entry],
|
|
18
45
|
files: file_entries(loader),
|
|
19
|
-
configs: config_entries(loader) + env_only_config_entries(loader)
|
|
46
|
+
configs: config_entries(loader) + env_only_config_entries(loader) + engine
|
|
20
47
|
)
|
|
21
48
|
end
|
|
22
49
|
|
|
@@ -25,8 +52,10 @@ module Rigor
|
|
|
25
52
|
# MISS, by the dependency descriptor ({Runner#run_dependency_descriptor}). So a warm HIT never digests
|
|
26
53
|
# the (large, vendored) RBS tree. {RunDescriptor} is NOT a {Descriptor} — it is never composed, hashed,
|
|
27
54
|
# or `==`'d, only its four readers are consulted — so deferring `files` costs no soundness, and `gems`
|
|
28
|
-
# / `configs` match {.build}'s shared slots (the key is unchanged; the
|
|
29
|
-
#
|
|
55
|
+
# / `configs` match {.build}'s shared slots (the key is unchanged; the slots the env key adds on top,
|
|
56
|
+
# {.env_only_config_entries} and the #1014 `engine-source` row, are never read here — `RunCacheKey`
|
|
57
|
+
# contributes the identical engine-source row itself, from the same
|
|
58
|
+
# `EngineSource.key_config_entries`).
|
|
30
59
|
def self.build_run(loader)
|
|
31
60
|
RunDescriptor.new(loader: loader, gems: [rbs_gem_entry], configs: config_entries(loader))
|
|
32
61
|
end
|
|
@@ -112,7 +141,8 @@ module Rigor
|
|
|
112
141
|
# overlays. Rigor's own `data/` trees ({RbsLoader.vendored_gem_sig_paths}, `.core_overlay_sig_paths`,
|
|
113
142
|
# `.capability_role_sig_paths`) are engine-owned: a file appears under one only when the engine tree is
|
|
114
143
|
# edited, which normally arrives with the `lib/` change that reads it and so re-keys every
|
|
115
|
-
# computed-value slot through {EngineSource}
|
|
144
|
+
# computed-value slot through {EngineSource} — including, since #1014, this descriptor itself, which
|
|
145
|
+
# until then carried no such row. They get file rows and no glob — a glob there would cost
|
|
116
146
|
# every project a stat walk of the vendored gem-signature tree per warm run to catch an
|
|
117
147
|
# engine-development edit.
|
|
118
148
|
def self.glob_entries(loader)
|
data/lib/rigor/cache/store.rb
CHANGED
|
@@ -25,7 +25,8 @@ module Rigor
|
|
|
25
25
|
# On-disk byte-layout version. Bumped on incompatible format changes (independent of
|
|
26
26
|
# {Descriptor::SCHEMA_VERSION}, which covers the descriptor schema rather than the byte layout). v2
|
|
27
27
|
# (ADR-54 WD2): the value payload is zlib-deflated on write and inflated on read — Marshal blobs
|
|
28
|
-
#
|
|
28
|
+
# compressed to 13–16 % at zlib's default level (now {DEFLATE_LEVEL}) at an inflate cost an order of
|
|
29
|
+
# magnitude below their `Marshal.load`. v1 entries
|
|
29
30
|
# fail the header check and read as silent misses; the `schema_version.txt` marker additionally carries
|
|
30
31
|
# this version, so the first writable run after a bump clears the root and reclaims the unreadable
|
|
31
32
|
# bytes.
|
|
@@ -60,12 +61,19 @@ module Rigor
|
|
|
60
61
|
|
|
61
62
|
STALE_TEMP_FILE_AGE_SECONDS = 60 * 60
|
|
62
63
|
|
|
64
|
+
# #1507 — the zlib level for everything written under the cache root: these value payloads (ADR-54 WD2),
|
|
65
|
+
# the incremental snapshot, and `rigor unused`'s scan bundle. The snapshot and the scan bundle are
|
|
66
|
+
# rewritten whole whenever a run changed anything, so their deflate is on the edit path. On Mastodon the
|
|
67
|
+
# default level took 122 ms for the snapshot (9.5 MB raw) and 131 ms for `rbs.environment` (22.8 MB raw);
|
|
68
|
+
# `BEST_SPEED` takes 38 ms and 49 ms, for a cache about a quarter larger. Inflate reads any level, so the
|
|
69
|
+
# level is not part of the format.
|
|
70
|
+
DEFLATE_LEVEL = Zlib::BEST_SPEED
|
|
71
|
+
|
|
63
72
|
# Header literal: 5-byte ASCII magic, 1-byte separator, 1-byte format version.
|
|
64
73
|
HEADER = "RIGOR\x00#{FORMAT_VERSION.chr}".b.freeze
|
|
65
74
|
|
|
66
75
|
VALID_PRODUCER_ID = /\A[a-z][a-z0-9._-]*\z/
|
|
67
76
|
|
|
68
|
-
# @param root — cache root directory.
|
|
69
77
|
# @param read_only — when true, every disk-side side-effect is suppressed: `fetch_or_compute`
|
|
70
78
|
# still reads existing entries (hits, gated on a current `schema_version.txt` marker — see
|
|
71
79
|
# {#ensure_schema_version!}) and still runs the producer block on miss, but it does NOT write the
|
|
@@ -242,8 +250,13 @@ module Rigor
|
|
|
242
250
|
# validation always re-checks the filesystem — but a single run only looks up once.
|
|
243
251
|
#
|
|
244
252
|
# `generation_cap:` carries the same producer-declared compaction budget as {#fetch_or_compute}.
|
|
253
|
+
#
|
|
254
|
+
# `on_hit:` is called with the entry's stored dependency descriptor on a fresh hit, before the value
|
|
255
|
+
# is returned, and never on a miss. A caller whose own dependency record must carry a served value's
|
|
256
|
+
# inputs replays them from it (#1558, {Plugin::Base#cache_for}): a hit runs no block, so nothing else
|
|
257
|
+
# reports what the value was computed from.
|
|
245
258
|
def fetch_or_validate(producer_id:, key_descriptor:, generation_cap:, params: {},
|
|
246
|
-
serialize: nil, deserialize: nil)
|
|
259
|
+
serialize: nil, deserialize: nil, on_hit: nil)
|
|
247
260
|
validate_producer_id!(producer_id)
|
|
248
261
|
declare_generation_cap(producer_id, generation_cap)
|
|
249
262
|
disk = ensure_schema_version!
|
|
@@ -253,6 +266,7 @@ module Rigor
|
|
|
253
266
|
cached = path && read_entry(path, deserialize: deserialize)
|
|
254
267
|
if (validated = fresh_pair_value(cached))
|
|
255
268
|
@monitor.synchronize { record(:hits, producer_id) }
|
|
269
|
+
on_hit&.call(validated[1])
|
|
256
270
|
return validated[0]
|
|
257
271
|
end
|
|
258
272
|
|
|
@@ -286,6 +300,51 @@ module Rigor
|
|
|
286
300
|
validated[0]
|
|
287
301
|
end
|
|
288
302
|
|
|
303
|
+
# ADR-45 WD2 (#1507) — a record-and-validate entry's value WITHOUT validating its dependencies. The
|
|
304
|
+
# `--incremental` session reads the previous run-result slot to carry forward the rows a narrowed run
|
|
305
|
+
# cannot re-derive, and that slot is expected to be stale: the edit that started this run is in it. So
|
|
306
|
+
# the answer here is never served — only rows are taken from it, and they are validated again, with
|
|
307
|
+
# everything else, the next time the slot is read. nil on a miss, a malformed entry or an unavailable
|
|
308
|
+
# disk tier. Records neither a hit nor a miss, since nothing was looked up to be served.
|
|
309
|
+
def peek_unvalidated(producer_id:, key_descriptor:, params: {})
|
|
310
|
+
validate_producer_id!(producer_id)
|
|
311
|
+
return nil unless ensure_schema_version!
|
|
312
|
+
|
|
313
|
+
key = key_descriptor.cache_key_for(producer_id: producer_id, params: params)
|
|
314
|
+
pair = read_entry(entry_path(producer_id, key))&.value
|
|
315
|
+
return nil unless pair.is_a?(Array) && pair.size == 2 && pair[1].is_a?(Descriptor)
|
|
316
|
+
|
|
317
|
+
pair[0]
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# ADR-45 WD2 (#1507) — the write half of {#fetch_or_validate} on its own, and unconditional. That method
|
|
321
|
+
# writes only after it failed to validate what is there; a writer that has just computed an answer of its
|
|
322
|
+
# own must store THAT answer even when the entry already on disk still validates, because it is the one
|
|
323
|
+
# the run printed. Same entry format and the same failure contract as {#fetch_or_validate}'s write: a
|
|
324
|
+
# disk-side failure is swallowed, a value `Marshal` cannot dump raises. Returns whether it wrote.
|
|
325
|
+
def store_validated(producer_id:, key_descriptor:, generation_cap:, value:, dependencies:, params: {})
|
|
326
|
+
validate_producer_id!(producer_id)
|
|
327
|
+
declare_generation_cap(producer_id, generation_cap)
|
|
328
|
+
return false unless ensure_schema_version!
|
|
329
|
+
|
|
330
|
+
key = key_descriptor.cache_key_for(producer_id: producer_id, params: params)
|
|
331
|
+
wrote = try_write_entry(entry_path(producer_id, key), key_descriptor, [value, dependencies])
|
|
332
|
+
@monitor.synchronize { record(:writes, producer_id) } if wrote
|
|
333
|
+
wrote
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# ADR-45 WD2 (#1507) — removes one entry, for a writer that has just superseded it under another key and
|
|
337
|
+
# knows nothing will ask for it again. Best-effort like every other unlink here; a no-op on a read-only
|
|
338
|
+
# store.
|
|
339
|
+
def discard(producer_id:, key_descriptor:, params: {})
|
|
340
|
+
validate_producer_id!(producer_id)
|
|
341
|
+
return if @read_only || !ensure_schema_version!
|
|
342
|
+
|
|
343
|
+
path = entry_path(producer_id, key_descriptor.cache_key_for(producer_id: producer_id, params: params))
|
|
344
|
+
unlink_entry_and_shard?(path) if File.file?(path)
|
|
345
|
+
nil
|
|
346
|
+
end
|
|
347
|
+
|
|
289
348
|
# ADR-6 § "Eviction" — compaction pass over the on-disk cache. No-op when the store is read-only.
|
|
290
349
|
#
|
|
291
350
|
# The generation cap of pass 2 comes from what the producers themselves declared through this Store's
|
|
@@ -470,7 +529,7 @@ module Rigor
|
|
|
470
529
|
FileUtils.mkdir_p(File.dirname(path))
|
|
471
530
|
|
|
472
531
|
descriptor_bytes = descriptor.to_canonical_bytes
|
|
473
|
-
value_bytes = Zlib::Deflate.deflate(serialize_value(value, serialize))
|
|
532
|
+
value_bytes = Zlib::Deflate.deflate(serialize_value(value, serialize), DEFLATE_LEVEL)
|
|
474
533
|
|
|
475
534
|
body = +"".b
|
|
476
535
|
body << HEADER
|