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,611 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "json"
|
|
5
|
+
require "open3"
|
|
6
|
+
require "securerandom"
|
|
7
|
+
require "tempfile"
|
|
8
|
+
require_relative "../../../hecks"
|
|
9
|
+
# The era/lineage plugin does not load with core (ADR 0033); the ledger's binding needs it.
|
|
10
|
+
require_relative "../../ports/persistence/plugins/era"
|
|
11
|
+
require_relative "../../fuzzing"
|
|
12
|
+
require_relative "../../fuzzing/self_consistency"
|
|
13
|
+
require_relative "../../fuzzing/differential"
|
|
14
|
+
require_relative "../../fuzzing/era_boundary"
|
|
15
|
+
require_relative "../../fuzzing/concurrent_dispatch"
|
|
16
|
+
require_relative "qa_sweep/parsing"
|
|
17
|
+
require_relative "qa_sweep/all_mode"
|
|
18
|
+
require_relative "qa_sweep/checks"
|
|
19
|
+
require_relative "qa_sweep/dials"
|
|
20
|
+
require_relative "qa_sweep/release_mode"
|
|
21
|
+
require_relative "qa_sweep/finding_report"
|
|
22
|
+
|
|
23
|
+
module Hecks
|
|
24
|
+
module QualityControlCli
|
|
25
|
+
# The command behind `hecks quality_control ask run`: runs one claim, sweep, conclude and
|
|
26
|
+
# release cycle against a `QualityControl` target (`USAGE` lists the forms, from a bare sweep
|
|
27
|
+
# of the least recently swept target to `--all` and `--release`). It stops with a report and
|
|
28
|
+
# exit 2 on the first surprised check, and never logs a `Bug` itself.
|
|
29
|
+
#
|
|
30
|
+
# Exit codes: 0 clean; 1 operational error; 2 found something (the sweep stays open and the
|
|
31
|
+
# target is suspended by the ledger's `SuspendOnSurprise` policy until `--release`).
|
|
32
|
+
#
|
|
33
|
+
# `QA_SWEEP_DOMAIN_DIR` boots a throwaway ledger instead of `qa/bluebook` and is inherited by
|
|
34
|
+
# `--all` children; `QA_SWEEP_RUST_DIR` points differential mode at a fixture crate;
|
|
35
|
+
# `QA_SWEEP_COVERAGE_CORPUS_DIR` points the coverage corpus at a throwaway directory;
|
|
36
|
+
# `QA_SWEEP_TRACE=1` prints a per-phase stderr timer.
|
|
37
|
+
class QaSweep
|
|
38
|
+
EXIT_OK = 0
|
|
39
|
+
EXIT_ERROR = 1
|
|
40
|
+
EXIT_FOUND_SOMETHING = 2
|
|
41
|
+
|
|
42
|
+
USAGE = "usage: hecks quality_control ask run [target=<reference>] arguments=\"[--seeds N] [--steps N] " \
|
|
43
|
+
"[--adversarial FRACTION] [--role-draw FRACTION] [--dry-run FRACTION] " \
|
|
44
|
+
"[--self-consistency true|false] [--modes a,b,c]\"\n " \
|
|
45
|
+
"arguments=\"--all [--seeds N] [--steps N] [--adversarial FRACTION] [--role-draw FRACTION] " \
|
|
46
|
+
"[--dry-run FRACTION] [--self-consistency true|false] [--modes a,b,c] [--no-parity]\"\n " \
|
|
47
|
+
"target=<reference> arguments=\"--persistence-parity [--seeds N]\"\n " \
|
|
48
|
+
"target=<reference> arguments=\"--release --notes 'what a person concluded, 40+ chars'\""
|
|
49
|
+
|
|
50
|
+
# Modes run when the ledger declares no `QualityControlDials::MODES` (an isolated spec's
|
|
51
|
+
# fixture).
|
|
52
|
+
DEFAULT_MODES = %i[differential ruby_only self_consistency properties_in_differential
|
|
53
|
+
structural_skip_report persistence_parity].freeze
|
|
54
|
+
|
|
55
|
+
DEFAULT_TARGETS = { "pizzas" => "examples/pizzas", "banking" => "examples/banking" }.freeze
|
|
56
|
+
|
|
57
|
+
# Modes with no per-seed loop; when only these are active the seed machinery is skipped.
|
|
58
|
+
SEEDLESS_MODES = %i[era_boundary].freeze
|
|
59
|
+
|
|
60
|
+
# The disposable scratch database the parity and concurrency modes create schemas in.
|
|
61
|
+
SCRATCH_DATABASE = "hecks_qa_persistence_parity"
|
|
62
|
+
|
|
63
|
+
include Parsing
|
|
64
|
+
include AllMode
|
|
65
|
+
include Checks
|
|
66
|
+
include Dials
|
|
67
|
+
include ReleaseMode
|
|
68
|
+
include FindingReport
|
|
69
|
+
|
|
70
|
+
# Sweeps.
|
|
71
|
+
#
|
|
72
|
+
# @param argv [Array<String>] the flags in the usage line
|
|
73
|
+
# @param root [String] the repository root
|
|
74
|
+
# @param env [Hash{String => String}] the `QA_SWEEP_*` variables
|
|
75
|
+
# @return [Integer] 0 clean, 2 found something, 1 an operational error
|
|
76
|
+
# @raise [SystemExit] on a bad argument or a refused sweep
|
|
77
|
+
def self.call(argv, root:, env: ENV)
|
|
78
|
+
new(root: root, env: env).call(argv)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @param root [String] the repository root
|
|
82
|
+
# @param env [Hash{String => String}] the `QA_SWEEP_*` variables
|
|
83
|
+
def initialize(root:, env: ENV)
|
|
84
|
+
@root = root
|
|
85
|
+
@domain_dir = env.fetch("QA_SWEEP_DOMAIN_DIR", File.join(root, "qa/bluebook"))
|
|
86
|
+
@rust_dir = env.fetch("QA_SWEEP_RUST_DIR", File.join(root, "rust"))
|
|
87
|
+
@coverage_corpus_dir = env.fetch("QA_SWEEP_COVERAGE_CORPUS_DIR", File.join(root, "tmp/qa-coverage-corpus"))
|
|
88
|
+
@trace_enabled = env["QA_SWEEP_TRACE"] == "1"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @param argv [Array<String>] the flags in the usage line
|
|
92
|
+
# @return [Integer] the exit status
|
|
93
|
+
# @raise [SystemExit] on a bad argument or a refused sweep
|
|
94
|
+
def call(argv)
|
|
95
|
+
@trace_last = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
96
|
+
trace("require")
|
|
97
|
+
return EXIT_OK if parse_arguments(argv.dup) == :help
|
|
98
|
+
|
|
99
|
+
@runtime = boot_ledger
|
|
100
|
+
trace("ledger boot")
|
|
101
|
+
read_dials
|
|
102
|
+
resolve_enabled_modes
|
|
103
|
+
return run_all_mode(modes: @enabled_modes, parity_wave: parity_wave?, deferred_wave_modes: deferred_waves) if @all_mode
|
|
104
|
+
|
|
105
|
+
sweep_one_target
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
# Prints a per-phase stderr timer under `QA_SWEEP_TRACE=1`; each print is the delta since the
|
|
111
|
+
# previous mark.
|
|
112
|
+
def trace(label)
|
|
113
|
+
return unless @trace_enabled
|
|
114
|
+
|
|
115
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
116
|
+
warn format("[qa_sweep_trace pid=%<pid>d] %<label>-24s +%<delta>6.3fs",
|
|
117
|
+
pid: Process.pid, label: label, delta: now - @trace_last)
|
|
118
|
+
@trace_last = now
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Default `install_doors` so `QualityControl::Target` and friends are top-level constants.
|
|
122
|
+
def boot_ledger
|
|
123
|
+
Hecks.boot(@domain_dir)
|
|
124
|
+
rescue StandardError => e
|
|
125
|
+
abort "the QualityControl ledger did not boot — fix the ledger itself before sweeping against it " \
|
|
126
|
+
"(#{e.class}: #{e.message})"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def query(name, **args) = @runtime.query("QualityControl::#{name}", **args)
|
|
130
|
+
|
|
131
|
+
# A stored `Target.path` is either relative to this repo's root (the common case) or an
|
|
132
|
+
# absolute path to a real external checkout. `File.join(root, path)` does not special-case an
|
|
133
|
+
# absolute second argument the way `File.expand_path` does, so an absolute path would
|
|
134
|
+
# otherwise be silently mangled. It matches `QaDiscoverExternalDomains`'s own normalization.
|
|
135
|
+
def resolve_target_path(path)
|
|
136
|
+
path.start_with?("/") ? File.expand_path(path) : File.expand_path(path, @root)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# A target reference may itself contain `/` (`hecks quality_control discover_external_domains`
|
|
140
|
+
# suggests `repo/entity`-shaped references for an external domain), but every reference also
|
|
141
|
+
# gets folded into a single filename component: a log prefix, a shrunk-repro filename, a
|
|
142
|
+
# coverage-corpus filename. Left raw, an embedded `/` is read as an extra path segment that
|
|
143
|
+
# nothing creates, breaking the write. This never changes what is stored as the
|
|
144
|
+
# `Target`/`Sweep` reference itself.
|
|
145
|
+
def filesystem_safe_component(reference)
|
|
146
|
+
reference.gsub(/[^A-Za-z0-9_.-]/, "-")
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Reads the sticky per-check `ever_surprised` bit rather than a loop's local, so a surprise
|
|
150
|
+
# resolved before concluding never reads as clean. `&.` because rows logged before that
|
|
151
|
+
# attribute existed lack the key; missing means no sticky surprise.
|
|
152
|
+
def sweep_was_clean?(sweep)
|
|
153
|
+
sweep.checks.none? { |c| c[:ever_surprised]&.[](:value) == "yes" }
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Decays the target's yield_score, adds this period's finds, and moves the streak (see
|
|
157
|
+
# `Target.Release`).
|
|
158
|
+
def release_target!(target, capabilities, current_streak, surprises:, clean:)
|
|
159
|
+
next_yield_score = Hecks::Fuzzing::RotationPriority.next_yield_score(
|
|
160
|
+
old_score: target.yield_score.value, surprises_this_period: surprises
|
|
161
|
+
)
|
|
162
|
+
target.release!(now: { value: Time.now.to_i }, yield_score: { value: next_yield_score },
|
|
163
|
+
next_streak: { value: clean ? current_streak + 1 : 0 },
|
|
164
|
+
capabilities: { value: capabilities.join(",") })
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Existence-checked: a held or shelved target is invisible to `Rotation`, and re-`Identify`ing
|
|
168
|
+
# an existing id is refused.
|
|
169
|
+
def seed_default_targets!
|
|
170
|
+
existing = query("Target.All").map { |row| row[:reference][:value] }
|
|
171
|
+
DEFAULT_TARGETS.each do |reference, path|
|
|
172
|
+
next if existing.include?(reference)
|
|
173
|
+
|
|
174
|
+
::QualityControl::Target.identify!(reference: { value: reference }, path: { value: path })
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# The only pick with no name given, where `RotationPriority.pick`'s yield weighting matters
|
|
179
|
+
# (`--all` sweeps every row regardless of order).
|
|
180
|
+
def pick_target_row
|
|
181
|
+
if @target_ref
|
|
182
|
+
row = query("Target.All").find { |r| r[:reference][:value] == @target_ref }
|
|
183
|
+
return row if row
|
|
184
|
+
|
|
185
|
+
known = query("Target.All").map { |r| r[:reference][:value] }
|
|
186
|
+
abort "no such target: #{@target_ref.inspect} — known targets: #{known.inspect}"
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
rotation = query("Target.Rotation")
|
|
190
|
+
if rotation.empty?
|
|
191
|
+
seed_default_targets!
|
|
192
|
+
rotation = query("Target.Rotation")
|
|
193
|
+
end
|
|
194
|
+
if rotation.empty?
|
|
195
|
+
abort "the rotation is empty even after seeding pizzas/banking — nothing waiting to sweep " \
|
|
196
|
+
"(every known target is currently held or shelved)"
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
Hecks::Fuzzing::RotationPriority.pick(rotation, now: Time.now.to_i)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# One target, start to finish.
|
|
203
|
+
def sweep_one_target
|
|
204
|
+
row = pick_target_row
|
|
205
|
+
@target_reference = row[:reference][:value]
|
|
206
|
+
@target_path = row[:path][:value]
|
|
207
|
+
@domain_path = resolve_target_path(@target_path)
|
|
208
|
+
unless File.directory?(@domain_path)
|
|
209
|
+
abort "target #{@target_reference.inspect} names a path that does not exist on disk: #{@domain_path}"
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Before the claim: a suspended target cannot be claimed, and must not be until a person
|
|
213
|
+
# runs this.
|
|
214
|
+
return release_suspended_target if @release_mode
|
|
215
|
+
|
|
216
|
+
resolve_target_modes
|
|
217
|
+
claim_target
|
|
218
|
+
resolve_depth
|
|
219
|
+
announce_resolution
|
|
220
|
+
open_sweep
|
|
221
|
+
choose_seat
|
|
222
|
+
prepare_scratch_schemas
|
|
223
|
+
announce_sweep
|
|
224
|
+
surprise = run_seeds
|
|
225
|
+
return report_finding(surprise) if surprise
|
|
226
|
+
|
|
227
|
+
conclude_clean_sweep
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Before the claim: an ineligible target is an operational error (exit 1), never a finding,
|
|
231
|
+
# and must not leave the target held. Deferred modes run only when the whole ask is deferred;
|
|
232
|
+
# mixed with others they wait for `--all`'s later wave.
|
|
233
|
+
def resolve_target_modes
|
|
234
|
+
@capabilities = Hecks::Fuzzing::TargetCapabilities.infer(@domain_path, rust_dir: @rust_dir)
|
|
235
|
+
resolved = Hecks::Fuzzing::TargetCapabilities.resolve(@enabled_modes, @capabilities)
|
|
236
|
+
trace("capabilities+resolve")
|
|
237
|
+
explicit_all_deferred = @explicit_modes && !@explicit_modes.empty? &&
|
|
238
|
+
(@explicit_modes - Hecks::Fuzzing::TargetCapabilities::DEFERRED_MODES).empty?
|
|
239
|
+
@deferred_modes = explicit_all_deferred ? [] : resolved & Hecks::Fuzzing::TargetCapabilities::DEFERRED_MODES
|
|
240
|
+
@active_modes = resolved - @deferred_modes
|
|
241
|
+
|
|
242
|
+
if @persistence_parity_mode && !@active_modes.include?(:persistence_parity)
|
|
243
|
+
abort "target #{@target_reference.inspect} (#{@target_path}) declares no persisted_by(\"PostgresEra\") " \
|
|
244
|
+
"binding in its own .hecksagon (capabilities: #{@capabilities.join(',')}) — " \
|
|
245
|
+
"--persistence-parity has nothing to compare Memory against for this domain. " \
|
|
246
|
+
"examples/directory is the known example that qualifies."
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# `era_boundary` is seedless and meaningful with no primary seat; `concurrency` is a seat,
|
|
250
|
+
# so it counts here too.
|
|
251
|
+
unless @active_modes.intersect?(%i[differential ruby_only persistence_parity concurrency era_boundary])
|
|
252
|
+
abort "target #{@target_reference.inspect} (#{@target_path}) resolves no comparison mode at all — " \
|
|
253
|
+
"enabled #{@enabled_modes.join(',')}, capabilities #{@capabilities.join(',')} " \
|
|
254
|
+
"(deferred: #{@deferred_modes.join(',')}). Nothing here can be swept without at least one of " \
|
|
255
|
+
"differential/ruby_only/persistence_parity/concurrency/era_boundary."
|
|
256
|
+
end
|
|
257
|
+
@seeded_modes = @active_modes - SEEDLESS_MODES
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def claim_target
|
|
261
|
+
@target = ::QualityControl::Target.find(@target_reference)
|
|
262
|
+
abort "target #{@target_reference.inspect} vanished between being queried and being claimed" unless @target
|
|
263
|
+
|
|
264
|
+
begin
|
|
265
|
+
@target = @target.claim!(held_by: { value: @engineer }, now: { value: Time.now.to_i })
|
|
266
|
+
rescue Hecks::Runtime::GivenNotMet => e
|
|
267
|
+
abort "target #{@target_reference.inspect} is already held and its claim has not gone stale yet " \
|
|
268
|
+
"(#{e.message})"
|
|
269
|
+
end
|
|
270
|
+
trace("claim")
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Read once after claiming: nothing before `Target.Release` changes `clean_streak`, and it
|
|
274
|
+
# sizes this sweep and seeds `next_streak`.
|
|
275
|
+
def resolve_depth
|
|
276
|
+
@current_streak = @target.clean_streak.value
|
|
277
|
+
default_seeds, default_steps = Hecks::Fuzzing::SweepDepth.for_streak(@current_streak, tiers: @widening_tiers)
|
|
278
|
+
@seeds = @seeds_override || default_seeds
|
|
279
|
+
@steps_per_sequence = @steps_override || default_steps
|
|
280
|
+
|
|
281
|
+
# Only when depth is auto-resolved from the streak: an explicit `--seeds N` is a person's
|
|
282
|
+
# manual debugging/reproduction run, and stays the predictable `1..N`. Growing this with the
|
|
283
|
+
# streak is what keeps a matured target's sweep moving into seed integers it has never
|
|
284
|
+
# generated before (see `Hecks::Fuzzing::SweepDepth.seed_offset`).
|
|
285
|
+
@seed_offset = @seeds_override ? 0 : Hecks::Fuzzing::SweepDepth.seed_offset(@current_streak, default_seeds)
|
|
286
|
+
clamp_seeds
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Clamps down (never up) only for `--persistence-parity`: real PostgresEra I/O per dispatch is
|
|
290
|
+
# expensive even for a target on the widest tier. `concurrency` has a tighter cap: each seed
|
|
291
|
+
# pays for two disposable schemas and a real fork; it is reached via `--modes concurrency`.
|
|
292
|
+
def clamp_seeds
|
|
293
|
+
if @persistence_parity_mode && @seeds > ::QualityControlDials::PERSISTENCE_PARITY_SEED_CAP
|
|
294
|
+
cap = ::QualityControlDials::PERSISTENCE_PARITY_SEED_CAP
|
|
295
|
+
puts "note: --seeds #{@seeds} exceeds QualityControlDials::PERSISTENCE_PARITY_SEED_CAP " \
|
|
296
|
+
"(#{cap}) — clamping down. Real PostgresEra I/O per " \
|
|
297
|
+
"dispatch is genuinely expensive; edit that dial in qa/settings.yml to run more."
|
|
298
|
+
@seeds = cap
|
|
299
|
+
end
|
|
300
|
+
return unless @active_modes.include?(:concurrency) && @seeds > ::QualityControlDials::CONCURRENCY_SEED_CAP
|
|
301
|
+
|
|
302
|
+
cap = ::QualityControlDials::CONCURRENCY_SEED_CAP
|
|
303
|
+
puts "note: --seeds #{@seeds} exceeds QualityControlDials::CONCURRENCY_SEED_CAP " \
|
|
304
|
+
"(#{cap}) — clamping down. Each seed forks two real racers against " \
|
|
305
|
+
"real PostgresEra I/O; edit that dial in qa/settings.yml to run more."
|
|
306
|
+
@seeds = cap
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def announce_resolution
|
|
310
|
+
puts "resolved depth: seeds=#{@seeds} steps=#{@steps_per_sequence} (clean_streak=#{@current_streak})"
|
|
311
|
+
puts "seed range: #{@seed_offset + 1}..#{@seed_offset + @seeds}" if @seeded_modes.any?
|
|
312
|
+
line = "resolved modes: #{@active_modes.join(',')} (capabilities=#{@capabilities.join(',')}"
|
|
313
|
+
line += "; deferred=#{@deferred_modes.join(',')}" unless @deferred_modes.empty?
|
|
314
|
+
puts "#{line})"
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# The reference embeds the target: `Sweep` is not scoped to its target, so two sweeps opened
|
|
318
|
+
# in the same second must not collide.
|
|
319
|
+
def open_sweep
|
|
320
|
+
@sweep_reference = "SW-#{@target_reference}-#{Time.now.to_i}"
|
|
321
|
+
@sweep = ::QualityControl::Sweep.open!(target: @target.id, reference: { value: @sweep_reference },
|
|
322
|
+
engineer: { value: @engineer })
|
|
323
|
+
@differ = build_differ
|
|
324
|
+
@feature = File.basename(@domain_path).downcase
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Includes `RustConformanceHelpers` outside RSpec; `structural_skips` collects verbs the
|
|
328
|
+
# binary's manifest declares not generated.
|
|
329
|
+
def build_differ
|
|
330
|
+
require File.join(@root, "spec/support/rust_conformance_helpers")
|
|
331
|
+
Class.new do
|
|
332
|
+
include RustConformanceHelpers
|
|
333
|
+
|
|
334
|
+
attr_reader :structural_skips
|
|
335
|
+
|
|
336
|
+
def initialize
|
|
337
|
+
@structural_skips = Set.new
|
|
338
|
+
end
|
|
339
|
+
end.new
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# The primary seat is the first of persistence_parity, concurrency, differential, ruby_only
|
|
343
|
+
# that is active, or none for a seedless-only sweep; differential degrades to ruby_only if the
|
|
344
|
+
# build fails.
|
|
345
|
+
def choose_seat
|
|
346
|
+
@binary = nil
|
|
347
|
+
return @mode = :seedless if @seeded_modes.empty?
|
|
348
|
+
return @mode = :persistence_parity if @active_modes.include?(:persistence_parity)
|
|
349
|
+
return @mode = :concurrency if @active_modes.include?(:concurrency)
|
|
350
|
+
|
|
351
|
+
@binary = build_rust_binary if @active_modes.include?(:differential)
|
|
352
|
+
return @mode = :differential if @binary
|
|
353
|
+
|
|
354
|
+
degrade_to_ruby_only if @active_modes.include?(:differential)
|
|
355
|
+
@mode = :ruby_only
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def build_rust_binary
|
|
359
|
+
@differ.build_rust_for(@feature, @rust_dir)
|
|
360
|
+
rescue RustConformanceHelpers::BuildFailed => e
|
|
361
|
+
@rust_build_failure = e
|
|
362
|
+
nil
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def degrade_to_ruby_only
|
|
366
|
+
# `build_rust_for` answers nil only for an undeclared feature; a declared one that fails
|
|
367
|
+
# raises, so its cargo stderr is printed here.
|
|
368
|
+
reason = @rust_build_failure ? @rust_build_failure.message : "rust/Cargo.toml declares no #{@feature} feature"
|
|
369
|
+
puts "note: no #{@feature} Rust binary — degrading this sweep to ruby_only. Run `hecks project_rust` for it " \
|
|
370
|
+
"and re-sweep.\n#{reason}"
|
|
371
|
+
@active_modes = (@active_modes - %i[differential properties_in_differential structural_skip_report]) |
|
|
372
|
+
[:ruby_only]
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# Safe under concurrent callers: several sweeps can reach this at once (the parity wave runs
|
|
376
|
+
# up to `QualityControlDials::SWEEP_MAX_PARALLEL` targets in parallel), so the existence check
|
|
377
|
+
# is only a fast-path optimization; the actual safety net is rescuing the create's own race,
|
|
378
|
+
# since two processes can both see the database missing and both attempt `CREATE DATABASE`.
|
|
379
|
+
# Postgres reports that race two different ways: a `duplicate_database` error (42P04) from its
|
|
380
|
+
# own pre-create name check, or, when two backends run that check at nearly the same instant,
|
|
381
|
+
# a raw `unique_violation` (23505) on `pg_database`'s name index once both proceed to insert.
|
|
382
|
+
def ensure_scratch_database!(name)
|
|
383
|
+
admin = PG.connect(dbname: "postgres")
|
|
384
|
+
exists = admin.exec_params("SELECT 1 FROM pg_database WHERE datname = $1", [name]).ntuples.positive?
|
|
385
|
+
admin.exec(%(CREATE DATABASE "#{name}")) unless exists
|
|
386
|
+
rescue PG::DuplicateDatabase, PG::UniqueViolation
|
|
387
|
+
nil
|
|
388
|
+
ensure
|
|
389
|
+
admin&.close
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# The scratch database is a fixed name, created if missing and never dropped (a concurrent
|
|
393
|
+
# sweep may use another schema in it); only this run's uniquely named schemas are dropped, in
|
|
394
|
+
# `drop_scratch_schemas`. `concurrency` needs two: the real fork race and its sequential
|
|
395
|
+
# oracle.
|
|
396
|
+
def prepare_scratch_schemas
|
|
397
|
+
@parity_database = @concurrency_database = SCRATCH_DATABASE
|
|
398
|
+
@parity_schema = @race_schema = @reference_schema = nil
|
|
399
|
+
if @active_modes.include?(:persistence_parity)
|
|
400
|
+
require "pg"
|
|
401
|
+
@parity_schema = "qa_pp_#{@target_reference}_#{Process.pid}_#{SecureRandom.hex(4)}"
|
|
402
|
+
ensure_scratch_database!(@parity_database)
|
|
403
|
+
end
|
|
404
|
+
return unless @active_modes.include?(:concurrency)
|
|
405
|
+
|
|
406
|
+
require "pg"
|
|
407
|
+
@race_schema = "qa_cc_race_#{@target_reference}_#{Process.pid}_#{SecureRandom.hex(4)}"
|
|
408
|
+
@reference_schema = "qa_cc_ref_#{@target_reference}_#{Process.pid}_#{SecureRandom.hex(4)}"
|
|
409
|
+
ensure_scratch_database!(@concurrency_database)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def mode_label
|
|
413
|
+
case @mode
|
|
414
|
+
when :differential then "Ruby vs compiled Rust (#{@feature})"
|
|
415
|
+
when :persistence_parity then "Memory vs real PostgresEra (#{@feature}) — persistence-adapter parity"
|
|
416
|
+
when :concurrency
|
|
417
|
+
"real forked cross-process dispatch vs its own sequential oracle (#{@feature}) — write-lock serialization"
|
|
418
|
+
when :seedless
|
|
419
|
+
"no primary seat (#{@active_modes.join(',')} — audits this target's own real state, generates nothing)"
|
|
420
|
+
else "Ruby-only property/exception check (no compiled Rust binary for #{@feature})"
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def announce_sweep
|
|
425
|
+
if @mode == :seedless
|
|
426
|
+
puts "sweeping #{@target_reference} (#{@target_path}) — #{mode_label}, " \
|
|
427
|
+
"active modes #{@active_modes.join(',')}"
|
|
428
|
+
else
|
|
429
|
+
puts "sweeping #{@target_reference} (#{@target_path}) — #{mode_label}, #{@seeds} seed(s), " \
|
|
430
|
+
"#{@steps_per_sequence} steps each, adversarial fraction #{@adversarial}, role draw #{@role_draw}, " \
|
|
431
|
+
"dry-run fraction #{@dry_run}, active modes #{@active_modes.join(',')}"
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# @return [Hash, nil] the first surprise, or nil when every check held
|
|
436
|
+
def run_seeds
|
|
437
|
+
surprise = nil
|
|
438
|
+
begin
|
|
439
|
+
# One campaign per sweep, resumed from a prior tick's corpus for this exact (target, mode)
|
|
440
|
+
# so guided generation's knowledge compounds across ticks instead of rebuilding an
|
|
441
|
+
# identical corpus from an identical trace every time. Nil when guided generation is off,
|
|
442
|
+
# or this is a seedless-only sweep that will never record anything into it.
|
|
443
|
+
@coverage_path = coverage_corpus_path(@target_reference, @mode) if @guided_generation && @seeded_modes.any?
|
|
444
|
+
@campaign = build_campaign
|
|
445
|
+
surprise = seed_loop
|
|
446
|
+
surprise ||= structural_skip_step
|
|
447
|
+
surprise ||= era_boundary_step
|
|
448
|
+
shrink_surprise(surprise)
|
|
449
|
+
ensure
|
|
450
|
+
# Drops this run's disposable schemas whether the sweep ends clean, surprised or crashes;
|
|
451
|
+
# the databases stay. Persists whatever this run's campaign learned, clean, surprised or
|
|
452
|
+
# crashed alike: the corpus a surprised run built up to that point is still real coverage
|
|
453
|
+
# a later tick should not have to rediscover from scratch.
|
|
454
|
+
drop_scratch_schemas
|
|
455
|
+
save_coverage_state!(@coverage_path, @campaign) if @campaign
|
|
456
|
+
end
|
|
457
|
+
surprise
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def build_campaign
|
|
461
|
+
return unless @coverage_path
|
|
462
|
+
|
|
463
|
+
saved = load_coverage_state(@coverage_path)
|
|
464
|
+
if saved
|
|
465
|
+
Hecks::Fuzzing::CoverageCampaign.load(saved, splice_probability: @corpus_splice_probability,
|
|
466
|
+
favor_count: @favor_rare_verbs)
|
|
467
|
+
else
|
|
468
|
+
Hecks::Fuzzing::CoverageCampaign.new(splice_probability: @corpus_splice_probability,
|
|
469
|
+
favor_count: @favor_rare_verbs)
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# A seedless-only sweep skips the loop so it never claims to have generated sequences.
|
|
474
|
+
def seed_loop
|
|
475
|
+
return unless @seeded_modes.any?
|
|
476
|
+
|
|
477
|
+
surprise = nil
|
|
478
|
+
((@seed_offset + 1)..(@seed_offset + @seeds)).each do |seed|
|
|
479
|
+
result = run_one_seed(@mode, seed)
|
|
480
|
+
@campaign.record(seed, result[:plan], result[:trace]) if @campaign && result[:trace]
|
|
481
|
+
|
|
482
|
+
surprised = log_seed_checks(result)
|
|
483
|
+
if surprised.empty?
|
|
484
|
+
puts " seed #{seed}: held (#{result[:checks].map { |c| c[:mode] }.join(', ')})"
|
|
485
|
+
next
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
puts " seed #{seed}: SURPRISED (#{surprised.map { |c| c[:mode] }.join(', ')})"
|
|
489
|
+
surprise = result.merge(seed: seed, checks: surprised)
|
|
490
|
+
break
|
|
491
|
+
end
|
|
492
|
+
puts " #{@campaign.summary}" if @campaign
|
|
493
|
+
surprise
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
# Every mode's check is logged before the loop decides, so a seed that held differentially but
|
|
497
|
+
# surprised on properties records both.
|
|
498
|
+
#
|
|
499
|
+
# @return [Array<Hash>] the checks that surprised
|
|
500
|
+
def log_seed_checks(result)
|
|
501
|
+
result[:checks].reject do |check|
|
|
502
|
+
log_and_mark(check)
|
|
503
|
+
check[:clean]
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
def log_and_mark(check)
|
|
508
|
+
@sweep = log_check!(@sweep, subject: check[:subject], expectation: check[:expectation])
|
|
509
|
+
sequence = @sweep.checks.last[:sequence][:value]
|
|
510
|
+
if check[:clean]
|
|
511
|
+
mark_held!(@sweep, sequence, check[:observation])
|
|
512
|
+
else
|
|
513
|
+
mark_surprised!(@sweep, sequence, check[:observation], @target_reference)
|
|
514
|
+
end
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
# Structural-skip check only after a differential loop that ran clean to the end.
|
|
518
|
+
def structural_skip_step
|
|
519
|
+
return unless @mode == :differential && @active_modes.include?(:structural_skip_report)
|
|
520
|
+
|
|
521
|
+
check = structural_skip_check(@differ, @binary, @structural_boundary)
|
|
522
|
+
log_and_mark(check)
|
|
523
|
+
if check[:clean]
|
|
524
|
+
puts " structural skips: #{check[:observation]}"
|
|
525
|
+
nil
|
|
526
|
+
else
|
|
527
|
+
puts " structural skips: SURPRISED — #{check[:observation]}"
|
|
528
|
+
{ seed: nil, steps: [], checks: [check] }
|
|
529
|
+
end
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# Era-boundary check, once per sweep, independent of the primary seat; skipped once anything
|
|
533
|
+
# surprised. A target with no lineage logs no Check: holding one would report an unaudited
|
|
534
|
+
# target as clean.
|
|
535
|
+
def era_boundary_step
|
|
536
|
+
return unless @active_modes.include?(:era_boundary)
|
|
537
|
+
|
|
538
|
+
check = era_boundary_check(@domain_path)
|
|
539
|
+
if check[:skip]
|
|
540
|
+
puts " era boundary: #{check[:observation]} — no Check logged"
|
|
541
|
+
return nil
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
log_and_mark(check)
|
|
545
|
+
if check[:clean]
|
|
546
|
+
puts " era boundary: #{check[:observation]}"
|
|
547
|
+
nil
|
|
548
|
+
else
|
|
549
|
+
puts " era boundary: SURPRISED — #{check[:observation]}"
|
|
550
|
+
{ seed: nil, steps: [], checks: [check] }
|
|
551
|
+
end
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
# Shrinks inside the guarded block: the parity schema is dropped afterwards, and candidates
|
|
555
|
+
# need it.
|
|
556
|
+
def shrink_surprise(surprise)
|
|
557
|
+
return unless surprise && surprise[:seed] && @shrink_budget.positive?
|
|
558
|
+
|
|
559
|
+
surprise[:shrunk] = surprise[:checks].filter_map do |check|
|
|
560
|
+
next unless SHRINKABLE_MODES.include?(check[:mode])
|
|
561
|
+
|
|
562
|
+
puts " shrinking [#{check[:mode]}] (#{surprise[:steps].size} steps, budget #{@shrink_budget})…"
|
|
563
|
+
shrink_check(check, @mode, surprise[:steps])
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
def drop_scratch_schemas
|
|
568
|
+
if @parity_schema
|
|
569
|
+
admin = PG.connect(dbname: @parity_database)
|
|
570
|
+
admin.exec("SET client_min_messages = warning")
|
|
571
|
+
admin.exec("DROP SCHEMA IF EXISTS #{admin.quote_ident(@parity_schema)} CASCADE")
|
|
572
|
+
admin.close
|
|
573
|
+
end
|
|
574
|
+
return unless @race_schema || @reference_schema
|
|
575
|
+
|
|
576
|
+
admin = PG.connect(dbname: @concurrency_database)
|
|
577
|
+
admin.exec("SET client_min_messages = warning")
|
|
578
|
+
[@race_schema, @reference_schema].compact.each do |schema|
|
|
579
|
+
admin.exec("DROP SCHEMA IF EXISTS #{admin.quote_ident(schema)} CASCADE")
|
|
580
|
+
end
|
|
581
|
+
admin.close
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
def report_finding(surprise)
|
|
585
|
+
print_finding(surprise)
|
|
586
|
+
EXIT_FOUND_SOMETHING
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
# Reached only when every seed held; re-reads the sticky `ever_surprised` bit, not the loop's
|
|
590
|
+
# local.
|
|
591
|
+
def conclude_clean_sweep
|
|
592
|
+
notes = "domain=#{@target_reference} path=#{@target_path} mode=#{@mode} modes=#{@active_modes.join(',')} " \
|
|
593
|
+
"capabilities=#{@capabilities.join(',')} feature=#{@feature} seeds=#{@seeds} " \
|
|
594
|
+
"steps=#{@steps_per_sequence} adversarial=#{@adversarial} role_draw=#{@role_draw} " \
|
|
595
|
+
"dry_run=#{@dry_run} — every seed held, no divergence or property violation found."
|
|
596
|
+
@sweep.conclude!(notes: { value: notes })
|
|
597
|
+
trace("seed loop+conclude")
|
|
598
|
+
|
|
599
|
+
target = release_target!(@target, @capabilities, @current_streak, surprises: 0,
|
|
600
|
+
clean: sweep_was_clean?(@sweep))
|
|
601
|
+
trace("release")
|
|
602
|
+
|
|
603
|
+
puts
|
|
604
|
+
puts "clean — #{@target_reference} concluded and released. clean_streak: #{@current_streak} -> " \
|
|
605
|
+
"#{target.clean_streak.value}. capabilities: #{target.capabilities.value.inspect}"
|
|
606
|
+
puts notes
|
|
607
|
+
EXIT_OK
|
|
608
|
+
end
|
|
609
|
+
end
|
|
610
|
+
end
|
|
611
|
+
end
|