rigortype 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/docs/manual/04-diagnostics.md +7 -4
- data/docs/manual/06-baseline.md +5 -2
- data/docs/manual/15-type-protection-coverage.md +6 -4
- data/lib/rigor/analysis/baseline.rb +56 -96
- data/lib/rigor/analysis/buffer_binding.rb +13 -20
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
- data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
- data/lib/rigor/analysis/check_rules.rb +174 -274
- data/lib/rigor/analysis/dependency_recorder.rb +26 -38
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
- data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
- data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
- data/lib/rigor/analysis/diagnostic.rb +48 -75
- data/lib/rigor/analysis/erb_template_detector.rb +13 -18
- data/lib/rigor/analysis/fact_store.rb +12 -22
- data/lib/rigor/analysis/incremental.rb +47 -63
- data/lib/rigor/analysis/incremental_session.rb +74 -100
- data/lib/rigor/analysis/project_scan.rb +12 -23
- data/lib/rigor/analysis/result.rb +3 -5
- data/lib/rigor/analysis/rule_catalog.rb +67 -95
- data/lib/rigor/analysis/run_stats.rb +38 -61
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
- data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
- data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
- data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
- data/lib/rigor/analysis/runner.rb +220 -333
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
- data/lib/rigor/analysis/worker_session.rb +64 -102
- data/lib/rigor/ast/type_node.rb +6 -8
- data/lib/rigor/ast.rb +6 -10
- data/lib/rigor/bleeding_edge.rb +27 -35
- data/lib/rigor/builtins/hkt_builtins.rb +7 -14
- data/lib/rigor/builtins/imported_refinements.rb +106 -149
- data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
- data/lib/rigor/builtins/regex_refinement.rb +32 -43
- data/lib/rigor/builtins/static_return_refinements.rb +57 -81
- data/lib/rigor/cache/descriptor.rb +58 -94
- data/lib/rigor/cache/incremental_snapshot.rb +32 -48
- data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
- data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
- data/lib/rigor/cache/rbs_constant_table.rb +8 -13
- data/lib/rigor/cache/rbs_descriptor.rb +8 -13
- data/lib/rigor/cache/rbs_environment.rb +11 -19
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
- data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
- data/lib/rigor/cache/store.rb +96 -151
- data/lib/rigor/cli/annotate_command.rb +51 -88
- data/lib/rigor/cli/baseline_command.rb +10 -17
- data/lib/rigor/cli/check_command.rb +121 -199
- data/lib/rigor/cli/check_runner_factory.rb +8 -14
- data/lib/rigor/cli/ci_detector.rb +9 -14
- data/lib/rigor/cli/command.rb +8 -14
- data/lib/rigor/cli/coverage_command.rb +19 -26
- data/lib/rigor/cli/coverage_mutation.rb +14 -19
- data/lib/rigor/cli/coverage_report.rb +0 -2
- data/lib/rigor/cli/coverage_scan.rb +7 -11
- data/lib/rigor/cli/diagnostic_formats.rb +43 -62
- data/lib/rigor/cli/diff_command.rb +10 -20
- data/lib/rigor/cli/docs_command.rb +26 -40
- data/lib/rigor/cli/doctor_command.rb +8 -9
- data/lib/rigor/cli/explain_command.rb +4 -7
- data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
- data/lib/rigor/cli/fused_protection_report.rb +6 -8
- data/lib/rigor/cli/lsp_command.rb +11 -19
- data/lib/rigor/cli/mcp_command.rb +4 -6
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
- data/lib/rigor/cli/mutation_protection_report.rb +6 -9
- data/lib/rigor/cli/options.rb +11 -19
- data/lib/rigor/cli/plugin_command.rb +18 -30
- data/lib/rigor/cli/plugins_command.rb +29 -51
- data/lib/rigor/cli/plugins_renderer.rb +12 -20
- data/lib/rigor/cli/prism_colorizer.rb +13 -19
- data/lib/rigor/cli/protection_renderer.rb +5 -7
- data/lib/rigor/cli/protection_report.rb +27 -17
- data/lib/rigor/cli/renderable.rb +4 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
- data/lib/rigor/cli/sig_gen_command.rb +14 -26
- data/lib/rigor/cli/skill_command.rb +30 -47
- data/lib/rigor/cli/skill_describe.rb +40 -64
- data/lib/rigor/cli/trace_command.rb +9 -16
- data/lib/rigor/cli/trace_renderer.rb +35 -51
- data/lib/rigor/cli/triage_command.rb +6 -10
- data/lib/rigor/cli/triage_renderer.rb +1 -2
- data/lib/rigor/cli/type_of_command.rb +14 -23
- data/lib/rigor/cli/type_of_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_command.rb +6 -10
- data/lib/rigor/cli/type_scan_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_report.rb +2 -3
- data/lib/rigor/cli/upgrade_command.rb +2 -3
- data/lib/rigor/cli.rb +16 -28
- data/lib/rigor/config_audit.rb +35 -44
- data/lib/rigor/configuration/dependencies.rb +41 -70
- data/lib/rigor/configuration/severity_profile.rb +28 -42
- data/lib/rigor/configuration.rb +150 -241
- data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
- data/lib/rigor/environment/class_registry.rb +19 -26
- data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
- data/lib/rigor/environment/lockfile_resolver.rb +31 -47
- data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
- data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
- data/lib/rigor/environment/rbs_loader.rb +420 -518
- data/lib/rigor/environment/reflection.rb +28 -53
- data/lib/rigor/environment/reporters.rb +13 -24
- data/lib/rigor/environment.rb +176 -291
- data/lib/rigor/flow_contribution/conflict.rb +16 -27
- data/lib/rigor/flow_contribution/element.rb +7 -12
- data/lib/rigor/flow_contribution/fact.rb +33 -57
- data/lib/rigor/flow_contribution/merge_result.rb +6 -9
- data/lib/rigor/flow_contribution/merger.rb +32 -47
- data/lib/rigor/flow_contribution.rb +37 -55
- data/lib/rigor/inference/acceptance.rb +133 -219
- data/lib/rigor/inference/block_parameter_binder.rb +52 -82
- data/lib/rigor/inference/body_fixpoint.rb +22 -30
- data/lib/rigor/inference/budget_trace.rb +43 -63
- data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
- data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
- data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
- data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
- data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
- data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
- data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
- data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
- data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
- data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
- data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
- data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
- data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
- data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
- data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
- data/lib/rigor/inference/coverage_scanner.rb +13 -18
- data/lib/rigor/inference/def_return_typer.rb +8 -14
- data/lib/rigor/inference/dynamic_origin.rb +23 -20
- data/lib/rigor/inference/expression_typer.rb +774 -1152
- data/lib/rigor/inference/fallback.rb +8 -12
- data/lib/rigor/inference/fallback_tracer.rb +4 -10
- data/lib/rigor/inference/flow_tracer.rb +25 -36
- data/lib/rigor/inference/hkt_body.rb +45 -68
- data/lib/rigor/inference/hkt_body_parser.rb +33 -52
- data/lib/rigor/inference/hkt_reducer.rb +38 -59
- data/lib/rigor/inference/hkt_registry.rb +50 -76
- data/lib/rigor/inference/indexed_narrowing.rb +55 -81
- data/lib/rigor/inference/macro_block_self_type.rb +21 -34
- data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
- data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
- data/lib/rigor/inference/method_dispatcher.rb +378 -524
- data/lib/rigor/inference/method_parameter_binder.rb +81 -114
- data/lib/rigor/inference/multi_target_binder.rb +51 -68
- data/lib/rigor/inference/mutation_widening.rb +115 -158
- data/lib/rigor/inference/narrowing.rb +509 -727
- data/lib/rigor/inference/origin_lookup.rb +38 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +100 -100
- data/lib/rigor/inference/precision_scanner.rb +26 -35
- data/lib/rigor/inference/project_patched_methods.rb +20 -32
- data/lib/rigor/inference/project_patched_scanner.rb +23 -37
- data/lib/rigor/inference/protection_scanner.rb +16 -18
- data/lib/rigor/inference/rbs_type_translator.rb +49 -66
- data/lib/rigor/inference/scope_indexer.rb +473 -817
- data/lib/rigor/inference/statement_evaluator.rb +706 -1111
- data/lib/rigor/inference/struct_fold_safety.rb +34 -48
- data/lib/rigor/inference/synthetic_method.rb +9 -16
- data/lib/rigor/inference/synthetic_method_index.rb +20 -35
- data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
- data/lib/rigor/language_server/buffer_resolution.rb +6 -9
- data/lib/rigor/language_server/buffer_table.rb +11 -18
- data/lib/rigor/language_server/completion_provider.rb +69 -116
- data/lib/rigor/language_server/debouncer.rb +16 -25
- data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
- data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
- data/lib/rigor/language_server/folding_range_provider.rb +11 -19
- data/lib/rigor/language_server/hover_provider.rb +8 -11
- data/lib/rigor/language_server/hover_renderer.rb +56 -93
- data/lib/rigor/language_server/loop.rb +14 -19
- data/lib/rigor/language_server/project_context.rb +46 -74
- data/lib/rigor/language_server/selection_range_provider.rb +9 -12
- data/lib/rigor/language_server/server.rb +55 -83
- data/lib/rigor/language_server/signature_help_provider.rb +29 -46
- data/lib/rigor/language_server/synchronized_writer.rb +4 -7
- data/lib/rigor/language_server/uri.rb +8 -13
- data/lib/rigor/language_server.rb +4 -6
- data/lib/rigor/mcp/loop.rb +2 -3
- data/lib/rigor/mcp/server.rb +4 -7
- data/lib/rigor/mcp.rb +3 -6
- data/lib/rigor/plugin/access_denied_error.rb +5 -8
- data/lib/rigor/plugin/additional_initializer.rb +21 -31
- data/lib/rigor/plugin/base.rb +335 -518
- data/lib/rigor/plugin/blueprint.rb +14 -23
- data/lib/rigor/plugin/box.rb +18 -29
- data/lib/rigor/plugin/fact_store.rb +16 -26
- data/lib/rigor/plugin/inflector.rb +37 -53
- data/lib/rigor/plugin/io_boundary.rb +33 -56
- data/lib/rigor/plugin/isolation.rb +42 -55
- data/lib/rigor/plugin/load_error.rb +10 -15
- data/lib/rigor/plugin/loader.rb +30 -49
- data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
- data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
- data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
- data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
- data/lib/rigor/plugin/macro.rb +10 -15
- data/lib/rigor/plugin/manifest.rb +85 -144
- data/lib/rigor/plugin/node_context.rb +14 -22
- data/lib/rigor/plugin/node_rule_walk.rb +49 -74
- data/lib/rigor/plugin/protocol_contract.rb +25 -39
- data/lib/rigor/plugin/registry.rb +132 -205
- data/lib/rigor/plugin/services.rb +21 -33
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
- data/lib/rigor/plugin/trust_policy.rb +24 -38
- data/lib/rigor/plugin/type_node_resolver.rb +15 -24
- data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
- data/lib/rigor/protection/mutation_scanner.rb +27 -35
- data/lib/rigor/protection/mutator.rb +50 -70
- data/lib/rigor/protection/test_suite_oracle.rb +20 -27
- data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
- data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
- data/lib/rigor/rbs_extended/reporter.rb +24 -40
- data/lib/rigor/rbs_extended.rb +107 -197
- data/lib/rigor/reflection.rb +68 -86
- data/lib/rigor/scope/discovery_index.rb +14 -19
- data/lib/rigor/scope.rb +255 -310
- data/lib/rigor/sig_gen/classification.rb +6 -10
- data/lib/rigor/sig_gen/generator.rb +197 -323
- data/lib/rigor/sig_gen/layout_index.rb +12 -20
- data/lib/rigor/sig_gen/method_candidate.rb +12 -17
- data/lib/rigor/sig_gen/observation_collector.rb +38 -70
- data/lib/rigor/sig_gen/observed_call.rb +13 -23
- data/lib/rigor/sig_gen/path_mapper.rb +17 -29
- data/lib/rigor/sig_gen/renderer.rb +7 -13
- data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
- data/lib/rigor/sig_gen/write_result.rb +8 -16
- data/lib/rigor/sig_gen/writer.rb +95 -174
- data/lib/rigor/sig_gen.rb +3 -6
- data/lib/rigor/signature_path_audit.rb +24 -30
- data/lib/rigor/source/constant_path.rb +10 -14
- data/lib/rigor/source/literals.rb +31 -45
- data/lib/rigor/source/node_locator.rb +9 -11
- data/lib/rigor/source/node_walker.rb +9 -13
- data/lib/rigor/source.rb +3 -4
- data/lib/rigor/testing.rb +16 -20
- data/lib/rigor/triage/catalogue.rb +38 -62
- data/lib/rigor/triage.rb +31 -52
- data/lib/rigor/trinary.rb +9 -13
- data/lib/rigor/type/acceptance_router.rb +4 -6
- data/lib/rigor/type/accepts_result.rb +10 -14
- data/lib/rigor/type/app.rb +19 -27
- data/lib/rigor/type/bot.rb +4 -6
- data/lib/rigor/type/bound_method.rb +10 -15
- data/lib/rigor/type/combinator.rb +165 -257
- data/lib/rigor/type/constant.rb +23 -34
- data/lib/rigor/type/data_class.rb +10 -15
- data/lib/rigor/type/data_instance.rb +14 -20
- data/lib/rigor/type/difference.rb +21 -32
- data/lib/rigor/type/dynamic.rb +3 -5
- data/lib/rigor/type/hash_shape.rb +32 -18
- data/lib/rigor/type/integer_range.rb +11 -16
- data/lib/rigor/type/intersection.rb +27 -42
- data/lib/rigor/type/nominal.rb +10 -15
- data/lib/rigor/type/plain_lattice.rb +9 -13
- data/lib/rigor/type/refined.rb +67 -114
- data/lib/rigor/type/singleton.rb +4 -6
- data/lib/rigor/type/struct_class.rb +11 -16
- data/lib/rigor/type/struct_instance.rb +15 -21
- data/lib/rigor/type/tuple.rb +14 -19
- data/lib/rigor/type/union.rb +30 -42
- data/lib/rigor/type_node/generic.rb +14 -26
- data/lib/rigor/type_node/identifier.rb +12 -19
- data/lib/rigor/type_node.rb +3 -12
- data/lib/rigor/value_semantics.rb +16 -21
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +118 -176
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +71 -108
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +30 -47
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +79 -129
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +36 -57
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +148 -235
- data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
- data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
- data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
- data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
- data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
- data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
- data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
- data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +42 -74
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +280 -497
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +54 -89
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
- data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
- data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
- data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
- data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
- data/sig/rigor/scope.rbs +6 -0
- metadata +2 -1
|
@@ -2,25 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module Rigor
|
|
4
4
|
module Plugin
|
|
5
|
-
# Frozen, `Ractor.shareable?` description of how to materialise
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# worker calls {#materialize} once at startup, then owns its
|
|
11
|
-
# plugin instances (and their mutable per-run accumulators)
|
|
12
|
-
# for the lifetime of the worker.
|
|
5
|
+
# Frozen, `Ractor.shareable?` description of how to materialise a single plugin instance inside a worker.
|
|
6
|
+
# [ADR-15](../../../docs/adr/15-ractor-concurrency.md) Phase 3 introduces the carrier so the eventual worker
|
|
7
|
+
# pool can pass `Array<Blueprint>` across a Ractor boundary verbatim; each worker calls {#materialize} once
|
|
8
|
+
# at startup, then owns its plugin instances (and their mutable per-run accumulators) for the lifetime of
|
|
9
|
+
# the worker.
|
|
13
10
|
#
|
|
14
|
-
# Holds the constant path (`String`) of the plugin class — NOT
|
|
15
|
-
# the
|
|
16
|
-
#
|
|
17
|
-
# resolves the same constant via `Object.const_get`.
|
|
11
|
+
# Holds the constant path (`String`) of the plugin class — NOT the class object itself. Plugin gems are
|
|
12
|
+
# required from the main Ractor BEFORE any worker spawns, so every Ractor resolves the same constant via
|
|
13
|
+
# `Object.const_get`.
|
|
18
14
|
#
|
|
19
|
-
# The `config` Hash is deep-copied + made shareable at
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
# Hash held by the loader is therefore unaffected by Blueprint
|
|
23
|
-
# construction.
|
|
15
|
+
# The `config` Hash is deep-copied + made shareable at construction so the Blueprint stays decoupled from
|
|
16
|
+
# whatever Hash the project configuration emitted. The original config Hash held by the loader is therefore
|
|
17
|
+
# unaffected by Blueprint construction.
|
|
24
18
|
class Blueprint
|
|
25
19
|
attr_reader :klass_name, :config
|
|
26
20
|
|
|
@@ -30,12 +24,9 @@ module Rigor
|
|
|
30
24
|
freeze
|
|
31
25
|
end
|
|
32
26
|
|
|
33
|
-
# Resolves the plugin class via `Object.const_get`, builds a
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
# {Rigor::Plugin::Loader#instantiate} bit-for-bit so the
|
|
37
|
-
# blueprint-driven path stays consistent with the
|
|
38
|
-
# configuration-driven load path.
|
|
27
|
+
# Resolves the plugin class via `Object.const_get`, builds a fresh instance bound to the supplied services
|
|
28
|
+
# container, and calls `#init(services)`. Mirrors {Rigor::Plugin::Loader#instantiate} bit-for-bit so the
|
|
29
|
+
# blueprint-driven path stays consistent with the configuration-driven load path.
|
|
39
30
|
def materialize(services:)
|
|
40
31
|
klass = Object.const_get(@klass_name)
|
|
41
32
|
plugin = klass.new(services: services, config: @config)
|
data/lib/rigor/plugin/box.rb
CHANGED
|
@@ -2,45 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
module Rigor
|
|
4
4
|
module Plugin
|
|
5
|
-
# ADR-39 slice 5 — the isolation boundary for target-library
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# space**. `Ruby::Box` is not a security sandbox (the gem's code still
|
|
11
|
-
# runs in-process) — it is the *contamination* boundary the rule
|
|
12
|
-
# needs, which is sufficient because the rule only ever invokes a
|
|
5
|
+
# ADR-39 slice 5 — the isolation boundary for target-library invocation. When Rigor is launched with
|
|
6
|
+
# `RUBY_BOX=1` (opt-in; the launcher re-execs only when a project opts in), a target library is loaded and
|
|
7
|
+
# called inside a `Ruby::Box` so its **core-class monkey-patches and gem version cannot contaminate Rigor's
|
|
8
|
+
# main space**. `Ruby::Box` is not a security sandbox (the gem's code still runs in-process) — it is the
|
|
9
|
+
# *contamination* boundary the rule needs, which is sufficient because the rule only ever invokes a
|
|
13
10
|
# declared, trusted, pure target library.
|
|
14
11
|
#
|
|
15
|
-
# **Disabled by default.** {enabled?} is false unless the experimental
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# the trusted, project-agnostic target libraries (inflection rules,
|
|
20
|
-
# the Rack status table); per-project / exact-version boxes are a
|
|
21
|
-
# later slice.
|
|
12
|
+
# **Disabled by default.** {enabled?} is false unless the experimental `Ruby::Box` feature is active, so
|
|
13
|
+
# every consumer keeps a non-box path and the default behaviour (loading the trusted library into the main
|
|
14
|
+
# space, per slices 2/4) is unchanged. The box only ever holds the trusted, project-agnostic target
|
|
15
|
+
# libraries (inflection rules, the Rack status table); per-project / exact-version boxes are a later slice.
|
|
22
16
|
module Box
|
|
23
17
|
module_function
|
|
24
18
|
|
|
25
|
-
# True only when the experimental `Ruby::Box` feature is present and
|
|
26
|
-
#
|
|
27
|
-
# answers false so a consumer silently keeps its non-box path.
|
|
19
|
+
# True only when the experimental `Ruby::Box` feature is present and active (the process was started with
|
|
20
|
+
# `RUBY_BOX=1`). Any error answers false so a consumer silently keeps its non-box path.
|
|
28
21
|
def enabled?
|
|
29
22
|
defined?(::Ruby::Box) && ::Ruby::Box.respond_to?(:enabled?) && ::Ruby::Box.enabled?
|
|
30
23
|
rescue StandardError
|
|
31
24
|
false
|
|
32
25
|
end
|
|
33
26
|
|
|
34
|
-
# The process-shared box for trusted, project-agnostic target
|
|
35
|
-
# libraries. Built lazily on first use.
|
|
27
|
+
# The process-shared box for trusted, project-agnostic target libraries. Built lazily on first use.
|
|
36
28
|
def shared
|
|
37
29
|
@shared ||= ::Ruby::Box.new
|
|
38
30
|
end
|
|
39
31
|
|
|
40
|
-
# Requires `feature` into the shared box exactly once. Returns true
|
|
41
|
-
# when
|
|
42
|
-
#
|
|
43
|
-
# into the main space, which would defeat the boundary).
|
|
32
|
+
# Requires `feature` into the shared box exactly once. Returns true when the feature is available in the
|
|
33
|
+
# box, false when it could not be loaded (the caller then declines — never falls back to loading into the
|
|
34
|
+
# main space, which would defeat the boundary).
|
|
44
35
|
def require_feature(feature)
|
|
45
36
|
@required ||= {}
|
|
46
37
|
return @required[feature] if @required.key?(feature)
|
|
@@ -51,11 +42,9 @@ module Rigor
|
|
|
51
42
|
@required[feature] = false
|
|
52
43
|
end
|
|
53
44
|
|
|
54
|
-
# Evaluates `code` inside the shared box and returns the result
|
|
55
|
-
#
|
|
56
|
-
# interpolate
|
|
57
|
-
# round-trips as a safe Ruby literal) and only ever interpolate
|
|
58
|
-
# method names from a fixed allow-list, never free user input.
|
|
45
|
+
# Evaluates `code` inside the shared box and returns the result across the box boundary. The caller MUST
|
|
46
|
+
# build `code` safely — interpolate value arguments through `String#inspect` (which round-trips as a safe
|
|
47
|
+
# Ruby literal) and only ever interpolate method names from a fixed allow-list, never free user input.
|
|
59
48
|
def eval(code)
|
|
60
49
|
shared.eval(code)
|
|
61
50
|
end
|
|
@@ -4,23 +4,17 @@ module Rigor
|
|
|
4
4
|
module Plugin
|
|
5
5
|
# Per-run cross-plugin fact store. ADR-9 § "Plugin::FactStore".
|
|
6
6
|
#
|
|
7
|
-
# A plugin publishes typed `(plugin_id, name) -> value` tuples
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# `
|
|
11
|
-
#
|
|
12
|
-
# and discarded at the end — caching the underlying expensive
|
|
13
|
-
# computation is the producer's job (`Plugin::Base.producer`);
|
|
14
|
-
# the FactStore just publishes a *reference* to that
|
|
7
|
+
# A plugin publishes typed `(plugin_id, name) -> value` tuples in its `Plugin::Base#prepare(services)` hook
|
|
8
|
+
# (slice 3); other plugins read them in `#diagnostics_for_file` via
|
|
9
|
+
# `services.fact_store.read(plugin_id:, name:)`. The store is constructed fresh at the start of every
|
|
10
|
+
# `Analysis::Runner.run` and discarded at the end — caching the underlying expensive computation is the
|
|
11
|
+
# producer's job (`Plugin::Base.producer`); the FactStore just publishes a *reference* to that
|
|
15
12
|
# already-cached result.
|
|
16
13
|
#
|
|
17
|
-
# `(plugin_id, name)` is a unique key. A second `publish` with
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# when a single plugin publishes twice with differing values —
|
|
22
|
-
# the conflict signals a plugin-author bug, never a load-time
|
|
23
|
-
# interaction between unrelated plugins.
|
|
14
|
+
# `(plugin_id, name)` is a unique key. A second `publish` with the same value is a no-op (`==` comparison);
|
|
15
|
+
# a second `publish` with a different value raises {Conflict}. Since `plugin_id` namespaces the key, a real
|
|
16
|
+
# conflict only happens when a single plugin publishes twice with differing values — the conflict signals a
|
|
17
|
+
# plugin-author bug, never a load-time interaction between unrelated plugins.
|
|
24
18
|
class FactStore
|
|
25
19
|
Fact = Data.define(:plugin_id, :name, :value)
|
|
26
20
|
|
|
@@ -45,15 +39,13 @@ module Rigor
|
|
|
45
39
|
@mutex = Mutex.new
|
|
46
40
|
end
|
|
47
41
|
|
|
48
|
-
# Writes a `(plugin_id, name) -> value` triple. Idempotent if
|
|
49
|
-
#
|
|
50
|
-
# {Conflict} if the values differ.
|
|
42
|
+
# Writes a `(plugin_id, name) -> value` triple. Idempotent if the same value is published twice (`==`);
|
|
43
|
+
# raises {Conflict} if the values differ.
|
|
51
44
|
#
|
|
52
45
|
# @param plugin_id [String] producing plugin's manifest id.
|
|
53
|
-
# @param name [Symbol, String] fact name (canonicalised to
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
# producer chose to publish. The value is stored as-is.
|
|
46
|
+
# @param name [Symbol, String] fact name (canonicalised to Symbol for lookup).
|
|
47
|
+
# @param value [Object] frozen-shape value object the producer chose to publish. The value is stored
|
|
48
|
+
# as-is.
|
|
57
49
|
def publish(plugin_id:, name:, value:)
|
|
58
50
|
plugin_id = plugin_id.to_s
|
|
59
51
|
name = name.to_sym
|
|
@@ -68,10 +60,8 @@ module Rigor
|
|
|
68
60
|
nil
|
|
69
61
|
end
|
|
70
62
|
|
|
71
|
-
# @return [Object, nil] the published value, or `nil` when no
|
|
72
|
-
#
|
|
73
|
-
# `manifest(consumes:)` (slice 4) is the dependency
|
|
74
|
-
# declaration mechanism.
|
|
63
|
+
# @return [Object, nil] the published value, or `nil` when no fact is registered. Reads do NOT establish
|
|
64
|
+
# a dependency — `manifest(consumes:)` (slice 4) is the dependency declaration mechanism.
|
|
75
65
|
def read(plugin_id:, name:)
|
|
76
66
|
fact = @mutex.synchronize { @facts[[plugin_id.to_s, name.to_sym]] }
|
|
77
67
|
fact&.value
|
|
@@ -2,58 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
module Rigor
|
|
4
4
|
module Plugin
|
|
5
|
-
# ADR-39 — the shared inflection helper for the Rails-family plugins
|
|
6
|
-
#
|
|
5
|
+
# ADR-39 — the shared inflection helper for the Rails-family plugins (`rigor-rails-routes`,
|
|
6
|
+
# `rigor-activerecord`, `rigor-actionpack`).
|
|
7
7
|
#
|
|
8
|
-
# Inflection (`posts` ↔ `post`, `BlogPost` → `blog_posts`) drives
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# **only** with the real `ActiveSupport::Inflector` — the authority
|
|
14
|
-
# Rails itself uses — and carries **no built-in approximation**: an
|
|
15
|
-
# approximation would be exactly the source of wrong facts the
|
|
8
|
+
# Inflection (`posts` ↔ `post`, `BlogPost` → `blog_posts`) drives route-helper and model-name resolution, so
|
|
9
|
+
# an inflection that diverges from Rails' *actual* rules produces a wrong helper / model name and therefore
|
|
10
|
+
# a **false positive on working code** (a bogus `unknown-helper` / `unknown-permit-key`). Rigor therefore
|
|
11
|
+
# inflects **only** with the real `ActiveSupport::Inflector` — the authority Rails itself uses — and
|
|
12
|
+
# carries **no built-in approximation**: an approximation would be exactly the source of wrong facts the
|
|
16
13
|
# false-positive discipline forbids.
|
|
17
14
|
#
|
|
18
|
-
# Per ADR-39 this is a permitted *target-library* invocation: the
|
|
19
|
-
#
|
|
20
|
-
# (
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
# ingested by *statically parsing* `config/initializers/inflections.rb`
|
|
24
|
-
# in a later slice, never by executing it.)
|
|
15
|
+
# Per ADR-39 this is a permitted *target-library* invocation: the methods called are a fixed, pure,
|
|
16
|
+
# allow-listed set ({ALLOWED_METHODS}) on a trusted gem the consuming plugins declare as a dependency.
|
|
17
|
+
# (`ActiveSupport::Inflector` is loaded, not the analyzed application's code; project-specific inflection
|
|
18
|
+
# rules are ingested by *statically parsing* `config/initializers/inflections.rb` in a later slice, never
|
|
19
|
+
# by executing it.)
|
|
25
20
|
#
|
|
26
|
-
# **Absence is silence, never a guess.** When `ActiveSupport::Inflector`
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
# coverage, never a wrong fact. A consumer that wants to fail cleanly
|
|
33
|
-
# up front can gate on {available?} and emit a single load-error.
|
|
21
|
+
# **Absence is silence, never a guess.** When `ActiveSupport::Inflector` cannot be loaded (a
|
|
22
|
+
# misconfiguration — the consuming plugins declare it as a dependency, so it is present in practice), the
|
|
23
|
+
# inflection methods raise {Unavailable} rather than approximate. That raise propagates to the caller's
|
|
24
|
+
# per-plugin rescue boundary, so the inflection-dependent check degrades to **no diagnostics** — reduced
|
|
25
|
+
# coverage, never a wrong fact. A consumer that wants to fail cleanly up front can gate on {available?}
|
|
26
|
+
# and emit a single load-error.
|
|
34
27
|
module Inflector
|
|
35
|
-
# The pure, side-effect-free `ActiveSupport::Inflector` methods this
|
|
36
|
-
#
|
|
37
|
-
# is fixed and greppable — never a dynamic `public_send`.
|
|
28
|
+
# The pure, side-effect-free `ActiveSupport::Inflector` methods this helper is permitted to call (ADR-39
|
|
29
|
+
# § "safety harness"). The set is fixed and greppable — never a dynamic `public_send`.
|
|
38
30
|
#
|
|
39
|
-
# `tableize` is deliberately NOT delegated: `ActiveSupport::Inflector.
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
# method). So {.tableize} composes the AS-backed `underscore` /
|
|
44
|
-
# `pluralize` with the `::`→`_` flattening AR really uses.
|
|
31
|
+
# `tableize` is deliberately NOT delegated: `ActiveSupport::Inflector.tableize("Admin::User")` returns
|
|
32
|
+
# `"admin/users"`, but ActiveRecord's *actual* table name flattens the namespace to `"admin_users"` (the
|
|
33
|
+
# table-name computation does more than the pure `tableize` string method). So {.tableize} composes the
|
|
34
|
+
# AS-backed `underscore` / `pluralize` with the `::`→`_` flattening AR really uses.
|
|
45
35
|
ALLOWED_METHODS = %i[underscore camelize singularize pluralize classify].freeze
|
|
46
36
|
|
|
47
|
-
# The target library + the constant the allow-listed methods are
|
|
48
|
-
#
|
|
49
|
-
# configured isolation strategy (none / ruby_box / process).
|
|
37
|
+
# The target library + the constant the allow-listed methods are called on. Passed to {Isolation} so the
|
|
38
|
+
# call runs under the configured isolation strategy (none / ruby_box / process).
|
|
50
39
|
FEATURE = "active_support/inflector"
|
|
51
40
|
RECEIVER = "ActiveSupport::Inflector"
|
|
52
41
|
|
|
53
|
-
# Raised when `ActiveSupport::Inflector` is required for an
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
# rather than a wrong inflection.
|
|
42
|
+
# Raised when `ActiveSupport::Inflector` is required for an inflection but cannot be loaded. Caught by
|
|
43
|
+
# the per-plugin isolation boundary, so it surfaces as "this plugin produced no diagnostics" rather than
|
|
44
|
+
# a wrong inflection.
|
|
57
45
|
class Unavailable < StandardError
|
|
58
46
|
end
|
|
59
47
|
|
|
@@ -84,17 +72,15 @@ module Rigor
|
|
|
84
72
|
invoke(:classify, table_name)
|
|
85
73
|
end
|
|
86
74
|
|
|
87
|
-
# `BlogPost` → `blog_posts`; `Admin::User` → `admin_users`.
|
|
88
|
-
#
|
|
89
|
-
# way ActiveRecord's table naming does — see {ALLOWED_METHODS}.
|
|
75
|
+
# `BlogPost` → `blog_posts`; `Admin::User` → `admin_users`. Composed (not delegated) so the namespace
|
|
76
|
+
# flattens with `_` the way ActiveRecord's table naming does — see {ALLOWED_METHODS}.
|
|
90
77
|
def tableize(class_name)
|
|
91
78
|
underscored = underscore(class_name.to_s.gsub("::", "/")).tr("/", "_")
|
|
92
79
|
pluralize(underscored)
|
|
93
80
|
end
|
|
94
81
|
|
|
95
|
-
# Whether inflection is available under the configured isolation
|
|
96
|
-
#
|
|
97
|
-
# emit a single clean load-error instead of letting the first
|
|
82
|
+
# Whether inflection is available under the configured isolation strategy. A consumer can gate
|
|
83
|
+
# inflection-dependent work on this to emit a single clean load-error instead of letting the first
|
|
98
84
|
# inflection raise. Probes with a trivial pluralization.
|
|
99
85
|
def available?
|
|
100
86
|
invoke(:pluralize, "rigor_inflector_probe")
|
|
@@ -103,12 +89,10 @@ module Rigor
|
|
|
103
89
|
false
|
|
104
90
|
end
|
|
105
91
|
|
|
106
|
-
# Delegates an allow-listed method to the real
|
|
107
|
-
#
|
|
108
|
-
# strategy
|
|
109
|
-
#
|
|
110
|
-
# reached in that strategy — the caller's per-plugin rescue turns
|
|
111
|
-
# that into silence, never a wrong inflection.
|
|
92
|
+
# Delegates an allow-listed method to the real `ActiveSupport::Inflector` through the configured
|
|
93
|
+
# isolation strategy ({Isolation}: none / ruby_box / process). Raises {Unavailable} (never approximates)
|
|
94
|
+
# when the library cannot be reached in that strategy — the caller's per-plugin rescue turns that into
|
|
95
|
+
# silence, never a wrong inflection.
|
|
112
96
|
def invoke(name, arg)
|
|
113
97
|
raise ArgumentError, "method not allow-listed: #{name}" unless ALLOWED_METHODS.include?(name)
|
|
114
98
|
|
|
@@ -6,46 +6,30 @@ require_relative "access_denied_error"
|
|
|
6
6
|
|
|
7
7
|
module Rigor
|
|
8
8
|
module Plugin
|
|
9
|
-
# Analyzer-side helper plugins go through to read files and
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# of every read so plugin contributions stay invalidatable
|
|
13
|
-
# alongside their inputs.
|
|
9
|
+
# Analyzer-side helper plugins go through to read files and (eventually) reach the network. The boundary
|
|
10
|
+
# enforces the active {TrustPolicy} and accumulates a {Cache::Descriptor} of every read so plugin
|
|
11
|
+
# contributions stay invalidatable alongside their inputs.
|
|
14
12
|
#
|
|
15
|
-
# ADR-2 § "Plugin Trust and I/O Policy" is the binding
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# when plugin code goes through this surface, reads stay
|
|
20
|
-
# within the trust scope and feed the cache descriptor;
|
|
21
|
-
# contributions built on top of out-of-scope reads will not
|
|
13
|
+
# ADR-2 § "Plugin Trust and I/O Policy" is the binding contract. The boundary is **not** a sandbox: a
|
|
14
|
+
# plugin that uses `File.read` directly bypasses everything here, and the ADR explicitly accepts that
|
|
15
|
+
# trade-off. The discipline is: when plugin code goes through this surface, reads stay within the trust
|
|
16
|
+
# scope and feed the cache descriptor; contributions built on top of out-of-scope reads will not
|
|
22
17
|
# invalidate correctly.
|
|
23
18
|
#
|
|
24
19
|
# Slice 2 ships a minimal surface:
|
|
25
20
|
#
|
|
26
|
-
# - `#read_file(path)` — validates against the policy, returns
|
|
27
|
-
# the
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
# `allowed_url_hosts
|
|
33
|
-
# {
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# `plugins_io.allowed_url_hosts: [...]`. The HTTP fetch
|
|
39
|
-
# is GET-only over HTTPS, capped at {URL_TIMEOUT_SECONDS}
|
|
40
|
-
# wall time and {URL_MAX_BYTES} body size; non-2xx
|
|
41
|
-
# responses raise {AccessDeniedError} so plugin code
|
|
42
|
-
# doesn't have to rescue mid-build.
|
|
43
|
-
# - `#cache_descriptor` — flushes the accumulated entries into
|
|
44
|
-
# a fresh {Cache::Descriptor} for the contribution that
|
|
45
|
-
# built it. URL fetches contribute `ConfigEntry` rows
|
|
46
|
-
# keyed `"url:#{url}"` with the response body's SHA-256
|
|
47
|
-
# so contributions invalidate when the remote document
|
|
48
|
-
# changes.
|
|
21
|
+
# - `#read_file(path)` — validates against the policy, returns the file's contents, and adds a
|
|
22
|
+
# digest-keyed {Cache::Descriptor::FileEntry} to the boundary's accumulated descriptor.
|
|
23
|
+
# - `#open_url(url)` — fetches the URL when the policy permits it (`network_policy: :allowlist` plus an
|
|
24
|
+
# `allowed_url_hosts` match) and raises {AccessDeniedError} otherwise. v0.1.2 ships the allowlist
|
|
25
|
+
# surface; the default project policy still has `network_policy: :disabled` so plugins that want network
|
|
26
|
+
# access opt in explicitly through `.rigor.yml`'s `plugins_io.network: allowlist` plus
|
|
27
|
+
# `plugins_io.allowed_url_hosts: [...]`. The HTTP fetch is GET-only over HTTPS, capped at
|
|
28
|
+
# {URL_TIMEOUT_SECONDS} wall time and {URL_MAX_BYTES} body size; non-2xx responses raise
|
|
29
|
+
# {AccessDeniedError} so plugin code doesn't have to rescue mid-build.
|
|
30
|
+
# - `#cache_descriptor` — flushes the accumulated entries into a fresh {Cache::Descriptor} for the
|
|
31
|
+
# contribution that built it. URL fetches contribute `ConfigEntry` rows keyed `"url:#{url}"` with the
|
|
32
|
+
# response body's SHA-256 so contributions invalidate when the remote document changes.
|
|
49
33
|
class IoBoundary
|
|
50
34
|
URL_TIMEOUT_SECONDS = 10
|
|
51
35
|
URL_MAX_BYTES = 10 * 1024 * 1024
|
|
@@ -61,10 +45,9 @@ module Rigor
|
|
|
61
45
|
@mutex = Mutex.new
|
|
62
46
|
end
|
|
63
47
|
|
|
64
|
-
# Reads the file at `path` after validating it against the
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
# so the resulting cache slice invalidates on content change.
|
|
48
|
+
# Reads the file at `path` after validating it against the policy. Raises {AccessDeniedError} when the
|
|
49
|
+
# path is outside every allowed read root. Records a `:digest` {FileEntry} so the resulting cache slice
|
|
50
|
+
# invalidates on content change.
|
|
68
51
|
def read_file(path)
|
|
69
52
|
absolute = File.expand_path(path.to_s)
|
|
70
53
|
unless @policy.allow_read?(absolute)
|
|
@@ -81,15 +64,11 @@ module Rigor
|
|
|
81
64
|
contents
|
|
82
65
|
end
|
|
83
66
|
|
|
84
|
-
# Fetches the URL when the policy permits it. Returns the
|
|
85
|
-
#
|
|
86
|
-
# is
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
# ({URL_TIMEOUT_SECONDS}). On success, records a
|
|
90
|
-
# `ConfigEntry` keyed `"url:#{url}"` with the body's
|
|
91
|
-
# SHA-256 so the cache descriptor invalidates if the remote
|
|
92
|
-
# document changes.
|
|
67
|
+
# Fetches the URL when the policy permits it. Returns the response body. Raises {AccessDeniedError} when
|
|
68
|
+
# the policy is `:disabled`, the URL scheme is not `https`, the host is not on the allowlist, the
|
|
69
|
+
# response is non-2xx, the body exceeds {URL_MAX_BYTES}, or the request times out
|
|
70
|
+
# ({URL_TIMEOUT_SECONDS}). On success, records a `ConfigEntry` keyed `"url:#{url}"` with the body's
|
|
71
|
+
# SHA-256 so the cache descriptor invalidates if the remote document changes.
|
|
93
72
|
def open_url(url)
|
|
94
73
|
url_string = url.to_s
|
|
95
74
|
unless @policy.allow_url?(url_string)
|
|
@@ -107,10 +86,9 @@ module Rigor
|
|
|
107
86
|
body
|
|
108
87
|
end
|
|
109
88
|
|
|
110
|
-
# @return [Rigor::Cache::Descriptor] frozen snapshot of every
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
# reads expand the underlying record tables.
|
|
89
|
+
# @return [Rigor::Cache::Descriptor] frozen snapshot of every file / URL the boundary has read so far.
|
|
90
|
+
# Calling this multiple times yields equal descriptors; subsequent reads expand the underlying record
|
|
91
|
+
# tables.
|
|
114
92
|
def cache_descriptor
|
|
115
93
|
files, configs = @mutex.synchronize { [@file_entries.values.dup, @config_entries.values.dup] }
|
|
116
94
|
Cache::Descriptor.new(files: files, configs: configs)
|
|
@@ -132,10 +110,9 @@ module Rigor
|
|
|
132
110
|
end
|
|
133
111
|
end
|
|
134
112
|
|
|
135
|
-
# Default HTTP client wrapping `Net::HTTP`. Wraps a single
|
|
136
|
-
#
|
|
137
|
-
#
|
|
138
|
-
# tests don't require network access.
|
|
113
|
+
# Default HTTP client wrapping `Net::HTTP`. Wraps a single `GET` over HTTPS. Specs inject a fake client
|
|
114
|
+
# that conforms to the same `#get(url, timeout:, max_bytes:)` shape so the tests don't require network
|
|
115
|
+
# access.
|
|
139
116
|
class DefaultHttpClient
|
|
140
117
|
def get(url, timeout:, max_bytes:)
|
|
141
118
|
require "net/http"
|