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,373 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "tmpdir"
|
|
5
|
+
require "tempfile"
|
|
6
|
+
require "etc"
|
|
7
|
+
require "fileutils"
|
|
8
|
+
require_relative "../tools"
|
|
9
|
+
|
|
10
|
+
module Hecks
|
|
11
|
+
module Tools
|
|
12
|
+
# Fuzzes domains with generated command and query sequences, replaying each and checking the
|
|
13
|
+
# properties, then shrinks each distinct finding and saves it under `tmp/fuzz-failures/`.
|
|
14
|
+
#
|
|
15
|
+
# hecks fuzz [domain] [--seeds N] [--steps N] [--workers N] [--adapter memory|sqlite|postgres]
|
|
16
|
+
#
|
|
17
|
+
# The sweep is single-runtime on purpose; the cross-runtime differential is
|
|
18
|
+
# `spec/rust_conformance_fuzz_spec.rb`. Without a domain it sweeps every domain
|
|
19
|
+
# `Hecks::Corpus.fuzzable_domains` finds, one forked child per core.
|
|
20
|
+
module FuzzSweep
|
|
21
|
+
# How many seeds are drawn for each seed that must execute.
|
|
22
|
+
DRAW_LIMIT = 5
|
|
23
|
+
|
|
24
|
+
# Domain name => failure signature => Regexps that every "; "-joined offender must match.
|
|
25
|
+
# Entries excuse one named finding and are removed when its bug is fixed.
|
|
26
|
+
KNOWN_FUZZ_FINDINGS = {}.freeze
|
|
27
|
+
|
|
28
|
+
# Why a domain is skipped, by the reason `known_unfuzzable_wiring_gap` answers.
|
|
29
|
+
SKIPPED_DOMAIN_REASONS = {
|
|
30
|
+
skip_compute_rekey: "declares compute/rekey, which only an era/Postgres-bound boot can interpret"
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
# The persistence an ephemeral boot may use.
|
|
34
|
+
ADAPTERS = %i[memory sqlite postgres].freeze
|
|
35
|
+
|
|
36
|
+
module_function
|
|
37
|
+
|
|
38
|
+
# Runs a sweep as the command line asks.
|
|
39
|
+
#
|
|
40
|
+
# @param argv [Array<String>] a domain path, `--seeds N`, `--steps N`, `--workers N`,
|
|
41
|
+
# `--adapter memory|sqlite|postgres`
|
|
42
|
+
# @param root [String] the checkout, where `tmp/fuzz-failures/` is written
|
|
43
|
+
# @return [Integer] 0 when every domain is clean, 1 when a sequence found something or an
|
|
44
|
+
# argument is refused
|
|
45
|
+
def main(argv, root: Tools::ROOT)
|
|
46
|
+
require "hecks"
|
|
47
|
+
require "hecks/fuzzing"
|
|
48
|
+
options = parse(argv.dup)
|
|
49
|
+
return 1 unless options
|
|
50
|
+
|
|
51
|
+
return 1 if options[:adapter] == :postgres && !postgres_reachable?
|
|
52
|
+
|
|
53
|
+
# Every discoverable domain, so one added anywhere is swept without a list. Paths the sweep
|
|
54
|
+
# cannot boot are routed by Hecks::Corpus::ROUTES to the check that owns them.
|
|
55
|
+
domains = options[:domain] ? [options[:domain]] : Hecks::Corpus.fuzzable_domains(root)
|
|
56
|
+
# One child per core; a real Postgres runs one at a time.
|
|
57
|
+
workers = options[:workers] || (options[:adapter] == :postgres ? 1 : Etc.nprocessors)
|
|
58
|
+
ok = sweep(domains, workers, options, root)
|
|
59
|
+
|
|
60
|
+
puts(ok ? "CLEAN — no generated sequence broke a property or the interpreter." : "FUZZ FOUND SOMETHING.")
|
|
61
|
+
ok ? 0 : 1
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @param args [Array<String>] the command line, consumed
|
|
65
|
+
# @return [Hash, nil] `seeds`, `steps`, `workers`, `adapter` and `domain`; nil after a refusal
|
|
66
|
+
def parse(args)
|
|
67
|
+
options = { seeds: 20, steps: 30, workers: nil, adapter: :memory, domain: nil }
|
|
68
|
+
until args.empty?
|
|
69
|
+
case (arg = args.shift)
|
|
70
|
+
when "--seeds" then options[:seeds] = Integer(args.shift)
|
|
71
|
+
when "--steps" then options[:steps] = Integer(args.shift)
|
|
72
|
+
when "--workers" then options[:workers] = Integer(args.shift)
|
|
73
|
+
when "--adapter"
|
|
74
|
+
options[:adapter] = args.shift.to_s.downcase.to_sym
|
|
75
|
+
unless ADAPTERS.include?(options[:adapter])
|
|
76
|
+
warn "unknown --adapter #{options[:adapter]} — memory, sqlite, or postgres"
|
|
77
|
+
return nil
|
|
78
|
+
end
|
|
79
|
+
else options[:domain] = arg
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
options
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# `postgres` needs a reachable local server and is slower per dispatch, so pass smaller
|
|
86
|
+
# `--seeds` and `--steps`.
|
|
87
|
+
#
|
|
88
|
+
# @return [Boolean] whether a local Postgres answers; warns why when it does not
|
|
89
|
+
def postgres_reachable?
|
|
90
|
+
require "pg"
|
|
91
|
+
PG.connect(dbname: "postgres").close
|
|
92
|
+
true
|
|
93
|
+
rescue LoadError, PG::Error => e
|
|
94
|
+
warn "--adapter postgres needs a reachable local Postgres — #{e.class}: #{e.message}"
|
|
95
|
+
false
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @param domains [Array<String>] the domain directories
|
|
99
|
+
# @param workers [Integer] how many children may run at once
|
|
100
|
+
# @param options [Hash] the parsed options
|
|
101
|
+
# @param root [String] the checkout
|
|
102
|
+
# @return [Boolean] whether every domain was clean
|
|
103
|
+
def sweep(domains, workers, options, root)
|
|
104
|
+
if workers > 1 && domains.size > 1
|
|
105
|
+
# Booted here so every child inherits the grammar instead of each paying for a cold boot.
|
|
106
|
+
require "hecks/bluebook/meta_validator"
|
|
107
|
+
Hecks::Bluebook::MetaValidator.grammar_registry
|
|
108
|
+
$stdout.flush
|
|
109
|
+
fuzz_in_pool(domains, workers) do |domain|
|
|
110
|
+
fuzz_domain(domain, options[:seeds], options[:steps], options[:adapter], root)
|
|
111
|
+
end
|
|
112
|
+
else
|
|
113
|
+
domains.reduce(true) do |all_ok, domain|
|
|
114
|
+
fuzz_domain(domain, options[:seeds], options[:steps], options[:adapter], root) && all_ok
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Writes a shrunk (or original) step sequence to disk as a replayable script.
|
|
120
|
+
#
|
|
121
|
+
# @param steps [Array<Hash>] the step sequence to save
|
|
122
|
+
# @param path [String] the file path to write the script to
|
|
123
|
+
# @param seed [Integer] the seed that produced `steps`, recorded in the script's own note
|
|
124
|
+
# @param domain_name [String] the domain's basename, for the script's `name` field
|
|
125
|
+
# @return [void]
|
|
126
|
+
def write_script(steps, path, seed, domain_name)
|
|
127
|
+
File.write(path, JSON.pretty_generate(
|
|
128
|
+
name: "#{domain_name}-fuzz",
|
|
129
|
+
note: "generated by hecks fuzz — seed #{seed}",
|
|
130
|
+
steps: steps
|
|
131
|
+
))
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# An exception escaping Replay's per-step isolation is an interpreter defect, not a domain
|
|
135
|
+
# refusal.
|
|
136
|
+
#
|
|
137
|
+
# @param domain [String] the domain directory
|
|
138
|
+
# @param steps [Array<Hash>] the sequence to replay
|
|
139
|
+
# @param adapter [Symbol] the persistence to boot on
|
|
140
|
+
# @return [Array(Symbol, String)] the verdict (`:clean`, `:property_violation`, `:crash`) and
|
|
141
|
+
# its message
|
|
142
|
+
def outcome(domain, steps, adapter = :memory)
|
|
143
|
+
history = Hecks::Fuzzing::Replay.call(domain, steps, adapter: adapter)
|
|
144
|
+
violations = Hecks::Fuzzing::Properties.check(history).reject { |_, result| result == true }
|
|
145
|
+
return [:clean, nil] if violations.empty?
|
|
146
|
+
|
|
147
|
+
[:property_violation, violations.map { |name, message| "#{name}: #{message}" }.join("; ")]
|
|
148
|
+
rescue StandardError => e
|
|
149
|
+
[:crash, "#{e.class}: #{e.message}"]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Groups findings by property name or exception class, stable across shrink candidates.
|
|
153
|
+
#
|
|
154
|
+
# @param message [String, nil] a finding's message
|
|
155
|
+
# @return [String] the property name or exception class it opens with
|
|
156
|
+
def signature_of(message)
|
|
157
|
+
return "unknown" unless message
|
|
158
|
+
|
|
159
|
+
message.split(":").first
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# A compound finding that mixes a known shape with any other offender is not known and still
|
|
163
|
+
# fails.
|
|
164
|
+
#
|
|
165
|
+
# @param name [String] the domain's name
|
|
166
|
+
# @param failure [Hash] a finding: `signature` and `message`
|
|
167
|
+
# @return [Boolean] whether every offender is an allowlisted one
|
|
168
|
+
def known_finding?(name, failure)
|
|
169
|
+
matchers = KNOWN_FUZZ_FINDINGS.dig(name, failure[:signature])
|
|
170
|
+
return false unless matchers
|
|
171
|
+
|
|
172
|
+
failure[:message].split("; ").all? { |chunk| matchers.any? { |regex| regex.match?(chunk) } }
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Skips only exact, already-tracked wiring gaps; any other WiringError is re-raised.
|
|
176
|
+
#
|
|
177
|
+
# @param error [Hecks::Runtime::WiringError] what generating a sequence raised
|
|
178
|
+
# @return [Symbol] a key of `SKIPPED_DOMAIN_REASONS`
|
|
179
|
+
def known_unfuzzable_wiring_gap(error)
|
|
180
|
+
case error.message
|
|
181
|
+
when %r{a compute/rekey rule is declared}
|
|
182
|
+
# compute/rekey needs an era/Postgres migration, so it cannot boot under IsolatedBoot's
|
|
183
|
+
# Memory rebind. Real coverage lives in spec/adapters/driven/postgres_era/*_spec.rb.
|
|
184
|
+
:skip_compute_rekey
|
|
185
|
+
else
|
|
186
|
+
raise error
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Draws seeds until `seeds` results execute or the draw cap is hit.
|
|
191
|
+
#
|
|
192
|
+
# @param domain [String] the domain directory
|
|
193
|
+
# @param seeds [Integer] how many executing seeds to ask for
|
|
194
|
+
# @param steps [Integer] how many steps each sequence asks for
|
|
195
|
+
# @param adapter [Symbol] the persistence to boot on
|
|
196
|
+
# @param root [String] the checkout
|
|
197
|
+
# @return [Boolean] true if clean or skipped
|
|
198
|
+
def fuzz_domain(domain, seeds, steps, adapter = :memory, root = Tools::ROOT)
|
|
199
|
+
domain = domain.chomp("/")
|
|
200
|
+
name = File.basename(domain)
|
|
201
|
+
puts "── #{name}#{" (#{adapter})" unless adapter == :memory}"
|
|
202
|
+
|
|
203
|
+
failures = []
|
|
204
|
+
clean = 0
|
|
205
|
+
drawn = 0
|
|
206
|
+
cap = seeds * DRAW_LIMIT
|
|
207
|
+
|
|
208
|
+
while (clean + failures.length) < seeds && drawn < cap
|
|
209
|
+
drawn += 1
|
|
210
|
+
seed = drawn
|
|
211
|
+
generated = begin
|
|
212
|
+
Hecks::Fuzzing::SequenceGenerator.generate(domain, seed: seed, steps: steps, adapter: adapter)
|
|
213
|
+
rescue Hecks::Runtime::WiringError => e
|
|
214
|
+
known_unfuzzable_wiring_gap(e)
|
|
215
|
+
end
|
|
216
|
+
if (reason = SKIPPED_DOMAIN_REASONS[generated])
|
|
217
|
+
puts " skipped — #{reason} (not fuzzable under :#{adapter})"
|
|
218
|
+
return true
|
|
219
|
+
end
|
|
220
|
+
verdict, message = outcome(domain, generated, adapter)
|
|
221
|
+
|
|
222
|
+
if verdict == :clean
|
|
223
|
+
determinism = Hecks::Fuzzing::Properties.replay_is_deterministic(domain, generated, adapter: adapter)
|
|
224
|
+
if determinism == true
|
|
225
|
+
clean += 1
|
|
226
|
+
next
|
|
227
|
+
end
|
|
228
|
+
verdict = :nondeterminism
|
|
229
|
+
message = determinism
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
failures << { seed: seed, steps: generated, verdict: verdict, message: message,
|
|
233
|
+
signature: "#{verdict}: #{signature_of(message)}" }
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Known findings print but count as clean, so the sweep does not read as short.
|
|
237
|
+
known, failures = failures.partition { |failure| known_finding?(name, failure) }
|
|
238
|
+
known.each do |failure|
|
|
239
|
+
puts " KNOWN (allowlisted, not failing) — #{failure[:signature]} — #{failure[:message]}"
|
|
240
|
+
end
|
|
241
|
+
clean += known.length
|
|
242
|
+
|
|
243
|
+
report(domain, name, seeds, drawn, clean, failures, adapter, root)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Shrinks steps, then arguments, keeping a removal only while the same finding reproduces.
|
|
247
|
+
#
|
|
248
|
+
# @param domain [String] the domain directory
|
|
249
|
+
# @param steps [Array<Hash>] the failing sequence
|
|
250
|
+
# @param signature [String] the finding to keep
|
|
251
|
+
# @param adapter [Symbol] the persistence to boot on
|
|
252
|
+
# @return [Array<Hash>] the smallest sequence found
|
|
253
|
+
def shrink(domain, steps, signature, adapter = :memory)
|
|
254
|
+
Hecks::Fuzzing::Shrinker.call(steps) { |candidate| same_finding?(domain, candidate, signature, adapter) }.steps
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# @param domain [String] the domain directory
|
|
258
|
+
# @param candidate [Array<Hash>] a sequence to replay
|
|
259
|
+
# @param signature [String] the finding to keep
|
|
260
|
+
# @param adapter [Symbol] the persistence to boot on
|
|
261
|
+
# @return [Boolean] whether the candidate sequence reproduces the finding
|
|
262
|
+
def same_finding?(domain, candidate, signature, adapter)
|
|
263
|
+
verdict, message = outcome(domain, candidate, adapter)
|
|
264
|
+
"#{verdict}: #{signature_of(message)}" == signature
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# @param step [Hash] one step of a sequence
|
|
268
|
+
# @return [Hash] its arguments
|
|
269
|
+
def args_of(step) = Hecks::Fuzzing::Shrinker.args_of(step)
|
|
270
|
+
|
|
271
|
+
# The argument pass alone.
|
|
272
|
+
#
|
|
273
|
+
# @param domain [String] the domain directory
|
|
274
|
+
# @param steps [Array<Hash>] the failing sequence
|
|
275
|
+
# @param signature [String] the finding to keep
|
|
276
|
+
# @param adapter [Symbol] the persistence to boot on
|
|
277
|
+
# @return [Array<Hash>] the sequence with every argument the finding does not need dropped
|
|
278
|
+
def shrink_arguments(domain, steps, signature, adapter = :memory)
|
|
279
|
+
Hecks::Fuzzing::Shrinker.drop_arguments(steps, Hecks::Fuzzing::Shrinker::Meter.new(nil)) do |candidate|
|
|
280
|
+
same_finding?(domain, candidate, signature, adapter)
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Prints the final report, then shrinks and saves one repro script per distinct finding.
|
|
285
|
+
#
|
|
286
|
+
# @param domain [String] the domain directory
|
|
287
|
+
# @param name [String] its name
|
|
288
|
+
# @param seeds [Integer] how many executing seeds were asked for
|
|
289
|
+
# @param drawn [Integer] how many were drawn
|
|
290
|
+
# @param clean [Integer] how many ran clean
|
|
291
|
+
# @param failures [Array<Hash>] the findings
|
|
292
|
+
# @param adapter [Symbol] the persistence the sweep booted on
|
|
293
|
+
# @param root [String] the checkout, where the repro scripts are saved
|
|
294
|
+
# @return [Boolean] whether the domain had no findings
|
|
295
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
296
|
+
def report(domain, name, seeds, drawn, clean, failures, adapter = :memory, root = Tools::ROOT) # rubocop:disable Naming/PredicateMethod
|
|
297
|
+
executed = clean + failures.length
|
|
298
|
+
puts " #{executed} seeds executed (#{drawn} drawn) — #{clean} clean, #{failures.length} found something"
|
|
299
|
+
|
|
300
|
+
if executed < seeds
|
|
301
|
+
puts " SHORT — asked for #{seeds} executing seeds and drew #{drawn} to find #{executed}."
|
|
302
|
+
puts " The generator is not reaching state ; this sweep proves less than it looks."
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
if failures.empty?
|
|
306
|
+
puts
|
|
307
|
+
return true
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
grouped = failures.group_by { |failure| failure[:signature] }
|
|
311
|
+
puts " #{grouped.size} distinct finding(s):"
|
|
312
|
+
save_dir = File.join(root, "tmp/fuzz-failures")
|
|
313
|
+
FileUtils.mkdir_p(save_dir)
|
|
314
|
+
|
|
315
|
+
grouped.each_value.with_index(1) do |group, number|
|
|
316
|
+
seeds_hit = group.map { |failure| failure[:seed] }
|
|
317
|
+
smallest = group.min_by { |failure| failure[:steps].length }
|
|
318
|
+
|
|
319
|
+
puts
|
|
320
|
+
puts " (#{number}) #{group.first[:signature]} — #{group.first[:message]}"
|
|
321
|
+
puts " #{group.length} of #{seeds} seeds: #{seeds_hit.first(8).join(', ')}#{', …' if seeds_hit.length > 8}"
|
|
322
|
+
|
|
323
|
+
shrunk = shrink(domain, smallest[:steps], smallest[:signature], adapter)
|
|
324
|
+
save_path = File.join(save_dir, "#{name}-seed#{smallest[:seed]}.json")
|
|
325
|
+
write_script(shrunk, save_path, smallest[:seed], name)
|
|
326
|
+
|
|
327
|
+
puts " shrunk #{smallest[:steps].length} steps -> #{shrunk.length}"
|
|
328
|
+
puts " reproduce: hecks run #{domain} #{save_path}"
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
puts
|
|
332
|
+
false
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Forks one child per domain, up to `workers`, printing each child's output whole in domain
|
|
336
|
+
# order. A child that crashes exits non-zero and counts as a finding.
|
|
337
|
+
#
|
|
338
|
+
# @param domains [Array<String>] the domain directories
|
|
339
|
+
# @param workers [Integer] how many children may run at once
|
|
340
|
+
# @yieldparam domain [String] a domain directory
|
|
341
|
+
# @yieldreturn [Boolean] whether it was clean
|
|
342
|
+
# @return [Boolean] true if every domain's child exited 0, false if any exited non-zero
|
|
343
|
+
def fuzz_in_pool(domains, workers) # rubocop:disable Naming/PredicateMethod
|
|
344
|
+
pending = domains.each_with_index.to_a
|
|
345
|
+
running = {}
|
|
346
|
+
results = {}
|
|
347
|
+
until pending.empty? && running.empty?
|
|
348
|
+
while running.size < workers && (domain, index = pending.shift)
|
|
349
|
+
out = Tempfile.new("fuzz")
|
|
350
|
+
pid = fork do
|
|
351
|
+
$stdout.reopen(out)
|
|
352
|
+
$stderr.reopen(out)
|
|
353
|
+
exit(yield(domain) ? 0 : 1)
|
|
354
|
+
end
|
|
355
|
+
running[pid] = [index, out, Process.clock_gettime(Process::CLOCK_MONOTONIC)]
|
|
356
|
+
end
|
|
357
|
+
pid, status = Process.wait2
|
|
358
|
+
index, out, started = running.delete(pid)
|
|
359
|
+
results[index] = [status.success?, out, Process.clock_gettime(Process::CLOCK_MONOTONIC) - started]
|
|
360
|
+
end
|
|
361
|
+
slowest = results.max_by(5) { |_, (_, _, seconds)| seconds }
|
|
362
|
+
.map { |index, (_, _, seconds)| "#{File.basename(domains[index])} #{seconds.round}s" }
|
|
363
|
+
warn "slowest domains: #{slowest.join(', ')}"
|
|
364
|
+
results.sort.map do |_, (clean, out, _)|
|
|
365
|
+
out.rewind
|
|
366
|
+
print out.read
|
|
367
|
+
out.close!
|
|
368
|
+
clean
|
|
369
|
+
end.all?
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hecks"
|
|
4
|
+
require_relative "../tools"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Tools
|
|
8
|
+
# Compacts a Heki-backed aggregate's journal to empty; the snapshot keeps the current state.
|
|
9
|
+
# Refuses any aggregate a `projected_by` binding reads, since projections replay the full
|
|
10
|
+
# journal.
|
|
11
|
+
#
|
|
12
|
+
# hecks compact_heki <domain> [aggregate_name ...] # dry run
|
|
13
|
+
# hecks compact_heki <domain> [aggregate_name ...] --force # apply
|
|
14
|
+
module HekiCompaction
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
# Reports, or with `--force` applies, the compaction of each matching aggregate's journal.
|
|
18
|
+
#
|
|
19
|
+
# @param argv [Array<String>] the domain directory, aggregate names and `--force`
|
|
20
|
+
# @param root [String] unused: the domain is read from the path given
|
|
21
|
+
# @return [Integer] 0, or 1 when the domain does not exist or a projection refuses one
|
|
22
|
+
def main(argv, **)
|
|
23
|
+
argv = argv.dup
|
|
24
|
+
force = argv.delete("--force")
|
|
25
|
+
domain = argv.shift
|
|
26
|
+
if domain.nil? || !Dir.exist?(domain)
|
|
27
|
+
warn "hecks compact_heki: no such domain #{domain.inspect}"
|
|
28
|
+
warn "usage: hecks compact_heki <domain> [aggregate_name ...] [--force]"
|
|
29
|
+
return 1
|
|
30
|
+
end
|
|
31
|
+
wanted = argv.dup
|
|
32
|
+
|
|
33
|
+
registry = Hecks.boot(domain).registry
|
|
34
|
+
candidates = candidates(registry, wanted)
|
|
35
|
+
if candidates.empty?
|
|
36
|
+
puts "hecks compact_heki: no Heki-backed aggregate matched " \
|
|
37
|
+
"#{wanted.empty? ? '(any)' : wanted.inspect} in #{domain}"
|
|
38
|
+
return 0
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
refused = candidates.map { |candidate| refused_candidate?(registry, candidate, force: force) }
|
|
42
|
+
refused.any? ? 1 : 0
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @param registry [Hecks::Runtime::Registry] the booted registry
|
|
46
|
+
# @param wanted [Array<String>] aggregate names, or empty for every aggregate
|
|
47
|
+
# @return [Array<Hash>] each compactable aggregate, its repository and adapter
|
|
48
|
+
def candidates(registry, wanted)
|
|
49
|
+
registry.bluebooks.each_with_object([]) do |(domain_name, bluebook), all|
|
|
50
|
+
bluebook.aggregates.each do |aggregate|
|
|
51
|
+
next unless wanted.empty? || wanted.include?(aggregate.hecks_name) || wanted.include?(aggregate.storage_name)
|
|
52
|
+
|
|
53
|
+
repository = registry.repository(domain_name, aggregate)
|
|
54
|
+
next unless repository.is_a?(Hecks::Ports::Persistence::AppendOnly)
|
|
55
|
+
|
|
56
|
+
adapter = repository.adapter
|
|
57
|
+
next unless adapter.respond_to?(:compact!)
|
|
58
|
+
|
|
59
|
+
all << { domain_name: domain_name, aggregate: aggregate, repository: repository, adapter: adapter }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @return [Boolean] true when a `projected_by` binding refuses the candidate
|
|
65
|
+
def refused_candidate?(registry, candidate, force:)
|
|
66
|
+
aggregate = candidate[:aggregate]
|
|
67
|
+
adapter = candidate[:adapter]
|
|
68
|
+
bound = Hecks::Ports::Projection.binds_for(registry, candidate[:domain_name], aggregate)
|
|
69
|
+
|
|
70
|
+
if bound.any?
|
|
71
|
+
warn "REFUSED #{aggregate.storage_name}: a projected_by binding reads its full journal " \
|
|
72
|
+
"(#{bound.map(&:adapter).join(', ')}) — compacting would silently break " \
|
|
73
|
+
"Projection::Worker#catch_up!/Registry#projection_current?. Not compacted."
|
|
74
|
+
return true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
journal_path = "#{adapter.path}.journal"
|
|
78
|
+
entry_count = adapter.entries.length
|
|
79
|
+
byte_size = File.exist?(journal_path) ? File.size(journal_path) : 0
|
|
80
|
+
|
|
81
|
+
if entry_count.zero?
|
|
82
|
+
puts "SKIP #{aggregate.storage_name}: journal already empty"
|
|
83
|
+
return false
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
unless force
|
|
87
|
+
puts "DRY RUN #{aggregate.storage_name}: would discard #{entry_count} journal " \
|
|
88
|
+
"entries (#{byte_size} bytes) at #{journal_path}; current state (#{adapter.count} " \
|
|
89
|
+
"records) is unaffected. Re-run with --force to apply."
|
|
90
|
+
return false
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
adapter.compact!
|
|
94
|
+
puts "COMPACTED #{aggregate.storage_name}: discarded #{entry_count} journal entries " \
|
|
95
|
+
"(#{byte_size} bytes freed); #{adapter.count} records preserved at #{adapter.path}"
|
|
96
|
+
false
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|