hecks 2.9.0 → 3.0.0
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 +4 -2
- 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 +7 -4
- 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 +6 -3
- 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,214 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require "tmpdir"
|
|
3
|
+
require "tempfile"
|
|
4
|
+
require "json"
|
|
5
|
+
require_relative "isolated_boot"
|
|
6
|
+
require_relative "../naming"
|
|
7
|
+
require_relative "../quality_control/cli/child"
|
|
8
|
+
|
|
9
|
+
module Hecks
|
|
10
|
+
module Fuzzing
|
|
11
|
+
# Races a generated sequence's own command step across two real OS processes
|
|
12
|
+
# against a live domain, checking the outcome against a sequential oracle to catch a broken
|
|
13
|
+
# cross-process lock.
|
|
14
|
+
module ConcurrentDispatch
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
COMMAND_STEP = ->(step) { step["verb"] && !step["query"] && !step["dry_run"] }
|
|
18
|
+
|
|
19
|
+
# `database:` is the shared, never-dropped scratch database; `race_schema:`
|
|
20
|
+
# and `reference_schema:` are this call's own disposable schemas.
|
|
21
|
+
def check(domain_path, steps, database:, race_schema:, reference_schema:)
|
|
22
|
+
normalized = steps.map { |step| step.transform_keys(&:to_s) }
|
|
23
|
+
lockable, probe_errors = lockable_verbs(domain_path, normalized, database: database, schema: reference_schema)
|
|
24
|
+
race_index = pick_race_index(normalized, lockable)
|
|
25
|
+
unless race_index
|
|
26
|
+
# `[]` must mean "nothing to race" (a query/dry-run-only sequence,
|
|
27
|
+
# a legitimate clean result), never a probe failure for every verb —
|
|
28
|
+
# see lockable_verbs, which distinguishes the two below.
|
|
29
|
+
return [] if probe_errors.empty?
|
|
30
|
+
|
|
31
|
+
return [{ field: "concurrency_unraceable",
|
|
32
|
+
detail: "no command step could be raced because the cross-process-lock probe failed: " \
|
|
33
|
+
"#{probe_errors.uniq.join('; ')}" }]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
setup_steps = normalized[0...race_index]
|
|
37
|
+
race_step = normalized[race_index]
|
|
38
|
+
|
|
39
|
+
reference = reference_outcomes(domain_path, setup_steps, race_step, database: database, schema: reference_schema)
|
|
40
|
+
concurrent = concurrent_outcomes(domain_path, setup_steps, race_step, database: database, schema: race_schema)
|
|
41
|
+
|
|
42
|
+
divergences_for(race_step, reference, concurrent)
|
|
43
|
+
rescue StandardError => e
|
|
44
|
+
[{ field: "process", detail: "#{e.class}: #{e.message}" }]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Picks the command step closest to the middle of the sequence (best odds of
|
|
48
|
+
# conflicting with itself); nil if no step is eligible after lockable_verbs filters.
|
|
49
|
+
def pick_race_index(steps, lockable_verbs = nil)
|
|
50
|
+
command_indices = steps.each_index.select { |i| COMMAND_STEP.call(steps[i]) }
|
|
51
|
+
command_indices = command_indices.select { |i| lockable_verbs.include?(steps[i]["verb"]) } if lockable_verbs
|
|
52
|
+
return nil if command_indices.empty?
|
|
53
|
+
|
|
54
|
+
command_indices[command_indices.size / 2]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Filters to verbs whose aggregate resolves to a `:cross_process_lock`-declaring
|
|
58
|
+
# adapter — a Memory-backed aggregate (e.g. one only `uses_framework`s) can never
|
|
59
|
+
# agree with the sequential oracle across two processes, a guaranteed false positive.
|
|
60
|
+
def lockable_verbs(domain_path, steps, database:, schema:)
|
|
61
|
+
verbs = steps.select { |step| COMMAND_STEP.call(step) }.map { |step| step["verb"] }.uniq
|
|
62
|
+
lockable = []
|
|
63
|
+
probe_errors = []
|
|
64
|
+
boot_preserving_schema(domain_path, database: database, schema: schema) do |copy|
|
|
65
|
+
runtime = Hecks.boot(copy, environment: nil)
|
|
66
|
+
verbs.each do |verb|
|
|
67
|
+
lockable << verb if verb_cross_process_lockable?(runtime, verb, probe_errors)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
[lockable, probe_errors]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# false (with a recorded probe error) for a verb this boot cannot resolve, not a guess.
|
|
74
|
+
def verb_cross_process_lockable?(runtime, verb, probe_errors = [])
|
|
75
|
+
domain, aggregate_name, = Naming.split_verb(verb)
|
|
76
|
+
return false unless domain && aggregate_name
|
|
77
|
+
|
|
78
|
+
aggregate = runtime.registry.bluebook(domain)&.aggregate(aggregate_name)
|
|
79
|
+
return false unless aggregate
|
|
80
|
+
|
|
81
|
+
repository = runtime.registry.repository(domain, aggregate)
|
|
82
|
+
repository.capabilities.include?(:cross_process_lock)
|
|
83
|
+
rescue StandardError => e
|
|
84
|
+
# Still `false` — a verb this boot cannot resolve is not raced on a
|
|
85
|
+
# guess — but never silent: `check` needs to tell "nothing here
|
|
86
|
+
# declares a cross-process lock" from "asking broke".
|
|
87
|
+
probe_errors << "#{verb}: #{e.class}: #{e.message}"
|
|
88
|
+
false
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def divergences_for(race_step, reference, concurrent)
|
|
92
|
+
crashes = (reference + concurrent).select { |outcome| outcome.start_with?("crashed:") }.uniq
|
|
93
|
+
return crashes.map { |c| { field: "concurrency_crash", verb: race_step["verb"], detail: c } } if crashes.any?
|
|
94
|
+
|
|
95
|
+
return [] if reference.sort == concurrent.sort
|
|
96
|
+
|
|
97
|
+
[{ field: "concurrency_race", verb: race_step["verb"], reference: reference, concurrent: concurrent,
|
|
98
|
+
detail: "two concurrent cross-process dispatches of #{race_step['verb']} settled as #{concurrent.sort} " \
|
|
99
|
+
"where the identical pair, dispatched sequentially with no contention, settled as " \
|
|
100
|
+
"#{reference.sort} — the cross-process write lock did not correctly serialize this write" }]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# The oracle: one boot, one process, setup then the race step twice in a row.
|
|
104
|
+
# Nothing else touches this schema, so the outcome is correct by construction.
|
|
105
|
+
def reference_outcomes(domain_path, setup_steps, race_step, database:, schema:)
|
|
106
|
+
outcomes = []
|
|
107
|
+
IsolatedBoot.call(domain_path, adapter: :postgres_era, database: database, schema: schema) do |copy|
|
|
108
|
+
runtime = Hecks.boot(copy, environment: nil)
|
|
109
|
+
dispatch_all!(runtime, setup_steps)
|
|
110
|
+
outcomes << dispatch_one(runtime, race_step)
|
|
111
|
+
outcomes << dispatch_one(runtime, race_step)
|
|
112
|
+
end
|
|
113
|
+
outcomes
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Setup replays once, sequentially, before either racer starts, against the
|
|
117
|
+
# already-wiped schema. Real OS processes, not threads, race it —
|
|
118
|
+
# Runtime::AggregateLock's in-process registry would serialize threads
|
|
119
|
+
# regardless of the lock fix. Process.spawn, not Process.fork — forking
|
|
120
|
+
# duplicates open file descriptors, including this process's own live
|
|
121
|
+
# Postgres connection, which corrupted it when tried here.
|
|
122
|
+
def concurrent_outcomes(domain_path, setup_steps, race_step, database:, schema:)
|
|
123
|
+
IsolatedBoot.call(domain_path, adapter: :postgres_era, database: database, schema: schema) do |copy|
|
|
124
|
+
dispatch_all!(Hecks.boot(copy, environment: nil), setup_steps)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
root = File.expand_path("../../..", __dir__)
|
|
128
|
+
args_json = JSON.generate(race_step["args"] || {})
|
|
129
|
+
logs = Array.new(2) { Tempfile.new(["qa-concurrency-racer-", ".log"]) }
|
|
130
|
+
logs.each(&:unlink)
|
|
131
|
+
|
|
132
|
+
pids = logs.map do |log|
|
|
133
|
+
racer = Hecks::QualityControlCli::Child.argv(root, "qa_concurrency_racer", domain_path, database, schema,
|
|
134
|
+
race_step["verb"], args_json)
|
|
135
|
+
Process.spawn(*racer, out: log, err: log, chdir: root)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
pids.each { |pid| Process.wait(pid) }
|
|
139
|
+
logs.map do |log|
|
|
140
|
+
log.rewind
|
|
141
|
+
output = log.read
|
|
142
|
+
log.close
|
|
143
|
+
output.strip.empty? ? "crashed:no output from the concurrency racer" : output.lines.last.chomp
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Never raises: a declared domain refusal maps to "refused"; anything else
|
|
148
|
+
# escaping maps to "crashed:<class>: <message>" instead of killing a racer.
|
|
149
|
+
def dispatch_one(runtime, step)
|
|
150
|
+
args = (step["args"] || {}).transform_keys(&:to_sym)
|
|
151
|
+
runtime.dispatch_flat(step["verb"], args)
|
|
152
|
+
"succeeded"
|
|
153
|
+
rescue *Hecks::Runtime::DOMAIN_REFUSALS, Hecks::Bluebook::Expression::EvaluationError
|
|
154
|
+
"refused"
|
|
155
|
+
rescue StandardError => e
|
|
156
|
+
"crashed:#{e.class}: #{e.message}"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Setup tolerates an ordinary refusal but never a crash — an unexpected
|
|
160
|
+
# exception here leaves the schema in an unknown state, not safe to race against.
|
|
161
|
+
def dispatch_all!(runtime, steps)
|
|
162
|
+
steps.each do |step|
|
|
163
|
+
outcome = dispatch_one(runtime, step)
|
|
164
|
+
raise "setup step #{step['verb']} #{outcome}" if outcome.start_with?("crashed:")
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Same copy-and-rebind IsolatedBoot.call(..., adapter: :postgres_era) does,
|
|
169
|
+
# minus the schema wipe — this schema already holds what setup wrote, and
|
|
170
|
+
# the race must run against that, not a blank one.
|
|
171
|
+
def boot_preserving_schema(domain_path, database:, schema:)
|
|
172
|
+
Dir.mktmpdir("hecks-concurrency") do |tmp|
|
|
173
|
+
copy = File.join(tmp, File.basename(domain_path))
|
|
174
|
+
IsolatedBoot.copy_dereferencing(domain_path, copy)
|
|
175
|
+
FileUtils.rm_rf(File.join(copy, "data"))
|
|
176
|
+
IsolatedBoot.rewrite_bindings!(copy, "PostgresEra")
|
|
177
|
+
write_postgres_era_world!(copy, database: database, schema: schema)
|
|
178
|
+
yield copy
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# One world file per directory, merging every hecksagon name found there,
|
|
183
|
+
# replacing any other `.world` file the copy already carries.
|
|
184
|
+
def write_postgres_era_world!(copy, database:, schema:)
|
|
185
|
+
worlds_by_dir = Hash.new { |h, k| h[k] = [] }
|
|
186
|
+
Dir.glob(File.join(copy, "**", "*.hecksagon")).each do |hecksagon_path|
|
|
187
|
+
names = File.read(hecksagon_path).scan(/Hecks\.hecksagon\s+"([^"]+)"/).flatten
|
|
188
|
+
worlds_by_dir[File.dirname(hecksagon_path)].concat(names)
|
|
189
|
+
end
|
|
190
|
+
worlds_by_dir.each do |dir, names|
|
|
191
|
+
names = names.uniq
|
|
192
|
+
next if names.empty?
|
|
193
|
+
|
|
194
|
+
world_path = File.join(dir, "hecks_fuzz_postgres_era.world")
|
|
195
|
+
File.write(world_path, names.map do |name|
|
|
196
|
+
<<~WORLD
|
|
197
|
+
Hecks.world "#{name}" do
|
|
198
|
+
persisted_by("PostgresEra") do
|
|
199
|
+
database "#{database}"
|
|
200
|
+
schema "#{schema}"
|
|
201
|
+
allow_superuser true
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
WORLD
|
|
205
|
+
end.join("\n"))
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
Dir.glob(File.join(copy, "**", "*.world")).each do |path|
|
|
209
|
+
File.delete(path) unless File.basename(path) == "hecks_fuzz_postgres_era.world"
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Fuzzing
|
|
3
|
+
# Remembers which (verb, kind, state, mutation, outcome) tuples a sweep has reached.
|
|
4
|
+
# Later seeds splice corpus prefixes and favor rarely hit verbs.
|
|
5
|
+
#
|
|
6
|
+
# Reproducible: the only randomness is `Random.new(seed)`, and each `Plan` is printable.
|
|
7
|
+
class CoverageCampaign
|
|
8
|
+
# One seed's generation instructions, printable and reproducible.
|
|
9
|
+
Plan = Struct.new(:prefix, :favor, keyword_init: true) do
|
|
10
|
+
# The keyword arguments `SequenceGenerator.generate` and `.trace` accept.
|
|
11
|
+
def generator_options = { prefix: prefix, favor: favor }
|
|
12
|
+
|
|
13
|
+
def spliced? = !prefix.nil?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :corpus
|
|
17
|
+
|
|
18
|
+
# @param splice_probability [Float] chance, per seed with a non-empty corpus, of splicing
|
|
19
|
+
# @param favor_count [Integer] rare verbs to favor per plan; zero or less disables favoring
|
|
20
|
+
# @param corpus_limit [Integer] entries kept; the oldest is dropped past this
|
|
21
|
+
# @param max_prefix_depth [Integer] nested-prefix depth beyond which entries are not admitted
|
|
22
|
+
def initialize(splice_probability:, favor_count:, corpus_limit: 64, max_prefix_depth: 6)
|
|
23
|
+
@splice_probability = splice_probability.to_f
|
|
24
|
+
@favor_count = favor_count.to_i
|
|
25
|
+
@corpus_limit = corpus_limit
|
|
26
|
+
@max_prefix_depth = max_prefix_depth
|
|
27
|
+
@corpus = []
|
|
28
|
+
@seen = Set.new
|
|
29
|
+
@verb_hits = Hash.new(0)
|
|
30
|
+
@declared_verbs = Set.new
|
|
31
|
+
@seeds = 0
|
|
32
|
+
@spliced = 0
|
|
33
|
+
@seeds_with_new = 0
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Builds this seed's plan: a possible corpus splice, plus the verbs to favor.
|
|
37
|
+
#
|
|
38
|
+
# @param seed [Integer] drives the splice coin flip, the corpus entry and the prefix length
|
|
39
|
+
# @return [Fuzzing::CoverageCampaign::Plan]
|
|
40
|
+
def plan(seed)
|
|
41
|
+
random = Random.new(seed)
|
|
42
|
+
prefix = nil
|
|
43
|
+
if !@corpus.empty? && random.rand < @splice_probability
|
|
44
|
+
entry = @corpus[random.rand(@corpus.size)]
|
|
45
|
+
prefix = entry[:spec].merge("steps" => random.rand(1..entry[:attempts]))
|
|
46
|
+
end
|
|
47
|
+
Plan.new(prefix: prefix, favor: rare_verbs)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Folds one seed's trace into the running coverage; admits a corpus entry if it reached
|
|
51
|
+
# a new tuple.
|
|
52
|
+
#
|
|
53
|
+
# @param seed [Integer] the seed the trace was generated from
|
|
54
|
+
# @param plan [Fuzzing::CoverageCampaign::Plan] the plan that seed ran with
|
|
55
|
+
# @param trace [Fuzzing::SequenceGenerator::Trace] from `SequenceGenerator.trace`
|
|
56
|
+
# @return [void]
|
|
57
|
+
def record(seed, plan, trace)
|
|
58
|
+
@seeds += 1
|
|
59
|
+
@spliced += 1 if plan.spliced?
|
|
60
|
+
@declared_verbs.merge(trace.verbs)
|
|
61
|
+
|
|
62
|
+
new_at = trace.coverage.filter_map do |index, tuple|
|
|
63
|
+
@verb_hits[tuple.split(" | ").first] += 1
|
|
64
|
+
index if @seen.add?(tuple)
|
|
65
|
+
end
|
|
66
|
+
return if new_at.empty?
|
|
67
|
+
|
|
68
|
+
@seeds_with_new += 1
|
|
69
|
+
admit(seed, plan, new_at.max + 1)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# The number of distinct coverage tuples reached so far.
|
|
73
|
+
def tuples_seen = @seen.size
|
|
74
|
+
|
|
75
|
+
# One line of running totals: tuples seen, seeds run, seeds with new coverage, splices.
|
|
76
|
+
def summary
|
|
77
|
+
"coverage: #{@seen.size} distinct (verb, kind, state, mutation, outcome) tuple(s) over #{@seeds} seed(s); " \
|
|
78
|
+
"#{@seeds_with_new} seed(s) reached something new; #{@spliced} spliced from a corpus of #{@corpus.size}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Serializes the accumulated corpus and coverage knowledge, JSON-safe, so a later process can
|
|
82
|
+
# pick this campaign up where it left off. This campaign's own dials — splice probability,
|
|
83
|
+
# favor count, corpus and prefix-depth bounds — are configuration a caller supplies fresh each
|
|
84
|
+
# time, not state, so they are not part of this.
|
|
85
|
+
#
|
|
86
|
+
# @return [Hash] with string keys "corpus", "seen", "verb_hits", "declared_verbs"
|
|
87
|
+
def to_h
|
|
88
|
+
{ "corpus" => @corpus.map { |entry| { "spec" => entry[:spec], "attempts" => entry[:attempts] } },
|
|
89
|
+
"seen" => @seen.to_a,
|
|
90
|
+
"verb_hits" => @verb_hits.dup,
|
|
91
|
+
"declared_verbs" => @declared_verbs.to_a }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Replaces this campaign's accumulated corpus and coverage knowledge with what a prior
|
|
95
|
+
# campaign's `#to_h` serialized, leaving this campaign's own dials untouched.
|
|
96
|
+
#
|
|
97
|
+
# @param state [Hash] as `#to_h` produced it; string or symbol keys both accepted
|
|
98
|
+
# @return [void]
|
|
99
|
+
def restore!(state)
|
|
100
|
+
state = state.transform_keys(&:to_s)
|
|
101
|
+
@corpus = Array(state["corpus"]).map { |entry| { spec: entry["spec"], attempts: entry["attempts"] } }
|
|
102
|
+
@seen = Set.new(Array(state["seen"]))
|
|
103
|
+
@verb_hits = Hash.new(0).merge(state["verb_hits"] || {})
|
|
104
|
+
@declared_verbs = Set.new(Array(state["declared_verbs"]))
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Builds a campaign that already knows what a prior campaign's `#to_h` reached, configured
|
|
108
|
+
# with this process's own dials rather than whatever the saved state's own process ran with.
|
|
109
|
+
#
|
|
110
|
+
# @param state [Hash] as `#to_h` produced it; string or symbol keys both accepted, since a
|
|
111
|
+
# round trip through JSON turns every key into a string
|
|
112
|
+
# @param splice_probability [Float] see `#initialize`
|
|
113
|
+
# @param favor_count [Integer] see `#initialize`
|
|
114
|
+
# @param corpus_limit [Integer] see `#initialize`
|
|
115
|
+
# @param max_prefix_depth [Integer] see `#initialize`
|
|
116
|
+
# @return [Fuzzing::CoverageCampaign]
|
|
117
|
+
def self.load(state, splice_probability:, favor_count:, corpus_limit: 64, max_prefix_depth: 6)
|
|
118
|
+
new(splice_probability: splice_probability, favor_count: favor_count,
|
|
119
|
+
corpus_limit: corpus_limit, max_prefix_depth: max_prefix_depth).tap { |campaign| campaign.restore!(state) }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def admit(seed, plan, attempts)
|
|
125
|
+
return if depth(plan.prefix) + 1 > @max_prefix_depth
|
|
126
|
+
|
|
127
|
+
spec = { "seed" => seed, "favor" => plan.favor }
|
|
128
|
+
spec["prefix"] = plan.prefix if plan.prefix
|
|
129
|
+
@corpus << { spec: spec, attempts: attempts }
|
|
130
|
+
@corpus.shift while @corpus.size > @corpus_limit
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def depth(prefix) = prefix.nil? ? 0 : 1 + depth(prefix["prefix"])
|
|
134
|
+
|
|
135
|
+
# Ties break by name so a plan is a pure function of what was recorded.
|
|
136
|
+
def rare_verbs
|
|
137
|
+
return [] unless @favor_count.positive?
|
|
138
|
+
|
|
139
|
+
(@declared_verbs | @verb_hits.keys).min_by(@favor_count) { |verb| [@verb_hits[verb], verb] }
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "open3"
|
|
3
|
+
require_relative "replay"
|
|
4
|
+
require_relative "properties"
|
|
5
|
+
require_relative "self_consistency"
|
|
6
|
+
require_relative "rust_gap_manifest"
|
|
7
|
+
require_relative "nondeterministic"
|
|
8
|
+
|
|
9
|
+
module Hecks
|
|
10
|
+
module Fuzzing
|
|
11
|
+
# The Ruby-vs-Rust comparison of one generated sequence, shared by the QualityControl sweep
|
|
12
|
+
# (`hecks quality_control ask run`) and `hecks quality_control check_generated_domains --rust`.
|
|
13
|
+
#
|
|
14
|
+
# `differ` is duck-typed (the `RustConformanceHelpers` comparison helpers plus a
|
|
15
|
+
# `structural_skips` set) so lib never requires spec/.
|
|
16
|
+
module Differential
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# Drops both sides' rows for verbs `gaps` declares not generated; every other
|
|
20
|
+
# divergence, however worded, stays in. A tolerated verb Rust nonetheless answered
|
|
21
|
+
# is reported as stale, so a wrong manifest cannot silently hold.
|
|
22
|
+
#
|
|
23
|
+
# @param gaps [Hecks::Fuzzing::RustGapManifest] the compiled binary's manifest
|
|
24
|
+
# @param ruby_refusals [Array<Hash>] Ruby's own refusal rows for this history
|
|
25
|
+
# @param rust_refusals [Array<Hash>] the Rust binary's own refusal rows
|
|
26
|
+
# @param ruby_queries [Array<Hash>] Ruby's own query rows for this history
|
|
27
|
+
# @param rust_queries [Array<Hash>] the Rust binary's own query rows
|
|
28
|
+
# @return [Hash] the four row lists with tolerated verbs removed, `skipped:` (a
|
|
29
|
+
# `Set<String>` of tolerated verbs reached) and `stale:` (`Array<Hash>` divergences)
|
|
30
|
+
def manifest_partition(gaps, ruby_refusals:, rust_refusals:, ruby_queries:, rust_queries:)
|
|
31
|
+
verb_of = ->(row) { row.key?("verb") ? row["verb"] : row["query"] }
|
|
32
|
+
declared = ->(row) { gaps.not_generated?(verb_of.call(row)) }
|
|
33
|
+
stale = rust_queries.select(&declared).map do |row|
|
|
34
|
+
{ field: "manifest", verb: row["query"],
|
|
35
|
+
detail: "#{row['query']} is declared generated: false in manifest.json " \
|
|
36
|
+
"(#{gaps.not_generated(row['query']).values_at('gap_class', 'construct').join('/')}), " \
|
|
37
|
+
"but the Rust binary answered it — regenerate with hecks project_rust" }
|
|
38
|
+
end
|
|
39
|
+
reached = (ruby_refusals + rust_refusals + ruby_queries).select(&declared)
|
|
40
|
+
{ ruby_refusals: ruby_refusals.reject(&declared), rust_refusals: rust_refusals.reject(&declared),
|
|
41
|
+
ruby_queries: ruby_queries.reject(&declared), rust_queries: rust_queries.reject(&declared),
|
|
42
|
+
skipped: reached.to_set(&verb_of), stale: stale }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Runs the standard property battery on a replayed history.
|
|
46
|
+
#
|
|
47
|
+
# @param history [Hash] as returned by `Replay.call`
|
|
48
|
+
# @return [Array<Hash>] one `{field:, detail:}` entry per failed property
|
|
49
|
+
def property_divergences(history)
|
|
50
|
+
Properties.check(history).reject { |_, result| result == true }
|
|
51
|
+
.map { |name, message| { field: name.to_s, detail: message } }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Flattens `history[:self_consistency]`'s per-check divergence lists.
|
|
55
|
+
#
|
|
56
|
+
# @param history [Hash] as returned by `Replay.call`, optionally with `:self_consistency`
|
|
57
|
+
# @return [Array<Hash>] empty if `history` carries no `:self_consistency`
|
|
58
|
+
def self_consistency_divergences(history)
|
|
59
|
+
return [] unless history[:self_consistency]
|
|
60
|
+
|
|
61
|
+
history[:self_consistency].values.flatten(1)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
65
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
66
|
+
# rubocop:disable-next Metrics/PerceivedComplexity
|
|
67
|
+
# rubocop:disable-next Metrics/MethodLength
|
|
68
|
+
# @param differ [Object] duck-typed `RustConformanceHelpers` interface
|
|
69
|
+
# @param domain_path [String] the domain directory to replay
|
|
70
|
+
# @param steps [Array<Hash>] the step sequence replayed on both engines
|
|
71
|
+
# @param binary [String] the compiled Rust conformance binary
|
|
72
|
+
# @param modes [Array<Symbol>] `:differential` (always run), `:self_consistency`,
|
|
73
|
+
# `:properties_in_differential`, `:adapter_parity_sqlite`
|
|
74
|
+
# @param adapter_parity_sqlite [Proc, nil] the caller's comparison; `nil` skips the mode
|
|
75
|
+
# @return [Hash{Symbol => Array<Hash>}] one divergence list per active mode
|
|
76
|
+
def diff(differ, domain_path, steps, binary, modes:, adapter_parity_sqlite: nil)
|
|
77
|
+
self_consistency = modes.include?(:self_consistency)
|
|
78
|
+
ruby_result = Replay.call(domain_path, steps, self_consistency: self_consistency)
|
|
79
|
+
ruby_instances = JSON.parse(JSON.generate(ruby_result[:instances]))
|
|
80
|
+
ruby_events = JSON.parse(JSON.generate(ruby_result[:events]))
|
|
81
|
+
ruby_refusals = ruby_result[:refusals].map do |r|
|
|
82
|
+
{ "verb" => r[:verb].to_s, "kind" => r[:kind].to_s.split("::").last, "error" => r[:error] }
|
|
83
|
+
end
|
|
84
|
+
ruby_queries = JSON.parse(JSON.generate(ruby_result[:queries].map { |row| Nondeterministic.strip(row, :query_row) }))
|
|
85
|
+
ruby_sagas = JSON.parse(JSON.generate(ruby_result[:sagas]))
|
|
86
|
+
ruby_dry_runs = ruby_result[:dry_runs].map { |d| { "verb" => d[:verb].to_s, "ok" => d[:ok] } }
|
|
87
|
+
|
|
88
|
+
outcomes = {}
|
|
89
|
+
outcomes[:properties_in_differential] = property_divergences(ruby_result) if modes.include?(:properties_in_differential)
|
|
90
|
+
if modes.include?(:adapter_parity_sqlite) && adapter_parity_sqlite
|
|
91
|
+
outcomes[:adapter_parity_sqlite] = adapter_parity_sqlite.call
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
stdout, status = Open3.capture2(binary, stdin_data: JSON.generate({ "steps" => steps }))
|
|
95
|
+
unless status.success?
|
|
96
|
+
return outcomes.merge(differential: [{ field: "process",
|
|
97
|
+
detail: "rust binary exited #{status.exitstatus}: #{stdout}" }])
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
rust_output = JSON.parse(stdout)
|
|
101
|
+
rust_live_instances = JSON.parse(JSON.generate(rust_output["instances"])) if self_consistency
|
|
102
|
+
differ.strip_emitted_flags!(rust_output["instances"])
|
|
103
|
+
differ.strip_emitted_flags!(rust_output["queries"])
|
|
104
|
+
differ.strip_occurred_at!(rust_output["events"])
|
|
105
|
+
|
|
106
|
+
divergences = []
|
|
107
|
+
divergences << { field: "instances", ruby: ruby_instances, rust: rust_output["instances"] } \
|
|
108
|
+
unless rust_output["instances"] == ruby_instances
|
|
109
|
+
divergences << { field: "events", ruby: ruby_events, rust: rust_output["events"] } \
|
|
110
|
+
unless rust_output["events"] == ruby_events
|
|
111
|
+
|
|
112
|
+
kept = manifest_partition(RustGapManifest.for_binary(binary),
|
|
113
|
+
ruby_refusals: ruby_refusals, rust_refusals: rust_output["refusals"],
|
|
114
|
+
ruby_queries: ruby_queries, rust_queries: rust_output["queries"])
|
|
115
|
+
differ.structural_skips.merge(kept[:skipped])
|
|
116
|
+
divergences.concat(kept[:stale])
|
|
117
|
+
|
|
118
|
+
by_kind = ->(r) { r.slice("verb", "kind") }
|
|
119
|
+
rust_refusals = kept[:rust_refusals].map(&by_kind)
|
|
120
|
+
kept_ruby_refusals = kept[:ruby_refusals].map(&by_kind)
|
|
121
|
+
divergences << { field: "refusals", ruby: kept_ruby_refusals, rust: rust_refusals } \
|
|
122
|
+
unless rust_refusals == kept_ruby_refusals
|
|
123
|
+
|
|
124
|
+
wordless = ->(row) { differ.reduce_to_wire_precision(row.except("error", "reference_error")) }
|
|
125
|
+
rust_queries = kept[:rust_queries].map(&wordless)
|
|
126
|
+
kept_ruby_queries = kept[:ruby_queries].map(&wordless)
|
|
127
|
+
divergences << { field: "queries", ruby: kept_ruby_queries, rust: rust_queries } \
|
|
128
|
+
unless rust_queries == kept_ruby_queries
|
|
129
|
+
|
|
130
|
+
divergences << { field: "sagas", ruby: ruby_sagas, rust: rust_output["sagas"] } \
|
|
131
|
+
unless rust_output["sagas"] == ruby_sagas
|
|
132
|
+
|
|
133
|
+
cross_domain = differ.cross_domain_policy_names(rust_output)
|
|
134
|
+
kept_ruby_reactions = JSON.parse(JSON.generate(ruby_result[:reactions]))
|
|
135
|
+
.reject { |r| cross_domain.include?(r["policy"]) }
|
|
136
|
+
rust_reactions = rust_output.fetch("reactions")
|
|
137
|
+
divergences << { field: "reactions", ruby: kept_ruby_reactions, rust: rust_reactions } \
|
|
138
|
+
unless rust_reactions == kept_ruby_reactions
|
|
139
|
+
|
|
140
|
+
rust_dry_runs = Array(rust_output["dry_runs"]).map { |d| d.slice("verb", "ok") }
|
|
141
|
+
divergences << { field: "dry_runs", ruby: ruby_dry_runs, rust: rust_dry_runs } \
|
|
142
|
+
unless rust_dry_runs == ruby_dry_runs
|
|
143
|
+
outcomes[:differential] = divergences
|
|
144
|
+
|
|
145
|
+
if self_consistency
|
|
146
|
+
outcomes[:self_consistency] =
|
|
147
|
+
self_consistency_divergences(ruby_result) +
|
|
148
|
+
SelfConsistency.check_rust_rehydration(binary, differ, rust_live_instances) +
|
|
149
|
+
SelfConsistency.check_rust_idempotency(binary, differ, rust_live_instances)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
outcomes
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|