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,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "../../hecks"
|
|
5
|
+
# The `:shape` projection reads `Runtime::StorageShape`, which lives in the era persistence plugin
|
|
6
|
+
# core does not load by default (ADR 0033).
|
|
7
|
+
require_relative "../ports/persistence/plugins/era"
|
|
8
|
+
|
|
9
|
+
module Hecks
|
|
10
|
+
module CLI
|
|
11
|
+
# The command behind `hecks shape` and `hecks shape`: a bluebook's storage-shape projection, the
|
|
12
|
+
# same hash PostgresEra mints an era's label from, so two runs diff to show whether a bluebook
|
|
13
|
+
# change would bump an era.
|
|
14
|
+
module Shape
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
# Prints the projection of a bluebook file as JSON, or one `<Domain> <label>` line per domain
|
|
18
|
+
# of a directory (files in subdirectories are not read).
|
|
19
|
+
#
|
|
20
|
+
# @param argv [Array<String>] the bluebook file or directory, first
|
|
21
|
+
# @param program [String] the name the usage message calls this command by
|
|
22
|
+
# @return [void]
|
|
23
|
+
# @raise [SystemExit] when `argv` is empty, the path is missing or holds no bluebook
|
|
24
|
+
def call(argv, program: "hecks shape")
|
|
25
|
+
target = argv.first or abort "usage: #{program} <bluebook | directory of *.bluebook files>"
|
|
26
|
+
puts render(target)
|
|
27
|
+
rescue Runtime::NotFound => e
|
|
28
|
+
abort e.message
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param target [String] a bluebook file, or a directory of them
|
|
32
|
+
# @return [String] the storage-shape projection as JSON for a file, or one
|
|
33
|
+
# `<Domain> <label>` line per domain for a directory
|
|
34
|
+
# @raise [Runtime::NotFound] if the path does not exist or holds no bluebook
|
|
35
|
+
def render(target)
|
|
36
|
+
raise Runtime::NotFound, "#{target} does not exist" unless File.exist?(target)
|
|
37
|
+
|
|
38
|
+
directory = File.directory?(target)
|
|
39
|
+
files = directory ? Dir[File.join(target, "*.bluebook")] : [target]
|
|
40
|
+
raise Runtime::NotFound, "no *.bluebook files in #{target}" if files.empty?
|
|
41
|
+
|
|
42
|
+
registry = load_files(files)
|
|
43
|
+
return labels(registry) if directory
|
|
44
|
+
|
|
45
|
+
bluebook = registry.bluebooks.values.first or raise Runtime::NotFound, "#{target} declares no bluebook"
|
|
46
|
+
JSON.pretty_generate(Projector.call(:shape, bluebook: bluebook))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @param files [Array<String>] bluebook files
|
|
50
|
+
# @return [Runtime::Registry] a registry holding every bluebook the files declare
|
|
51
|
+
def load_files(files)
|
|
52
|
+
registry = Runtime::Registry.new
|
|
53
|
+
loading = Ports::Loading.bootstrap
|
|
54
|
+
Hecks.with_registry(registry) do
|
|
55
|
+
loading.load_library
|
|
56
|
+
files.each { |file| Kernel.eval(File.read(file), TOPLEVEL_BINDING, File.expand_path(file), 1) }
|
|
57
|
+
end
|
|
58
|
+
registry
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @param registry [Runtime::Registry] a loaded registry
|
|
62
|
+
# @return [String] one `<Domain> <label>` line per bluebook, sorted by name
|
|
63
|
+
def labels(registry)
|
|
64
|
+
registry.bluebooks.sort_by { |name, _| name.to_s }
|
|
65
|
+
.map { |name, bluebook| "#{name} #{Runtime::StorageShape.mint_label(bluebook)}" }
|
|
66
|
+
.join("\n")
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "openssl"
|
|
5
|
+
require "json"
|
|
6
|
+
require "optparse"
|
|
7
|
+
|
|
8
|
+
module Hecks
|
|
9
|
+
module CLI
|
|
10
|
+
# One run of the checks against one service.
|
|
11
|
+
class SmokeHttp
|
|
12
|
+
# The signature header value for a body, per scheme.
|
|
13
|
+
module Signature
|
|
14
|
+
SCHEMES = %w[timestamped sha256 hex].freeze
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
# Signs a body the way the chosen scheme's sender would.
|
|
19
|
+
def header_value(scheme, secret, body, at: Time.now)
|
|
20
|
+
case scheme
|
|
21
|
+
when "timestamped"
|
|
22
|
+
stamp = at.to_i
|
|
23
|
+
"t=#{stamp},v1=#{digest(secret, "#{stamp}.#{body}")}"
|
|
24
|
+
when "sha256" then "sha256=#{digest(secret, body)}"
|
|
25
|
+
when "hex" then digest(secret, body)
|
|
26
|
+
else raise ArgumentError, "unknown scheme #{scheme.inspect}; expected one of #{SCHEMES.join(', ')}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Computes the keyed digest every scheme is built from.
|
|
31
|
+
def digest(secret, text)
|
|
32
|
+
OpenSSL::HMAC.hexdigest("SHA256", secret, text)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Runs the checks as the `hecks smoke_http` script does: settings from the arguments and the
|
|
37
|
+
# environment, a refusal worded with the program's name.
|
|
38
|
+
#
|
|
39
|
+
# @param argv [Array<String>] the flags, consumed
|
|
40
|
+
# @param env [Hash{String => String}] the environment defaults
|
|
41
|
+
# @param program [String] the name a refusal is prefixed with
|
|
42
|
+
# @return [Integer] the exit status: 1 when any check failed
|
|
43
|
+
# @raise [SystemExit] when the settings are refused
|
|
44
|
+
def self.main(argv, env: ENV, program: "hecks smoke_http")
|
|
45
|
+
new(settings(argv, env)).run
|
|
46
|
+
rescue ArgumentError, OptionParser::ParseError, Errno::ENOENT => e
|
|
47
|
+
abort "#{program}: #{e.message}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Reads the command line and environment into the settings a run needs.
|
|
51
|
+
#
|
|
52
|
+
# @param argv [Array<String>] the flags, consumed
|
|
53
|
+
# @param env [Hash{String => String}] the environment defaults
|
|
54
|
+
# @return [Hash{Symbol => String}] the settings
|
|
55
|
+
# @raise [ArgumentError] if the path or the secret is missing or the scheme is unknown
|
|
56
|
+
def self.settings(argv, env)
|
|
57
|
+
options = {}
|
|
58
|
+
parser(options).parse!(argv)
|
|
59
|
+
resolve(options, env)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Merges explicit options over the environment's defaults and validates the result.
|
|
63
|
+
#
|
|
64
|
+
# @param options [Hash{Symbol => String}] the settings offered explicitly
|
|
65
|
+
# @param env [Hash{String => String}] the environment defaults
|
|
66
|
+
# @return [Hash{Symbol => String}] the settings
|
|
67
|
+
# @raise [ArgumentError] if the path or the secret is missing or the scheme is unknown
|
|
68
|
+
def self.resolve(options, env)
|
|
69
|
+
settings = {
|
|
70
|
+
url: env.fetch("SMOKE_TARGET_URL", "http://127.0.0.1:4322"), path: env["SMOKE_WEBHOOK_PATH"],
|
|
71
|
+
secret: env["SMOKE_WEBHOOK_SECRET"], header: env.fetch("SMOKE_SIGNATURE_HEADER", "X-Signature"),
|
|
72
|
+
scheme: env.fetch("SMOKE_SIGNATURE_SCHEME", "timestamped")
|
|
73
|
+
}.merge(options)
|
|
74
|
+
validate!(settings)
|
|
75
|
+
settings
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @api private
|
|
79
|
+
def self.parser(options)
|
|
80
|
+
OptionParser.new do |parser|
|
|
81
|
+
%i[url path secret header scheme payload health_path state_path].each do |name|
|
|
82
|
+
flag = "--#{name.to_s.tr('_', '-')}"
|
|
83
|
+
parser.on("#{flag} VALUE") { |value| options[name] = value }
|
|
84
|
+
end
|
|
85
|
+
parser.on("--payload-file FILE") { |file| options[:payload] = File.read(file) }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Refuses settings a run cannot start from.
|
|
90
|
+
#
|
|
91
|
+
# @param settings [Hash{Symbol => String}] the settings to judge
|
|
92
|
+
# @return [void]
|
|
93
|
+
# @raise [ArgumentError] if the path or the secret is missing or the scheme is unknown
|
|
94
|
+
def self.validate!(settings)
|
|
95
|
+
raise ArgumentError, "no webhook path: pass --path or set SMOKE_WEBHOOK_PATH" if settings[:path].to_s.empty?
|
|
96
|
+
raise ArgumentError, "no signing secret: set SMOKE_WEBHOOK_SECRET (or pass --secret)" if settings[:secret].to_s.empty?
|
|
97
|
+
return if Signature::SCHEMES.include?(settings[:scheme])
|
|
98
|
+
|
|
99
|
+
raise ArgumentError, "unknown scheme #{settings[:scheme].inspect}; expected one of #{Signature::SCHEMES.join(', ')}"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @param settings [Hash{Symbol => String}] validated settings
|
|
103
|
+
# @param out [IO] where the checks are reported
|
|
104
|
+
def initialize(settings, out: $stdout)
|
|
105
|
+
@settings = settings
|
|
106
|
+
@out = out
|
|
107
|
+
@failures = []
|
|
108
|
+
@target = URI(settings.fetch(:url))
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def run
|
|
112
|
+
@out.puts "== signed webhook checks against #{@target}#{@settings[:path]} =="
|
|
113
|
+
check_health
|
|
114
|
+
check_unsigned
|
|
115
|
+
check_wrong_secret
|
|
116
|
+
check_tampered_body
|
|
117
|
+
check_signed
|
|
118
|
+
check_repeat
|
|
119
|
+
summarize
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def check(label)
|
|
125
|
+
@out.print " #{label}... "
|
|
126
|
+
yield
|
|
127
|
+
@out.puts "ok"
|
|
128
|
+
rescue StandardError => e
|
|
129
|
+
@out.puts "FAILED: #{e.message}"
|
|
130
|
+
@failures << label
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def check_health
|
|
134
|
+
return unless @settings[:health_path]
|
|
135
|
+
|
|
136
|
+
check("GET #{@settings[:health_path]} answers 200") { expect_status(get(@settings[:health_path]), 200) }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def check_unsigned
|
|
140
|
+
check("a delivery with no signature is refused") { expect_refused(post(payload, {})) }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def check_wrong_secret
|
|
144
|
+
check("a delivery signed with the wrong secret is refused") do
|
|
145
|
+
expect_refused(post(payload, signature_header("not-the-secret-#{run_id}", payload)))
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# A trailing space is enough: the signature covers the exact bytes.
|
|
150
|
+
def check_tampered_body
|
|
151
|
+
check("a delivery whose body changed after signing is refused") do
|
|
152
|
+
expect_refused(post("#{payload} ", signature_header(@settings.fetch(:secret), payload)))
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def check_signed
|
|
157
|
+
check("a correctly signed delivery is accepted") { expect_success(post(payload, signed_headers)) }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def check_repeat
|
|
161
|
+
check("a repeated delivery of the same payload is accepted again, not an error") do
|
|
162
|
+
before = state
|
|
163
|
+
expect_success(post(payload, signed_headers))
|
|
164
|
+
after = state
|
|
165
|
+
raise "the state changed on a repeated delivery:\n before #{before}\n after #{after}" unless before == after
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def summarize
|
|
170
|
+
if @failures.empty?
|
|
171
|
+
@out.puts "\nSMOKE HTTP PASSED"
|
|
172
|
+
0
|
|
173
|
+
else
|
|
174
|
+
@out.puts "\nSMOKE HTTP FAILED (#{@failures.size}):"
|
|
175
|
+
@failures.each { |label| @out.puts " - #{label}" }
|
|
176
|
+
1
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def run_id
|
|
181
|
+
@run_id ||= "smoke-#{Process.pid}-#{rand(1_000_000)}"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def payload
|
|
185
|
+
@payload ||= @settings[:payload] || JSON.generate(id: run_id, type: "smoke.ping")
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def signed_headers
|
|
189
|
+
signature_header(@settings.fetch(:secret), payload)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def signature_header(secret, body)
|
|
193
|
+
{ @settings.fetch(:header) => Signature.header_value(@settings.fetch(:scheme), secret, body) }
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def state
|
|
197
|
+
@settings[:state_path] ? get(@settings[:state_path]).body : nil
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def get(path)
|
|
201
|
+
request(Net::HTTP::Get.new(URI.join(@target, path)))
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def post(body, headers)
|
|
205
|
+
req = Net::HTTP::Post.new(URI.join(@target, @settings.fetch(:path)))
|
|
206
|
+
headers.each { |name, value| req[name] = value }
|
|
207
|
+
req["Content-Type"] = "application/json"
|
|
208
|
+
req.body = body
|
|
209
|
+
request(req)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def request(req)
|
|
213
|
+
options = { use_ssl: req.uri.scheme == "https", read_timeout: 8, open_timeout: 8 }
|
|
214
|
+
Net::HTTP.start(req.uri.host, req.uri.port, **options) { |http| http.request(req) }
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def expect_status(res, code)
|
|
218
|
+
raise "expected #{code}, got #{res.code}" unless res.code.to_i == code
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def expect_success(res)
|
|
222
|
+
raise "expected 2xx, got #{res.code}: #{res.body.to_s[0, 200]}" unless res.code.to_i.between?(200, 299)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def expect_refused(res)
|
|
226
|
+
raise "expected a 4xx refusal, got #{res.code}" unless res.code.to_i.between?(400, 499)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
data/lib/hecks/cli/smoke_test.rb
CHANGED
|
@@ -3,7 +3,7 @@ require_relative "../bluebook/smoke_test"
|
|
|
3
3
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module CLI
|
|
6
|
-
# The command behind `
|
|
6
|
+
# The command behind `hecks smoke_test`: boots a real domain
|
|
7
7
|
# and dispatches one synthesized call per declared command and report
|
|
8
8
|
# (`Bluebook::SmokeTest`), printing every failure rather than the first.
|
|
9
9
|
#
|
|
@@ -14,8 +14,10 @@ module Hecks
|
|
|
14
14
|
module_function
|
|
15
15
|
|
|
16
16
|
def call(argv, root:)
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
missing = argv.reject { |dir| File.directory?(dir) }
|
|
18
|
+
abort "smoke_test: no such domain #{missing.first.inspect}" unless missing.empty?
|
|
19
|
+
|
|
20
|
+
targets = argv.empty? ? Dir.glob("#{root}/examples/*/").map { |d| d.chomp("/") }.select { |d| wired?(d) } : argv
|
|
19
21
|
abort "no wired example domains found (none of examples/* has a .hecksagon)" if targets.empty?
|
|
20
22
|
|
|
21
23
|
ok = targets.reduce(true) { |all_ok, dir| clean?(dir) && all_ok }
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rspec/core"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module CLI
|
|
7
|
+
# The command behind `hecks run_spec_example`: runs one spec file filtered to one example
|
|
8
|
+
# through `RSpec::Core::Runner`, so a `hecks quality_control log --demonstration` string
|
|
9
|
+
# needs no `-e` quoting.
|
|
10
|
+
module SpecExample
|
|
11
|
+
USAGE = "usage: hecks run_spec_example <spec_file> <example description substring>"
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# Runs the example whose description contains the substring.
|
|
16
|
+
#
|
|
17
|
+
# @param argv [Array<String>] the spec file, then the description substring
|
|
18
|
+
# @return [Integer] RSpec's own exit status: 0 when every example passed
|
|
19
|
+
# @raise [SystemExit] with the usage line unless exactly two arguments are given
|
|
20
|
+
def call(argv)
|
|
21
|
+
abort USAGE if argv.length != 2
|
|
22
|
+
|
|
23
|
+
RSpec::Core::Runner.run(["--example", argv[1], argv[0]])
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../hecks"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module CLI
|
|
7
|
+
# The command behind `hecks statements`: prints a booted domain's declared facts as plain
|
|
8
|
+
# English sentences, through `Projections::Statements`.
|
|
9
|
+
module Statements
|
|
10
|
+
USAGE = "usage: hecks statements <domain-path> <ChapterName>"
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
# Boots the domain and prints one sentence per declared fact of the named chapter.
|
|
15
|
+
#
|
|
16
|
+
# @param argv [Array<String>] the domain path, then the chapter's name
|
|
17
|
+
# @param cwd [String] what a relative domain path resolves against
|
|
18
|
+
# @param out [IO] where the sentences go
|
|
19
|
+
# @return [Integer] the exit status, 0 once printed
|
|
20
|
+
# @raise [SystemExit] with the usage line when an argument is missing
|
|
21
|
+
def call(argv, cwd: Dir.pwd, out: $stdout)
|
|
22
|
+
domain_path, chapter_name = argv
|
|
23
|
+
abort USAGE unless domain_path && chapter_name
|
|
24
|
+
|
|
25
|
+
runtime = Hecks.boot(File.expand_path(domain_path, cwd))
|
|
26
|
+
chapter = runtime.registry.bluebook(chapter_name)
|
|
27
|
+
Hecks::Projector.call(:statements, bluebook: chapter).each { |statement| out.puts statement }
|
|
28
|
+
0
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/hecks/cli/stores.rb
CHANGED
|
@@ -3,8 +3,8 @@ require_relative "../../hecks"
|
|
|
3
3
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module CLI
|
|
6
|
-
# The command behind `
|
|
7
|
-
# records as JSON (the head, not the journal — see `
|
|
6
|
+
# The command behind `hecks stores`: every aggregate's current
|
|
7
|
+
# records as JSON (the head, not the journal — see `hecks history` for that).
|
|
8
8
|
module Stores
|
|
9
9
|
module_function
|
|
10
10
|
|
|
@@ -13,10 +13,9 @@ module Hecks
|
|
|
13
13
|
# @param argv [Array<String>] the domain directory, first
|
|
14
14
|
# @param program [String] the name the error message calls this command by
|
|
15
15
|
# @return [void]
|
|
16
|
-
# @raise [
|
|
17
|
-
# @raise [SystemExit] when the domain directory does not exist
|
|
16
|
+
# @raise [SystemExit] when `argv` is empty or the domain directory does not exist
|
|
18
17
|
def call(argv, program:)
|
|
19
|
-
domain = argv.
|
|
18
|
+
domain = argv.first or abort "usage: #{program} <domain>"
|
|
20
19
|
unless Dir.exist?(domain)
|
|
21
20
|
warn "#{program}: no such domain #{domain.inspect}"
|
|
22
21
|
exit 1
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "etc"
|
|
4
|
+
require "open3"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
|
|
7
|
+
module Hecks
|
|
8
|
+
module CLI
|
|
9
|
+
# The command behind `hecks stress_concurrency`: reruns the thread-safety specs under many
|
|
10
|
+
# seeds, several OS processes at a time. A clean run shows only "not in this many tries", never
|
|
11
|
+
# "impossible".
|
|
12
|
+
#
|
|
13
|
+
# CI can call it gated to pull requests that touch `lib/hecks/runtime/**`, as its own job so the
|
|
14
|
+
# repetition does not tax the single-pass suite; a GitHub Actions matrix leg can also run
|
|
15
|
+
# `--runs 1 --parallel 1 --seed-start N`.
|
|
16
|
+
module StressConcurrencySpecs
|
|
17
|
+
# The specs that prove a shared-state race is closed, listed by hand: a glob would also
|
|
18
|
+
# catch specs that only use threads for fixture setup. In-process specs share no external
|
|
19
|
+
# resource, so concurrent copies are safe.
|
|
20
|
+
PARALLEL_SAFE_SPEC_FILES = [
|
|
21
|
+
"spec/runtime/dispatcher_spec.rb",
|
|
22
|
+
"spec/adapters/driven/in_process_concurrent_dispatch_spec.rb"
|
|
23
|
+
].freeze
|
|
24
|
+
|
|
25
|
+
# The Postgres spec drops and recreates one fixed database, so overlapping copies would fail
|
|
26
|
+
# with `PG::ConnectionBad`; it runs one process at a time.
|
|
27
|
+
SERIAL_ONLY_SPEC_FILES = [
|
|
28
|
+
"spec/adapters/driven/postgres_concurrent_dispatch_spec.rb"
|
|
29
|
+
].freeze
|
|
30
|
+
|
|
31
|
+
USAGE = <<~TEXT
|
|
32
|
+
Usage: hecks stress_concurrency [--runs N] [--parallel N] [--seed-start N]
|
|
33
|
+
|
|
34
|
+
--runs N How many times to run EACH group below (default 30).
|
|
35
|
+
Each run uses a different --seed (seed-start + run
|
|
36
|
+
index).
|
|
37
|
+
--parallel N How many parallel-safe-group runs to have going as
|
|
38
|
+
separate OS processes AT THE SAME TIME (default: this
|
|
39
|
+
machine's own core count, via Etc.nprocessors) — real
|
|
40
|
+
concurrent scheduler/CPU contention, not just varied
|
|
41
|
+
seeds one after another. The Postgres-backed group
|
|
42
|
+
always runs one process at a time regardless of this
|
|
43
|
+
flag.
|
|
44
|
+
--seed-start N First --seed value (default 1); runs use seed_start,
|
|
45
|
+
seed_start + 1, ... seed_start + runs - 1.
|
|
46
|
+
|
|
47
|
+
Exits 0 if every run's every example passed, 1 if anything failed -
|
|
48
|
+
failing runs' full output is saved under tmp/stress-failures/
|
|
49
|
+
for a real repro (same seed, same command, just add CI=true).
|
|
50
|
+
TEXT
|
|
51
|
+
|
|
52
|
+
module_function
|
|
53
|
+
|
|
54
|
+
# Runs both groups under every seed and reports.
|
|
55
|
+
#
|
|
56
|
+
# @param argv [Array<String>] `--runs N`, `--parallel N`, `--seed-start N`, `--help`
|
|
57
|
+
# @param root [String] the checkout the specs run in
|
|
58
|
+
# @param out [IO] where the progress and verdict go
|
|
59
|
+
# @return [Integer] 0 when every run passed (or for `--help`), 1 on flakiness, 64 for an
|
|
60
|
+
# unknown argument
|
|
61
|
+
def call(argv, root:, out: $stdout)
|
|
62
|
+
argv = argv.dup
|
|
63
|
+
settings = { runs: 30, parallel: Etc.nprocessors, seed_start: 1 }
|
|
64
|
+
status = parse!(argv, settings, out)
|
|
65
|
+
return status if status
|
|
66
|
+
|
|
67
|
+
results = run_all(root, settings, out)
|
|
68
|
+
report(root, results, out)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @param argv [Array<String>] the arguments; consumed
|
|
72
|
+
# @param settings [Hash{Symbol => Integer}] filled in from the flags
|
|
73
|
+
# @param out [IO] where the usage goes for `--help`
|
|
74
|
+
# @return [Integer, nil] an exit status to stop with, or nil to go on
|
|
75
|
+
def parse!(argv, settings, out)
|
|
76
|
+
flags = { "--runs" => :runs, "--parallel" => :parallel, "--seed-start" => :seed_start }
|
|
77
|
+
until argv.empty?
|
|
78
|
+
arg = argv.shift
|
|
79
|
+
if flags.key?(arg)
|
|
80
|
+
settings[flags[arg]] = Integer(argv.shift)
|
|
81
|
+
elsif %w[--help -h].include?(arg)
|
|
82
|
+
out.puts USAGE
|
|
83
|
+
return 0
|
|
84
|
+
else
|
|
85
|
+
warn "unrecognized argument: #{arg.inspect} (--help for usage)"
|
|
86
|
+
return 64
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @param root [String] the checkout the specs run in
|
|
93
|
+
# @param settings [Hash{Symbol => Integer}] `runs`, `parallel` and `seed_start`
|
|
94
|
+
# @param out [IO] where the progress goes
|
|
95
|
+
# @return [Array<Hash>] one result per run
|
|
96
|
+
def run_all(root, settings, out)
|
|
97
|
+
runs = settings[:runs]
|
|
98
|
+
seed_start = settings[:seed_start]
|
|
99
|
+
seeds = (seed_start...(seed_start + runs)).to_a
|
|
100
|
+
out.puts "Stress-running the parallel-safe group #{runs} time(s) (#{settings[:parallel]} at a time), " \
|
|
101
|
+
"seeds #{seed_start}..#{seed_start + runs - 1}:"
|
|
102
|
+
PARALLEL_SAFE_SPEC_FILES.each { |f| out.puts " - #{f}" }
|
|
103
|
+
out.puts "...and the Postgres-backed group #{runs} time(s), ONE PROCESS AT A TIME:"
|
|
104
|
+
SERIAL_ONLY_SPEC_FILES.each { |f| out.puts " - #{f}" }
|
|
105
|
+
out.puts
|
|
106
|
+
|
|
107
|
+
results = []
|
|
108
|
+
seeds.each_slice(settings[:parallel]) do |batch|
|
|
109
|
+
batch_results = run_batch(root, PARALLEL_SAFE_SPEC_FILES, batch, label: "parallel-safe")
|
|
110
|
+
results.concat(batch_results)
|
|
111
|
+
batch_results.each { |result| out.print result[:success] ? "." : "F" }
|
|
112
|
+
out.flush
|
|
113
|
+
end
|
|
114
|
+
seeds.each do |seed|
|
|
115
|
+
result = run_once(root, SERIAL_ONLY_SPEC_FILES, seed, label: "postgres (serial)")
|
|
116
|
+
results << result
|
|
117
|
+
out.print result[:success] ? "." : "F"
|
|
118
|
+
out.flush
|
|
119
|
+
end
|
|
120
|
+
out.puts
|
|
121
|
+
out.puts
|
|
122
|
+
results
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# `CI=true` un-excludes the `io: true` Postgres spec, which self-skips without a reachable
|
|
126
|
+
# server.
|
|
127
|
+
#
|
|
128
|
+
# @param root [String] the checkout to run in
|
|
129
|
+
# @param spec_files [Array<String>] the spec files
|
|
130
|
+
# @param seed [Integer] the `--seed` value
|
|
131
|
+
# @param label [String] which group this run belongs to
|
|
132
|
+
# @return [Hash] `label`, `seed`, `success`, `output` and how to `reproduce` it
|
|
133
|
+
def run_once(root, spec_files, seed, label:)
|
|
134
|
+
command = ["bundle", "exec", "rspec", *spec_files, "--seed", seed.to_s, "--format", "progress"]
|
|
135
|
+
stdout, status = Open3.capture2e({ "CI" => "true" }, *command, chdir: root)
|
|
136
|
+
{ label: label, seed: seed, success: status.success?, output: stdout,
|
|
137
|
+
reproduce: "CI=true bundle exec rspec #{spec_files.join(' ')} --seed #{seed}" }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# One thread per child process: `Open3.capture2e` already spawns a real process per call.
|
|
141
|
+
#
|
|
142
|
+
# @param root [String] the checkout to run in
|
|
143
|
+
# @param spec_files [Array<String>] the spec files
|
|
144
|
+
# @param seeds [Array<Integer>] the seeds to run at the same time
|
|
145
|
+
# @param label [String] which group these runs belong to
|
|
146
|
+
# @return [Array<Hash>] one result per seed
|
|
147
|
+
def run_batch(root, spec_files, seeds, label:)
|
|
148
|
+
seeds.map { |seed| Thread.new { run_once(root, spec_files, seed, label: label) } }.map(&:value)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# @param root [String] the checkout; failing runs' output is saved under its `tmp/`
|
|
152
|
+
# @param results [Array<Hash>] every run's result
|
|
153
|
+
# @param out [IO] where the verdict goes
|
|
154
|
+
# @return [Integer] 0 when every run passed, else 1
|
|
155
|
+
def report(root, results, out)
|
|
156
|
+
failures = results.reject { |r| r[:success] }
|
|
157
|
+
if failures.empty?
|
|
158
|
+
out.puts "CLEAN — #{results.size}/#{results.size} runs passed. " \
|
|
159
|
+
"No new flakiness beyond a single ordinary `rspec` run found in this many tries."
|
|
160
|
+
return 0
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
dir = File.join(root, "tmp/stress-failures")
|
|
164
|
+
FileUtils.mkdir_p(dir)
|
|
165
|
+
out.puts "FOUND FLAKINESS — #{failures.size}/#{results.size} runs failed:"
|
|
166
|
+
failures.each do |failure|
|
|
167
|
+
path = File.join(dir, "#{failure[:label].tr(' ', '_').gsub(/[()]/, '')}-seed#{failure[:seed]}.log")
|
|
168
|
+
File.write(path, failure[:output])
|
|
169
|
+
out.puts " [#{failure[:label]}] seed #{failure[:seed]} — output saved to #{path.sub("#{root}/", '')}"
|
|
170
|
+
out.puts " reproduce: #{failure[:reproduce]}"
|
|
171
|
+
end
|
|
172
|
+
out.puts
|
|
173
|
+
1
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|