hecks 2.9.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/exe/hecks +51 -6
- data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
- data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +1 -1
- data/lib/hecks/adapters/driven/memory.rb +16 -3
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +6 -2
- data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
- data/lib/hecks/adapters/driven/postgres.rb +13 -4
- data/lib/hecks/adapters/driven/prism.rb +41 -8
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +73 -16
- data/lib/hecks/behaviors/expectations.rb +1 -1
- data/lib/hecks/behaviors.rb +1 -1
- data/lib/hecks/bench/cli.rb +62 -0
- data/lib/hecks/bench/environment.rb +71 -0
- data/lib/hecks/bench/postgres_probe.rb +30 -0
- data/lib/hecks/bench/report.rb +76 -0
- data/lib/hecks/bench/ruby_runner.rb +93 -0
- data/lib/hecks/bench/run.rb +55 -0
- data/lib/hecks/bench/rust_runner.rb +101 -0
- data/lib/hecks/bench/stats.rb +46 -0
- data/lib/hecks/bench/suite.rb +98 -0
- data/lib/hecks/bench/workload.rb +89 -0
- data/lib/hecks/bench.rb +17 -0
- data/lib/hecks/bluebook/assembly/contracts.rb +2 -0
- data/lib/hecks/bluebook/attribute.rb +1 -1
- data/lib/hecks/bluebook/behaviour/aggregate.rb +30 -1
- data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -0
- data/lib/hecks/bluebook/chapter.rb +6 -2
- data/lib/hecks/bluebook/domain_port.rb +36 -5
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +10 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +8 -0
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -1
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +31 -3
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +122 -3
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/query_builder.rb +27 -1
- data/lib/hecks/bluebook/dsl/rule_reference.rb +1 -1
- data/lib/hecks/bluebook/dsl/world_builder.rb +29 -2
- data/lib/hecks/bluebook/expression/canonical_form.rb +1 -1
- data/lib/hecks/bluebook/expression/resolver.rb +3 -2
- data/lib/hecks/bluebook/hexagon.rb +13 -5
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
- data/lib/hecks/bluebook/meta_validator.rb +9 -4
- data/lib/hecks/bluebook/model_check.rb +23 -0
- data/lib/hecks/bluebook/pattern_subset.rb +38 -0
- data/lib/hecks/bluebook/query.rb +20 -5
- data/lib/hecks/bluebook/smoke_test.rb +1 -1
- data/lib/hecks/canonical_json.rb +30 -0
- data/lib/hecks/chapters.rb +102 -0
- data/lib/hecks/cli/behaviors.rb +61 -0
- data/lib/hecks/cli/canonicalise.rb +25 -0
- data/lib/hecks/cli/check_engine_agreement.rb +32 -0
- data/lib/hecks/cli/check_era.rb +88 -0
- data/lib/hecks/cli/console.rb +66 -0
- data/lib/hecks/cli/deploy_template_diff.rb +40 -0
- data/lib/hecks/cli/doc_coverage.rb +32 -0
- data/lib/hecks/cli/document.rb +2 -2
- data/lib/hecks/cli/expression_projection.rb +41 -0
- data/lib/hecks/cli/follow.rb +88 -0
- data/lib/hecks/cli/history.rb +44 -0
- data/lib/hecks/cli/ir.rb +1 -1
- data/lib/hecks/cli/mcp.rb +3 -3
- data/lib/hecks/cli/model_check.rb +15 -8
- data/lib/hecks/cli/pattern_cases.rb +47 -0
- data/lib/hecks/cli/present.rb +39 -0
- data/lib/hecks/cli/project_cli.rb +224 -32
- data/lib/hecks/cli/project_diagrams.rb +4 -3
- data/lib/hecks/cli/project_glossary.rb +44 -0
- data/lib/hecks/cli/reference.rb +46 -0
- data/lib/hecks/cli/refresh_projections.rb +44 -0
- data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
- data/lib/hecks/cli/release.rb +70 -0
- data/lib/hecks/cli/release_gem.rb +59 -0
- data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
- data/lib/hecks/cli/rspec_shard_files.rb +66 -0
- data/lib/hecks/cli/run.rb +3 -3
- data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
- data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
- data/lib/hecks/cli/shape.rb +70 -0
- data/lib/hecks/cli/smoke_http.rb +230 -0
- data/lib/hecks/cli/smoke_test.rb +5 -3
- data/lib/hecks/cli/spec_example.rb +27 -0
- data/lib/hecks/cli/statements.rb +32 -0
- data/lib/hecks/cli/stores.rb +4 -5
- data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
- data/lib/hecks/cli.rb +201 -52
- data/lib/hecks/codemod.rb +324 -0
- data/lib/hecks/corpus.rb +423 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +591 -23
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -4
- data/lib/hecks/deploy/oidc.json +82 -2
- data/lib/hecks/doc/reference.rb +351 -0
- data/lib/hecks/doors/cli_door.rb +196 -0
- data/lib/hecks/doors/cli_runner.rb +297 -0
- data/lib/hecks/{facade → doors}/command_request.rb +1 -1
- data/lib/hecks/{facade → doors}/handle.rb +2 -2
- data/lib/hecks/{facade → doors}/json_door.rb +4 -4
- data/lib/hecks/doors/launcher_options.rb +129 -0
- data/lib/hecks/{cli → doors}/mcp_door.rb +7 -7
- data/lib/hecks/doors/mcp_door_scope.rb +97 -0
- data/lib/hecks/{facade/surface → doors/ruby_door}/aggregate_door.rb +8 -3
- data/lib/hecks/{facade/surface → doors/ruby_door}/chapter.rb +3 -3
- data/lib/hecks/{facade/surface.rb → doors/ruby_door.rb} +36 -5
- data/lib/hecks/doors.rb +61 -0
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +1 -1
- data/lib/hecks/embryonaut_bluebook.rb +1 -1
- data/lib/hecks/engine_agreement.rb +156 -0
- data/lib/hecks/facade.rb +24 -37
- data/lib/hecks/forms/app.rb +6 -6
- data/lib/hecks/forms/banking_presentation.rb +50 -0
- data/lib/hecks/forms/field_shape.rb +2 -1
- data/lib/hecks/forms/port_argument.rb +1 -1
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
- data/lib/hecks/fuzzing/combination_miner.rb +86 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
- data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
- data/lib/hecks/fuzzing/differential.rb +156 -0
- data/lib/hecks/fuzzing/domain_generator.rb +615 -0
- data/lib/hecks/fuzzing/era_boundary.rb +71 -0
- data/lib/hecks/fuzzing/form_census.rb +136 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
- data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
- data/lib/hecks/fuzzing/outside_world.rb +45 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
- data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
- data/lib/hecks/fuzzing/properties/guards.rb +190 -0
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
- data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
- data/lib/hecks/fuzzing/properties/querying.rb +175 -0
- data/lib/hecks/fuzzing/properties.rb +162 -0
- data/lib/hecks/fuzzing/qa_settings.rb +123 -0
- data/lib/hecks/fuzzing/replay.rb +505 -0
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
- data/lib/hecks/fuzzing/self_consistency.rb +339 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
- data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
- data/lib/hecks/fuzzing/shrinker.rb +166 -0
- data/lib/hecks/fuzzing/structural_skips.rb +33 -0
- data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
- data/lib/hecks/fuzzing/value_generator.rb +216 -0
- data/lib/hecks/fuzzing.rb +23 -0
- data/lib/hecks/grammar/evolve.rb +336 -0
- data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
- data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
- data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
- data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
- data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
- data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
- data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
- data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
- data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
- data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
- data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
- data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
- data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
- data/lib/hecks/hecks/adapters/git.adapter +7 -0
- data/lib/hecks/hecks/adapters/git.rb +102 -0
- data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
- data/lib/hecks/hecks/adapters/host_http.rb +65 -0
- data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
- data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
- data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
- data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
- data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
- data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
- data/lib/hecks/hecks/adapters/local_files.rb +46 -0
- data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
- data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
- data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
- data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
- data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
- data/lib/hecks/hecks/adapters/shell.adapter +7 -0
- data/lib/hecks/hecks/adapters/shell.rb +49 -0
- data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
- data/lib/hecks/hecks/adapters/terminal.rb +74 -0
- data/lib/hecks/hecks/codebase.behaviors +172 -0
- data/lib/hecks/hecks/codebase.bluebook +2453 -0
- data/lib/hecks/hecks/context_map.hecksagon +13 -0
- data/lib/hecks/hecks/custodian.behaviors +179 -0
- data/lib/hecks/hecks/custodian.bluebook +1847 -0
- data/lib/hecks/hecks/deploy.behaviors +74 -0
- data/lib/hecks/hecks/deploy.world +12 -0
- data/lib/hecks/hecks/environments/memory.world +10 -0
- data/lib/hecks/hecks/governance.world +7 -0
- data/lib/hecks/hecks/hecks.behaviors +62 -0
- data/lib/hecks/hecks/hecks.bluebook +124 -0
- data/lib/hecks/hecks/hecks.hecksagon +390 -0
- data/lib/hecks/hecks/hecks.world +42 -0
- data/lib/hecks/kernel_capabilities.rb +194 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +11 -0
- data/lib/hecks/language/bluebook/query.bluebook +7 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +4 -4
- data/lib/hecks/language/hecksagon/domain_port.bluebook +10 -0
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +20 -0
- data/lib/hecks/mcp_stdio_guard.rb +2 -2
- data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
- data/lib/hecks/persistence_legacy_fixture.rb +113 -0
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +8 -5
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +4 -1
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +15 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +32 -15
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +15 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +43 -24
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
- data/lib/hecks/ports/persistence/state_codec.rb +24 -5
- data/lib/hecks/projection_files.rb +285 -0
- data/lib/hecks/projections/bootstrap_table.rb +1 -1
- data/lib/hecks/projections/deploy/fargate.rb +5 -5
- data/lib/hecks/projections/deploy/lambda.rb +43 -32
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/preview/template.rb +1 -1
- data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
- data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
- data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/shared.rb +1 -1
- data/lib/hecks/projections/diagrams.rb +3 -3
- data/lib/hecks/projections/model/deviations.rb +1 -1
- data/lib/hecks/projections/parser_table.rb +4 -4
- data/lib/hecks/projections/reference.rb +2 -3
- data/lib/hecks/projections/rust_vocabulary.rb +1 -1
- data/lib/hecks/projector/cli_projector.rb +106 -37
- data/lib/hecks/projector.rb +1 -1
- data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
- data/lib/hecks/quality_control/adapters/agent.rb +58 -0
- data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
- data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
- data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
- data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
- data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
- data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
- data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
- data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
- data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
- data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
- data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
- data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
- data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
- data/lib/hecks/quality_control/cli/child.rb +45 -0
- data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
- data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
- data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
- data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
- data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
- data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
- data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
- data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
- data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
- data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
- data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
- data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
- data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
- data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
- data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
- data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
- data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
- data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
- data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
- data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
- data/lib/hecks/query_ir.rb +346 -0
- data/lib/hecks/query_ir_mcp.rb +198 -0
- data/lib/hecks/release/runner/ci_publisher.rb +2 -2
- data/lib/hecks/release/runner/clean_tree.rb +42 -0
- data/lib/hecks/release/runner/console.rb +6 -0
- data/lib/hecks/release/runner/gem_publisher.rb +14 -7
- data/lib/hecks/release/runner/npm_publisher.rb +6 -36
- data/lib/hecks/release/runner/preflight.rb +26 -2
- data/lib/hecks/release/runner/published.rb +8 -26
- data/lib/hecks/release/runner/tagger.rb +30 -15
- data/lib/hecks/release/runner.rb +57 -6
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/runtime/adapter_lookup.rb +124 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +4 -0
- data/lib/hecks/runtime/dependency_planning.rb +20 -2
- data/lib/hecks/runtime/dispatcher.rb +53 -3
- data/lib/hecks/runtime/interpreting.rb +3 -0
- data/lib/hecks/runtime/loader.rb +65 -12
- data/lib/hecks/runtime/outbox.rb +1 -1
- data/lib/hecks/runtime/policy_interpreter.rb +5 -2
- data/lib/hecks/runtime/port_operation_interpreter.rb +2 -10
- data/lib/hecks/runtime/query_interpreter.rb +77 -0
- data/lib/hecks/runtime/reaction_outcome.rb +89 -0
- data/lib/hecks/runtime/refusal_wording.rb +23 -1
- data/lib/hecks/runtime/registry/verification.rb +163 -4
- data/lib/hecks/runtime/registry.rb +57 -3
- data/lib/hecks/runtime/remote_dispatcher.rb +31 -2
- data/lib/hecks/runtime/value/coercion.rb +23 -135
- data/lib/hecks/runtime/value/field_checks.rb +172 -0
- data/lib/hecks/runtime/value.rb +4 -1
- data/lib/hecks/runtime.rb +20 -6
- data/lib/hecks/rust_build/conformance.rb +158 -0
- data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
- data/lib/hecks/rust_build/coverage.rb +218 -0
- data/lib/hecks/rust_build/native_build.rb +124 -0
- data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
- data/lib/hecks/rust_build/project_rust.rb +332 -0
- data/lib/hecks/rust_build/wasm.rb +105 -0
- data/lib/hecks/rust_build/wasm_browser.rb +113 -0
- data/lib/hecks/rust_build.rb +144 -0
- data/lib/hecks/storehouse.rb +16 -16
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +2 -2
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +1 -1
- data/lib/hecks/three_zero/forms.yml +127 -0
- data/lib/hecks/three_zero.rb +12 -0
- data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
- data/lib/hecks/tools/comment_style.rb +1070 -0
- data/lib/hecks/tools/corpus_report.rb +87 -0
- data/lib/hecks/tools/deploy_recipe.rb +255 -0
- data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
- data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
- data/lib/hecks/tools/era_projection_backfill.rb +94 -0
- data/lib/hecks/tools/era_reattest.rb +146 -0
- data/lib/hecks/tools/evolve_run.rb +189 -0
- data/lib/hecks/tools/fuzz_sweep.rb +373 -0
- data/lib/hecks/tools/heki_compaction.rb +100 -0
- data/lib/hecks/tools/hoist_local_givens.rb +246 -0
- data/lib/hecks/tools/journal_compaction.rb +89 -0
- data/lib/hecks/tools/oidc_manifests.rb +70 -0
- data/lib/hecks/tools/query_ir_run.rb +81 -0
- data/lib/hecks/tools/regeneration_run.rb +166 -0
- data/lib/hecks/tools/rust_comment_style.rb +642 -0
- data/lib/hecks/tools/sequence_script.rb +62 -0
- data/lib/hecks/tools/tail_merge.rb +117 -0
- data/lib/hecks/tools/tenant_provisioning.rb +142 -0
- data/lib/hecks/tools/translation_audit.rb +208 -0
- data/lib/hecks/tools/translation_scaffold.rb +131 -0
- data/lib/hecks/tools.rb +86 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks.rb +32 -15
- data/qa/settings.yml +66 -0
- data/rust/.cargo/config.toml +6 -0
- data/rust/.gitignore +2 -0
- data/rust/Cargo.lock +54 -0
- data/rust/Cargo.toml +61 -0
- data/rust/build/.cargo/config.toml +3 -0
- data/rust/build/.gitignore +1 -0
- data/rust/build/Cargo.lock +7 -0
- data/rust/build/Cargo.toml +19 -0
- data/rust/build/src/build_artifact.rs +47 -0
- data/rust/build/src/cargo_sync.rs +248 -0
- data/rust/build/src/json.rs +421 -0
- data/rust/build/src/lineage_pass.rs +285 -0
- data/rust/build/src/main.rs +51 -0
- data/rust/build/src/optional_pass.rs +150 -0
- data/rust/build/src/pipeline.rs +264 -0
- data/rust/build/src/reserved_names.rs +76 -0
- data/rust/build/src/resolve.rs +124 -0
- data/rust/build/src/root.rs +25 -0
- data/rust/build/src/sidecars.rs +73 -0
- data/rust/build/src/subprocess.rs +51 -0
- data/rust/build/src/tmp.rs +31 -0
- data/rust/codegen/.cargo/config.toml +3 -0
- data/rust/codegen/.gitignore +1 -0
- data/rust/codegen/Cargo.lock +7 -0
- data/rust/codegen/Cargo.toml +20 -0
- data/rust/codegen/src/attr.rs +32 -0
- data/rust/codegen/src/bridging.rs +481 -0
- data/rust/codegen/src/commands.rs +1226 -0
- data/rust/codegen/src/constraints.rs +109 -0
- data/rust/codegen/src/dependency_planning.rs +209 -0
- data/rust/codegen/src/domain_generator.rs +1288 -0
- data/rust/codegen/src/exemplar.rs +230 -0
- data/rust/codegen/src/expr_emitter.rs +87 -0
- data/rust/codegen/src/fielded.rs +192 -0
- data/rust/codegen/src/hecks_naming.rs +33 -0
- data/rust/codegen/src/json.rs +335 -0
- data/rust/codegen/src/json_codec.rs +697 -0
- data/rust/codegen/src/literal.rs +207 -0
- data/rust/codegen/src/main.rs +290 -0
- data/rust/codegen/src/manifest.rs +227 -0
- data/rust/codegen/src/mutations.rs +784 -0
- data/rust/codegen/src/naming.rs +286 -0
- data/rust/codegen/src/ports.rs +208 -0
- data/rust/codegen/src/prelude.rs +116 -0
- data/rust/codegen/src/queries.rs +786 -0
- data/rust/codegen/src/reactions.rs +532 -0
- data/rust/codegen/src/read_models.rs +697 -0
- data/rust/codegen/src/reference_specs.rs +40 -0
- data/rust/codegen/src/registry.rs +966 -0
- data/rust/codegen/src/reserved_names.rs +76 -0
- data/rust/codegen/src/shared.rs +93 -0
- data/rust/codegen/src/skip_reason.rs +25 -0
- data/rust/codegen/src/types.rs +362 -0
- data/rust/host/.cargo/config.toml +3 -0
- data/rust/host/.gitignore +1 -0
- data/rust/host/Cargo.lock +4297 -0
- data/rust/host/Cargo.toml +61 -0
- data/rust/host/HECKS_RELEASE +1 -0
- data/rust/host/rds-ca-bundle.pem +2736 -0
- data/rust/host/src/api.rs +1596 -0
- data/rust/host/src/approval.rs +652 -0
- data/rust/host/src/auth.rs +1287 -0
- data/rust/host/src/bin/expr_harness.rs +46 -0
- data/rust/host/src/bin/lineage_harness.rs +119 -0
- data/rust/host/src/bin/mint_harness.rs +135 -0
- data/rust/host/src/checkout.rs +265 -0
- data/rust/host/src/dispatch.rs +1443 -0
- data/rust/host/src/expr_json.rs +1117 -0
- data/rust/host/src/field_hints.rs +52 -0
- data/rust/host/src/ir.rs +733 -0
- data/rust/host/src/journal.rs +874 -0
- data/rust/host/src/lambda_client.rs +378 -0
- data/rust/host/src/log.rs +147 -0
- data/rust/host/src/main.rs +474 -0
- data/rust/host/src/mint.rs +1865 -0
- data/rust/host/src/payments/keystore.rs +330 -0
- data/rust/host/src/payments/tests.rs +1400 -0
- data/rust/host/src/payments.rs +767 -0
- data/rust/host/src/presentation.rs +698 -0
- data/rust/host/src/presentation_write.rs +1431 -0
- data/rust/host/src/query_step.rs +149 -0
- data/rust/host/src/rate_limit.rs +904 -0
- data/rust/host/src/reference_transform.rs +241 -0
- data/rust/host/src/reference_validate.rs +338 -0
- data/rust/host/src/resend.rs +296 -0
- data/rust/host/src/secrets.rs +106 -0
- data/rust/host/src/server.rs +663 -0
- data/rust/host/src/storage_shape.rs +175 -0
- data/rust/host/src/test_pg.rs +40 -0
- data/rust/host/src/ui_schema.rs +1351 -0
- data/rust/host/src/wasm_runner.rs +191 -0
- data/rust/host/src/web/newsletter/cooldown.rs +126 -0
- data/rust/host/src/web/newsletter/tests.rs +104 -0
- data/rust/host/src/web/newsletter.rs +310 -0
- data/rust/host/src/web/newsletter_send.rs +357 -0
- data/rust/host/src/web/registrations.rs +1505 -0
- data/rust/host/src/web.rs +2937 -0
- data/rust/lsp/.gitignore +1 -0
- data/rust/lsp/Cargo.lock +7 -0
- data/rust/lsp/Cargo.toml +16 -0
- data/rust/lsp/README.md +133 -0
- data/rust/lsp/src/diagnostics.rs +165 -0
- data/rust/lsp/src/json.rs +385 -0
- data/rust/lsp/src/main.rs +399 -0
- data/rust/lsp/src/outline.rs +177 -0
- data/rust/lsp/src/rpc.rs +67 -0
- data/rust/parser/.cargo/config.toml +5 -0
- data/rust/parser/.gitignore +1 -0
- data/rust/parser/Cargo.lock +7 -0
- data/rust/parser/Cargo.toml +21 -0
- data/rust/parser/src/build/closed_sets.rs +27 -0
- data/rust/parser/src/build/identity.rs +276 -0
- data/rust/parser/src/build/mod.rs +12 -0
- data/rust/parser/src/build/naming.rs +141 -0
- data/rust/parser/src/build/pattern_subset.rs +161 -0
- data/rust/parser/src/build/query_derive.rs +72 -0
- data/rust/parser/src/build/query_inference.rs +269 -0
- data/rust/parser/src/build/query_options.rs +100 -0
- data/rust/parser/src/build/read_model.rs +94 -0
- data/rust/parser/src/build/references.rs +32 -0
- data/rust/parser/src/canonical.rs +114 -0
- data/rust/parser/src/diag.rs +55 -0
- data/rust/parser/src/emit.rs +953 -0
- data/rust/parser/src/expr/ast_json.rs +147 -0
- data/rust/parser/src/expr/evaluator.rs +81 -0
- data/rust/parser/src/expr/mod.rs +84 -0
- data/rust/parser/src/expr/resolver.rs +497 -0
- data/rust/parser/src/ir.rs +387 -0
- data/rust/parser/src/keywords.rs +585 -0
- data/rust/parser/src/lex.rs +662 -0
- data/rust/parser/src/main.rs +252 -0
- data/rust/parser/src/parse/aggregate.rs +612 -0
- data/rust/parser/src/parse/chapter.rs +517 -0
- data/rust/parser/src/parse/command.rs +637 -0
- data/rust/parser/src/parse/domain_port.rs +127 -0
- data/rust/parser/src/parse/entity.rs +527 -0
- data/rust/parser/src/parse/file.rs +67 -0
- data/rust/parser/src/parse/hecksagon.rs +231 -0
- data/rust/parser/src/parse/lifecycle.rs +162 -0
- data/rust/parser/src/parse/mod.rs +1262 -0
- data/rust/parser/src/parse/policy.rs +87 -0
- data/rust/parser/src/parse/process_manager.rs +305 -0
- data/rust/parser/src/parse/query.rs +100 -0
- data/rust/parser/src/parse/read_model.rs +214 -0
- data/rust/parser/src/parse/value_object.rs +250 -0
- data/rust/parser/src/ruby_value.rs +422 -0
- data/rust/project/bridging.rb +257 -0
- data/rust/project/commands.rb +877 -0
- data/rust/project/constraints.rb +86 -0
- data/rust/project/dependency_planning.rb +178 -0
- data/rust/project/domain_generator.rb +980 -0
- data/rust/project/exemplar.rb +148 -0
- data/rust/project/expr_emitter.rb +72 -0
- data/rust/project/fielded.rb +141 -0
- data/rust/project/json_codec.rb +641 -0
- data/rust/project/mutations.rb +721 -0
- data/rust/project/naming.rb +205 -0
- data/rust/project/ports.rb +65 -0
- data/rust/project/queries.rb +493 -0
- data/rust/project/reactions.rb +317 -0
- data/rust/project/read_models.rb +690 -0
- data/rust/project/reference_specs.rb +27 -0
- data/rust/project/registry.rb +396 -0
- data/rust/project/skip_reason.rb +29 -0
- data/rust/project/types.rb +225 -0
- data/rust/project/write_if_changed.rb +83 -0
- data/rust/project.rb +30 -0
- data/rust/project_rust_pipeline.rb +383 -0
- data/rust/src/exemplar/commands.rs +279 -0
- data/rust/src/exemplar/constraints.rs +18 -0
- data/rust/src/exemplar/fielded.rs +280 -0
- data/rust/src/exemplar/json.rs +277 -0
- data/rust/src/exemplar/mod.rs +17 -0
- data/rust/src/exemplar/mutations.rs +80 -0
- data/rust/src/exemplar/queries.rs +33 -0
- data/rust/src/exemplar/reactions.rs +88 -0
- data/rust/src/exemplar/read_models.rs +48 -0
- data/rust/src/exemplar/registry.rs +166 -0
- data/rust/src/exemplar/types.rs +44 -0
- data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
- data/rust/src/kernel/attribute_shapes/list.rs +18 -0
- data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
- data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
- data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
- data/rust/src/kernel/cli.rs +585 -0
- data/rust/src/kernel/dispatch.rs +1090 -0
- data/rust/src/kernel/expr.rs +458 -0
- data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
- data/rust/src/kernel/expression_operators/comparison.rs +48 -0
- data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
- data/rust/src/kernel/expression_operators/logical.rs +13 -0
- data/rust/src/kernel/expression_operators/membership.rs +18 -0
- data/rust/src/kernel/expression_operators/mod.rs +68 -0
- data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
- data/rust/src/kernel/expression_operators/positional.rs +126 -0
- data/rust/src/kernel/expression_operators/presence.rs +101 -0
- data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
- data/rust/src/kernel/expression_operators/sized.rs +35 -0
- data/rust/src/kernel/expression_operators/text.rs +79 -0
- data/rust/src/kernel/expression_operators/to_string.rs +18 -0
- data/rust/src/kernel/json.rs +779 -0
- data/rust/src/kernel/mod.rs +133 -0
- data/rust/src/kernel/named_query.rs +219 -0
- data/rust/src/kernel/orchestrate.rs +1810 -0
- data/rust/src/kernel/pattern.rs +307 -0
- data/rust/src/kernel/query_comparators.rs +206 -0
- data/rust/src/kernel/query_ordering.rs +103 -0
- data/rust/src/kernel/read_model.rs +914 -0
- data/rust/src/kernel/reference_lookup.rs +149 -0
- data/rust/src/kernel/repository.rs +468 -0
- data/rust/src/kernel/routing.rs +612 -0
- data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
- data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
- data/rust/src/kernel/vocab/field_hint.rs +70 -0
- data/rust/src/kernel/vocab/mod.rs +20 -0
- data/rust/src/kernel/vocab/query_comparator.rs +65 -0
- data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
- data/rust/src/lib.rs +8 -0
- data/rust/src/main.rs +16 -0
- data/rust/web/.gitignore +1 -0
- data/rust/web/Cargo.lock +119 -0
- data/rust/web/Cargo.toml +18 -0
- data/rust/web/src/lib.rs +8 -0
- metadata +477 -12
- data/lib/hecks/facade/cli_door.rb +0 -96
- data/lib/hecks/facade/cli_runner.rb +0 -127
- data/lib/hecks/mcp_door_scope.rb +0 -95
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Fuzzing
|
|
3
|
+
# Shrinks a failing step list to a small one that still reproduces the finding.
|
|
4
|
+
# The caller's block decides "same finding"; this module never replays anything.
|
|
5
|
+
#
|
|
6
|
+
# Pass 1 removes step chunks (delta debugging) until the list is 1-minimal.
|
|
7
|
+
# Pass 2 drops argument keys one at a time inside each surviving step.
|
|
8
|
+
# `budget:` caps candidate checks; when spent, the best candidate so far is returned.
|
|
9
|
+
module Shrinker
|
|
10
|
+
Result = Struct.new(:steps, :attempts, :exhausted, keyword_init: true)
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
# Shrinks `steps` to the smallest list the block still accepts as reproducing.
|
|
15
|
+
#
|
|
16
|
+
# @param steps [Array<Hash>] the step list to shrink
|
|
17
|
+
# @param budget [Integer, nil] maximum candidate checks to spend; nil for unbounded
|
|
18
|
+
# @yieldparam candidate [Array<Hash>] a step-dropped or argument-trimmed copy of `steps`
|
|
19
|
+
# @yieldreturn [Boolean] true if `candidate` still reproduces the finding
|
|
20
|
+
# @return [Fuzzing::Shrinker::Result] `steps:` the shrunk list, `attempts:` checks
|
|
21
|
+
# spent, `exhausted:` whether the budget ran out
|
|
22
|
+
# @raise [ArgumentError] if no block is given
|
|
23
|
+
def call(steps, budget: nil, &reproduces)
|
|
24
|
+
raise ArgumentError, "Shrinker.call needs a block answering whether a candidate reproduces" unless reproduces
|
|
25
|
+
|
|
26
|
+
meter = Meter.new(budget)
|
|
27
|
+
current = drop_steps(steps.dup, meter, &reproduces)
|
|
28
|
+
current = drop_arguments(current, meter, &reproduces)
|
|
29
|
+
Result.new(steps: current, attempts: meter.used, exhausted: meter.exhausted?)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def drop_steps(steps, meter, &reproduces)
|
|
33
|
+
current = steps
|
|
34
|
+
chunk = [current.length / 2, 1].max
|
|
35
|
+
loop do
|
|
36
|
+
changed = false
|
|
37
|
+
index = 0
|
|
38
|
+
while index < current.length
|
|
39
|
+
return current if meter.exhausted?
|
|
40
|
+
|
|
41
|
+
candidate = current[0...index] + (current[(index + chunk)..] || [])
|
|
42
|
+
if candidate.empty?
|
|
43
|
+
index += chunk
|
|
44
|
+
next
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
if meter.try { reproduces.call(candidate) }
|
|
48
|
+
current = candidate
|
|
49
|
+
changed = true
|
|
50
|
+
else
|
|
51
|
+
index += chunk
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
if chunk > 1
|
|
56
|
+
chunk = [chunk / 2, 1].max
|
|
57
|
+
elsif !changed
|
|
58
|
+
break
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
current
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def drop_arguments(steps, meter, &reproduces)
|
|
65
|
+
steps.each_index do |position|
|
|
66
|
+
original = args_of(steps[position])
|
|
67
|
+
next unless original.is_a?(Hash)
|
|
68
|
+
|
|
69
|
+
original.each_key do |key|
|
|
70
|
+
return steps if meter.exhausted?
|
|
71
|
+
|
|
72
|
+
step = steps[position]
|
|
73
|
+
trimmed = args_of(step).reject { |name, _| name == key }
|
|
74
|
+
candidate = steps.map(&:dup)
|
|
75
|
+
candidate[position] = step.merge("args" => trimmed)
|
|
76
|
+
steps = candidate if meter.try { reproduces.call(candidate) }
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
steps
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# `key?` first, never `||`, which cannot tell a stored `false` from an absent key.
|
|
83
|
+
def args_of(step)
|
|
84
|
+
step.key?("args") ? step["args"] : step[:args]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
LIST_FIELDS = %w[refusals queries dry_runs reactions].freeze
|
|
88
|
+
|
|
89
|
+
CRASH_FIELDS = %w[crash process generator_crash].freeze
|
|
90
|
+
|
|
91
|
+
# The identity a shrink candidate has to keep, as a set of stable strings.
|
|
92
|
+
#
|
|
93
|
+
# `field` alone is too loose for list comparisons: a candidate could show a different
|
|
94
|
+
# refusal split and the shrinker would hand back the wrong bug. So list fields add the
|
|
95
|
+
# verbs in the symmetric difference, `instances` adds the differing aggregates, and
|
|
96
|
+
# crash fields add the exception class.
|
|
97
|
+
#
|
|
98
|
+
# @param divergences [Array<Hash>] entries carrying at least `{field:, detail:}`
|
|
99
|
+
# @return [Set<String>] identity strings
|
|
100
|
+
def signature(divergences)
|
|
101
|
+
divergences.each_with_object(Set.new) do |divergence, keys|
|
|
102
|
+
field = divergence[:field].to_s
|
|
103
|
+
keys << field
|
|
104
|
+
keys.merge(detail_keys(field, divergence))
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def detail_keys(field, divergence)
|
|
109
|
+
left, right = divergence.except(:field, :detail).values.select { |v| v.is_a?(Array) || v.is_a?(Hash) }
|
|
110
|
+
if LIST_FIELDS.include?(field) then list_keys(field, left, right)
|
|
111
|
+
elsif field == "instances" then instance_keys(left, right)
|
|
112
|
+
elsif CRASH_FIELDS.include?(field) && divergence[:detail]
|
|
113
|
+
["#{field}:#{divergence[:detail].to_s[/\A\w+(?:::\w+)*/]}"]
|
|
114
|
+
else []
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def list_keys(field, left, right)
|
|
119
|
+
return [] unless left.is_a?(Array) && right.is_a?(Array)
|
|
120
|
+
|
|
121
|
+
((left - right) + (right - left)).map { |row| "#{field}:#{named(row)}" }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# The `#id` suffix is dropped: the generator mints ids, so they name the record,
|
|
125
|
+
# not the finding, and keeping them would pin every creating step.
|
|
126
|
+
def instance_keys(left, right)
|
|
127
|
+
return [] unless left.is_a?(Hash) && right.is_a?(Hash)
|
|
128
|
+
|
|
129
|
+
(left.keys | right.keys).reject { |key| left[key] == right[key] }
|
|
130
|
+
.map { |key| "instances:#{key.to_s.split('#').first}" }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# True when the candidate's signature contains the original's.
|
|
134
|
+
# Removing steps may add a second divergence but must never lose the one being shrunk.
|
|
135
|
+
def reproduces?(original_signature, divergences)
|
|
136
|
+
!divergences.empty? && original_signature.subset?(signature(divergences))
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def named(row)
|
|
140
|
+
return row.to_s unless row.is_a?(Hash)
|
|
141
|
+
|
|
142
|
+
key = [%w[verb query policy], %i[verb query policy]].flatten.find { |name| row.key?(name) }
|
|
143
|
+
(key ? row[key] : row).to_s
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Counts candidate checks against the budget.
|
|
147
|
+
class Meter
|
|
148
|
+
attr_reader :used
|
|
149
|
+
|
|
150
|
+
# @param budget [Integer, nil] maximum candidate checks to allow; nil for unbounded
|
|
151
|
+
def initialize(budget)
|
|
152
|
+
@budget = budget
|
|
153
|
+
@used = 0
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def exhausted? = !@budget.nil? && @used >= @budget
|
|
157
|
+
|
|
158
|
+
# Spends one check against the budget and yields.
|
|
159
|
+
def try
|
|
160
|
+
@used += 1
|
|
161
|
+
yield
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Fuzzing
|
|
3
|
+
# Names the construct family behind each query verb the Ruby/Rust comparison skipped.
|
|
4
|
+
# The construct is read from the binary's manifest entry, not guessed from the declaration.
|
|
5
|
+
module StructuralSkips
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
# Attributes each skipped verb to the construct family its manifest entry names.
|
|
9
|
+
# A verb the manifest does not declare answers `unknown`, which no boundary admits.
|
|
10
|
+
#
|
|
11
|
+
# @param gaps [Fuzzing::RustGapManifest] the binary's not-generated manifest entries
|
|
12
|
+
# @param verbs [Array<String>] the skipped query/read-model verbs to attribute
|
|
13
|
+
# @return [Array<Hash{Symbol => Object}>] one `{ verb:, constructs: }` per verb,
|
|
14
|
+
# sorted by verb
|
|
15
|
+
def attribute(gaps, verbs)
|
|
16
|
+
verbs.sort.map do |verb|
|
|
17
|
+
entry = gaps.not_generated(verb)
|
|
18
|
+
{ verb: verb, constructs: entry ? [entry.fetch("construct")] : %w[unknown] }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Selects the attributed entries whose constructs are not all inside `boundary`.
|
|
23
|
+
#
|
|
24
|
+
# @param attributed [Array<Hash{Symbol => Object}>] entries as returned by `attribute`
|
|
25
|
+
# @param boundary [Array<String, Symbol>] the construct families the codegen boundary admits
|
|
26
|
+
# @return [Array<Hash{Symbol => Object}>] the entries naming an unadmitted construct
|
|
27
|
+
def outside_boundary(attributed, boundary)
|
|
28
|
+
admitted = boundary.map(&:to_s)
|
|
29
|
+
attributed.select { |entry| entry[:constructs].empty? || (entry[:constructs] - admitted).any? }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Fuzzing
|
|
3
|
+
# Maps a QA target's clean streak to how hard the next sweep fuzzes.
|
|
4
|
+
module SweepDepth
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
# Fallback tiers for a ledger with no QualityControlDials configured.
|
|
8
|
+
DEFAULT_TIERS = [
|
|
9
|
+
{ upto: 4, seeds: 10, steps: 25 },
|
|
10
|
+
{ upto: 19, seeds: 25, steps: 50 },
|
|
11
|
+
{ upto: Float::INFINITY, seeds: 50, steps: 100 }
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
# Rows are read in order; the last row must keep upto: Float::INFINITY.
|
|
15
|
+
def for_streak(streak, tiers: DEFAULT_TIERS)
|
|
16
|
+
raise ArgumentError, "streak must not be negative" if streak.negative?
|
|
17
|
+
|
|
18
|
+
tier = tiers.find { |row| streak <= row[:upto] }
|
|
19
|
+
raise ArgumentError, "no tier covers a streak of #{streak} — the last row must be upto: Float::INFINITY" unless tier
|
|
20
|
+
|
|
21
|
+
[tier[:seeds], tier[:steps]]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# The first seed a streak-widened sweep counts from. Once a target's streak has widened past
|
|
25
|
+
# the ceiling tier, `for_streak` keeps returning the same seed count forever; sweeping
|
|
26
|
+
# `1..seeds` every time would regenerate the identical sequences on every tick. Advancing
|
|
27
|
+
# this with the streak keeps the swept range moving into seed integers no earlier tick of
|
|
28
|
+
# this streak ever reached, without a new persisted field: `streak` already ties consecutive
|
|
29
|
+
# tiers' ranges back to back (each tier's width times its own streak count), and a streak
|
|
30
|
+
# reset (back to 0) lands this back at 0 too, so a fresh or just-released target still sweeps
|
|
31
|
+
# the familiar `1..seeds`.
|
|
32
|
+
#
|
|
33
|
+
# @param streak [Integer] `Target#clean_streak`, non-negative
|
|
34
|
+
# @param seeds [Integer] the seed count `for_streak` resolved for this same streak
|
|
35
|
+
# @return [Integer] the offset; the sweep should cover `(offset + 1)..(offset + seeds)`
|
|
36
|
+
def seed_offset(streak, seeds)
|
|
37
|
+
raise ArgumentError, "streak must not be negative" if streak.negative?
|
|
38
|
+
|
|
39
|
+
streak * seeds
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Fuzzing
|
|
3
|
+
# Infers what a sweep target can be checked for from its directory, not a stored list.
|
|
4
|
+
# `modes_to_run = enabled ∩ eligible`: `infer` finds capabilities, `resolve` applies it.
|
|
5
|
+
module TargetCapabilities
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
# Matches the `[features]` table only, so `[package] name = "rust"` never reads as a feature.
|
|
9
|
+
FEATURES_TABLE = /^\[features\](?:\n(?!\[).*)*$/
|
|
10
|
+
|
|
11
|
+
# Both spellings: aggregate-scoped `.persisted_by("PostgresEra")` and the bare
|
|
12
|
+
# domain-level `persisted_by "PostgresEra"`.
|
|
13
|
+
POSTGRES_ERA_BINDING = /persisted_by\s*\(?\s*"PostgresEra"/
|
|
14
|
+
|
|
15
|
+
# A local assigned the bare literal on its own line; any other right-hand side is not one.
|
|
16
|
+
POSTGRES_ERA_VARIABLE = /^\s*([a-z_]\w*)\s*=\s*"PostgresEra"\s*(?:#.*)?$/
|
|
17
|
+
|
|
18
|
+
# Captures the member a hecksagon attaches; authorization is read off its declaration.
|
|
19
|
+
FRAMEWORK_ATTACHED = /uses_framework\s*\(?\s*"([^"]+)"/
|
|
20
|
+
|
|
21
|
+
# A command-level `role "..."`, the only construct a role check can compare a caller against.
|
|
22
|
+
ROLE_GATED = /^\s*role\s+"/
|
|
23
|
+
|
|
24
|
+
TENANT_SCOPED = /\btenant:/
|
|
25
|
+
|
|
26
|
+
PROCESS_MANAGER = /^\s*process_manager\s+"/
|
|
27
|
+
|
|
28
|
+
# Capabilities each mode needs; an empty list means any target.
|
|
29
|
+
# `resolve` drops `ruby_only` when `differential` resolves too: same seat, Rust is better.
|
|
30
|
+
MODE_REQUIREMENTS = {
|
|
31
|
+
differential: %w[rust],
|
|
32
|
+
ruby_only: [],
|
|
33
|
+
self_consistency: [],
|
|
34
|
+
properties_in_differential: %w[rust],
|
|
35
|
+
structural_skip_report: %w[rust],
|
|
36
|
+
adapter_parity_sqlite: %w[sqlite],
|
|
37
|
+
persistence_parity: %w[postgres_era],
|
|
38
|
+
adapter_parity_postgres: %w[postgres_era],
|
|
39
|
+
era_boundary: %w[translations postgres_era],
|
|
40
|
+
concurrency: %w[postgres_era],
|
|
41
|
+
wasm_front: %w[rust]
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
# The modes `hecks quality_control ask run` can run; one absent here is refused at start, not
|
|
45
|
+
# resolved.
|
|
46
|
+
RUNNABLE_MODES = %i[differential ruby_only self_consistency properties_in_differential
|
|
47
|
+
structural_skip_report adapter_parity_sqlite persistence_parity
|
|
48
|
+
era_boundary concurrency].freeze
|
|
49
|
+
|
|
50
|
+
# Expensive passes run only when named (`--modes`) or as `--all`'s second wave.
|
|
51
|
+
DEFERRED_MODES = %i[persistence_parity adapter_parity_postgres era_boundary concurrency].freeze
|
|
52
|
+
|
|
53
|
+
# Reads a target directory to find out which capabilities it actually has.
|
|
54
|
+
#
|
|
55
|
+
# @param domain_path [String] filesystem path to the target domain's directory
|
|
56
|
+
# @param rust_dir [String] path to the Rust project root, checked for a matching
|
|
57
|
+
# Cargo feature; defaults to this repo's own `rust/` directory
|
|
58
|
+
# @return [Array<String>] the target's capabilities, sorted; a subset of
|
|
59
|
+
# `sqlite`, `rust`, `postgres_era`, `translations`, `governance`, `role_gated`,
|
|
60
|
+
# `tenant`, `sagas`
|
|
61
|
+
def infer(domain_path, rust_dir: File.expand_path("../../../rust", __dir__))
|
|
62
|
+
capabilities = %w[sqlite]
|
|
63
|
+
capabilities << "rust" if rust_feature?(domain_path, rust_dir)
|
|
64
|
+
capabilities << "postgres_era" if postgres_era_bound?(domain_path)
|
|
65
|
+
capabilities << "translations" if Dir.glob(File.join(domain_path, "**", "translations", "*.bluebook")).any?
|
|
66
|
+
capabilities << "governance" if authorization_attached?(domain_path)
|
|
67
|
+
capabilities << "role_gated" if any_file?(domain_path, "*.bluebook", ROLE_GATED)
|
|
68
|
+
capabilities << "tenant" if any_file?(domain_path, "*.bluebook", TENANT_SCOPED)
|
|
69
|
+
capabilities << "sagas" if any_file?(domain_path, "*.bluebook", PROCESS_MANAGER)
|
|
70
|
+
capabilities.sort
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Answers whether `mode` can run against a target with `capabilities`.
|
|
74
|
+
#
|
|
75
|
+
# @param mode [Symbol, String] a key of `MODE_REQUIREMENTS`, such as `:differential`
|
|
76
|
+
# @param capabilities [Array<String>] the target's capabilities, as `infer` returns
|
|
77
|
+
# @return [Boolean] whether `capabilities` covers every capability `mode` requires
|
|
78
|
+
# @raise [ArgumentError] if `mode` names no entry in `MODE_REQUIREMENTS`
|
|
79
|
+
def eligible?(mode, capabilities)
|
|
80
|
+
required = MODE_REQUIREMENTS.fetch(mode.to_sym) { raise ArgumentError, "unknown sweep mode #{mode.inspect}" }
|
|
81
|
+
(required - capabilities).empty?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Filters the enabled modes down to those the target is eligible for, keeping their order.
|
|
85
|
+
#
|
|
86
|
+
# @param enabled [Array<Symbol, String>] modes turned on, in the dial's own
|
|
87
|
+
# declaration order
|
|
88
|
+
# @param capabilities [Array<String>] the target's capabilities, as `infer` returns
|
|
89
|
+
# @return [Array<Symbol>] `enabled`'s eligible modes, in `enabled`'s order, with
|
|
90
|
+
# `:ruby_only` dropped whenever `:differential` is also eligible
|
|
91
|
+
def resolve(enabled, capabilities)
|
|
92
|
+
resolved = enabled.map(&:to_sym).select { |mode| eligible?(mode, capabilities) }
|
|
93
|
+
resolved.delete(:ruby_only) if resolved.include?(:differential)
|
|
94
|
+
resolved
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Answers whether `rust_dir`'s Cargo.toml declares a feature named after
|
|
98
|
+
# `domain_path`'s own directory.
|
|
99
|
+
#
|
|
100
|
+
# @param domain_path [String] filesystem path to the target domain's directory;
|
|
101
|
+
# its basename, lowercased, is the feature name looked up
|
|
102
|
+
# @param rust_dir [String] path to the Rust project root (holds `Cargo.toml`)
|
|
103
|
+
# @return [Boolean] whether a matching Cargo feature is declared; false if
|
|
104
|
+
# `rust_dir` has no `Cargo.toml`
|
|
105
|
+
def rust_feature?(domain_path, rust_dir)
|
|
106
|
+
cargo_toml = File.join(rust_dir, "Cargo.toml")
|
|
107
|
+
return false unless File.file?(cargo_toml)
|
|
108
|
+
|
|
109
|
+
feature = File.basename(domain_path).downcase
|
|
110
|
+
features = File.read(cargo_toml)[FEATURES_TABLE] || ""
|
|
111
|
+
features.match?(/^#{Regexp.escape(feature)}\s*=\s*\[\]/)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Answers whether any `.hecksagon` under `domain_path` binds PostgresEra,
|
|
115
|
+
# by literal name or through a local variable assigned `"PostgresEra"`.
|
|
116
|
+
#
|
|
117
|
+
# @param domain_path [String] filesystem path to the target domain's directory
|
|
118
|
+
# @return [Boolean] whether a PostgresEra `persisted_by` binding is found
|
|
119
|
+
def postgres_era_bound?(domain_path)
|
|
120
|
+
Dir.glob(File.join(domain_path, "**", "*.hecksagon")).any? do |path|
|
|
121
|
+
text = File.read(path)
|
|
122
|
+
text.match?(POSTGRES_ERA_BINDING) || postgres_era_variable_bound?(text)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Answers whether `text` passes a variable assigned `"PostgresEra"` to `persisted_by`.
|
|
127
|
+
#
|
|
128
|
+
# @param text [String] the contents of one `.hecksagon` file
|
|
129
|
+
# @return [Boolean] whether some `persisted_by` call names such a variable
|
|
130
|
+
def postgres_era_variable_bound?(text)
|
|
131
|
+
text.scan(POSTGRES_ERA_VARIABLE).flatten.any? do |name|
|
|
132
|
+
text.match?(/persisted_by\s*\(?\s*#{Regexp.escape(name)}(?!\w)/)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Answers whether any `.hecksagon` under `domain_path` attaches a member
|
|
137
|
+
# that provides authorization.
|
|
138
|
+
#
|
|
139
|
+
# The capability label stays "governance" because it is the value stored in the ledger.
|
|
140
|
+
#
|
|
141
|
+
# @param domain_path [String] filesystem path to the target domain's directory
|
|
142
|
+
# @return [Boolean] whether the domain attaches a member that provides the
|
|
143
|
+
# `Bluebook::Capabilities::AUTHORIZATION` capability
|
|
144
|
+
def authorization_attached?(domain_path)
|
|
145
|
+
attached = Dir.glob(File.join(domain_path, "**", "*.hecksagon"))
|
|
146
|
+
.flat_map { |path| File.read(path).scan(FRAMEWORK_ATTACHED).flatten }.uniq
|
|
147
|
+
providers = Framework.providers_of(Bluebook::Capabilities::AUTHORIZATION)
|
|
148
|
+
attached.intersect?(providers)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Answers whether any file under `domain_path` matching `glob` contains `pattern`.
|
|
152
|
+
#
|
|
153
|
+
# @param domain_path [String] filesystem path to the target domain's directory
|
|
154
|
+
# @param glob [String] a `Dir.glob` pattern relative to `domain_path`, such as
|
|
155
|
+
# `"*.bluebook"`
|
|
156
|
+
# @param pattern [Regexp] the pattern each matching file's contents is tested against
|
|
157
|
+
# @return [Boolean] whether any matching file's contents match `pattern`
|
|
158
|
+
def any_file?(domain_path, glob, pattern)
|
|
159
|
+
Dir.glob(File.join(domain_path, "**", glob)).any? { |path| File.read(path).match?(pattern) }
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
require_relative "../runtime/value"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Fuzzing
|
|
5
|
+
# Generates one JSON-safe value per attribute, in the shape the hand-written corpus uses.
|
|
6
|
+
# Edge cases are frequent but not dominant; name-aware shapes reach deep state.
|
|
7
|
+
module ValueGenerator
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
EDGE_CASE_PROBABILITY = 0.2
|
|
11
|
+
INVALID_MEMBER_PROBABILITY = 0.1
|
|
12
|
+
INVALID_REFERENCE_PROBABILITY = 0.2
|
|
13
|
+
# Exercises the bare-scalar form a single-field value object accepts.
|
|
14
|
+
BARE_SCALAR_PROBABILITY = 0.2
|
|
15
|
+
|
|
16
|
+
STRING_EDGE_CASES = [
|
|
17
|
+
"", "x", "with \"quotes\"", "with, a comma", "with a \\backslash",
|
|
18
|
+
"unicode héllo wörld 🎉", "x" * 200, " leading and trailing "
|
|
19
|
+
].freeze
|
|
20
|
+
# Includes Bignums past i64, which a Rust kernel value cannot represent (`kernel/json.rs`).
|
|
21
|
+
INTEGER_EDGE_CASES = [0, -1, 2_147_483_647, -2_147_483_648, 2**100, -(2**100)].freeze
|
|
22
|
+
# Clock- and count-shaped fields stay under f64's exact-integer ceiling: `Json::Num` is an
|
|
23
|
+
# f64, so a Bignum there only re-hits the known precision-loss class.
|
|
24
|
+
SAFE_INTEGER_EDGE_CASES = [0, -1, 2_147_483_647, -2_147_483_648].freeze
|
|
25
|
+
# Tested against the value object's name plus the attribute's name; deliberately narrow, so
|
|
26
|
+
# "amount"/"cents"/"sequence" keep the full Bignum pool.
|
|
27
|
+
CLOCK_OR_COUNT_NAME_PATTERN = /clock|instant|expir|ttl|\bnow\b|timestamp|epoch|count/i
|
|
28
|
+
# NaN and infinities are safe to generate; -0.0 covers signed zero.
|
|
29
|
+
FLOAT_EDGE_CASES = [0.0, -0.0, -0.5, -100.25, Float::NAN, Float::INFINITY, -Float::INFINITY].freeze
|
|
30
|
+
WORDS = %w[alpha bravo charlie delta echo foxtrot golf hotel india juliet].freeze
|
|
31
|
+
CURRENCY_CODES = %w[USD EUR GBP JPY].freeze
|
|
32
|
+
|
|
33
|
+
# The value for one command/query/value-object attribute.
|
|
34
|
+
#
|
|
35
|
+
# `context` carries the enclosing value object's name, since `EmailAddress`'s `address` field
|
|
36
|
+
# only looks email-shaped through the name of the object around it.
|
|
37
|
+
#
|
|
38
|
+
# @param attribute [Bluebook::Attribute] the attribute to generate a value for
|
|
39
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate `attribute` belongs to
|
|
40
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
41
|
+
# @param known_ids [Hash{String => Array<String>}] real ids by aggregate or entity name
|
|
42
|
+
# @param context [String, nil] the enclosing value object's name; nil at the top level
|
|
43
|
+
# @return [String, Integer, Float, Boolean, Hash] a JSON-safe value: a nested Hash for a
|
|
44
|
+
# value-object type, a bare id String for a reference, a primitive otherwise
|
|
45
|
+
def value_for(attribute, aggregate, random:, known_ids: {}, context: nil)
|
|
46
|
+
return reference_value(attribute, random: random, known_ids: known_ids) if attribute.reference?
|
|
47
|
+
|
|
48
|
+
# Same lookup the runtime uses: an identity value object may live on a sibling aggregate.
|
|
49
|
+
value_object = Runtime::Value.value_object_for(aggregate, attribute.type.to_s)
|
|
50
|
+
return object_for(value_object, aggregate, random: random, known_ids: known_ids) if value_object
|
|
51
|
+
|
|
52
|
+
primitive(attribute.type.to_s, random: random, name: "#{context} #{attribute.name}")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Generates a nested value for a whole value object: a random admitted
|
|
56
|
+
# row for a closed set, or a value per declared attribute otherwise —
|
|
57
|
+
# occasionally unwrapped to a bare scalar for a genuinely single-field
|
|
58
|
+
# value object.
|
|
59
|
+
#
|
|
60
|
+
# @param value_object [Bluebook::ValueObject] the value object to generate a value for
|
|
61
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate `value_object` is reached from
|
|
62
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
63
|
+
# @param known_ids [Hash{String => Array<String>}] real ids by aggregate or entity name
|
|
64
|
+
# @return [Hash, Object] `{field name => value, ...}`, or the lone value when the
|
|
65
|
+
# value object has one field and the bare-scalar draw hits
|
|
66
|
+
def object_for(value_object, aggregate, random:, known_ids:)
|
|
67
|
+
fields =
|
|
68
|
+
if value_object.closed_set? && !value_object.members.empty?
|
|
69
|
+
return invalid_member(value_object, random: random) if random.rand < INVALID_MEMBER_PROBABILITY
|
|
70
|
+
|
|
71
|
+
member = value_object.members.sample(random: random)
|
|
72
|
+
member.to_h { |field, value| [field.to_s, value] }
|
|
73
|
+
else
|
|
74
|
+
value_object.attributes.to_h do |field|
|
|
75
|
+
[field.name.to_s,
|
|
76
|
+
value_for(field, aggregate, random: random, known_ids: known_ids, context: value_object.hecks_name)]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Unwrap only a genuinely single-field value object, never `invalid_member`: a wrong
|
|
81
|
+
# combination stays a Hash so its wrongness is what gets exercised.
|
|
82
|
+
return fields.values.first if fields.size == 1 && random.rand < BARE_SCALAR_PROBABILITY
|
|
83
|
+
|
|
84
|
+
fields
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# A combination that (almost certainly) isn't one of the closed set's admitted rows,
|
|
88
|
+
# to exercise the refusal a `one_of` exists to enforce.
|
|
89
|
+
#
|
|
90
|
+
# @param value_object [Bluebook::ValueObject] the closed-set value object to
|
|
91
|
+
# generate a non-admitted combination for
|
|
92
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
93
|
+
# @return [Hash] `{field name => value, ...}` for every declared attribute, each
|
|
94
|
+
# drawn independently rather than sampled from an admitted row
|
|
95
|
+
def invalid_member(value_object, random:)
|
|
96
|
+
value_object.attributes.to_h do |field|
|
|
97
|
+
[field.name.to_s, primitive(field.type.to_s, random: random, name: field.name.to_s)]
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# The ID itself, unwrapped: the payload gate refuses `{"value" => id}` for a reference.
|
|
102
|
+
#
|
|
103
|
+
# @param attribute [Bluebook::Attribute] the reference-typed attribute to
|
|
104
|
+
# generate a value for
|
|
105
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
106
|
+
# @param known_ids [Hash{String => Array<String>}] known real ids, keyed by
|
|
107
|
+
# aggregate or entity name; looked up under `attribute.type.target_name`
|
|
108
|
+
# @return [String] a real id drawn from the matching pool most of the time; a
|
|
109
|
+
# fabricated `"missing-..."` id when the pool is empty or the invalid-reference
|
|
110
|
+
# draw hits
|
|
111
|
+
def reference_value(attribute, random:, known_ids:)
|
|
112
|
+
pool = known_ids[attribute.type.target_name.to_s] || []
|
|
113
|
+
return "missing-#{random.bytes(4).unpack1('H*')}" if pool.empty? || random.rand < INVALID_REFERENCE_PROBABILITY
|
|
114
|
+
|
|
115
|
+
pool.sample(random: random)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Generates a value for one Ruby-primitive-typed attribute.
|
|
119
|
+
#
|
|
120
|
+
# @param type_name [String] the primitive type name: `"String"`, `"Integer"`,
|
|
121
|
+
# `"Float"`, `"Boolean"`, `"TrueClass"`, or `"FalseClass"`
|
|
122
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
123
|
+
# @param name [String, nil] a name hint (attribute name, optionally
|
|
124
|
+
# context-prefixed) that draws an email-/currency-shaped string or a
|
|
125
|
+
# clock/count-shaped integer
|
|
126
|
+
# @return [String, Integer, Float, Boolean] a value of the type `type_name` names
|
|
127
|
+
# @raise [ArgumentError] if `type_name` names anything else
|
|
128
|
+
def primitive(type_name, random:, name: nil)
|
|
129
|
+
case type_name
|
|
130
|
+
when "String" then string_value(random, name: name)
|
|
131
|
+
when "Integer" then integer_value(random, name: name)
|
|
132
|
+
when "Float" then float_value(random)
|
|
133
|
+
when "TrueClass", "FalseClass", "Boolean" then random.rand(2).zero?
|
|
134
|
+
else raise ArgumentError, "ValueGenerator does not know primitive type #{type_name.inspect}"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Generates a String value, name-aware for shapes an invariant needs (email, currency).
|
|
139
|
+
#
|
|
140
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
141
|
+
# @param name [String, nil] a name hint; an `"email"` or `"currency"` match
|
|
142
|
+
# draws a shaped value instead of random words
|
|
143
|
+
# @return [String] an edge-case string, an email address, a currency code, or
|
|
144
|
+
# 1-3 random words joined by spaces
|
|
145
|
+
def string_value(random, name: nil)
|
|
146
|
+
return STRING_EDGE_CASES.sample(random: random) if random.rand < EDGE_CASE_PROBABILITY
|
|
147
|
+
return email_value(random) if name&.match?(/email/i)
|
|
148
|
+
return CURRENCY_CODES.sample(random: random) if name&.match?(/currency/i)
|
|
149
|
+
|
|
150
|
+
Array.new(random.rand(1..3)) { WORDS.sample(random: random) }.join(" ")
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Generates a fabricated, syntactically valid email address.
|
|
154
|
+
#
|
|
155
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
156
|
+
# @return [String] a `"word@word.example"` address
|
|
157
|
+
def email_value(random)
|
|
158
|
+
"#{WORDS.sample(random: random)}@#{WORDS.sample(random: random)}.example"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Generates an Integer value, skewed positive so `positive?`-style invariants are reachable.
|
|
162
|
+
# Zero and negatives still come through the edge-case pool.
|
|
163
|
+
#
|
|
164
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
165
|
+
# @param name [String, nil] a name hint; a clock/count-shaped match narrows
|
|
166
|
+
# the edge-case pool to `SAFE_INTEGER_EDGE_CASES`
|
|
167
|
+
# @return [Integer] an edge-case integer sometimes, else a random count in 1..1000
|
|
168
|
+
def integer_value(random, name: nil)
|
|
169
|
+
if random.rand < EDGE_CASE_PROBABILITY
|
|
170
|
+
pool = clock_or_count_shaped?(name) ? SAFE_INTEGER_EDGE_CASES : INTEGER_EDGE_CASES
|
|
171
|
+
return pool.sample(random: random)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
random.rand(1..1000)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Reports whether a field name reads as a clock reading or a policy-capped count.
|
|
178
|
+
#
|
|
179
|
+
# @param name [String, nil] the name to test
|
|
180
|
+
# @return [Boolean] true if `name` matches the clock/count name pattern
|
|
181
|
+
def clock_or_count_shaped?(name)
|
|
182
|
+
name.to_s.match?(CLOCK_OR_COUNT_NAME_PATTERN)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Generates a Float value.
|
|
186
|
+
#
|
|
187
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
188
|
+
# @return [Float] an edge-case float sometimes, else a random value in
|
|
189
|
+
# 0.01..1000.0, rounded to 2 decimal places
|
|
190
|
+
def float_value(random)
|
|
191
|
+
return FLOAT_EDGE_CASES.sample(random: random) if random.rand < EDGE_CASE_PROBABILITY
|
|
192
|
+
|
|
193
|
+
random.rand(0.01..1000.0).round(2)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# The bare scalar a generated identity value stands for, for recording into `known_ids`.
|
|
197
|
+
# A reference to this record is already that scalar; the two are not interchangeable.
|
|
198
|
+
#
|
|
199
|
+
# @param identity_value [Hash, Object] a generated identity value, as `value_for`
|
|
200
|
+
# produced it — a Hash for a multi-field value object, or the bare scalar already
|
|
201
|
+
# for a single-field one
|
|
202
|
+
# @return [String] the identity's bare scalar, stringified
|
|
203
|
+
def scalar_of(identity_value)
|
|
204
|
+
identity_value.is_a?(Hash) ? identity_value.values.first.to_s : identity_value.to_s
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Generates a fabricated id unrelated to anything `known_ids` tracks.
|
|
208
|
+
#
|
|
209
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
210
|
+
# @return [String] a `"gen-"`-prefixed id with 8 random hex characters
|
|
211
|
+
def random_id(random)
|
|
212
|
+
"gen-#{random.bytes(4).unpack1('H*')}"
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
# The fuzzing toolkit hecks fuzz and hecks generate_sequence drive. Not required by
|
|
3
|
+
# lib/hecks.rb on purpose — a booted domain never needs it.
|
|
4
|
+
module Fuzzing
|
|
5
|
+
end
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
require_relative "fuzzing/value_generator"
|
|
9
|
+
require_relative "fuzzing/invalid_value_generator"
|
|
10
|
+
require_relative "fuzzing/sequence_generator"
|
|
11
|
+
require_relative "fuzzing/nondeterministic"
|
|
12
|
+
require_relative "fuzzing/replay"
|
|
13
|
+
require_relative "fuzzing/shrinker"
|
|
14
|
+
require_relative "fuzzing/properties"
|
|
15
|
+
require_relative "fuzzing/qa_settings"
|
|
16
|
+
require_relative "fuzzing/rotation_priority"
|
|
17
|
+
require_relative "fuzzing/sweep_depth"
|
|
18
|
+
require_relative "fuzzing/persistence_parity"
|
|
19
|
+
require_relative "fuzzing/form_census"
|
|
20
|
+
require_relative "fuzzing/target_capabilities"
|
|
21
|
+
require_relative "fuzzing/rust_gap_manifest"
|
|
22
|
+
require_relative "fuzzing/structural_skips"
|
|
23
|
+
require_relative "fuzzing/coverage_campaign"
|