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
|
@@ -46,14 +46,32 @@ module Hecks
|
|
|
46
46
|
module ExpressionReads
|
|
47
47
|
module_function
|
|
48
48
|
|
|
49
|
+
# Paths by canonical text: parsing is a pure function of the text, and the analyzer asks
|
|
50
|
+
# for the same rule text on every dispatch of a command. A text that fails to parse
|
|
51
|
+
# raises before it is stored, so a failure is never cached. Written from dispatch
|
|
52
|
+
# threads, so every access holds PATHS_LOCK; the cache is cleared when it reaches
|
|
53
|
+
# PATHS_CACHE_LIMIT entries, so distinct texts cannot grow it without bound.
|
|
54
|
+
# rubocop:disable-next Style/MutableConstant
|
|
55
|
+
PATHS_CACHE = {}
|
|
56
|
+
PATHS_LOCK = Mutex.new
|
|
57
|
+
PATHS_CACHE_LIMIT = 4096
|
|
58
|
+
private_constant :PATHS_CACHE, :PATHS_LOCK
|
|
59
|
+
|
|
49
60
|
# Finds every dotted path a canonical expression reads.
|
|
50
61
|
#
|
|
51
62
|
# Walks the parsed nodes the evaluator uses; only Lookup nodes carry dependencies.
|
|
52
63
|
#
|
|
53
64
|
# @param canonical [String] the canonical expression text
|
|
54
|
-
# @return [Array<String>] the dotted paths the expression reads
|
|
65
|
+
# @return [Array<String>] the dotted paths the expression reads (frozen)
|
|
55
66
|
def paths(canonical)
|
|
56
|
-
|
|
67
|
+
cached = PATHS_LOCK.synchronize { PATHS_CACHE[canonical] }
|
|
68
|
+
return cached if cached
|
|
69
|
+
|
|
70
|
+
found = collect(Bluebook::Expression::Evaluator.parse(canonical), Set.new).freeze
|
|
71
|
+
PATHS_LOCK.synchronize do
|
|
72
|
+
PATHS_CACHE.clear if PATHS_CACHE.size >= PATHS_CACHE_LIMIT && !PATHS_CACHE.key?(canonical)
|
|
73
|
+
PATHS_CACHE[canonical] ||= found
|
|
74
|
+
end
|
|
57
75
|
end
|
|
58
76
|
|
|
59
77
|
# Walks one parsed node, skipping names bound by an enclosing block predicate.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require_relative "errors"
|
|
2
2
|
require_relative "refusal_wording"
|
|
3
|
+
require_relative "reaction_outcome"
|
|
3
4
|
require_relative "caller"
|
|
4
5
|
require_relative "invocation"
|
|
5
6
|
require_relative "command_rules"
|
|
@@ -19,7 +20,36 @@ module Hecks
|
|
|
19
20
|
class Dispatcher
|
|
20
21
|
MAX_REACTION_DEPTH = 5
|
|
21
22
|
|
|
22
|
-
Result = Struct.new(:verb, :instance, :events, :execution_plan, :persistence_outcome,
|
|
23
|
+
Result = Struct.new(:verb, :instance, :events, :execution_plan, :persistence_outcome,
|
|
24
|
+
:refused_reactions, :blocking_reactions, :reaction_defects,
|
|
25
|
+
keyword_init: true) do
|
|
26
|
+
# Lists the policy reactions this dispatch caused that the domain refused.
|
|
27
|
+
#
|
|
28
|
+
# A policy's trigger that a `given` or invariant refuses does not undo the command that
|
|
29
|
+
# fired it, which has already persisted, so the outcome above stays a success. The
|
|
30
|
+
# refusal is recorded here instead of vanishing into the reaction log.
|
|
31
|
+
#
|
|
32
|
+
# @return [Array<Hash{Symbol => Object}>] one `{ policy:, trigger:, reason: }` per refused
|
|
33
|
+
# reaction, oldest first; empty when every reaction was delivered
|
|
34
|
+
def refused_reactions = self[:refused_reactions] || []
|
|
35
|
+
|
|
36
|
+
# Lists the refused reactions that block the run, as opposed to a benign non-match.
|
|
37
|
+
#
|
|
38
|
+
# See `ReactionOutcome`: a refusal is benign when a sibling reaction to the same event
|
|
39
|
+
# delivered another command on the same aggregate. `--wait` exits 1 on any that remain.
|
|
40
|
+
#
|
|
41
|
+
# @return [Array<Hash{Symbol => Object}>] the subset of `refused_reactions` that blocks
|
|
42
|
+
def blocking_reactions = self[:blocking_reactions] || []
|
|
43
|
+
|
|
44
|
+
# Lists the reactions that crashed, as opposed to being refused by the domain.
|
|
45
|
+
#
|
|
46
|
+
# A crash is warned and never re-raised, since the emitting command has persisted; it is
|
|
47
|
+
# recorded here so `--wait` can fail on a chain that stopped halfway.
|
|
48
|
+
#
|
|
49
|
+
# @return [Array<Hash{Symbol => Object}>] `{ policy:, trigger:, reason:, error_class: }`
|
|
50
|
+
# per crashed reaction; empty when none crashed
|
|
51
|
+
def reaction_defects = self[:reaction_defects] || []
|
|
52
|
+
|
|
23
53
|
# Reads the identity of the record the dispatch settled on.
|
|
24
54
|
#
|
|
25
55
|
# @return [String, nil] the record's identity; nil for a port operation (no record)
|
|
@@ -126,6 +156,14 @@ module Hecks
|
|
|
126
156
|
end
|
|
127
157
|
|
|
128
158
|
def dispatch_invocation(verb, to:, with:, saga_correlation:, flat:)
|
|
159
|
+
@registry.collecting_reactions do |reactions|
|
|
160
|
+
dispatch_collecting(verb, reactions, to: to, with: with, saga_correlation: saga_correlation, flat: flat)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
private :dispatch_invocation
|
|
164
|
+
|
|
165
|
+
# One dispatch, with `reactions` receiving every reaction its events cause.
|
|
166
|
+
def dispatch_collecting(verb, reactions, to:, with:, saga_correlation:, flat:)
|
|
129
167
|
domain, aggregate_name, command_name = parse(verb)
|
|
130
168
|
aggregate = resolve_aggregate(domain, aggregate_name, verb)
|
|
131
169
|
|
|
@@ -161,9 +199,21 @@ module Hecks
|
|
|
161
199
|
react(announced, domain, aggregate, outbox_rows)
|
|
162
200
|
|
|
163
201
|
Result.new(verb: verb, instance: instance, events: announced,
|
|
164
|
-
execution_plan: execution_plan, persistence_outcome: persistence_outcome
|
|
202
|
+
execution_plan: execution_plan, persistence_outcome: persistence_outcome,
|
|
203
|
+
refused_reactions: refused_from(reactions),
|
|
204
|
+
blocking_reactions: ReactionOutcome.blocking(reactions, event_of: @registry.method(:reaction_event)),
|
|
205
|
+
reaction_defects: ReactionOutcome.defects(reactions))
|
|
165
206
|
end
|
|
166
|
-
private :
|
|
207
|
+
private :dispatch_collecting
|
|
208
|
+
|
|
209
|
+
# The reactions among `logged` that the domain refused, as plain facts.
|
|
210
|
+
#
|
|
211
|
+
# A crash in a reaction is a defect, not a refusal, and is warned where it happens.
|
|
212
|
+
def refused_from(logged)
|
|
213
|
+
logged.select { |entry| entry[:delivered] == false && !entry[:defect] }
|
|
214
|
+
.map { |entry| entry.slice(:policy, :trigger, :reason) }
|
|
215
|
+
end
|
|
216
|
+
private :refused_from
|
|
167
217
|
|
|
168
218
|
# Runs the policy and saga reactions owed because `announced` committed.
|
|
169
219
|
#
|
|
@@ -62,6 +62,9 @@ module Hecks
|
|
|
62
62
|
next unless normalized.key?(attribute.name)
|
|
63
63
|
|
|
64
64
|
Value.refuse_object_reference(command, attribute, normalized[attribute.name])
|
|
65
|
+
# A has_many argument mirrors the aggregate's reference list, which words its own refusal.
|
|
66
|
+
reference_list = aggregate.attribute(attribute.name)&.reference?
|
|
67
|
+
Value.refuse_scalar_list(command, attribute, normalized[attribute.name]) unless reference_list
|
|
65
68
|
normalized[attribute.name] = Value.for_attribute(aggregate, attribute, normalized[attribute.name], argument: true)
|
|
66
69
|
end
|
|
67
70
|
end
|
data/lib/hecks/runtime/loader.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require_relative "../
|
|
1
|
+
require_relative "../doors/ruby_door"
|
|
2
2
|
require_relative "../ports/loading"
|
|
3
3
|
require_relative "../ports/persistence"
|
|
4
4
|
require_relative "dispatcher"
|
|
@@ -11,18 +11,23 @@ module Hecks
|
|
|
11
11
|
# Boots a domain: loads its bluebook directory into a fresh Registry, runs
|
|
12
12
|
# every boot gate, and hands back the bound Dispatcher (or RemoteDispatcher).
|
|
13
13
|
class Loader
|
|
14
|
+
# Default for a boot's `environment:` keyword: read `HECKS_ENVIRONMENT`. An explicit
|
|
15
|
+
# `nil` means no overlay, whatever the variable holds.
|
|
16
|
+
FROM_ENV = :from_env
|
|
17
|
+
|
|
14
18
|
# Boots `path`: loads its bluebook directory into a fresh Registry, runs
|
|
15
19
|
# every registered boot gate, and returns the bound dispatcher. Pass
|
|
16
|
-
# `
|
|
20
|
+
# `install_doors: false` to skip the `Widget::Item.Add(...)` global
|
|
17
21
|
# facade sugar (only a caller dispatching by FQN string needs to).
|
|
18
22
|
#
|
|
19
23
|
# @param path [String] a domain directory to boot
|
|
20
24
|
# @param shared [String, nil] shared ports/adapters root; nil walks up from `path`
|
|
21
|
-
# @param environment [String, nil] environment overlay loaded after the domain;
|
|
25
|
+
# @param environment [String, nil] environment overlay loaded after the domain; defaults
|
|
26
|
+
# to `HECKS_ENVIRONMENT`, and an explicit nil loads none
|
|
22
27
|
# @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted dispatcher
|
|
23
28
|
# @raise [Errno::ENOENT] if `path` names no domain directory
|
|
24
29
|
# @raise [Runtime::WiringError] if a boot gate finds a wiring problem
|
|
25
|
-
def self.boot(path, shared: nil,
|
|
30
|
+
def self.boot(path, shared: nil, install_doors: true, install_facade: nil, environment: FROM_ENV)
|
|
26
31
|
loading = Ports::Loading.bootstrap
|
|
27
32
|
directory = loading.bluebook_directory(path)
|
|
28
33
|
root = loading.shared_root(shared, directory)
|
|
@@ -31,14 +36,55 @@ module Hecks
|
|
|
31
36
|
Hecks.with_registry(registry) do
|
|
32
37
|
loading.load_library
|
|
33
38
|
loading.load_project(root)
|
|
34
|
-
loading.load_domain(directory, environment: environment)
|
|
39
|
+
loading.load_domain(directory, environment: selected_environment(environment))
|
|
35
40
|
end
|
|
36
41
|
|
|
37
42
|
run_boot_gates!(registry, directory)
|
|
38
43
|
dispatcher = dispatcher_for(registry)
|
|
39
44
|
redrive_outbox!(dispatcher)
|
|
40
45
|
seed_privacy_markings!(dispatcher, registry)
|
|
41
|
-
install_facade ? bind_runtime(dispatcher) : dispatcher
|
|
46
|
+
Doors.install?(install_doors, install_facade) ? bind_runtime(dispatcher) : dispatcher
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# What `describe` answers: the loaded declarations and nothing bound to run them.
|
|
50
|
+
Described = Struct.new(:registry)
|
|
51
|
+
|
|
52
|
+
# Loads `path`'s declarations into a fresh Registry and stops: no boot gate runs, no
|
|
53
|
+
# persistence adapter is resolved or bound, nothing connects to a database.
|
|
54
|
+
#
|
|
55
|
+
# This is what answers a question about the domain's own shape (its verbs, arguments and
|
|
56
|
+
# usage) in any environment, including one where the bound adapter cannot load.
|
|
57
|
+
#
|
|
58
|
+
# @param path [String] a domain directory to read
|
|
59
|
+
# @param shared [String, nil] shared ports/adapters root; nil walks up from `path`
|
|
60
|
+
# @param environment [String, nil] environment overlay loaded after the domain
|
|
61
|
+
# @return [Described] answers `registry` like a booted dispatcher does
|
|
62
|
+
# @raise [Errno::ENOENT] if `path` names no domain directory
|
|
63
|
+
def self.describe(path, shared: nil, environment: FROM_ENV)
|
|
64
|
+
loading = Ports::Loading.bootstrap
|
|
65
|
+
directory = loading.bluebook_directory(path)
|
|
66
|
+
root = loading.shared_root(shared, directory)
|
|
67
|
+
registry = Registry.new(root: File.dirname(directory))
|
|
68
|
+
|
|
69
|
+
Hecks.with_registry(registry) do
|
|
70
|
+
loading.load_library
|
|
71
|
+
loading.load_project(root)
|
|
72
|
+
loading.load_domain(directory, environment: selected_environment(environment))
|
|
73
|
+
end
|
|
74
|
+
Described.new(registry)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# The overlay a boot loads: the caller's own choice (nil meaning none), else the
|
|
78
|
+
# `HECKS_ENVIRONMENT` variable when the caller left the keyword at its default. A domain
|
|
79
|
+
# with no `environments/<name>.*` files ignores either.
|
|
80
|
+
#
|
|
81
|
+
# @param environment [String, nil, Symbol] the overlay a caller passed, or `FROM_ENV`
|
|
82
|
+
# @return [String, nil] the overlay name; nil for none
|
|
83
|
+
def self.selected_environment(environment)
|
|
84
|
+
return environment unless environment == FROM_ENV
|
|
85
|
+
|
|
86
|
+
named = ENV["HECKS_ENVIRONMENT"].to_s.strip
|
|
87
|
+
named.empty? ? nil : named
|
|
42
88
|
end
|
|
43
89
|
|
|
44
90
|
# Redrives pending outbox rows after the dispatcher and saga
|
|
@@ -77,10 +123,11 @@ module Hecks
|
|
|
77
123
|
#
|
|
78
124
|
# @param paths [Array<String>, String] the exact file paths to boot, in load order
|
|
79
125
|
# @param shared [String, nil] shared ports/adapters root; nil walks up from the first path
|
|
80
|
-
# @param environment [String, nil] environment overlay loaded after the named files
|
|
126
|
+
# @param environment [String, nil] environment overlay loaded after the named files;
|
|
127
|
+
# defaults to `HECKS_ENVIRONMENT`, and an explicit nil loads none
|
|
81
128
|
# @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted dispatcher
|
|
82
129
|
# @raise [Runtime::WiringError] if a boot gate finds a wiring problem
|
|
83
|
-
def self.boot_files(paths, shared: nil,
|
|
130
|
+
def self.boot_files(paths, shared: nil, install_doors: true, install_facade: nil, environment: FROM_ENV)
|
|
84
131
|
loading = Ports::Loading.bootstrap
|
|
85
132
|
files = Array(paths).map { |path| File.expand_path(path) }
|
|
86
133
|
directory = File.dirname(files.first)
|
|
@@ -90,12 +137,12 @@ module Hecks
|
|
|
90
137
|
Hecks.with_registry(registry) do
|
|
91
138
|
loading.load_library
|
|
92
139
|
loading.load_project(root)
|
|
93
|
-
loading.load_selected(files, environment: environment)
|
|
140
|
+
loading.load_selected(files, environment: selected_environment(environment))
|
|
94
141
|
end
|
|
95
142
|
|
|
96
143
|
run_boot_gates!(registry, directory)
|
|
97
144
|
dispatcher = dispatcher_for(registry)
|
|
98
|
-
install_facade ? bind_runtime(dispatcher) : dispatcher
|
|
145
|
+
Doors.install?(install_doors, install_facade) ? bind_runtime(dispatcher) : dispatcher
|
|
99
146
|
end
|
|
100
147
|
|
|
101
148
|
# Runs every registered boot gate against `registry`, in order:
|
|
@@ -117,7 +164,8 @@ module Hecks
|
|
|
117
164
|
gates
|
|
118
165
|
end
|
|
119
166
|
|
|
120
|
-
# Resolves the Ruby implementation of every adapter a hecksagon binds
|
|
167
|
+
# Resolves the Ruby implementation of every adapter a hecksagon binds, and of every
|
|
168
|
+
# `default_adapter` a world names (a chapter that binds nothing takes its world's).
|
|
121
169
|
# An adapter's implementation can register its own persistence plugin as
|
|
122
170
|
# a side effect of loading (e.g. `PostgresEra`) — resolving here, before
|
|
123
171
|
# gates are collected, is what registers those plugins' gates without
|
|
@@ -131,6 +179,11 @@ module Hecks
|
|
|
131
179
|
next
|
|
132
180
|
end
|
|
133
181
|
end
|
|
182
|
+
registry.worlds.each_value do |world|
|
|
183
|
+
registry.adapter_class(world.default_adapter) if world.default_adapter
|
|
184
|
+
rescue WiringError
|
|
185
|
+
next
|
|
186
|
+
end
|
|
134
187
|
end
|
|
135
188
|
|
|
136
189
|
# A backstop only: fires when a translation declares a `computes`/
|
|
@@ -169,7 +222,7 @@ module Hecks
|
|
|
169
222
|
# `dispatcher`. The binding lives in the facade's own modules, not a
|
|
170
223
|
# class-level global, so two boots in one process don't share one name.
|
|
171
224
|
def self.bind_runtime(dispatcher)
|
|
172
|
-
|
|
225
|
+
Doors::RubyDoor.install(dispatcher)
|
|
173
226
|
dispatcher
|
|
174
227
|
end
|
|
175
228
|
end
|
data/lib/hecks/runtime/outbox.rb
CHANGED
|
@@ -240,7 +240,7 @@ module Hecks
|
|
|
240
240
|
when "policy"
|
|
241
241
|
policy = @registry.bluebook(home)&.policies&.find { |candidate| candidate.name == name } ||
|
|
242
242
|
raise(WiringError, "outbox row #{row.delivery_id} names policy #{fqn}, which no bluebook declares")
|
|
243
|
-
@policies.react(event, row.domain, only: [policy, home])
|
|
243
|
+
@policies.react(event, row.domain, only: [policy, home], event_uid: row.event_uid)
|
|
244
244
|
when "saga"
|
|
245
245
|
process_manager = @registry.bluebook(home)&.process_managers&.find { |candidate| candidate.name == name } ||
|
|
246
246
|
raise(WiringError,
|
|
@@ -31,15 +31,18 @@ module Hecks
|
|
|
31
31
|
# @param only [Array(Bluebook::Policy, String), nil] one `[policy, home_domain]`
|
|
32
32
|
# pair to run exactly (the outbox relay's consumer), instead of scanning every
|
|
33
33
|
# loaded bluebook for candidates
|
|
34
|
+
# @param event_uid [String, nil] the outbox uid every consumer of this event shares, when the
|
|
35
|
+
# event was rebuilt from an outbox row; otherwise the event object itself is the identity
|
|
34
36
|
# @return [void]
|
|
35
|
-
def react(event, domain, only: nil)
|
|
37
|
+
def react(event, domain, only: nil, event_uid: nil)
|
|
36
38
|
selected = only ? [only] : policies_for(event, domain)
|
|
37
39
|
selected.each do |policy, home_domain|
|
|
38
40
|
result = deliver(policy, event, home_domain)
|
|
39
41
|
next if result.nil?
|
|
40
42
|
|
|
41
43
|
# A for_each policy answers an array; Array(...) would explode a record Hash.
|
|
42
|
-
|
|
44
|
+
identity = event_uid || event.object_id
|
|
45
|
+
(result.is_a?(Array) ? result : [result]).each { |record| @registry.log_reaction(record, event: identity) }
|
|
43
46
|
end
|
|
44
47
|
end
|
|
45
48
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require_relative "adapter_lookup"
|
|
1
2
|
require_relative "interpreting"
|
|
2
3
|
require_relative "command_interpreter/argument_gate"
|
|
3
4
|
require_relative "event"
|
|
@@ -125,16 +126,7 @@ module Hecks
|
|
|
125
126
|
end
|
|
126
127
|
|
|
127
128
|
def adapter_for(ctx)
|
|
128
|
-
|
|
129
|
-
implementations = @registry.adapters.values.select { |adapter| adapter.port == name }
|
|
130
|
-
|
|
131
|
-
case implementations.size
|
|
132
|
-
when 1 then Adapters.const_get(implementations.first.name).new
|
|
133
|
-
when 0 then raise WiringError, "no adapter implements the #{name} port — nothing can answer #{ctx.operation.hecks_name}"
|
|
134
|
-
else raise WiringError,
|
|
135
|
-
"#{implementations.size} adapters implement the #{name} port " \
|
|
136
|
-
"(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
|
|
137
|
-
end
|
|
129
|
+
AdapterLookup.call(@registry, port_name_for(ctx), asked: ctx.operation.hecks_name)
|
|
138
130
|
end
|
|
139
131
|
|
|
140
132
|
# Adapters are handed plain data, never a Value.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require_relative "../naming"
|
|
2
|
+
require_relative "adapter_lookup"
|
|
2
3
|
require_relative "../ports/query"
|
|
3
4
|
require_relative "../ports/query/ordering"
|
|
4
5
|
require_relative "../query_specification/field_path"
|
|
@@ -33,6 +34,10 @@ module Hecks
|
|
|
33
34
|
|
|
34
35
|
declared = declared_query(aggregate, query_name)
|
|
35
36
|
args = normalize_args(aggregate, declared, args)
|
|
37
|
+
if (port = aggregate.query_binding(declared.name))
|
|
38
|
+
return answered_from_outside(aggregate, declared, args, port)
|
|
39
|
+
end
|
|
40
|
+
|
|
36
41
|
declared = TenantScope.apply(declared, args)
|
|
37
42
|
# Applied after TenantScope so its tenant clause rides through as an ordinary
|
|
38
43
|
# local clause; it is deliberately not propagated into the hop's inner query.
|
|
@@ -66,6 +71,11 @@ module Hecks
|
|
|
66
71
|
|
|
67
72
|
declared = declared_query(aggregate, query_name)
|
|
68
73
|
args = normalize_args(aggregate, declared, args)
|
|
74
|
+
# No records to interpret: the adapter is the only answer there is, and the reference
|
|
75
|
+
# takes it through the same shape check, so a diff against #call sees only the adapter.
|
|
76
|
+
port = aggregate.query_binding(declared.name)
|
|
77
|
+
return answered_from_outside(aggregate, declared, args, port) if port
|
|
78
|
+
|
|
69
79
|
declared = TenantScope.apply(declared, args)
|
|
70
80
|
reference_interpret(@registry.repository(domain, aggregate).all, declared, args,
|
|
71
81
|
domain: domain, shape: aggregate)
|
|
@@ -73,6 +83,73 @@ module Hecks
|
|
|
73
83
|
|
|
74
84
|
private
|
|
75
85
|
|
|
86
|
+
# Asks the port's adapter the question the hecksagon bound to this query, by the query's
|
|
87
|
+
# snake-cased name and with its arguments as plain data. Each row of the answer is built as
|
|
88
|
+
# the value object the query `returns`, so an answer that is not that shape is refused
|
|
89
|
+
# before it enters the domain. The aggregate's records are never read.
|
|
90
|
+
#
|
|
91
|
+
# @return [Array<Hash>] the rows as the returned value object's fields, frozen
|
|
92
|
+
# @raise [Runtime::WiringError] if no adapter answers the port or the adapter lacks the method
|
|
93
|
+
# @raise [Runtime::TypeMismatch, Runtime::InvariantViolation, Runtime::UnknownArgument,
|
|
94
|
+
# Runtime::AbsentArgument] naming the query, if the answer is not its declared shape
|
|
95
|
+
def answered_from_outside(aggregate, declared, args, port)
|
|
96
|
+
asked = "#{aggregate.hecks_name}.#{declared.name}"
|
|
97
|
+
refuse_offered_arguments!(declared, args, asked)
|
|
98
|
+
adapter = AdapterLookup.call(@registry, port.name, asked: asked)
|
|
99
|
+
method = Naming.snake(declared.name)
|
|
100
|
+
# The real adapter's class is what must answer; a fuzz replay's stand-in refuses instead.
|
|
101
|
+
klass = AdapterLookup.adapter_class(@registry, port.name, asked: asked)
|
|
102
|
+
AdapterLookup.check_answers!(klass, port.name, method, declared, asked: asked)
|
|
103
|
+
|
|
104
|
+
answer = adapter.public_send(method, **Value.materialize(args))
|
|
105
|
+
Freezer.deep(shaped(aggregate, declared, answer, asked))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Refuses arguments the adapter cannot be handed: a required one left out, or one the query
|
|
109
|
+
# does not declare. The adapter is asked by keyword, so either would otherwise reach it as a
|
|
110
|
+
# raw ArgumentError.
|
|
111
|
+
#
|
|
112
|
+
# @raise [Runtime::AbsentArgument] if a non-optional declared argument is missing
|
|
113
|
+
# @raise [Runtime::UnknownArgument] if `args` names an argument the query does not declare
|
|
114
|
+
def refuse_offered_arguments!(declared, args, asked)
|
|
115
|
+
names = declared.attributes.map { |attribute| attribute.name.to_sym }
|
|
116
|
+
offered = args.keys.map(&:to_sym)
|
|
117
|
+
unknown = (offered - names).sort
|
|
118
|
+
unless unknown.empty?
|
|
119
|
+
raise UnknownArgument, RefusalWording.render_site("UnknownArgument", "unknown_args",
|
|
120
|
+
command: asked, unknown: unknown, declared: names)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
required = declared.attributes.reject(&:optional?).map { |attribute| attribute.name.to_sym }
|
|
124
|
+
absent = (required - offered).sort
|
|
125
|
+
return if absent.empty?
|
|
126
|
+
|
|
127
|
+
raise AbsentArgument, RefusalWording.render_site("AbsentArgument", "absent_args",
|
|
128
|
+
command: asked, absent: absent, declared: names)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Builds the adapter's answer as the declared value object: one row, or a list of rows for
|
|
132
|
+
# `returns list_of(...)`. A refusal keeps its class and gains the query's name.
|
|
133
|
+
def shaped(aggregate, declared, answer, asked)
|
|
134
|
+
value_object = Value.value_object_for(aggregate, declared.returns_name) or
|
|
135
|
+
raise WiringError, "#{asked} returns #{declared.returns_name.inspect}, which the aggregate " \
|
|
136
|
+
"declares no value object for"
|
|
137
|
+
offered = declared.returns_list? ? answer : [answer]
|
|
138
|
+
unless offered.is_a?(Array) && offered.all?(Hash)
|
|
139
|
+
raise TypeMismatch, "#{asked} answered outside the domain, but #{answer.class} is not " \
|
|
140
|
+
"#{declared.returns_list? ? 'a list of' : 'a'} #{value_object.hecks_name} row"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
offered.map { |row| answered_row(value_object, row, aggregate, declared, asked) }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# One row of an outside answer, built and validated as the returned value object.
|
|
147
|
+
def answered_row(value_object, row, aggregate, declared, asked)
|
|
148
|
+
Value.build(value_object, row, aggregate).to_h
|
|
149
|
+
rescue TypeMismatch, InvariantViolation, UnknownArgument, AbsentArgument => e
|
|
150
|
+
raise e.class, "#{asked} answered outside the domain, but not as its #{declared.returns} — #{e.message}"
|
|
151
|
+
end
|
|
152
|
+
|
|
76
153
|
def declared_query(aggregate, query_name)
|
|
77
154
|
aggregate.query(query_name) ||
|
|
78
155
|
raise(UnknownVerb, RefusalWording.render_site("UnknownVerb", "no_query",
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require_relative "refusal_wording"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Runtime
|
|
5
|
+
# Tells a refused policy reaction that blocks a run from one that is a benign non-match.
|
|
6
|
+
#
|
|
7
|
+
# A policy pair such as `RecordTheMatch` / `RecordTheDrift` reacts to one event with two
|
|
8
|
+
# commands on one aggregate, each `given`-gated so exactly one applies; the other is refused
|
|
9
|
+
# by design and the run passed. A refusal is benign when another reaction to the same event
|
|
10
|
+
# instance delivered a different command on the same aggregate: an alternative took over. It
|
|
11
|
+
# is also benign when the creating command it triggered reports the record already exists (a
|
|
12
|
+
# re-run's idempotent registration: the record the run wanted is there). Every other refusal
|
|
13
|
+
# (a release's `Accept` refused by a dirty tree, with no alternative) blocks the outcome. A
|
|
14
|
+
# crash (`defect`) is never a refusal: it is warned where it happens and listed by
|
|
15
|
+
# `defects`.
|
|
16
|
+
module ReactionOutcome
|
|
17
|
+
# A reaction entry, with the identity of the event instance it answered, when known.
|
|
18
|
+
Row = Struct.new(:fields, :event) do
|
|
19
|
+
def delivered = fields[:delivered]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
# Picks the refused reactions that block the run.
|
|
25
|
+
#
|
|
26
|
+
# An alternative counts only when it answered the same event instance, which `event_of`
|
|
27
|
+
# reads (an outbox uid, or the event object's id). Without it (a remote host's JSON carries
|
|
28
|
+
# no event identity) the event's name is all there is to match.
|
|
29
|
+
#
|
|
30
|
+
# @param entries [Array<Hash>] every reaction one dispatch caused, delivered or not; keys
|
|
31
|
+
# are Symbols (local) or Strings (a remote host's JSON)
|
|
32
|
+
# @param event_of [#call, nil] maps an entry, as given, to the event instance it answered
|
|
33
|
+
# @return [Array<Hash{Symbol => Object}>] `{ policy:, trigger:, reason: }` per blocker
|
|
34
|
+
def blocking(entries, event_of: nil)
|
|
35
|
+
rows = Array(entries).map { |entry| Row.new(entry.transform_keys(&:to_sym), event_of&.call(entry)) }
|
|
36
|
+
rows.select { |row| row.delivered == false && !row.fields[:defect] }
|
|
37
|
+
.reject { |row| already_there?(row) }
|
|
38
|
+
.reject { |row| rows.any? { |other| alternative?(row, other) } }
|
|
39
|
+
.map { |row| row.fields.slice(:policy, :trigger, :reason) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Whether `row` is its own creating command reporting the record already exists.
|
|
43
|
+
def already_there?(row)
|
|
44
|
+
RefusalWording.already_exists?(row.fields[:reason], **creating(row.fields[:trigger]))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Whether `other` delivered a different command on the aggregate `refused` targeted, in
|
|
48
|
+
# answer to the same event.
|
|
49
|
+
def alternative?(refused, other)
|
|
50
|
+
other.delivered == true && same_event?(refused, other) &&
|
|
51
|
+
other.fields[:trigger] != refused.fields[:trigger] &&
|
|
52
|
+
aggregate_of(other.fields[:trigger]) == aggregate_of(refused.fields[:trigger])
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Whether both reactions answered one event: the same instance when both are known, else
|
|
56
|
+
# the same event name.
|
|
57
|
+
def same_event?(refused, other)
|
|
58
|
+
return false unless other.fields[:on] == refused.fields[:on]
|
|
59
|
+
|
|
60
|
+
refused.event.nil? || other.event.nil? || refused.event == other.event
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# The `Domain::Aggregate` part of a `Domain::Aggregate.Command` trigger.
|
|
64
|
+
def aggregate_of(trigger) = trigger.to_s.split(".").first
|
|
65
|
+
|
|
66
|
+
# The command and aggregate a trigger names, spelled as the AlreadyExists sentence does.
|
|
67
|
+
def creating(trigger)
|
|
68
|
+
target, command = trigger.to_s.split(".", 2)
|
|
69
|
+
return { command: target&.split("::")&.last } unless command
|
|
70
|
+
|
|
71
|
+
{ command: command.split(".").last, aggregate: target.split("::").last }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Picks the reactions that crashed, which no refusal rule excuses.
|
|
75
|
+
#
|
|
76
|
+
# A chain that crashes halfway leaves its record short of the state the run was meant to
|
|
77
|
+
# reach, so a caller that waits on the run must treat every one as a failure.
|
|
78
|
+
#
|
|
79
|
+
# @param entries [Array<Hash>] every reaction one dispatch caused, keys as for `blocking`
|
|
80
|
+
# @return [Array<Hash{Symbol => Object}>] `{ policy:, trigger:, reason:, error_class: }`
|
|
81
|
+
# per crashed reaction
|
|
82
|
+
def defects(entries)
|
|
83
|
+
Array(entries).map { |entry| entry.transform_keys(&:to_sym) }
|
|
84
|
+
.select { |row| row[:defect] }
|
|
85
|
+
.map { |row| row.slice(:policy, :trigger, :reason, :error_class) }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -19,6 +19,28 @@ module Hecks
|
|
|
19
19
|
substitute(template(refusal, site), values)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
# Whether `reason` is the AlreadyExists refusal for a duplicate creation, matched by the
|
|
23
|
+
# template's own wording so a remote host's reason reads the same as a local one.
|
|
24
|
+
#
|
|
25
|
+
# With `command` (and `aggregate`), the sentence must be that command's own: text that only
|
|
26
|
+
# resembles the template but names another command is not a duplicate creation.
|
|
27
|
+
#
|
|
28
|
+
# @param reason [String, nil] the refusal message
|
|
29
|
+
# @param command [String, nil] the bare name of the creating command that was refused
|
|
30
|
+
# @param aggregate [String, nil] the bare name of the aggregate it creates
|
|
31
|
+
# @return [Boolean]
|
|
32
|
+
def already_exists?(reason, command: nil, aggregate: nil)
|
|
33
|
+
given = { "command" => command, "aggregate" => aggregate }
|
|
34
|
+
pieces = TEMPLATES.fetch(%w[AlreadyExists creating_duplicate]).split(/(\{\w+\})/, -1)
|
|
35
|
+
pattern = pieces.map do |piece|
|
|
36
|
+
name = piece[/\A\{(\w+)\}\z/, 1]
|
|
37
|
+
next Regexp.escape(piece) unless name
|
|
38
|
+
|
|
39
|
+
given[name] ? Regexp.escape(given[name]) : ".*"
|
|
40
|
+
end.join
|
|
41
|
+
Regexp.new("\\A#{pattern}\\z", Regexp::MULTILINE).match?(reason.to_s)
|
|
42
|
+
end
|
|
43
|
+
|
|
22
44
|
# Renders a site's template from raw `arguments`, formatting each per its row.
|
|
23
45
|
# Raises ArgumentError on a missing or undeclared argument, KeyError on an unknown site.
|
|
24
46
|
def render_site(refusal, site, **arguments)
|
|
@@ -65,7 +87,7 @@ module Hecks
|
|
|
65
87
|
end
|
|
66
88
|
end
|
|
67
89
|
|
|
68
|
-
# Read lazily:
|
|
90
|
+
# Read lazily: hecks project_vocabulary boots this file before it writes these rows.
|
|
69
91
|
def argument_rows(refusal, site)
|
|
70
92
|
@argument_rows ||= Hecks::Vocabulary.rows("RefusalSiteArgument")
|
|
71
93
|
.group_by { |row| [row["refusal"], row["site"]] }
|