rigortype 0.3.8 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/capability_roles/capability_roles.rbs +36 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
- data/docs/handbook/02-everyday-types.md +17 -14
- data/docs/handbook/03-narrowing.md +37 -4
- data/docs/handbook/05-methods-and-blocks.md +1 -1
- data/docs/handbook/06-classes.md +2 -2
- data/docs/handbook/07-rbs-and-extended.md +14 -1
- data/docs/handbook/11-sig-gen.md +72 -1
- data/docs/handbook/README.md +1 -1
- data/docs/handbook/appendix-elixir.md +2 -2
- data/docs/handbook/appendix-go.md +2 -2
- data/docs/handbook/appendix-java-csharp.md +2 -2
- data/docs/handbook/appendix-mypy.md +2 -2
- data/docs/handbook/appendix-phpstan.md +1 -1
- data/docs/handbook/appendix-rust.md +1 -1
- data/docs/handbook/appendix-type-theory.md +4 -4
- data/docs/handbook/appendix-typescript.md +1 -1
- data/docs/llms.txt +2 -0
- data/docs/manual/02-cli-reference.md +32 -4
- data/docs/manual/03-configuration.md +11 -0
- data/docs/manual/04-diagnostics.md +11 -0
- data/docs/manual/07-plugins.md +15 -5
- data/docs/manual/08-skills.md +23 -2
- data/docs/manual/11-ci.md +9 -0
- data/docs/manual/15-type-protection-coverage.md +8 -0
- data/docs/manual/16-rbs-extended-annotations.md +6 -1
- data/docs/manual/18-removing-dead-code.md +10 -8
- data/docs/manual/plugins/rigor-actionpack.md +18 -0
- data/docs/manual/plugins/rigor-activerecord.md +28 -0
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
- data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
- data/lib/rigor/analysis/baseline.rb +2 -2
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
- data/lib/rigor/analysis/check_rules.rb +200 -32
- data/lib/rigor/analysis/crash_signature.rb +8 -24
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
- data/lib/rigor/analysis/erb_template_detector.rb +1 -2
- data/lib/rigor/analysis/fact_store.rb +6 -1
- data/lib/rigor/analysis/incremental.rb +16 -0
- data/lib/rigor/analysis/incremental_session.rb +81 -22
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
- data/lib/rigor/analysis/reachability/graph.rb +3 -5
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
- data/lib/rigor/analysis/reachability/project_files.rb +2 -2
- data/lib/rigor/analysis/reachability/scan.rb +5 -5
- data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
- data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
- data/lib/rigor/analysis/result.rb +1 -3
- data/lib/rigor/analysis/rule_catalog.rb +4 -1
- data/lib/rigor/analysis/run_cache_key.rb +1 -1
- data/lib/rigor/analysis/run_cache_probe.rb +3 -5
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +177 -26
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
- data/lib/rigor/analysis/runner/pool_coordinator.rb +256 -88
- data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +133 -35
- data/lib/rigor/analysis/worker_session.rb +9 -10
- data/lib/rigor/bleeding_edge.rb +12 -24
- data/lib/rigor/builtins/hkt_builtins.rb +2 -2
- data/lib/rigor/builtins/imported_refinements.rb +155 -21
- data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
- data/lib/rigor/builtins/regex_refinement.rb +4 -4
- data/lib/rigor/builtins/static_return_refinements.rb +5 -6
- data/lib/rigor/cache/annotation_location.rb +2 -4
- data/lib/rigor/cache/descriptor.rb +58 -20
- data/lib/rigor/cache/engine_source.rb +4 -4
- data/lib/rigor/cache/file_digest.rb +9 -1
- data/lib/rigor/cache/incremental_snapshot.rb +40 -6
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
- data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
- data/lib/rigor/cache/rbs_constant_table.rb +0 -3
- data/lib/rigor/cache/rbs_descriptor.rb +79 -11
- data/lib/rigor/cache/rbs_environment.rb +8 -4
- data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
- data/lib/rigor/cache/store.rb +77 -32
- data/lib/rigor/cli/annotate_command.rb +5 -6
- data/lib/rigor/cli/check_command.rb +57 -10
- data/lib/rigor/cli/check_invocation.rb +6 -11
- data/lib/rigor/cli/check_runner_factory.rb +1 -5
- data/lib/rigor/cli/coverage_command.rb +1 -1
- data/lib/rigor/cli/coverage_mutation.rb +12 -3
- data/lib/rigor/cli/coverage_scan.rb +1 -5
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/doc_links.rb +3 -3
- data/lib/rigor/cli/docs_command.rb +2 -2
- data/lib/rigor/cli/doctor_command.rb +85 -38
- data/lib/rigor/cli/effects_command.rb +2 -2
- data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
- data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +34 -1
- data/lib/rigor/cli/fused_protection_report.rb +7 -1
- data/lib/rigor/cli/lsp_command.rb +1 -1
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
- data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
- data/lib/rigor/cli/mutation_protection_report.rb +7 -1
- data/lib/rigor/cli/plugin_command.rb +1 -1
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/prism_colorizer.rb +2 -2
- data/lib/rigor/cli/protection_fork_scan.rb +6 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +2 -2
- data/lib/rigor/cli/skill_command.rb +1 -1
- data/lib/rigor/cli/skill_deep_probe.rb +4 -4
- data/lib/rigor/cli/skill_describe.rb +36 -35
- data/lib/rigor/cli/trace_command.rb +4 -4
- data/lib/rigor/cli/trace_renderer.rb +5 -6
- data/lib/rigor/cli/triage_command.rb +1 -1
- data/lib/rigor/cli/type_of_command.rb +10 -6
- data/lib/rigor/cli/type_scan_command.rb +4 -4
- data/lib/rigor/cli/unused_command.rb +11 -3
- data/lib/rigor/cli/upgrade_command.rb +1 -1
- data/lib/rigor/cli.rb +68 -6
- data/lib/rigor/config_audit.rb +30 -5
- data/lib/rigor/configuration/severity_profile.rb +6 -6
- data/lib/rigor/configuration.rb +43 -4
- data/lib/rigor/effects/attribution.rb +1 -3
- data/lib/rigor/effects/config_envelopes.rb +6 -8
- data/lib/rigor/effects/definition_lines.rb +25 -6
- data/lib/rigor/effects/effect_table.rb +0 -1
- data/lib/rigor/effects/entry_points.rb +0 -2
- data/lib/rigor/effects/envelope_check.rb +8 -8
- data/lib/rigor/effects/envelope_index.rb +5 -8
- data/lib/rigor/effects/framework_units.rb +4 -6
- data/lib/rigor/effects/identity.rb +4 -6
- data/lib/rigor/effects/inline_anchor.rb +7 -7
- data/lib/rigor/effects/label_intent.rb +3 -4
- data/lib/rigor/effects/liskov_check.rb +8 -8
- data/lib/rigor/effects/method_key.rb +1 -1
- data/lib/rigor/effects/plugin_facts.rb +4 -6
- data/lib/rigor/effects/propagator.rb +2 -3
- data/lib/rigor/effects/scanner.rb +1 -1
- data/lib/rigor/effects/signature_sources.rb +3 -5
- data/lib/rigor/effects/snapshot.rb +10 -10
- data/lib/rigor/effects/snapshot_diff.rb +1 -1
- data/lib/rigor/effects/unit_scan.rb +10 -10
- data/lib/rigor/effects/unknown_label_check.rb +3 -8
- data/lib/rigor/effects/unknown_label_report.rb +3 -4
- data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
- data/lib/rigor/environment/class_registry.rb +3 -1
- data/lib/rigor/environment/failure_slot.rb +2 -2
- data/lib/rigor/environment/installed_gem_set.rb +85 -0
- data/lib/rigor/environment/lockfile_resolver.rb +51 -4
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
- data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
- data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
- data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
- data/lib/rigor/environment/rbs_loader.rb +388 -59
- data/lib/rigor/environment.rb +105 -20
- data/lib/rigor/flow_contribution/merger.rb +0 -2
- data/lib/rigor/flow_contribution.rb +11 -13
- data/lib/rigor/inference/acceptance.rb +153 -8
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/body_fixpoint.rb +5 -5
- data/lib/rigor/inference/budget_trace.rb +1 -5
- data/lib/rigor/inference/captured_locals.rb +2 -3
- data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
- data/lib/rigor/inference/coverage_scanner.rb +4 -6
- data/lib/rigor/inference/dynamic_origin.rb +1 -1
- data/lib/rigor/inference/element_read_widening.rb +168 -0
- data/lib/rigor/inference/expression_typer.rb +763 -143
- data/lib/rigor/inference/fork_map.rb +5 -7
- data/lib/rigor/inference/hkt_reducer.rb +2 -3
- data/lib/rigor/inference/hkt_registry.rb +4 -7
- data/lib/rigor/inference/index_write_widening.rb +1 -5
- data/lib/rigor/inference/macro_block_self_type.rb +1 -4
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +12 -13
- data/lib/rigor/inference/method_parameter_binder.rb +4 -6
- data/lib/rigor/inference/multi_target_binder.rb +1 -3
- data/lib/rigor/inference/mutation_rejoin.rb +168 -0
- data/lib/rigor/inference/mutation_widening.rb +100 -51
- data/lib/rigor/inference/narrowing.rb +299 -46
- data/lib/rigor/inference/optimistic_origin.rb +1 -7
- data/lib/rigor/inference/origin_lookup.rb +2 -3
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +6 -5
- data/lib/rigor/inference/precision_scanner.rb +3 -4
- data/lib/rigor/inference/project_patched_methods.rb +2 -2
- data/lib/rigor/inference/project_patched_scanner.rb +3 -3
- data/lib/rigor/inference/protection_scanner.rb +1 -2
- data/lib/rigor/inference/range_constant.rb +57 -0
- data/lib/rigor/inference/rbs_type_translator.rb +49 -11
- data/lib/rigor/inference/receiver_alias.rb +3 -3
- data/lib/rigor/inference/refinement_mutation.rb +72 -0
- data/lib/rigor/inference/scope_indexer.rb +688 -151
- data/lib/rigor/inference/statement_evaluator.rb +221 -50
- data/lib/rigor/inference/string_mutation.rb +60 -0
- data/lib/rigor/inference/struct_fold_safety.rb +6 -7
- data/lib/rigor/inference/synthetic_method_index.rb +1 -2
- data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
- data/lib/rigor/inference/version_guard.rb +28 -21
- data/lib/rigor/inference/void_origin.rb +3 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -4
- data/lib/rigor/language_server/buffer_table.rb +4 -4
- data/lib/rigor/language_server/completion_provider.rb +1 -1
- data/lib/rigor/language_server/debouncer.rb +1 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -1
- data/lib/rigor/language_server/hover_provider.rb +1 -1
- data/lib/rigor/language_server/hover_renderer.rb +2 -2
- data/lib/rigor/language_server/incremental_sync.rb +6 -6
- data/lib/rigor/language_server/project_context.rb +2 -5
- data/lib/rigor/language_server/publish_batcher.rb +2 -2
- data/lib/rigor/language_server/selection_range_provider.rb +2 -2
- data/lib/rigor/language_server/server.rb +9 -9
- data/lib/rigor/language_server/signature_help_provider.rb +1 -1
- data/lib/rigor/language_server/uri.rb +1 -1
- data/lib/rigor/plugin/base.rb +90 -21
- data/lib/rigor/plugin/bundled_catalog.rb +167 -0
- data/lib/rigor/plugin/effect_attribution.rb +7 -7
- data/lib/rigor/plugin/effect_entry_points.rb +3 -3
- data/lib/rigor/plugin/fact_store.rb +6 -6
- data/lib/rigor/plugin/io_boundary.rb +93 -8
- data/lib/rigor/plugin/isolation.rb +29 -6
- data/lib/rigor/plugin/loader.rb +31 -12
- data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
- data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
- data/lib/rigor/plugin/manifest.rb +23 -1
- data/lib/rigor/plugin/registry.rb +42 -12
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
- data/lib/rigor/plugin/trust_policy.rb +2 -4
- data/lib/rigor/plugin/type_node_resolver.rb +3 -3
- data/lib/rigor/plugin_gap_advisory.rb +96 -0
- data/lib/rigor/project_environment.rb +138 -0
- data/lib/rigor/protection/analysis_guard.rb +93 -14
- data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
- data/lib/rigor/protection/dependency_closure.rb +5 -8
- data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
- data/lib/rigor/protection/discovery_seed.rb +11 -11
- data/lib/rigor/protection/kill_signature.rb +2 -4
- data/lib/rigor/protection/measurement_integrity.rb +1 -3
- data/lib/rigor/protection/mutation_cache.rb +6 -9
- data/lib/rigor/protection/mutation_scanner.rb +12 -15
- data/lib/rigor/protection/mutator.rb +2 -1
- data/lib/rigor/protection/test_suite_oracle.rb +5 -5
- data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/reporter.rb +36 -10
- data/lib/rigor/rbs_extended.rb +61 -21
- data/lib/rigor/reflection/constant_path.rb +126 -0
- data/lib/rigor/reflection.rb +26 -10
- data/lib/rigor/runtime/jit.rb +6 -8
- data/lib/rigor/scope/discovery_index.rb +30 -0
- data/lib/rigor/scope.rb +203 -4
- data/lib/rigor/sig_gen/generator.rb +115 -22
- data/lib/rigor/sig_gen/layout_index.rb +3 -4
- data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
- data/lib/rigor/sig_gen/observation_collector.rb +17 -15
- data/lib/rigor/sig_gen/path_mapper.rb +4 -6
- data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
- data/lib/rigor/sig_gen/renderer.rb +4 -5
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
- data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
- data/lib/rigor/sig_gen/writer.rb +23 -6
- data/lib/rigor/signature_path_audit.rb +153 -6
- data/lib/rigor/source/literals.rb +0 -23
- data/lib/rigor/source/node_children.rb +0 -2
- data/lib/rigor/source/node_locator.rb +5 -11
- data/lib/rigor/source/node_walker.rb +2 -5
- data/lib/rigor/triage/catalogue.rb +1 -3
- data/lib/rigor/triage.rb +3 -5
- data/lib/rigor/type/accepts_result.rb +24 -6
- data/lib/rigor/type/combinator.rb +68 -5
- data/lib/rigor/type/data_class.rb +2 -2
- data/lib/rigor/type/data_instance.rb +4 -4
- data/lib/rigor/type/float_range.rb +128 -0
- data/lib/rigor/type/hash_shape.rb +5 -5
- data/lib/rigor/type/integer_range.rb +13 -8
- data/lib/rigor/type/nominal.rb +8 -2
- data/lib/rigor/type/refined.rb +3 -3
- data/lib/rigor/type/struct_class.rb +3 -3
- data/lib/rigor/type/struct_instance.rb +4 -4
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +1 -1
- data/lib/rigor/type_node/range_literal.rb +25 -0
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
- data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
- data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
- data/sig/rigor/cache.rbs +2 -3
- data/sig/rigor/environment.rbs +5 -0
- data/sig/rigor/inference.rbs +3 -10
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/manifest.rbs +2 -1
- data/sig/rigor/reflection.rbs +3 -1
- data/sig/rigor/scope.rbs +24 -3
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
- data/sig/rigor/type.rbs +27 -1
- data/sig/rigor.rbs +1 -0
- data/skills/rigor-ask/SKILL.md +8 -6
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
- data/skills/rigor-project-init/SKILL.md +6 -3
- data/skills/rigor-project-init/references/02-configure.md +10 -6
- data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
- data/skills/rigor-type-oracle/SKILL.md +228 -0
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
- data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
- metadata +26 -3
- data/lib/rigor/cli/probe_environment.rb +0 -85
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The plugin entry point rather than {Loader} directly: {Registry} closes over constants the package only
|
|
4
|
+
# defines in `plugin.rb`'s order, so requiring a leaf of the package first leaves it half-built.
|
|
5
|
+
require_relative "../plugin"
|
|
6
|
+
require_relative "loader"
|
|
7
|
+
|
|
8
|
+
module Rigor
|
|
9
|
+
module Plugin
|
|
10
|
+
# ADR-96 WD1/WD2 — the gem-to-plugin index behind the plugin-gap advisory: for every plugin the engine
|
|
11
|
+
# bundles, the gems its manifest declares in `target_gems:`.
|
|
12
|
+
#
|
|
13
|
+
# The index is read off the manifests, never listed here. ADR-96's finding was that the same knowledge
|
|
14
|
+
# sat in four hand-maintained copies (`rigor doctor`, `rigor skill describe`, the project-init prose
|
|
15
|
+
# table, `plugins/README.md`) and none of them was the plugin; a fifth copy in this file would reproduce
|
|
16
|
+
# exactly the drift the field exists to end.
|
|
17
|
+
#
|
|
18
|
+
# Building it requires each bundled plugin's entry file. That loads plugin *classes* — it never runs
|
|
19
|
+
# plugin analysis, which is ADR-96 Criterion 2's line: gem presence is evidence for advice, not for
|
|
20
|
+
# execution. The gem-to-id registrations the requires produce are recorded through
|
|
21
|
+
# {Rigor::Plugin.record_gem_registration}, so a {Loader} running afterwards in the same process still
|
|
22
|
+
# resolves a bare-string `plugins:` entry whose `require` now no-ops (ADR-88 WD4b).
|
|
23
|
+
module BundledCatalog
|
|
24
|
+
# One bundled plugin: the gem name a `plugins:` entry spells, and the gems it models.
|
|
25
|
+
Entry = Data.define(:gem_name, :plugin_id, :target_gems)
|
|
26
|
+
|
|
27
|
+
BUNDLED_PLUGINS_ROOT = File.join(Loader::ENGINE_ROOT, "plugins")
|
|
28
|
+
|
|
29
|
+
@entries = nil
|
|
30
|
+
@load_failures = {}
|
|
31
|
+
@mutex = Mutex.new
|
|
32
|
+
|
|
33
|
+
class << self
|
|
34
|
+
# Every bundled plugin that declares at least one target gem, sorted by gem name. Memoised: the
|
|
35
|
+
# requires are process-wide and idempotent, and both callers ask once per command.
|
|
36
|
+
def entries
|
|
37
|
+
@mutex.synchronize { @entries ||= build_entries }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The bundled plugins modelling `gem_name`, as `Gemfile.lock` spells it.
|
|
41
|
+
def for_gem(gem_name)
|
|
42
|
+
name = gem_name.to_s
|
|
43
|
+
entries.select { |entry| entry.target_gems.include?(name) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# The bundled plugin gems whose entry file did not load this process, as `gem name => message`.
|
|
47
|
+
# Empty on a healthy process; a non-empty entry is why a bundled plugin is missing from {entries}.
|
|
48
|
+
def load_failures
|
|
49
|
+
entries
|
|
50
|
+
@mutex.synchronize { @load_failures.dup.freeze }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Drops the memo. For specs that stub the engine root only.
|
|
54
|
+
#
|
|
55
|
+
# {load_failures} deliberately survives: `require` is idempotent, so a rebuild after a reset no
|
|
56
|
+
# longer reaches the file that raised and would report a clean process while the entry is still
|
|
57
|
+
# missing.
|
|
58
|
+
def reset!
|
|
59
|
+
@mutex.synchronize { @entries = nil }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
# Bundledness is decided from the paths this module itself resolved and required, never by asking
|
|
65
|
+
# {FirstParty}: that predicate memoises a `File.file?` answer for the whole process, so one spec
|
|
66
|
+
# stubbing the engine root fixes every later answer in the suite — and the catalogue would then
|
|
67
|
+
# silently drop the plugins the advisory exists to name.
|
|
68
|
+
def build_entries
|
|
69
|
+
bundled = require_bundled_plugins
|
|
70
|
+
loaded_plugin_manifests.filter_map do |manifest|
|
|
71
|
+
gem_name = bundled[manifest.id]
|
|
72
|
+
next if gem_name.nil?
|
|
73
|
+
next if manifest.target_gems.empty?
|
|
74
|
+
|
|
75
|
+
Entry.new(gem_name: gem_name, plugin_id: manifest.id, target_gems: manifest.target_gems)
|
|
76
|
+
end.sort_by(&:gem_name).freeze
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# The manifests of every plugin class the process has loaded, one per id. Read off the classes
|
|
80
|
+
# rather than {Rigor::Plugin.registered}: `require` is idempotent, so a plugin required earlier and
|
|
81
|
+
# then dropped by `Rigor::Plugin.unregister!` (every spec does this) is absent from the registry
|
|
82
|
+
# while still loaded, and an index built from the registry at that moment silently lacks it — the
|
|
83
|
+
# advisory then reads the project's own enabled plugin as a gap and fails `rigor doctor`.
|
|
84
|
+
#
|
|
85
|
+
# A plugin class whose source file sits under the bundled `plugins/` tree wins its id outright. The
|
|
86
|
+
# suite defines throwaway `Class.new(Rigor::Plugin::Base)` plugins that reuse a bundled id (a
|
|
87
|
+
# `stub_const`-ed fake `activerecord`, for one), and those carry no `target_gems:`; picking one of
|
|
88
|
+
# them for the id erased the real plugin from the index in whatever `ObjectSpace` order that
|
|
89
|
+
# process happened to have. An anonymous class is still consulted, but only for an id no bundled
|
|
90
|
+
# class claims.
|
|
91
|
+
def loaded_plugin_manifests
|
|
92
|
+
bundled = {}
|
|
93
|
+
other = {}
|
|
94
|
+
ObjectSpace.each_object(Class) do |klass|
|
|
95
|
+
next unless klass < Plugin::Base
|
|
96
|
+
|
|
97
|
+
manifest = manifest_of(klass)
|
|
98
|
+
next if manifest.nil?
|
|
99
|
+
|
|
100
|
+
(bundled_source?(klass) ? bundled : other)[manifest.id] ||= manifest
|
|
101
|
+
end
|
|
102
|
+
other.merge(bundled).values
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def manifest_of(klass)
|
|
106
|
+
klass.manifest
|
|
107
|
+
rescue ArgumentError
|
|
108
|
+
nil
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Whether `klass` was defined by a file in the engine's own `plugins/` tree. An anonymous class
|
|
112
|
+
# answers false, and so does one whose constant a spec has since removed — which is exactly the
|
|
113
|
+
# shadowing case, since `stub_const` restores the name to nil at the end of the example.
|
|
114
|
+
def bundled_source?(klass)
|
|
115
|
+
name = klass.name
|
|
116
|
+
return false if name.nil?
|
|
117
|
+
|
|
118
|
+
location = Object.const_source_location(name)
|
|
119
|
+
return false if location.nil? || location.first.nil?
|
|
120
|
+
|
|
121
|
+
location.first.start_with?("#{BUNDLED_PLUGINS_ROOT}#{File::SEPARATOR}")
|
|
122
|
+
rescue NameError
|
|
123
|
+
false
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @return the gems whose entry file this call resolved, as `plugin id => gem name`.
|
|
127
|
+
def require_bundled_plugins
|
|
128
|
+
return {} unless File.directory?(BUNDLED_PLUGINS_ROOT)
|
|
129
|
+
|
|
130
|
+
add_bundled_lib_dirs
|
|
131
|
+
Dir.children(BUNDLED_PLUGINS_ROOT).sort.each_with_object({}) do |gem_name, resolved|
|
|
132
|
+
path = Loader.bundled_plugin_path(gem_name)
|
|
133
|
+
next if path.nil?
|
|
134
|
+
|
|
135
|
+
resolved[gem_name.delete_prefix(FirstParty::GEM_PREFIX)] = gem_name
|
|
136
|
+
require_and_record(gem_name, path)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# A bundled plugin's entry file requires its own tree by bare feature name (`require
|
|
141
|
+
# "rigor/plugin/activestorage"`), which resolves only with that plugin's `lib` on the load path — the
|
|
142
|
+
# loader gets it from the bundle, and this catalogue has to arrange it itself. Prepending the engine's
|
|
143
|
+
# own copies matches ADR-93 WD5's anti-skew rule, where the bundled copy always wins.
|
|
144
|
+
def add_bundled_lib_dirs
|
|
145
|
+
Dir.children(BUNDLED_PLUGINS_ROOT).sort.each do |gem_name|
|
|
146
|
+
dir = File.join(BUNDLED_PLUGINS_ROOT, gem_name, "lib")
|
|
147
|
+
$LOAD_PATH.unshift(dir) if File.directory?(dir) && !$LOAD_PATH.include?(dir)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# A plugin gem that fails to load is skipped rather than fatal: the catalogue exists to *advise*, and
|
|
152
|
+
# a broken bundled copy is already reported by `rigor doctor`'s plugin checks for the plugins the
|
|
153
|
+
# project actually enabled. The failure is recorded in {load_failures} so a missing entry stays
|
|
154
|
+
# diagnosable instead of silently narrowing the index.
|
|
155
|
+
def require_and_record(gem_name, path)
|
|
156
|
+
before = Plugin.registered.keys
|
|
157
|
+
require path
|
|
158
|
+
newly_registered = Plugin.registered.keys - before
|
|
159
|
+
Plugin.record_gem_registration(gem_name, newly_registered) unless newly_registered.empty?
|
|
160
|
+
rescue ::LoadError, StandardError => e
|
|
161
|
+
@load_failures[gem_name] = "#{e.class}: #{e.message}"
|
|
162
|
+
nil
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -77,15 +77,15 @@ module Rigor
|
|
|
77
77
|
# callable whose body is supplied by the application.
|
|
78
78
|
TAINT_CAUSES = %w[template-not-analysed opaque-callable].freeze
|
|
79
79
|
|
|
80
|
-
# @param receiver
|
|
81
|
-
# @param method
|
|
82
|
-
# @param singleton
|
|
80
|
+
# @param receiver — a class name or a receiver path (see above)
|
|
81
|
+
# @param method — the selector this row colours
|
|
82
|
+
# @param singleton — whether the row is `Receiver.method` rather than `Receiver#method`.
|
|
83
83
|
# Meaningless — and ignored — for a receiver path, whose head already fixes the receiver object.
|
|
84
|
-
# @param labels
|
|
85
|
-
# @param narrow
|
|
84
|
+
# @param labels — the effect labels the call contributes
|
|
85
|
+
# @param narrow — a {Rigor::Effects::Narrowing} handler name, when the call's own
|
|
86
86
|
# argument literals settle a question the row cannot (`connection.execute("SELECT …")`)
|
|
87
|
-
# @param discharge
|
|
88
|
-
# @param why
|
|
87
|
+
# @param discharge — see above; honoured only for a first-party bundled plugin
|
|
88
|
+
# @param why — the audit justification, required exactly as `data/effects/core.yml` requires
|
|
89
89
|
# one of every row: a label with no stated reason is a claim nobody can review.
|
|
90
90
|
def initialize(receiver:, method:, labels:, why:, singleton: false, narrow: nil, discharge: false, # rubocop:disable Metrics/ParameterLists
|
|
91
91
|
within: nil, on_result: false, taint: nil)
|
|
@@ -21,10 +21,10 @@ module Rigor
|
|
|
21
21
|
class EffectEntryPoints
|
|
22
22
|
attr_reader :name, :globs, :why
|
|
23
23
|
|
|
24
|
-
# @param name
|
|
24
|
+
# @param name — the token `reach:` adopts. Must satisfy
|
|
25
25
|
# {Rigor::Effects::EntryPoints::NAME_PATTERN}, checked at registration.
|
|
26
|
-
# @param globs
|
|
27
|
-
# @param why
|
|
26
|
+
# @param globs — project-relative path globs
|
|
27
|
+
# @param why — what the preset stands for, for the plugin's README and `rigor effects`' help
|
|
28
28
|
def initialize(name:, globs:, why: "")
|
|
29
29
|
@name = name.to_s.dup.freeze
|
|
30
30
|
@globs = Array(globs).map { |glob| glob.to_s.dup.freeze }.uniq.sort.freeze
|
|
@@ -42,9 +42,9 @@ module Rigor
|
|
|
42
42
|
# Writes a `(plugin_id, name) -> value` triple. Idempotent if the same value is published twice (`==`);
|
|
43
43
|
# raises {Conflict} if the values differ.
|
|
44
44
|
#
|
|
45
|
-
# @param plugin_id
|
|
46
|
-
# @param name
|
|
47
|
-
# @param value
|
|
45
|
+
# @param plugin_id — producing plugin's manifest id.
|
|
46
|
+
# @param name — fact name (canonicalised to Symbol for lookup).
|
|
47
|
+
# @param value — frozen-shape value object the producer chose to publish. The value is stored
|
|
48
48
|
# as-is.
|
|
49
49
|
def publish(plugin_id:, name:, value:)
|
|
50
50
|
plugin_id = plugin_id.to_s
|
|
@@ -60,19 +60,19 @@ module Rigor
|
|
|
60
60
|
nil
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
# @return
|
|
63
|
+
# @return the published value, or `nil` when no fact is registered. Reads do NOT establish
|
|
64
64
|
# a dependency — `manifest(consumes:)` (slice 4) is the dependency declaration mechanism.
|
|
65
65
|
def read(plugin_id:, name:)
|
|
66
66
|
fact = @mutex.synchronize { @facts[[plugin_id.to_s, name.to_sym]] }
|
|
67
67
|
fact&.value
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
# @return
|
|
70
|
+
# @return whether a fact is registered.
|
|
71
71
|
def published?(plugin_id:, name:)
|
|
72
72
|
@mutex.synchronize { @facts.key?([plugin_id.to_s, name.to_sym]) }
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
-
# @yield
|
|
75
|
+
# @yield every published fact in publication order.
|
|
76
76
|
def each_fact(&)
|
|
77
77
|
snapshot = @mutex.synchronize { @facts.values }
|
|
78
78
|
snapshot.each(&)
|
|
@@ -27,6 +27,12 @@ module Rigor
|
|
|
27
27
|
# A plugin that gates its read or its glob on one of these shapes its result on the answer, so the
|
|
28
28
|
# answer is a dependency; probing through `File` directly leaves that dependency unrecorded and the
|
|
29
29
|
# warm run serves the pre-appearance result.
|
|
30
|
+
# - `#list_directory(path)` — the directory-LISTING fingerprint (ADR-45 WD1c, #629): returns the
|
|
31
|
+
# directory's entry paths and records ONE {Cache::Descriptor::GlobEntry} for `path/*`, so any file
|
|
32
|
+
# added to or removed from the directory (and any content edit under it) invalidates. It is the row
|
|
33
|
+
# for a plugin that asks "which of these N candidate files exists here?" — rigor-actionpack tries
|
|
34
|
+
# nine template extensions per `render` call — where N per-path probe rows would put thousands of
|
|
35
|
+
# validations on every warm run and one listing row puts one per directory.
|
|
30
36
|
# - `#open_url(url)` — fetches the URL when the policy permits it (`network_policy: :allowlist` plus an
|
|
31
37
|
# `allowed_url_hosts` match) and raises {AccessDeniedError} otherwise. v0.1.2 ships the allowlist
|
|
32
38
|
# surface; the default project policy still has `network_policy: :disabled` so plugins that want network
|
|
@@ -48,8 +54,32 @@ module Rigor
|
|
|
48
54
|
@plugin_id = plugin_id.to_s.dup.freeze
|
|
49
55
|
@file_entries = {}
|
|
50
56
|
@config_entries = {}
|
|
57
|
+
@glob_entries = {}
|
|
51
58
|
@http_client = http_client
|
|
52
59
|
@mutex = Mutex.new
|
|
60
|
+
@refusal_count = 0
|
|
61
|
+
@first_refused_path = nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Issue #959 — `TrustPolicy#allow_read?` refuses a path purely by `File.expand_path` comparison
|
|
65
|
+
# (ADR-2's documented bound; no `File.realpath`), so a project rooted under a symlink (macOS'
|
|
66
|
+
# `/tmp` → `/private/tmp` alias is the common case) can have every plugin read refused with no
|
|
67
|
+
# visible cause: the boundary answered "not readable" and the plugin swallowed or surfaced only its
|
|
68
|
+
# own generic error. `#refusal_summary` is the accumulated record `Analysis::Runner::DiagnosticAggregator`
|
|
69
|
+
# reads once per run to surface ONE `:info` row per plugin that hit this, naming the count, the first
|
|
70
|
+
# refused path, and the read root it fell outside (so the message can suggest the fix: `cd "$(pwd -P)"`
|
|
71
|
+
# on a symlinked project root, or a `trust:` / `plugins_io.allowed_paths:` entry).
|
|
72
|
+
#
|
|
73
|
+
# @return `nil` when nothing was refused, else `{count:, first_path:, nearest_root:}` — `nearest_root`
|
|
74
|
+
# is the policy's first configured read root (the project root in the common case), named as the
|
|
75
|
+
# root a reader should compare the refused path against; it is a hint, not a claim that this
|
|
76
|
+
# specific root is the nearest by path distance.
|
|
77
|
+
def refusal_summary
|
|
78
|
+
@mutex.synchronize do
|
|
79
|
+
return nil if @refusal_count.zero?
|
|
80
|
+
|
|
81
|
+
{ count: @refusal_count, first_path: @first_refused_path, nearest_root: @policy.allowed_read_roots.first }
|
|
82
|
+
end
|
|
53
83
|
end
|
|
54
84
|
|
|
55
85
|
# Reads the file at `path` after validating it against the policy. Raises {AccessDeniedError} when the
|
|
@@ -68,6 +98,7 @@ module Rigor
|
|
|
68
98
|
def read_file(path)
|
|
69
99
|
absolute = File.expand_path(path.to_s)
|
|
70
100
|
unless @policy.allow_read?(absolute)
|
|
101
|
+
record_refusal(absolute)
|
|
71
102
|
raise AccessDeniedError.new(
|
|
72
103
|
"plugin #{@plugin_id.inspect} cannot read #{absolute.inspect}: " \
|
|
73
104
|
"path is outside the trusted-read scope",
|
|
@@ -93,8 +124,8 @@ module Rigor
|
|
|
93
124
|
# `return nil unless File.file?(config)` gate: the miss shaped the result and left no edge for the
|
|
94
125
|
# file's later appearance.
|
|
95
126
|
#
|
|
96
|
-
# @param path
|
|
97
|
-
# @return
|
|
127
|
+
# @param path — project path; relative paths expand against the working directory
|
|
128
|
+
# @return `File.file?`'s answer, unchanged
|
|
98
129
|
def file?(path)
|
|
99
130
|
probe(path) { |absolute| File.file?(absolute) }
|
|
100
131
|
end
|
|
@@ -103,12 +134,42 @@ module Rigor
|
|
|
103
134
|
# existence row. The discovery shape (`next [] unless directory?(root)` before a `Dir.glob`) depends
|
|
104
135
|
# on the root existing exactly as a config read depends on the config file existing.
|
|
105
136
|
#
|
|
106
|
-
# @param path
|
|
107
|
-
# @return
|
|
137
|
+
# @param path — project path; relative paths expand against the working directory
|
|
138
|
+
# @return `File.directory?`'s answer, unchanged
|
|
108
139
|
def directory?(path)
|
|
109
140
|
probe(path) { |absolute| File.directory?(absolute) }
|
|
110
141
|
end
|
|
111
142
|
|
|
143
|
+
# ADR-45 WD1c (#629) — the directory-listing fingerprint. Returns the paths `path` currently holds
|
|
144
|
+
# (`[]` when it is not a directory), and records a single {Cache::Descriptor::GlobEntry} over
|
|
145
|
+
# `path/*` so the recorded dependency is the directory's whole listing rather than one row per
|
|
146
|
+
# name the caller went on to test. An addition, a removal, or an edit under the directory all move
|
|
147
|
+
# the glob signature; a caller that answered "no such file" from the listing therefore invalidates
|
|
148
|
+
# the moment the file appears.
|
|
149
|
+
#
|
|
150
|
+
# Why not N {#file?} probes: a caller like rigor-actionpack's template lookup tries an extension
|
|
151
|
+
# family per candidate, so per-path rows scale with (renders × extensions × view roots) — thousands
|
|
152
|
+
# of stat validations per warm run on a Rails-scale project — while the listing row scales with the
|
|
153
|
+
# number of directories actually consulted. The listing row is also the STRICTLY stronger dependency
|
|
154
|
+
# of the two: it covers names the caller never thought to probe.
|
|
155
|
+
#
|
|
156
|
+
# Recording, not the answer, is what the policy gates, exactly as in {#probe}: an out-of-scope
|
|
157
|
+
# directory is listed truthfully and contributes no row.
|
|
158
|
+
#
|
|
159
|
+
# @param path — project directory; relative paths expand against the working directory
|
|
160
|
+
# @return the absolute paths directly under `path` in `Dir.glob` order; empty when not a directory
|
|
161
|
+
def list_directory(path)
|
|
162
|
+
absolute = File.expand_path(path.to_s)
|
|
163
|
+
entries = Dir.glob(File.join(absolute, "*"))
|
|
164
|
+
unless @policy.allow_read?(absolute)
|
|
165
|
+
record_refusal(absolute)
|
|
166
|
+
return entries
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
record_glob_entry(absolute, "*")
|
|
170
|
+
entries
|
|
171
|
+
end
|
|
172
|
+
|
|
112
173
|
# Fetches the URL when the policy permits it. Returns the response body. Raises {AccessDeniedError} when
|
|
113
174
|
# the policy is `:disabled`, the URL scheme is not `https`, the host is not on the allowlist, the
|
|
114
175
|
# response is non-2xx, the body exceeds {URL_MAX_BYTES}, or the request times out
|
|
@@ -131,12 +192,14 @@ module Rigor
|
|
|
131
192
|
body
|
|
132
193
|
end
|
|
133
194
|
|
|
134
|
-
# @return
|
|
195
|
+
# @return frozen snapshot of every file / URL the boundary has read so far.
|
|
135
196
|
# Calling this multiple times yields equal descriptors; subsequent reads expand the underlying record
|
|
136
197
|
# tables.
|
|
137
198
|
def cache_descriptor
|
|
138
|
-
files, configs = @mutex.synchronize
|
|
139
|
-
|
|
199
|
+
files, configs, globs = @mutex.synchronize do
|
|
200
|
+
[@file_entries.values.dup, @config_entries.values.dup, @glob_entries.values.dup]
|
|
201
|
+
end
|
|
202
|
+
Cache::Descriptor.new(files: files, configs: configs, globs: globs)
|
|
140
203
|
end
|
|
141
204
|
|
|
142
205
|
private
|
|
@@ -161,7 +224,10 @@ module Rigor
|
|
|
161
224
|
def probe(path)
|
|
162
225
|
absolute = File.expand_path(path.to_s)
|
|
163
226
|
answer = yield(absolute)
|
|
164
|
-
|
|
227
|
+
unless @policy.allow_read?(absolute)
|
|
228
|
+
record_refusal(absolute)
|
|
229
|
+
return answer
|
|
230
|
+
end
|
|
165
231
|
|
|
166
232
|
if answer
|
|
167
233
|
record_presence_entry(absolute)
|
|
@@ -209,6 +275,25 @@ module Rigor
|
|
|
209
275
|
@mutex.synchronize { @file_entries[path] ||= entry }
|
|
210
276
|
end
|
|
211
277
|
|
|
278
|
+
# ADR-45 WD1c (#629) — the listing row for {#list_directory}, deduplicated per (root, pattern) slot.
|
|
279
|
+
# Recomputed on each call rather than `||=`d, so a directory listed again after a mid-run mutation
|
|
280
|
+
# carries the signature of the state the LAST reader saw.
|
|
281
|
+
def record_glob_entry(root, pattern)
|
|
282
|
+
entry = Cache::Descriptor::GlobEntry.compute(root: root, pattern: pattern)
|
|
283
|
+
@mutex.synchronize { @glob_entries[entry.slot_key] = entry }
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Issue #959 — records that `path` fell outside every {TrustPolicy} read root, for {#refusal_summary}.
|
|
287
|
+
# Only the count and the FIRST path are kept (never the full list): the diagnostic reports one row per
|
|
288
|
+
# plugin regardless of how many paths it tried, so a plugin that walks a whole out-of-scope subtree
|
|
289
|
+
# costs one comparison per call here, not an unbounded accumulation.
|
|
290
|
+
def record_refusal(path)
|
|
291
|
+
@mutex.synchronize do
|
|
292
|
+
@refusal_count += 1
|
|
293
|
+
@first_refused_path ||= path
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
212
297
|
def record_url_entry(url, body)
|
|
213
298
|
digest = Digest::SHA256.hexdigest(body)
|
|
214
299
|
key = "url:#{url}"
|
|
@@ -7,8 +7,8 @@ module Rigor
|
|
|
7
7
|
# ADR-39 slice 5 — the selectable isolation strategy for target-library invocation. A plugin invokes a
|
|
8
8
|
# pure method on a trusted target library (e.g. `ActiveSupport::Inflector.pluralize("post")`) through
|
|
9
9
|
# {.call}; how much the invocation is isolated from Rigor's own process is a **configurable strategy**
|
|
10
|
-
# (`
|
|
11
|
-
#
|
|
10
|
+
# (`.rigor.yml`'s `plugins_isolation:` key, or the `RIGOR_PLUGIN_ISOLATION` env, which wins over it —
|
|
11
|
+
# see {.strategy_name}). Three backends behind one interface:
|
|
12
12
|
#
|
|
13
13
|
# - `none` — load into the main space and call directly. Lowest cost; no isolation. Used as the fallback
|
|
14
14
|
# where fork is unavailable; fine because the invoked library is trusted + pure.
|
|
@@ -98,11 +98,34 @@ module Rigor
|
|
|
98
98
|
# {#backend}).
|
|
99
99
|
DEFAULT = "process"
|
|
100
100
|
|
|
101
|
-
# The
|
|
102
|
-
#
|
|
101
|
+
# The strategy the loaded {Rigor::Configuration} named (`plugins_isolation:`), or nil when the
|
|
102
|
+
# project named none. Handed over as a module ivar for the same reason {target_bundle_root} is: the
|
|
103
|
+
# invocation happens deep inside a plugin, several layers below anything holding the configuration.
|
|
104
|
+
def configured_strategy
|
|
105
|
+
@configured_strategy
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Frozen, like {target_bundle_root=}, so a Ractor-pool worker may read it (an unshareable value in a
|
|
109
|
+
# module ivar is a `Ractor::IsolationError` to read from a non-main Ractor, not only to write).
|
|
110
|
+
def configured_strategy=(name)
|
|
111
|
+
@configured_strategy = name.nil? ? nil : name.to_s.dup.freeze
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# The strategy name in effect, defaulting to {DEFAULT} for any unset / unrecognised value.
|
|
115
|
+
#
|
|
116
|
+
# `RIGOR_PLUGIN_ISOLATION` WINS over the configuration (#911). The variable is the operator's
|
|
117
|
+
# one-invocation override — the thing you reach for when the fork worker misbehaves on this
|
|
118
|
+
# machine, or when a CI image cannot fork at all — and an override a committed file can veto is not
|
|
119
|
+
# an override. The config key is the project-wide default the override departs from. It is also the
|
|
120
|
+
# only ordering under which `ruby_box` works at all: `exe/rigor` re-execs on the variable alone.
|
|
103
121
|
def strategy_name
|
|
104
|
-
|
|
105
|
-
STRATEGIES.include?(
|
|
122
|
+
from_env = ENV["RIGOR_PLUGIN_ISOLATION"].to_s
|
|
123
|
+
return from_env if STRATEGIES.include?(from_env)
|
|
124
|
+
|
|
125
|
+
from_config = configured_strategy.to_s
|
|
126
|
+
return from_config if STRATEGIES.include?(from_config)
|
|
127
|
+
|
|
128
|
+
DEFAULT
|
|
106
129
|
end
|
|
107
130
|
|
|
108
131
|
# Invokes `receiver.method(*args)` on a target library, requiring `feature` first, under the configured
|
data/lib/rigor/plugin/loader.rb
CHANGED
|
@@ -43,12 +43,11 @@ module Rigor
|
|
|
43
43
|
# the engine's own bundled plugin copies.
|
|
44
44
|
ENGINE_ROOT = File.expand_path("../../..", __dir__)
|
|
45
45
|
|
|
46
|
-
# @param
|
|
47
|
-
# @param requirer [#call] takes a gem name OR an absolute file path (#194 slice 2 — a bundled plugin is
|
|
46
|
+
# @param requirer — takes a gem name OR an absolute file path (#194 slice 2 — a bundled plugin is
|
|
48
47
|
# required by its {.bundled_plugin_path}) and returns truthy on successful require. Defaulted to
|
|
49
48
|
# `Kernel.require` via a lambda, which accepts both forms; the spec injects a fake to avoid touching
|
|
50
49
|
# the real load path.
|
|
51
|
-
# @param feature_resolver
|
|
50
|
+
# @param feature_resolver — takes a gem name and returns the absolute path `require` resolved it
|
|
52
51
|
# to (or nil). Defaulted to {FEATURE_RESOLVER}; the spec injects a fake so it never has to mutate the
|
|
53
52
|
# real `$LOADED_FEATURES` global.
|
|
54
53
|
def initialize(services:, requirer: ->(name) { require name }, feature_resolver: FEATURE_RESOLVER)
|
|
@@ -90,8 +89,7 @@ module Rigor
|
|
|
90
89
|
File.directory?(path) ? path : nil
|
|
91
90
|
end
|
|
92
91
|
|
|
93
|
-
# @param entries
|
|
94
|
-
# @return [Registry]
|
|
92
|
+
# @param entries — the raw `plugins:` list from the configuration.
|
|
95
93
|
def load(entries)
|
|
96
94
|
plugins = []
|
|
97
95
|
load_errors = []
|
|
@@ -266,16 +264,37 @@ module Rigor
|
|
|
266
264
|
when 1
|
|
267
265
|
Plugin.registered_for(newly_registered.first)
|
|
268
266
|
else
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
"(e.g. `rigor-#{newly_registered.min}`), or select one with an explicit `id:` field.",
|
|
274
|
-
plugin_ref: entry[:gem]
|
|
275
|
-
)
|
|
267
|
+
own = own_id_among(entry[:gem], newly_registered)
|
|
268
|
+
raise meta_gem_error(entry, newly_registered) unless own
|
|
269
|
+
|
|
270
|
+
Plugin.registered_for(own)
|
|
276
271
|
end
|
|
277
272
|
end
|
|
278
273
|
|
|
274
|
+
# A gem that registers more than one plugin is a meta-gem only when NONE of the ids is its own. The
|
|
275
|
+
# FFI family (`rigor-sassc`, `rigor-ethon`, `rigor-ffi-rzmq`, `rigor-rbnacl`) each `require "rigor-ffi"`
|
|
276
|
+
# for the shared binding analyzer, so listing one of them ALONE registered two plugins — its own and
|
|
277
|
+
# `ffi` — and the meta-gem branch rejected the entry, telling the user to list `rigor-ffi` instead,
|
|
278
|
+
# which activates the wrong plugin. The failure was load-order dependent (listing `rigor-ffi` first
|
|
279
|
+
# made the nested require a no-op and the entry loaded), so it never showed up in the suite, where
|
|
280
|
+
# some earlier example has always loaded `rigor-ffi`.
|
|
281
|
+
#
|
|
282
|
+
# `rigor-rails` stays a meta-gem under this rule: it registers eight ids and none of them is `rails`.
|
|
283
|
+
def own_id_among(gem_name, ids)
|
|
284
|
+
own = gem_name.to_s.delete_prefix(Plugin::FirstParty::GEM_PREFIX)
|
|
285
|
+
ids.find { |id| id == own }
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def meta_gem_error(entry, newly_registered)
|
|
289
|
+
LoadError.new(
|
|
290
|
+
"plugin gem #{entry[:gem].inspect} bundles #{newly_registered.size} plugins " \
|
|
291
|
+
"(#{newly_registered.sort.inspect}) and cannot be activated as a single `plugins:` entry — " \
|
|
292
|
+
"it is a convenience meta-gem. List the individual plugin gems you want in `plugins:` " \
|
|
293
|
+
"(e.g. `rigor-#{newly_registered.min}`), or select one with an explicit `id:` field.",
|
|
294
|
+
plugin_ref: entry[:gem]
|
|
295
|
+
)
|
|
296
|
+
end
|
|
297
|
+
|
|
279
298
|
def validate_config!(manifest, config)
|
|
280
299
|
errors = manifest.validate_config(config)
|
|
281
300
|
return if errors.empty?
|
|
@@ -152,7 +152,7 @@ module Rigor
|
|
|
152
152
|
class ReturnsFromArg
|
|
153
153
|
attr_reader :position, :plugin_id, :fact
|
|
154
154
|
|
|
155
|
-
# @return
|
|
155
|
+
# @return coerced value class for a Hash / nil / ReturnsFromArg input. Raises
|
|
156
156
|
# on any other shape so manifest authoring failures surface at construction time.
|
|
157
157
|
def self.coerce(value)
|
|
158
158
|
return nil if value.nil?
|
|
@@ -105,7 +105,7 @@ module Rigor
|
|
|
105
105
|
to_h.hash
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
-
# @return
|
|
108
|
+
# @return fully-qualified module name for the given trait symbol, or nil when the
|
|
109
109
|
# registry doesn't know the symbol (caller emits a tier_b.unknown-trait provenance marker and falls
|
|
110
110
|
# through).
|
|
111
111
|
def module_for(symbol)
|
|
@@ -42,6 +42,12 @@ module Rigor
|
|
|
42
42
|
# {Rigor::Effects::Label} accepts as a root.
|
|
43
43
|
VALID_EFFECT_ROOT = /\A[a-z][a-z0-9_]*\z/
|
|
44
44
|
|
|
45
|
+
# ADR-96 WD1 — the gems a plugin models, spelled exactly as `Gemfile.lock` names them. Not derivable
|
|
46
|
+
# from the plugin id (`rigor-factorybot` models `factory_bot`, `rigor-rspec` models `rspec-core`), which
|
|
47
|
+
# is what makes it a declared field; an empty list is the meaningful answer for a plugin that models no
|
|
48
|
+
# gem at all.
|
|
49
|
+
attr_reader :target_gems
|
|
50
|
+
|
|
45
51
|
attr_reader :id, :version, :description, :config_schema, :config_defaults, :produces, :consumes,
|
|
46
52
|
:owns_receivers, :open_receivers, :type_node_resolvers, :block_as_methods,
|
|
47
53
|
:heredoc_templates, :nested_class_templates, :trait_registries,
|
|
@@ -52,7 +58,7 @@ module Rigor
|
|
|
52
58
|
|
|
53
59
|
def initialize( # rubocop:disable Metrics/ParameterLists
|
|
54
60
|
id:, version:,
|
|
55
|
-
description: nil, config_schema: {},
|
|
61
|
+
description: nil, config_schema: {}, target_gems: [],
|
|
56
62
|
produces: [], consumes: [], owns_receivers: [], open_receivers: [], type_node_resolvers: [],
|
|
57
63
|
block_as_methods: [], heredoc_templates: [], nested_class_templates: [],
|
|
58
64
|
trait_registries: [],
|
|
@@ -65,6 +71,7 @@ module Rigor
|
|
|
65
71
|
validate_version!(version)
|
|
66
72
|
validate_config_schema!(config_schema)
|
|
67
73
|
validate_produces!(produces)
|
|
74
|
+
validate_target_gems!(target_gems)
|
|
68
75
|
validate_owns_receivers!(owns_receivers)
|
|
69
76
|
validate_open_receivers!(open_receivers)
|
|
70
77
|
validate_type_node_resolvers!(type_node_resolvers)
|
|
@@ -85,6 +92,7 @@ module Rigor
|
|
|
85
92
|
open_receivers, type_node_resolvers, block_as_methods, heredoc_templates, trait_registries,
|
|
86
93
|
hkt_registrations, hkt_definitions, signature_paths, protocol_contracts,
|
|
87
94
|
source_rbs_synthesizer)
|
|
95
|
+
assign_target_gems(target_gems)
|
|
88
96
|
assign_nested_class_templates(nested_class_templates)
|
|
89
97
|
assign_additional_initializers(additional_initializers)
|
|
90
98
|
assign_effect_fields(effect_root, effect_labels, effect_attributions, effect_edges,
|
|
@@ -119,6 +127,12 @@ module Rigor
|
|
|
119
127
|
@source_rbs_synthesizer = source_rbs_synthesizer
|
|
120
128
|
end
|
|
121
129
|
|
|
130
|
+
# ADR-96 WD1 — assigned outside assign_fields, which already carries the maximum positional arity.
|
|
131
|
+
def assign_target_gems(target_gems)
|
|
132
|
+
@target_gems = target_gems.map { |g| g.to_s.dup.freeze }.uniq.freeze
|
|
133
|
+
end
|
|
134
|
+
private :assign_target_gems
|
|
135
|
+
|
|
122
136
|
# Assigned outside assign_fields (which already carries the maximum positional arity) — set in
|
|
123
137
|
# `initialize` before the final freeze. ADR-36 nested-class emission tier.
|
|
124
138
|
def assign_nested_class_templates(nested_class_templates)
|
|
@@ -189,6 +203,7 @@ module Rigor
|
|
|
189
203
|
"description" => description,
|
|
190
204
|
"config_schema" => config_schema.to_h { |k, v| [k, v.to_s] },
|
|
191
205
|
"config_defaults" => config_defaults,
|
|
206
|
+
"target_gems" => target_gems,
|
|
192
207
|
"produces" => produces.map(&:to_s),
|
|
193
208
|
"consumes" => consumes.map { |c| consumption_hash(c) },
|
|
194
209
|
"owns_receivers" => owns_receivers,
|
|
@@ -344,6 +359,13 @@ module Rigor
|
|
|
344
359
|
raise ArgumentError, "plugin manifest #{field} must be an Array of #{label}, got #{value.inspect}"
|
|
345
360
|
end
|
|
346
361
|
|
|
362
|
+
# ADR-96 WD1 — every entry is a gem name as `Gemfile.lock` spells it. The uniform Array-of-String check
|
|
363
|
+
# is all the manifest can do: whether the named gem exists is a fact about RubyGems, not about the
|
|
364
|
+
# manifest, and a typo surfaces as an advisory that never fires rather than as a load failure.
|
|
365
|
+
def validate_target_gems!(target_gems)
|
|
366
|
+
validate_array_of!("target_gems", target_gems, "non-empty String") { |g| g.is_a?(String) && !g.empty? }
|
|
367
|
+
end
|
|
368
|
+
|
|
347
369
|
def validate_produces!(produces)
|
|
348
370
|
validate_array_of!("produces", produces, "Symbol/String") { |p| p.is_a?(Symbol) || p.is_a?(String) }
|
|
349
371
|
end
|