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,77 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Fuzzing
|
|
3
|
+
class SequenceGenerator
|
|
4
|
+
# Which step to try next: eligibility (what is possible from the
|
|
5
|
+
# state so far) and weighting (what is likely to reach somewhere
|
|
6
|
+
# new).
|
|
7
|
+
module Picker
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def pick(catalog)
|
|
11
|
+
rest = catalog[:queries].dup
|
|
12
|
+
catalog[:instance].each { |entry| rest << entry if actionable?(catalog, entry) }
|
|
13
|
+
catalog[:entity_commands].each { |entry| rest << entry if actionable?(catalog, entry) }
|
|
14
|
+
catalog[:entity_queries].each { |entry| rest << entry if @known_ids[entry[:aggregate].hecks_name].any? }
|
|
15
|
+
catalog[:read_models].each { |entry| rest << entry if read_model_actionable?(entry) }
|
|
16
|
+
|
|
17
|
+
makers = catalog[:creating].select { |entry| satisfiable?(catalog, entry) }
|
|
18
|
+
pool = rest + makers.flat_map { |entry| [entry] * creating_weight(rest.size) }
|
|
19
|
+
pool += deep_entity_bias(rest) if adversarial?
|
|
20
|
+
|
|
21
|
+
steer(pool).sample(random: @random)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Weights up entity commands two or more hops deep; adversarial mode only,
|
|
25
|
+
# and eligibility still decides what is possible.
|
|
26
|
+
def deep_entity_bias(rest)
|
|
27
|
+
deep = rest.select { |entry| (entry[:chain] || []).size >= Adversary::DEEP_ENTITY_DEPTH }
|
|
28
|
+
deep * Adversary::DEEP_ENTITY_WEIGHT
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# While the store is empty, creation matches everything else combined, so a
|
|
32
|
+
# run does not spend its budget querying nothing.
|
|
33
|
+
def creating_weight(rest_size)
|
|
34
|
+
return CREATING_WEIGHT if @known_ids.each_value.any?(&:any?)
|
|
35
|
+
|
|
36
|
+
[rest_size, CREATING_WEIGHT].max
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def actionable?(catalog, entry)
|
|
40
|
+
@known_ids[entry[:aggregate].hecks_name].any? && satisfiable?(catalog, entry)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# A rootless report is always eligible; a rooted one needs an instance of its
|
|
44
|
+
# `reference_target`, or the ask is refused.
|
|
45
|
+
def read_model_actionable?(entry)
|
|
46
|
+
entry[:model].reference_target.nil? || @known_ids[entry[:model].reference_target].any?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# A command whose references point at nothing that exists is refused, so it
|
|
50
|
+
# is withheld until they do. A target no creating command can make (a
|
|
51
|
+
# cross-domain reference) is exempt, or the domain would starve.
|
|
52
|
+
def satisfiable?(catalog, entry)
|
|
53
|
+
entry[:command].attributes.select(&:reference?).all? do |attribute|
|
|
54
|
+
target = attribute.type.target_name.to_s
|
|
55
|
+
!catalog[:creatable].include?(target) || @known_ids[target].any?
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Weights up verbs this sequence has not dispatched yet. Only likelihood
|
|
60
|
+
# changes; a verb gated behind missing state stays out of the pool.
|
|
61
|
+
def steer(pool)
|
|
62
|
+
fresh = pool.reject { |entry| @exercised.include?(entry[:verb]) }
|
|
63
|
+
steered = fresh.empty? ? pool : pool + (fresh * UNEXERCISED_WEIGHT)
|
|
64
|
+
favor(steered)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Weights up the `favor:` verbs when eligible, never making one eligible.
|
|
68
|
+
def favor(pool)
|
|
69
|
+
return pool if @favor.empty?
|
|
70
|
+
|
|
71
|
+
favored = pool.uniq.select { |entry| @favor.include?(entry[:verb]) }
|
|
72
|
+
pool + (favored * SequenceGenerator::FAVOR_WEIGHT)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
require_relative "../../query_specification/field_path"
|
|
2
|
+
require_relative "../../runtime/value"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Fuzzing
|
|
6
|
+
class SequenceGenerator
|
|
7
|
+
# Query arguments drawn from rows the sequence has already written, so a
|
|
8
|
+
# query can match a stored row instead of a fresh word that matches nothing.
|
|
9
|
+
#
|
|
10
|
+
# After each effective command, the compared fields of every aggregate a
|
|
11
|
+
# query filters on are read back from the repository as one row per record.
|
|
12
|
+
# The choice uses its own `Random` (`restart_random`); drawing from `@random`
|
|
13
|
+
# would shift every later step of every seed.
|
|
14
|
+
module QueryBinding
|
|
15
|
+
# How often a query step takes its arguments from a written row; the
|
|
16
|
+
# rest of the time the generated miss is kept.
|
|
17
|
+
BOUND_QUERY_PROBABILITY = 0.75
|
|
18
|
+
|
|
19
|
+
# Comparators taking one scalar operand; `in` and `none_in_state` do not.
|
|
20
|
+
BINDABLE_OPERATORS = %i[eq ne lt lte gt gte].freeze
|
|
21
|
+
|
|
22
|
+
# Keeps the binding stream apart from `@random`, which is seeded with the bare seed.
|
|
23
|
+
BINDING_SEED_OFFSET = 0x9e3779b1
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# `{"Domain::Aggregate" => {domain:, aggregate:, fields: [String]}}`
|
|
28
|
+
# for every aggregate some query filters on, where `fields` is the
|
|
29
|
+
# union of the stored fields those queries compare.
|
|
30
|
+
def build_query_bindings(runtime)
|
|
31
|
+
runtime.registry.bluebooks.each_with_object({}) do |(domain_name, bluebook), bindings|
|
|
32
|
+
bluebook.aggregates.each do |aggregate|
|
|
33
|
+
fields = aggregate.queries.flat_map { |query| bound_fields(query).values }.uniq
|
|
34
|
+
next if fields.empty?
|
|
35
|
+
|
|
36
|
+
bindings["#{domain_name}::#{aggregate.hecks_name}"] =
|
|
37
|
+
{ domain: domain_name, aggregate: aggregate, fields: fields }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# `{parameter name => stored field}` for each `where` clause whose
|
|
43
|
+
# operand is one of the query's own parameters. A clause on a literal,
|
|
44
|
+
# or aimed at another aggregate's field (`target`), binds nothing.
|
|
45
|
+
def bound_fields(query)
|
|
46
|
+
query.wheres.each_with_object({}) do |clause, fields|
|
|
47
|
+
next unless clause.value.is_a?(Symbol) && clause.target.nil?
|
|
48
|
+
next unless BINDABLE_OPERATORS.include?(clause.op.to_sym)
|
|
49
|
+
|
|
50
|
+
fields[clause.value.to_s] ||= clause.field.to_s
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Remembers the compared fields of every stored record of every
|
|
55
|
+
# aggregate a query filters on, as one row per distinct record shape.
|
|
56
|
+
def harvest_written_rows(runtime, catalog)
|
|
57
|
+
catalog[:query_bindings].each do |key, binding|
|
|
58
|
+
repository = runtime.registry.repository(binding[:domain], binding[:aggregate])
|
|
59
|
+
repository.all.each do |record|
|
|
60
|
+
row = binding[:fields].to_h { |field| [field, written_value(field_of(record, field))] }.compact
|
|
61
|
+
@written_rows[key] << row unless row.empty? || @written_rows[key].include?(row)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def field_of(record, field) = QuerySpecification::FieldPath.dig(record.state, field)
|
|
67
|
+
|
|
68
|
+
# A stored value as the JSON-shaped argument a step carries: string
|
|
69
|
+
# keys, nested values opened. `nil` for anything a query argument
|
|
70
|
+
# cannot be (a missing value, a list, a timestamp object).
|
|
71
|
+
def written_value(value)
|
|
72
|
+
case value
|
|
73
|
+
when Runtime::Value then written_value(value.to_h)
|
|
74
|
+
when Hash then value.to_h { |key, inner| [key.to_s, written_value(inner)] }
|
|
75
|
+
when String, Integer, Float, true, false then value
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Replaces the generated arguments of a query step with a remembered
|
|
80
|
+
# row's, most of the time. Only a parameter the step already carries
|
|
81
|
+
# is replaced, so an optional argument left out, or a required one a
|
|
82
|
+
# malformation dropped, stays that way.
|
|
83
|
+
def bind_to_written_row!(args, entry)
|
|
84
|
+
return if entry[:entity]
|
|
85
|
+
|
|
86
|
+
rows = @written_rows[entry[:verb].split(".").first]
|
|
87
|
+
params = bound_fields(entry[:query]).select { |param, _| args.key?(param) }
|
|
88
|
+
return if rows.empty? || params.empty?
|
|
89
|
+
return unless @binding_random.rand < BOUND_QUERY_PROBABILITY
|
|
90
|
+
|
|
91
|
+
row = rows.select { |candidate| params.values.any? { |field| candidate.key?(field) } }
|
|
92
|
+
.sample(random: @binding_random)
|
|
93
|
+
params.each do |param, field|
|
|
94
|
+
args[param] = shaped_like(args[param], row[field]) if row&.key?(field)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# A single-field value object is written `{"value" => x}` but may be
|
|
99
|
+
# asked with the bare `x`; keeps whichever spelling the generator
|
|
100
|
+
# already chose for this argument.
|
|
101
|
+
def shaped_like(current, written)
|
|
102
|
+
return written unless written.is_a?(Hash) && written.size == 1 && !current.is_a?(Hash)
|
|
103
|
+
|
|
104
|
+
written.values.first
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
require_relative "../../bluebook/expression/resolver"
|
|
2
|
+
require_relative "../invalid_value_generator"
|
|
3
|
+
require_relative "../value_generator"
|
|
4
|
+
require_relative "../../runtime/errors"
|
|
5
|
+
require_relative "../../runtime/value"
|
|
6
|
+
require_relative "../../naming"
|
|
7
|
+
|
|
8
|
+
module Hecks
|
|
9
|
+
module Fuzzing
|
|
10
|
+
class SequenceGenerator
|
|
11
|
+
# Turn a picked entry into a corpus step: generate its arguments,
|
|
12
|
+
# occasionally malform exactly one of them, shape its identity, and
|
|
13
|
+
# dispatch it for real.
|
|
14
|
+
module StepBuilder
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def build_query_step(runtime, entry)
|
|
18
|
+
args = args_for(entry[:query].attributes, entry[:aggregate])
|
|
19
|
+
bind_to_written_row!(args, entry)
|
|
20
|
+
safe_call { runtime.query(entry[:verb], **symbolize(args)) }
|
|
21
|
+
{ "query" => entry[:verb], "args" => args }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# A report ask. Its only argument is `reference_name`, on a rooted model.
|
|
25
|
+
# The id stays a bare scalar: `ReadModelInterpreter#refuse_object_reference`
|
|
26
|
+
# rejects a wrapped identity.
|
|
27
|
+
def build_read_model_step(runtime, entry)
|
|
28
|
+
model = entry[:model]
|
|
29
|
+
args = model.reference_target.nil? ? {} : { model.reference_name.to_s => pick_known(model.reference_target) }
|
|
30
|
+
|
|
31
|
+
safe_call { runtime.query(entry[:verb], **symbolize(args)) }
|
|
32
|
+
{ "query" => entry[:verb], "args" => args }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The adversarial mutation happens here, after the args and identity are
|
|
36
|
+
# built and before the one inline dispatch, so the dispatch, the corpus
|
|
37
|
+
# step and both replay engines all see the same mutated payload.
|
|
38
|
+
#
|
|
39
|
+
# The caller draw and then the dry-run coin follow the mutation. The order
|
|
40
|
+
# is part of the seed contract; both draw nothing when off.
|
|
41
|
+
def build_command_step(runtime, catalog, entry)
|
|
42
|
+
args = args_for(entry[:command].attributes, entry[:aggregate])
|
|
43
|
+
add_identity!(args, entry)
|
|
44
|
+
steer_grant!(args, entry, catalog)
|
|
45
|
+
mutations = adversarial_mutations!(args, entry, catalog)
|
|
46
|
+
caller, caller_note = caller_draw!(entry, catalog)
|
|
47
|
+
mutations << caller_note if caller_note
|
|
48
|
+
@state_before = state_before(runtime, entry, args)
|
|
49
|
+
|
|
50
|
+
step =
|
|
51
|
+
if dry_run_draw?
|
|
52
|
+
safe_call { as_caller(caller) { runtime.dry_run?(entry[:verb], **symbolize(args)) } }
|
|
53
|
+
{ "dry_run" => entry[:verb], "args" => args }
|
|
54
|
+
else
|
|
55
|
+
outcome = safe_call { as_caller(caller) { runtime.dispatch_flat(entry[:verb], symbolize(args)) } }
|
|
56
|
+
if outcome
|
|
57
|
+
record_outcome(catalog, entry, args)
|
|
58
|
+
harvest_written_rows(runtime, catalog)
|
|
59
|
+
@event_count += outcome.events.length
|
|
60
|
+
end
|
|
61
|
+
{ "verb" => entry[:verb], "args" => args }
|
|
62
|
+
end
|
|
63
|
+
step.merge!(caller) if caller
|
|
64
|
+
step["adversarial"] = mutations unless mutations.empty?
|
|
65
|
+
step
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Draws nothing when the fraction is zero, like `adversarial?`.
|
|
69
|
+
def dry_run_draw? = @dry_run.positive? && @random.rand < @dry_run
|
|
70
|
+
|
|
71
|
+
# Runs the block under `Hecks.as_caller`, the binding `Fuzzing::Replay` makes
|
|
72
|
+
# from the step's keys, or yields bare when there is no caller.
|
|
73
|
+
def as_caller(caller, &)
|
|
74
|
+
return yield unless caller
|
|
75
|
+
|
|
76
|
+
Hecks.as_caller(role: caller["role"], actor_id: caller["actor_id"], &)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def args_for(attributes, aggregate)
|
|
80
|
+
args = attributes.each_with_object({}) do |attribute, built|
|
|
81
|
+
# Omitting an optional argument is an ordinary payload, not a malformation
|
|
82
|
+
# (see OPTIONAL_OMITTED_PROBABILITY).
|
|
83
|
+
next if attribute.optional? && @random.rand < SequenceGenerator::OPTIONAL_OMITTED_PROBABILITY
|
|
84
|
+
|
|
85
|
+
if attribute.list?
|
|
86
|
+
value = list_value_for(attribute, aggregate)
|
|
87
|
+
# `list_value_for` answers nil for a list of entities (those are
|
|
88
|
+
# populated by per-element append commands), so the step skips it.
|
|
89
|
+
next if value.nil?
|
|
90
|
+
|
|
91
|
+
built[attribute.name.to_s] = value
|
|
92
|
+
else
|
|
93
|
+
built[attribute.name.to_s] = ValueGenerator.value_for(attribute, aggregate, random: @random, known_ids: @known_ids)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
malform(args, attributes, aggregate)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# An array of 0-3 independently generated elements shaped like the bare
|
|
101
|
+
# element type. `nil`, not `[]`, when the element type is not a value object.
|
|
102
|
+
def list_value_for(attribute, aggregate)
|
|
103
|
+
value_object = Runtime::Value.value_object_for(aggregate, attribute.type.to_s)
|
|
104
|
+
return nil unless value_object
|
|
105
|
+
|
|
106
|
+
Array.new(@random.rand(0..3)) { ValueGenerator.value_for(attribute, aggregate, random: @random, known_ids: @known_ids) }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# At most one malformation per step, so the check that fired is identifiable.
|
|
110
|
+
# The rate stays low because refused steps reach no state.
|
|
111
|
+
def malform(args, attributes, aggregate)
|
|
112
|
+
return args if args.empty? || @random.rand >= MALFORMED_ARGUMENT_PROBABILITY
|
|
113
|
+
|
|
114
|
+
case @random.rand(3)
|
|
115
|
+
when 0 then corrupt_one(args, attributes, aggregate)
|
|
116
|
+
when 1 then drop_one(args, aggregate)
|
|
117
|
+
else args.merge([InvalidValueGenerator.undeclared_argument(random: @random)].to_h)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Never drops the identity: an auto-minted id is unreproducible, so the
|
|
122
|
+
# step's outcome could not be replayed.
|
|
123
|
+
def drop_one(args, aggregate)
|
|
124
|
+
identity = (aggregate.identified_by || :id).to_s
|
|
125
|
+
droppable = args.keys - [identity, "id"]
|
|
126
|
+
return args if droppable.empty?
|
|
127
|
+
|
|
128
|
+
args.reject { |name, _| name == droppable.sample(random: @random) }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def corrupt_one(args, attributes, aggregate)
|
|
132
|
+
named = attributes.reject(&:list?).select { |attribute| args.key?(attribute.name.to_s) }
|
|
133
|
+
return args if named.empty?
|
|
134
|
+
|
|
135
|
+
attribute = named.sample(random: @random)
|
|
136
|
+
args.merge(attribute.name.to_s => InvalidValueGenerator.corrupt(attribute, aggregate, random: @random))
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def add_identity!(args, entry)
|
|
140
|
+
aggregate = entry[:aggregate]
|
|
141
|
+
parent_key = (aggregate.identified_by || :id).to_s
|
|
142
|
+
|
|
143
|
+
if entry[:entity]
|
|
144
|
+
parent_scalar = pick_known(aggregate.hecks_name)
|
|
145
|
+
args[parent_key] = identity_shaped(aggregate, aggregate.identified_by, parent_scalar, aggregate)
|
|
146
|
+
# One identity per hop, drawn from that hop's pool (`entity_pool_key`), as
|
|
147
|
+
# flat args, which is what `EntityElement#locate_chain` reads.
|
|
148
|
+
scalars = [parent_scalar]
|
|
149
|
+
names = []
|
|
150
|
+
entry[:chain].each do |piece|
|
|
151
|
+
names << piece.hecks_name
|
|
152
|
+
scalar = pick_entity_known(entity_pool_key(aggregate.hecks_name, names, scalars))
|
|
153
|
+
args[(piece.identified_by || :id).to_s] = identity_shaped(piece, piece.identified_by, scalar, aggregate)
|
|
154
|
+
scalars << scalar
|
|
155
|
+
end
|
|
156
|
+
elsif entry[:command].creates?
|
|
157
|
+
# A composite identity's parts are already generated as the command's own
|
|
158
|
+
# attributes; a synthetic `id` would be refused as an undeclared argument.
|
|
159
|
+
unless composite_identity?(aggregate)
|
|
160
|
+
args[parent_key] ||= identity_shaped(aggregate, aggregate.identified_by, ValueGenerator.random_id(@random),
|
|
161
|
+
aggregate)
|
|
162
|
+
end
|
|
163
|
+
else
|
|
164
|
+
scalar = pick_known(aggregate.hecks_name)
|
|
165
|
+
args[parent_key] = identity_shaped(aggregate, aggregate.identified_by, scalar, aggregate)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# True only for a multi-field identity: `identified_by` is also nil for the
|
|
170
|
+
# untyped default, whose parts are not in `args`.
|
|
171
|
+
def composite_identity?(aggregate) = aggregate.identified_by.nil? && aggregate.identity_paths.size > 1
|
|
172
|
+
|
|
173
|
+
# Shapes a bare scalar id like the construct's identity field. A
|
|
174
|
+
# value-object-typed identity given a bare scalar is a TypeMismatch, so it
|
|
175
|
+
# must be wrapped; the untyped default `:id` stays bare.
|
|
176
|
+
def identity_shaped(construct, key, scalar, aggregate)
|
|
177
|
+
return scalar unless key
|
|
178
|
+
|
|
179
|
+
attribute = construct.attribute(key)
|
|
180
|
+
return scalar unless attribute
|
|
181
|
+
|
|
182
|
+
value_object = aggregate.value_object(attribute.type.to_s)
|
|
183
|
+
return scalar unless value_object
|
|
184
|
+
|
|
185
|
+
field = value_object.attributes.first
|
|
186
|
+
return scalar unless field
|
|
187
|
+
|
|
188
|
+
{ field.name.to_s => coerce_scalar(field.type.to_s, scalar) }
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def coerce_scalar(type_name, scalar)
|
|
192
|
+
case type_name
|
|
193
|
+
when "Integer" then scalar.to_i
|
|
194
|
+
when "Float" then scalar.to_f
|
|
195
|
+
else scalar.to_s
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def symbolize(args) = args.transform_keys(&:to_sym)
|
|
200
|
+
|
|
201
|
+
# A declined step is not a generator failure: nothing is recorded, the
|
|
202
|
+
# sequence carries on, and the step still enters the corpus as a refusal.
|
|
203
|
+
#
|
|
204
|
+
# EvaluationError counts as a refusal (an unreadable payload); any other
|
|
205
|
+
# error propagates and fails spec/fuzzing.
|
|
206
|
+
def safe_call
|
|
207
|
+
result = yield
|
|
208
|
+
@last_outcome = "ok"
|
|
209
|
+
result
|
|
210
|
+
rescue *Hecks::Runtime::DOMAIN_REFUSALS, Hecks::Bluebook::Expression::EvaluationError => e
|
|
211
|
+
@last_outcome = e.class.name.split("::").last
|
|
212
|
+
nil
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# The addressed aggregate's lifecycle value before this dispatch, or
|
|
216
|
+
# `exists`/`absent`; `?` when a mutation mangled the identity. Draws nothing from the RNG.
|
|
217
|
+
def state_before(runtime, entry, args)
|
|
218
|
+
aggregate = entry[:aggregate]
|
|
219
|
+
symbolic = symbolize(args)
|
|
220
|
+
id = Runtime::Identity.of(aggregate, symbolic) || Runtime::Identity.from(aggregate, symbolic, :id)
|
|
221
|
+
return "absent" unless id
|
|
222
|
+
|
|
223
|
+
record = runtime.registry.repository(entry[:verb].split("::").first, aggregate).find(id)
|
|
224
|
+
return "absent" unless record
|
|
225
|
+
|
|
226
|
+
lifecycle = aggregate.lifecycle
|
|
227
|
+
return "exists" unless lifecycle
|
|
228
|
+
|
|
229
|
+
key = record.state.key?(lifecycle.field) ? lifecycle.field : lifecycle.field.to_s
|
|
230
|
+
record.state[key].to_s
|
|
231
|
+
rescue StandardError
|
|
232
|
+
"?"
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require "tmpdir"
|
|
3
|
+
require_relative "isolated_boot"
|
|
4
|
+
require_relative "invalid_value_generator"
|
|
5
|
+
require_relative "value_generator"
|
|
6
|
+
require_relative "sequence_generator/catalog"
|
|
7
|
+
require_relative "sequence_generator/picker"
|
|
8
|
+
require_relative "sequence_generator/step_builder"
|
|
9
|
+
require_relative "sequence_generator/outcome_tracker"
|
|
10
|
+
require_relative "sequence_generator/query_binding"
|
|
11
|
+
require_relative "sequence_generator/adversary"
|
|
12
|
+
|
|
13
|
+
module Hecks
|
|
14
|
+
module Fuzzing
|
|
15
|
+
# A random-but-valid sequence of dispatches and queries, in the `{name, note, steps}`
|
|
16
|
+
# shape of `spec/corpus/*.json`, so a generated sequence replays as a corpus member.
|
|
17
|
+
# Each step is dispatched for real against a throwaway boot while the sequence is built.
|
|
18
|
+
class SequenceGenerator
|
|
19
|
+
include Catalog
|
|
20
|
+
include Picker
|
|
21
|
+
include StepBuilder
|
|
22
|
+
include OutcomeTracker
|
|
23
|
+
include QueryBinding
|
|
24
|
+
include Adversary
|
|
25
|
+
|
|
26
|
+
# Creating commands are always eligible; weighting them up reaches an actionable
|
|
27
|
+
# state sooner without starving domains that have few aggregates.
|
|
28
|
+
CREATING_WEIGHT = 2
|
|
29
|
+
|
|
30
|
+
# Low, because a refused step reaches no new state and a run of them is silent.
|
|
31
|
+
MALFORMED_ARGUMENT_PROBABILITY = 0.12
|
|
32
|
+
|
|
33
|
+
# A fair coin: an optional argument is legal present or absent. Separate from
|
|
34
|
+
# `malform`, whose dropped required argument is refused and so never stores a null.
|
|
35
|
+
OPTIONAL_OMITTED_PROBABILITY = 0.5
|
|
36
|
+
|
|
37
|
+
# Preference for a verb this sequence has not yet dispatched, so whole commands
|
|
38
|
+
# are not left untouched for a run.
|
|
39
|
+
UNEXERCISED_WEIGHT = 4
|
|
40
|
+
|
|
41
|
+
# Generates a random-but-valid step sequence for `domain_path`, dispatching each
|
|
42
|
+
# step against a throwaway boot. Fractions at `0.0` draw nothing from the RNG.
|
|
43
|
+
#
|
|
44
|
+
# @param seed [Integer] RNG seed; every draw is reproducible from it
|
|
45
|
+
# @param steps [Integer] number of generation attempts
|
|
46
|
+
# @param adversarial [Float] fraction of command steps mutated adversarially
|
|
47
|
+
# @param role_draw [Float] fraction of gated commands given a drawn caller
|
|
48
|
+
# @param dry_run [Float] fraction of command steps dispatched as dry runs
|
|
49
|
+
# @param prefix [Hash, nil] `{"seed", "steps", "favor", "prefix"}` replayed first
|
|
50
|
+
# @param favor [Array<String>] verbs the picker weights up
|
|
51
|
+
# @return [Array<Hash>] the generated steps
|
|
52
|
+
def self.generate(domain_path, seed:, steps:, **)
|
|
53
|
+
new(domain_path, seed: seed, steps: steps, **).call
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Coverage tuples reached (`[[attempt_index, tuple], ...]`) and every verb the
|
|
57
|
+
# booted catalog offered, so a campaign can tell an unhit verb from a missing one.
|
|
58
|
+
Trace = Struct.new(:steps, :coverage, :verbs, keyword_init: true)
|
|
59
|
+
|
|
60
|
+
# Generates one sequence like `.generate`, also returning the coverage tuples and
|
|
61
|
+
# verbs a campaign needs.
|
|
62
|
+
#
|
|
63
|
+
# @return [Trace] the generated steps, coverage tuples reached, and catalog verbs
|
|
64
|
+
def self.trace(domain_path, seed:, steps:, **)
|
|
65
|
+
generator = new(domain_path, seed: seed, steps: steps, **)
|
|
66
|
+
Trace.new(steps: generator.call, coverage: generator.coverage, verbs: generator.verbs)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Weight of a `favor:` verb; comparable to UNEXERCISED_WEIGHT so favor steers
|
|
70
|
+
# without drowning out untouched verbs.
|
|
71
|
+
FAVOR_WEIGHT = 4
|
|
72
|
+
|
|
73
|
+
attr_reader :coverage, :verbs
|
|
74
|
+
|
|
75
|
+
# Sum of every `Result#events` length across the run; hecks fuzz declares it as the
|
|
76
|
+
# script's `expectations.events` claim. Zero means no interesting state was reached.
|
|
77
|
+
attr_reader :event_count
|
|
78
|
+
|
|
79
|
+
# Takes the same keywords as `.generate`, plus `adapter:` (default `:memory`).
|
|
80
|
+
#
|
|
81
|
+
# @raise [ArgumentError] if `adversarial`, `role_draw`, or `dry_run` is not a
|
|
82
|
+
# Numeric between 0.0 and 1.0
|
|
83
|
+
def initialize(domain_path, seed:, steps:, adapter: :memory, adversarial: 0.0, role_draw: 0.0, dry_run: 0.0,
|
|
84
|
+
prefix: nil, favor: [])
|
|
85
|
+
{ adversarial: adversarial, role_draw: role_draw, dry_run: dry_run }.each do |name, fraction|
|
|
86
|
+
next if fraction.is_a?(Numeric) && fraction.between?(0, 1)
|
|
87
|
+
|
|
88
|
+
raise ArgumentError, "#{name}: must be a fraction between 0.0 and 1.0, got #{fraction.inspect}"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
@domain_path = domain_path
|
|
92
|
+
@seed = seed
|
|
93
|
+
@step_count = steps
|
|
94
|
+
@adapter = adapter
|
|
95
|
+
@adversarial = adversarial.to_f
|
|
96
|
+
@role_draw = role_draw.to_f
|
|
97
|
+
@dry_run = dry_run.to_f
|
|
98
|
+
restart_random(seed)
|
|
99
|
+
@known_ids = Hash.new { |h, k| h[k] = [] }
|
|
100
|
+
@entity_known_ids = Hash.new { |h, k| h[k] = [] }
|
|
101
|
+
@appended_identities = Hash.new { |h, k| h[k] = [] }
|
|
102
|
+
# "Domain::Aggregate" => stored rows a query filters on (query_binding.rb).
|
|
103
|
+
@written_rows = Hash.new { |h, k| h[k] = [] }
|
|
104
|
+
# Role => actor ids granted by this sequence's own `RoleAssignment.Assign` steps.
|
|
105
|
+
@granted = Hash.new { |h, k| h[k] = [] }
|
|
106
|
+
@precedence_caller = nil
|
|
107
|
+
@exercised = Set.new
|
|
108
|
+
@event_count = 0
|
|
109
|
+
@prefix = prefix
|
|
110
|
+
@favor = Array(favor)
|
|
111
|
+
@own_favor = @favor
|
|
112
|
+
@coverage = []
|
|
113
|
+
@verbs = []
|
|
114
|
+
@attempt = 0
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Runs the generation against a fresh, isolated boot of the domain.
|
|
118
|
+
#
|
|
119
|
+
# @return [Array<Hash>] the generated steps; a picker miss is dropped
|
|
120
|
+
def call
|
|
121
|
+
# Leftover data under the example's data/ would start the run from state
|
|
122
|
+
# known_ids does not track; IsolatedBoot resets it and rebinds persistence to Memory.
|
|
123
|
+
IsolatedBoot.call(@domain_path, adapter: @adapter) do |copy|
|
|
124
|
+
runtime = Hecks.boot(copy, environment: nil)
|
|
125
|
+
catalog = build_catalog(runtime)
|
|
126
|
+
@verbs = catalog.values_at(:creating, :instance, :entity_commands, :queries, :entity_queries, :read_models)
|
|
127
|
+
.flatten.map { |entry| entry[:verb] }.uniq
|
|
128
|
+
|
|
129
|
+
steps = []
|
|
130
|
+
if @prefix
|
|
131
|
+
realize_prefix(runtime, catalog, @prefix, prefix_limit(@prefix), steps)
|
|
132
|
+
restart_random(@seed)
|
|
133
|
+
@favor = @own_favor
|
|
134
|
+
end
|
|
135
|
+
@step_count.times { steps << attempt_step(runtime, catalog) }
|
|
136
|
+
steps.compact
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
# Re-runs the first `limit` attempts of another seed's generation (its own prefix
|
|
143
|
+
# first), carrying its known ids and exercised verbs into this seed. The prefix is
|
|
144
|
+
# on top of this seed's budget: taking it out of the budget left too little to explore.
|
|
145
|
+
def realize_prefix(runtime, catalog, spec, limit, steps)
|
|
146
|
+
return 0 unless limit.positive?
|
|
147
|
+
|
|
148
|
+
inner = spec["prefix"]
|
|
149
|
+
used = inner ? realize_prefix(runtime, catalog, inner, [prefix_limit(inner), limit].min, steps) : 0
|
|
150
|
+
restart_random(Integer(spec.fetch("seed")))
|
|
151
|
+
@favor = Array(spec["favor"])
|
|
152
|
+
(limit - used).times { steps << attempt_step(runtime, catalog) }
|
|
153
|
+
limit
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Both streams restart together so a prefix replay draws the same query bindings.
|
|
157
|
+
def restart_random(seed)
|
|
158
|
+
@random = Random.new(seed)
|
|
159
|
+
@binding_random = Random.new(seed + QueryBinding::BINDING_SEED_OFFSET)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def prefix_limit(spec) = Integer(spec.fetch("steps")).clamp(0, @step_count)
|
|
163
|
+
|
|
164
|
+
def attempt_step(runtime, catalog)
|
|
165
|
+
index = @attempt
|
|
166
|
+
@attempt += 1
|
|
167
|
+
entry = pick(catalog)
|
|
168
|
+
return nil unless entry
|
|
169
|
+
|
|
170
|
+
@exercised << entry[:verb]
|
|
171
|
+
@state_before = "-"
|
|
172
|
+
step =
|
|
173
|
+
if entry[:query] then build_query_step(runtime, entry)
|
|
174
|
+
elsif entry[:model] then build_read_model_step(runtime, entry)
|
|
175
|
+
else build_command_step(runtime, catalog, entry)
|
|
176
|
+
end
|
|
177
|
+
@coverage << [index, coverage_tuple(entry, step)]
|
|
178
|
+
step
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# `verb | kind | state before | mutation | outcome`; see CoverageCampaign.
|
|
182
|
+
def coverage_tuple(entry, step)
|
|
183
|
+
kind = %w[verb query dry_run].find { |key| step.key?(key) }
|
|
184
|
+
mutation = Array(step["adversarial"]).map { |m| [m["mutation"], m["shape"]].compact.join(":") }.join("+")
|
|
185
|
+
[entry[:verb], kind, @state_before, mutation.empty? ? "-" : mutation, @last_outcome].join(" | ")
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|