hecks 1.2.0 → 1.4.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/lib/hecks/adapters/driven/claude_code.rb +7 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +10 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
- data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
- data/lib/hecks/adapters/driven/heki/journal.rb +60 -2
- data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
- data/lib/hecks/adapters/driven/heki.rb +13 -7
- data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
- data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
- data/lib/hecks/adapters/driven/lambda.rb +39 -33
- data/lib/hecks/adapters/driven/local_storage.rb +17 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/postgres_era.adapter +5 -0
- data/lib/hecks/adapters/driven/prism.rb +4 -4
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- data/lib/hecks/adapters/driven.rb +4 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +145 -0
- data/lib/hecks/behaviors/dsl.rb +2 -2
- data/lib/hecks/behaviors/expectations.rb +51 -23
- data/lib/hecks/behaviors/ir.rb +1 -1
- data/lib/hecks/behaviors/rspec.rb +1 -1
- data/lib/hecks/behaviors/runner.rb +2 -2
- data/lib/hecks/behaviors.rb +1 -1
- data/lib/hecks/bluebook/aggregate.rb +13 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
- data/lib/hecks/bluebook/assembly/build.rb +1 -1
- data/lib/hecks/bluebook/assembly/contract.rb +39 -16
- data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
- data/lib/hecks/bluebook/assembly/marks.rb +18 -18
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
- data/lib/hecks/bluebook/assembly.rb +9 -9
- data/lib/hecks/bluebook/attribute.rb +9 -9
- data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
- data/lib/hecks/bluebook/behaviour/command.rb +23 -23
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -3
- data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
- data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
- data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
- data/lib/hecks/bluebook/behaviour/query.rb +1 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +28 -9
- data/lib/hecks/bluebook/command.rb +12 -12
- data/lib/hecks/bluebook/domain_port.rb +9 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +96 -96
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +56 -56
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +35 -35
- data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
- data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
- data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +11 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
- data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver.rb +60 -62
- data/lib/hecks/bluebook/hexagon.rb +1 -1
- data/lib/hecks/bluebook/lifecycle.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +122 -86
- data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
- data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
- data/lib/hecks/bluebook/meta_validator.rb +70 -70
- data/lib/hecks/bluebook/model_check.rb +301 -84
- data/lib/hecks/bluebook/pattern_subset.rb +9 -9
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +14 -14
- data/lib/hecks/bluebook/project_register.rb +6 -6
- data/lib/hecks/bluebook/query.rb +4 -4
- data/lib/hecks/bluebook/read_model.rb +14 -14
- data/lib/hecks/bluebook/reference.rb +8 -8
- data/lib/hecks/bluebook/smoke_test.rb +19 -19
- data/lib/hecks/bluebook/synthesizer.rb +12 -12
- data/lib/hecks/bluebook/translation.rb +4 -4
- data/lib/hecks/bluebook/value_object.rb +6 -6
- data/lib/hecks/bluebook.rb +2 -2
- data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
- data/lib/hecks/codemod.rb +36 -35
- data/lib/hecks/construct.rb +6 -6
- data/lib/hecks/corpus.rb +317 -0
- data/lib/hecks/deprecation.rb +95 -0
- data/lib/hecks/doc/reference.rb +19 -19
- data/lib/hecks/embryonaut_bluebook.rb +11 -11
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +79 -32
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +42 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +183 -20
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +2 -2
- data/lib/hecks/forms/query_form_renderer.rb +2 -2
- data/lib/hecks/forms/record_renderer.rb +2 -2
- data/lib/hecks/forms/record_table.rb +1 -1
- data/lib/hecks/forms/value_object_shape.rb +3 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +1 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework.rb +48 -17
- data/lib/hecks/freezer.rb +11 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
- data/lib/hecks/fuzzing/combination_miner.rb +119 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +361 -0
- data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
- data/lib/hecks/fuzzing/differential.rb +158 -0
- data/lib/hecks/fuzzing/domain_generator.rb +694 -0
- data/lib/hecks/fuzzing/era_boundary.rb +124 -0
- data/lib/hecks/fuzzing/form_census.rb +199 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +226 -38
- data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +161 -0
- data/lib/hecks/fuzzing/properties/corrections.rb +100 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +321 -41
- data/lib/hecks/fuzzing/properties/guards.rb +129 -26
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +31 -11
- data/lib/hecks/fuzzing/properties/outbox.rb +142 -0
- data/lib/hecks/fuzzing/properties/querying.rb +17 -17
- data/lib/hecks/fuzzing/properties.rb +84 -39
- data/lib/hecks/fuzzing/qa_settings.rb +152 -0
- data/lib/hecks/fuzzing/replay.rb +224 -114
- data/lib/hecks/fuzzing/rotation_priority.rb +94 -0
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
- data/lib/hecks/fuzzing/self_consistency.rb +676 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +526 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +101 -30
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +55 -8
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +31 -11
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +123 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +152 -31
- data/lib/hecks/fuzzing/shrinker.rb +197 -0
- data/lib/hecks/fuzzing/structural_skips.rb +39 -0
- data/lib/hecks/fuzzing/sweep_depth.rb +53 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +180 -0
- data/lib/hecks/fuzzing/value_generator.rb +66 -14
- data/lib/hecks/fuzzing.rb +11 -0
- data/lib/hecks/grammar/evolve.rb +10 -10
- data/lib/hecks/grammar.rb +7 -7
- data/lib/hecks/ir.rb +13 -13
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +365 -15
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +9 -9
- data/lib/hecks/naming.rb +89 -21
- data/lib/hecks/ports/access_control.rb +58 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +53 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +24 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +18 -1
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +160 -58
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +28 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +44 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +57 -4
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +338 -83
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +42 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +29 -6
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +3 -3
- data/lib/hecks/ports/query/ordering.rb +6 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/projections/bootstrap_table.rb +112 -0
- data/lib/hecks/projections/diagrams.rb +75 -75
- data/lib/hecks/projections/glossary/html.rb +250 -0
- data/lib/hecks/projections/glossary/markdown.rb +105 -0
- data/lib/hecks/projections/glossary/mermaid.rb +110 -0
- data/lib/hecks/projections/glossary/page.css +271 -0
- data/lib/hecks/projections/glossary/page.js +72 -0
- data/lib/hecks/projections/glossary/sections.rb +17 -0
- data/lib/hecks/projections/glossary/sentences.rb +205 -0
- data/lib/hecks/projections/glossary.rb +214 -286
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +18 -17
- data/lib/hecks/projections/model.rb +25 -21
- data/lib/hecks/projections/oidc.rb +7 -7
- data/lib/hecks/projections/parser_table.rb +5 -5
- data/lib/hecks/projections/reference.rb +3 -3
- data/lib/hecks/projections/rust_vocabulary.rb +443 -0
- data/lib/hecks/projections/shape.rb +2 -2
- data/lib/hecks/projections/statements.rb +11 -11
- data/lib/hecks/projections/vocabulary.rb +9 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +29 -29
- data/lib/hecks/projector/docs_projector.rb +13 -13
- data/lib/hecks/projector/exporter.rb +42 -21
- data/lib/hecks/projector/ir_projector.rb +1 -1
- data/lib/hecks/projector/narrate_projector.rb +15 -22
- data/lib/hecks/projector/target.rb +13 -13
- data/lib/hecks/projector.rb +15 -15
- data/lib/hecks/query_ir.rb +47 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +132 -24
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +57 -13
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +3 -3
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/aggregate_lock.rb +11 -11
- data/lib/hecks/runtime/boot_gates.rb +3 -3
- data/lib/hecks/runtime/caller.rb +8 -8
- data/lib/hecks/runtime/capability_graph.rb +2 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +64 -67
- data/lib/hecks/runtime/command_interpreter.rb +159 -102
- data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
- data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +94 -19
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +11 -11
- data/lib/hecks/runtime/dispatcher.rb +283 -90
- data/lib/hecks/runtime/entity_element.rb +282 -46
- data/lib/hecks/runtime/entity_interpreter.rb +160 -69
- data/lib/hecks/runtime/errors.rb +19 -19
- data/lib/hecks/runtime/event.rb +6 -6
- data/lib/hecks/runtime/identity.rb +22 -22
- data/lib/hecks/runtime/instance.rb +39 -14
- data/lib/hecks/runtime/interpreting.rb +12 -12
- data/lib/hecks/runtime/invocation.rb +276 -0
- data/lib/hecks/runtime/loader.rb +14 -14
- data/lib/hecks/runtime/outbox.rb +23 -23
- data/lib/hecks/runtime/policy_interpreter.rb +54 -54
- data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
- data/lib/hecks/runtime/query_interpreter.rb +111 -56
- data/lib/hecks/runtime/reaction_invocation.rb +76 -9
- data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
- data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
- data/lib/hecks/runtime/reference_hop.rb +6 -6
- data/lib/hecks/runtime/refusal_wording.rb +92 -112
- data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
- data/lib/hecks/runtime/registry/verification.rb +36 -26
- data/lib/hecks/runtime/registry.rb +56 -27
- data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
- data/lib/hecks/runtime/routing.rb +10 -88
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
- data/lib/hecks/runtime/saga_interpreter.rb +93 -56
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +9 -9
- data/lib/hecks/runtime/tenant_scope.rb +5 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +379 -226
- data/lib/hecks/runtime/value/entity_list_coercion.rb +248 -0
- data/lib/hecks/runtime/value.rb +28 -23
- data/lib/hecks/runtime.rb +7 -7
- data/lib/hecks/storehouse.rb +64 -64
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +207 -5
- data/lib/hecks.rb +13 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
- metadata +43 -2
data/lib/hecks/runtime/loader.rb
CHANGED
|
@@ -19,7 +19,7 @@ module Hecks
|
|
|
19
19
|
# `Widget::Item.Add(...)` sugar. A caller that only ever dispatches
|
|
20
20
|
# by FQN string (`SmokeTest`, the one caller so far) can pass
|
|
21
21
|
# `false` to skip it: `Facade::Surface.install` puts a bare global
|
|
22
|
-
# Ruby constant on `Object` per domain
|
|
22
|
+
# Ruby constant on `Object` per domain and per aggregate name, with
|
|
23
23
|
# no scoping and no cleanup hook, so a tool booting arbitrary
|
|
24
24
|
# throwaway domains under generic names ("Widget", "Item", "Tag")
|
|
25
25
|
# would otherwise leak those names into the rest of the process —
|
|
@@ -55,7 +55,7 @@ module Hecks
|
|
|
55
55
|
install_facade ? bind_runtime(dispatcher) : dispatcher
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
-
#
|
|
58
|
+
# The outbox's boot-time reconciliation — after the dispatcher
|
|
59
59
|
# exists (a row's consumer runs through its interpreters, so this
|
|
60
60
|
# cannot be a plain registry gate the way saga rehydration is) and
|
|
61
61
|
# after saga rehydration (a redriven row may advance a saga, which
|
|
@@ -69,7 +69,7 @@ module Hecks
|
|
|
69
69
|
dispatcher.outbox.redrive!
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
-
#
|
|
72
|
+
# The explicit-file form — `paths` names the exact bluebook/hecksagon/
|
|
73
73
|
# world files to boot, in place, wherever they actually live. `boot`
|
|
74
74
|
# above only ever takes a directory and globs it; that is the right
|
|
75
75
|
# shape for a real deployment (`examples/banking`, a domain someone
|
|
@@ -79,16 +79,16 @@ module Hecks
|
|
|
79
79
|
# (`Hecks::Behaviors`), but this carries no behaviors-specific
|
|
80
80
|
# logic and is not gated behind requiring that module.
|
|
81
81
|
#
|
|
82
|
-
#
|
|
82
|
+
# No copying, no temp directory. A prior port of this same idea
|
|
83
83
|
# (vendored into a downstream consumer, read before writing this)
|
|
84
84
|
# scoped a per-test boot by copying files into `Dir.mktmpdir` — which
|
|
85
85
|
# destroys real relative paths, and worse, makes a `persisted_by`
|
|
86
|
-
# path resolve against the
|
|
86
|
+
# path resolve against the temp copy's root instead of the project's
|
|
87
87
|
# own (confirmed there: a file adapter kept reading and writing the
|
|
88
88
|
# same deterministic tmp copy across an entire session, because
|
|
89
89
|
# `Hecks.boot`'s own `root` is always `File.dirname` of whatever
|
|
90
90
|
# directory it was handed). `directory` here is `File.dirname` of the
|
|
91
|
-
#
|
|
91
|
+
# first real path in `paths` — genuinely on disk, not a copy — so
|
|
92
92
|
# every downstream path (`EraCheck`, `persisted_by`, `shared_root`)
|
|
93
93
|
# resolves exactly as an ordinary directory boot's would.
|
|
94
94
|
def self.boot_files(paths, shared: nil, install_facade: true, environment: nil)
|
|
@@ -110,7 +110,7 @@ module Hecks
|
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
# ADR 0031 — replaces two previously-hardcoded, unconditional calls
|
|
113
|
-
# with a per-boot `BootGates` instance holding exactly the gates
|
|
113
|
+
# with a per-boot `BootGates` instance holding exactly the gates this
|
|
114
114
|
# registry's own bound adapters have a capability for. Ordering is
|
|
115
115
|
# preserved: era-checking (when a persistence plugin contributes one)
|
|
116
116
|
# still runs before `verify!`, saga rehydration still runs after
|
|
@@ -118,7 +118,7 @@ module Hecks
|
|
|
118
118
|
# comment).
|
|
119
119
|
#
|
|
120
120
|
# ADR 0033 — this loader no longer names `EraCheck`, or any other
|
|
121
|
-
# era-specific class, at all. Every
|
|
121
|
+
# era-specific class, at all. Every loaded persistence plugin
|
|
122
122
|
# (`Ports::Persistence.each_plugin` — nothing here if nothing was
|
|
123
123
|
# ever `require`d) is asked to contribute its own `:pre_verify`/
|
|
124
124
|
# `:post_verify` gates generically; `:saga_rehydration` is the one
|
|
@@ -144,7 +144,7 @@ module Hecks
|
|
|
144
144
|
# `TranslationCompute`/`TranslationRekey` data (`bluebook/
|
|
145
145
|
# translation.rb`, core, no era-specific class involved), so this
|
|
146
146
|
# needs nothing plugin-specific to ask "does anything declare a
|
|
147
|
-
# compute/rekey rule at all." A
|
|
147
|
+
# compute/rekey rule at all." A loaded persistence plugin (e.g. the
|
|
148
148
|
# era plugin's own `:era_compute_rules` gate, registered above) runs
|
|
149
149
|
# the real, adapter-aware version of this check and refuses by name
|
|
150
150
|
# ("...is bound to Memory") long before this ever would; this only
|
|
@@ -165,7 +165,7 @@ module Hecks
|
|
|
165
165
|
end
|
|
166
166
|
|
|
167
167
|
# `RemoteDispatcher` for a domain routed through Lambda,
|
|
168
|
-
# `Dispatcher` otherwise — the
|
|
168
|
+
# `Dispatcher` otherwise — the one place this decision gets
|
|
169
169
|
# made, so everything built on top (`Handle`, `AggregateDoor`,
|
|
170
170
|
# `Facade::Surface`) never has to know which class it's holding.
|
|
171
171
|
# `registry.bluebooks.keys.first` is the just-booted domain's own
|
|
@@ -173,7 +173,7 @@ module Hecks
|
|
|
173
173
|
# any `uses_framework` chapter, `bin/project_rust`'s own header
|
|
174
174
|
# draws the identical distinction), not a directory basename.
|
|
175
175
|
#
|
|
176
|
-
# `dispatched_by("Lambda")` is its
|
|
176
|
+
# `dispatched_by("Lambda")` is its own, explicit verb — not
|
|
177
177
|
# inferred from `deployed_to("AwsLambda")`'s mere presence. Both
|
|
178
178
|
# Banking's and Embryonaut's `.world` files already declare
|
|
179
179
|
# `deployed_to("AwsLambda")` (it only means "a deploy target
|
|
@@ -188,13 +188,13 @@ module Hecks
|
|
|
188
188
|
settings = registry.world(domain)&.for_verb("dispatched_by") || {}
|
|
189
189
|
return Dispatcher.new(registry) unless settings[:adapter] == "Lambda"
|
|
190
190
|
|
|
191
|
-
RemoteDispatcher.new(registry, region: settings.fetch(:region, "us-east-1"))
|
|
191
|
+
RemoteDispatcher.new(registry, region: settings.fetch(:region, "us-east-1"), function: settings[:function])
|
|
192
192
|
end
|
|
193
193
|
|
|
194
|
-
#
|
|
194
|
+
# The door is installed here, not stamped. This used to write the
|
|
195
195
|
# dispatcher onto every aggregate's class (`ruby_class.runtime =`) — the
|
|
196
196
|
# class-level global that made two boots in one process share one
|
|
197
|
-
# name. The facade's modules close over
|
|
197
|
+
# name. The facade's modules close over this dispatcher instead, so the
|
|
198
198
|
# binding lives in the surface a boot installs, not on anything shared.
|
|
199
199
|
def self.bind_runtime(dispatcher)
|
|
200
200
|
Facade::Surface.install(dispatcher)
|
data/lib/hecks/runtime/outbox.rb
CHANGED
|
@@ -6,25 +6,25 @@ require_relative "../naming"
|
|
|
6
6
|
|
|
7
7
|
module Hecks
|
|
8
8
|
module Runtime
|
|
9
|
-
#
|
|
9
|
+
# **The transactional outbox** — the durable hand-off between "a command
|
|
10
10
|
# committed" and "everything that was owed because it committed":
|
|
11
11
|
# the policies that react to its events, the process managers that
|
|
12
12
|
# advance on them, and (through a policy whose trigger is an
|
|
13
13
|
# outbound port operation) the external effects those reactions
|
|
14
14
|
# cause. `future-features.md` item 8, built.
|
|
15
15
|
#
|
|
16
|
-
#
|
|
16
|
+
# **The shape**. One row per (event, consumer). A consumer is a named
|
|
17
17
|
# policy or process manager that would react to the event — resolved
|
|
18
|
-
# at
|
|
19
|
-
#
|
|
18
|
+
# at enqueue time from the registry (`Fanout`), so the outbox records
|
|
19
|
+
# who was owed what, not just that an event happened. Rows move
|
|
20
20
|
# `pending → claimed → delivered | failed`:
|
|
21
21
|
#
|
|
22
|
-
# pending written in the
|
|
22
|
+
# pending written in the same adapter transaction as the
|
|
23
23
|
# aggregate save (`Interpreting#run_dispatch_order`
|
|
24
24
|
# wraps `save` + `emit` + enqueue in `repository.
|
|
25
25
|
# transaction`), so a row exists iff the state change
|
|
26
26
|
# it reacts to committed — never one without the other.
|
|
27
|
-
# claimed the relay is about to run this consumer. Set
|
|
27
|
+
# claimed the relay is about to run this consumer. Set before
|
|
28
28
|
# the reaction (a policy's `reenter`, a saga leg's
|
|
29
29
|
# dispatch, an adapter call) runs.
|
|
30
30
|
# delivered the consumer ran to completion — including the
|
|
@@ -33,7 +33,7 @@ module Hecks
|
|
|
33
33
|
# tracks delivery, not the domain's answer.
|
|
34
34
|
# failed the consumer raised a defect (non-refusal error).
|
|
35
35
|
#
|
|
36
|
-
#
|
|
36
|
+
# **Delivery is inline by default** — the dispatcher drains the rows it
|
|
37
37
|
# just wrote, in the same call, in the order C10.2 fixes (per event
|
|
38
38
|
# in `emits` order: that event's policy rows, then its saga rows —
|
|
39
39
|
# the emitting domain's own policies before other domains'). Nothing about the
|
|
@@ -44,20 +44,20 @@ module Hecks
|
|
|
44
44
|
# `Relay#redrive!` — run at boot by `Loader.run_boot_gates!` —
|
|
45
45
|
# finds it.
|
|
46
46
|
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
47
|
+
# **What redrive does, and deliberately doesn't**. A `pending` row is
|
|
48
|
+
# redriven: its consumer provably never started (claiming is the
|
|
49
49
|
# first thing delivery does), so running it now is exactly-once by
|
|
50
|
-
# construction. A `claimed` row is
|
|
50
|
+
# construction. A `claimed` row is not auto-redriven: the consumer
|
|
51
51
|
# started and the crash hid its outcome — the same reasoning
|
|
52
52
|
# `saga_pending_dispatch.rb` gives (a stalled transfer is a better
|
|
53
53
|
# defect than a double-credited one). It is surfaced loudly
|
|
54
54
|
# (`warn`, and a `stalled: true` entry in `Relay#log`) and left for
|
|
55
55
|
# `Relay#redrive!(claimed: true)` — an explicit operator decision,
|
|
56
56
|
# never a boot-time default. `delivery_id` (event uid + consumer) is
|
|
57
|
-
#
|
|
57
|
+
# unique per store, so a re-enqueue of the same fact to the same
|
|
58
58
|
# consumer is a no-op rather than a second row.
|
|
59
59
|
#
|
|
60
|
-
#
|
|
60
|
+
# **Which adapters**. Memory (in-process rows — visible to specs,
|
|
61
61
|
# gone with the process, exactly like everything else Memory holds),
|
|
62
62
|
# Sqlite and Postgres (a `hecks_outbox` table in the aggregate's own
|
|
63
63
|
# database — the only way the enqueue can share the save's
|
|
@@ -93,7 +93,7 @@ module Hecks
|
|
|
93
93
|
event.to_h.merge(correlation: event.correlation)
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
-
#
|
|
96
|
+
# The emitting domain's own bluebook first, then the rest in load
|
|
97
97
|
# order (C10.2) — the one policy ordering both `PolicyInterpreter#
|
|
98
98
|
# policies_for` and `Fanout.policies` read.
|
|
99
99
|
def bluebooks_home_first(registry, domain)
|
|
@@ -121,7 +121,7 @@ module Hecks
|
|
|
121
121
|
end
|
|
122
122
|
end
|
|
123
123
|
|
|
124
|
-
#
|
|
124
|
+
# **Who is owed what** — the same selection `PolicyInterpreter#policies_for`
|
|
125
125
|
# and `SagaInterpreter#advance` make at delivery time, made once at
|
|
126
126
|
# enqueue time so the row names its consumer. Policy rows first,
|
|
127
127
|
# then saga rows, event order preserved within each: exactly the
|
|
@@ -129,13 +129,13 @@ module Hecks
|
|
|
129
129
|
module Fanout
|
|
130
130
|
module_function
|
|
131
131
|
|
|
132
|
-
#
|
|
132
|
+
# One UID per event for this enqueue — the Event struct is
|
|
133
133
|
# frozen after `emit!`, so the uid lives on the rows rather than
|
|
134
134
|
# on it (and stays off `Event#to_h`, whose shape the golden and
|
|
135
135
|
# parity specs pin). Policy and saga rows for the same event
|
|
136
136
|
# share it, which is what makes `delivery_id` mean "this fact,
|
|
137
137
|
# this consumer".
|
|
138
|
-
#
|
|
138
|
+
# Row order is delivery order (C10.2): per event, in `emits`
|
|
139
139
|
# order — that event's policy rows, then its saga rows.
|
|
140
140
|
def rows_for(registry, events, domain)
|
|
141
141
|
uids = events.to_h { |event| [event, SecureRandom.uuid] }
|
|
@@ -194,13 +194,13 @@ module Hecks
|
|
|
194
194
|
end
|
|
195
195
|
end
|
|
196
196
|
|
|
197
|
-
#
|
|
197
|
+
# **The relay** — one per `Dispatcher`. Enqueues into a repository's
|
|
198
198
|
# store, drains rows inline, and redrives what a previous process
|
|
199
199
|
# left behind.
|
|
200
200
|
class Relay
|
|
201
201
|
attr_reader :registry, :log
|
|
202
202
|
|
|
203
|
-
#
|
|
203
|
+
# Not `saga_log`/`reaction_log` — those are ported byte-for-byte
|
|
204
204
|
# by the Rust kernel (`spec/rust_conformance_spec.rb`); this is
|
|
205
205
|
# an additive, Ruby-only log, the same rule `saga_dispatch_log`
|
|
206
206
|
# and `policy_dispatch_log` already follow.
|
|
@@ -213,7 +213,7 @@ module Hecks
|
|
|
213
213
|
# through (`Dispatcher#initialize`). Until then this relay can
|
|
214
214
|
# enqueue (that needs only the registry) but not deliver — and
|
|
215
215
|
# nothing can dispatch without a dispatcher, so nothing asks it
|
|
216
|
-
# to. The registry holds
|
|
216
|
+
# to. The registry holds one relay for its lifetime; a second
|
|
217
217
|
# dispatcher fronting the same registry re-attaches, which is
|
|
218
218
|
# fine because both dispatchers share every log and store.
|
|
219
219
|
def attach(policies:, sagas:)
|
|
@@ -224,7 +224,7 @@ module Hecks
|
|
|
224
224
|
|
|
225
225
|
def attached? = !@policies.nil?
|
|
226
226
|
|
|
227
|
-
# Called
|
|
227
|
+
# Called inside the save transaction by `Interpreting` for the
|
|
228
228
|
# command/entity paths, and outside one by `Dispatcher` for port
|
|
229
229
|
# operations (which save nothing, so there is no transaction to
|
|
230
230
|
# share). Returns the rows as stored (ids assigned), or nil when
|
|
@@ -243,7 +243,7 @@ module Hecks
|
|
|
243
243
|
# outbox here" — react directly, the pre-outbox path.
|
|
244
244
|
def deliver(rows, events, domain, repository)
|
|
245
245
|
if rows.nil?
|
|
246
|
-
#
|
|
246
|
+
# Per event, in `emits` order — its policies, then its sagas
|
|
247
247
|
# (C10.2, docs/semantics/bluebook-semantics.md); the same
|
|
248
248
|
# order `Fanout.rows_for` lays the outbox rows in.
|
|
249
249
|
events.each do |event|
|
|
@@ -287,9 +287,9 @@ module Hecks
|
|
|
287
287
|
stores.flat_map { |repository| repository.outbox_rows(status: status) }
|
|
288
288
|
end
|
|
289
289
|
|
|
290
|
-
#
|
|
290
|
+
# **Boot-time reconciliation**. Redrives `pending` rows (never
|
|
291
291
|
# claimed — safe by construction); surfaces `claimed` rows and
|
|
292
|
-
# redrives them
|
|
292
|
+
# redrives them only when told to (`claimed: true`).
|
|
293
293
|
def redrive!(claimed: false)
|
|
294
294
|
redriven = []
|
|
295
295
|
stores.each do |repository|
|
|
@@ -22,13 +22,13 @@ module Hecks
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# `deliver` returns `nil` for a policy whose `where` did not hold —
|
|
25
|
-
#
|
|
25
|
+
# silently, the same as a policy `policies_for` never selected at all
|
|
26
26
|
# (an `event_qualifier` miss carries no reaction_log entry either) —
|
|
27
|
-
# so nothing is appended for it. A `for_each` policy answers an
|
|
27
|
+
# so nothing is appended for it. A `for_each` policy answers an array
|
|
28
28
|
# (one record per matched row) rather than one record ; `Array(...)`
|
|
29
29
|
# is wrong here (it would explode a plain record Hash into its own
|
|
30
30
|
# key/value pairs), so the two shapes are told apart explicitly.
|
|
31
|
-
# `only:` —
|
|
31
|
+
# `only:` — one `[policy, home_domain]` pair, the outbox relay's
|
|
32
32
|
# way of running exactly the consumer a row names (`Runtime::
|
|
33
33
|
# Outbox::Relay#run_consumer`) instead of every policy that
|
|
34
34
|
# matches the event. Selection is otherwise identical.
|
|
@@ -44,13 +44,13 @@ module Hecks
|
|
|
44
44
|
|
|
45
45
|
private
|
|
46
46
|
|
|
47
|
-
#
|
|
48
|
-
# a policy commonly lives in the
|
|
47
|
+
# Scans every loaded bluebook, not just the emitting command's own —
|
|
48
|
+
# a policy commonly lives in the consumer's bluebook, reacting
|
|
49
49
|
# passively to an event a different domain's aggregate emits (the
|
|
50
50
|
# corpus-standard shape : `world/conception/bluebook/domain_cell.bluebook`'s
|
|
51
51
|
# ConceiveOnAbsorption reacts `on "DomainAbsorbed"`, an event
|
|
52
52
|
# `body/organs/bluebook/gut.bluebook`'s Gut.Absorb emits — two
|
|
53
|
-
# different bluebooks, joined only by the event's
|
|
53
|
+
# different bluebooks, joined only by the event's name). Restricting
|
|
54
54
|
# the scan to the emitting domain's own bluebook (the previous
|
|
55
55
|
# shape) silently drops every cross-domain reaction : the policy is
|
|
56
56
|
# never even a candidate, no refusal, no log entry, nothing —
|
|
@@ -58,12 +58,12 @@ module Hecks
|
|
|
58
58
|
# which encode this exact cross-bluebook cascade as their contract.
|
|
59
59
|
#
|
|
60
60
|
# Returns [policy, home_domain] pairs rather than bare policies —
|
|
61
|
-
# `deliver`/`deliver_for_each` fall back to the
|
|
62
|
-
#
|
|
61
|
+
# `deliver`/`deliver_for_each` fall back to the reacting policy's
|
|
62
|
+
# own domain (not the emitting one) when a trigger or for_each route
|
|
63
63
|
# is bare, and that fallback has to travel with each match now that
|
|
64
64
|
# a single event can surface policies from several different homes.
|
|
65
65
|
#
|
|
66
|
-
#
|
|
66
|
+
# The emitting domain's own policies first, in declaration order,
|
|
67
67
|
# then other bluebooks' in load order (C10.2) — the same order the
|
|
68
68
|
# outbox lays its rows in (`Outbox::Fanout.policies`) and the Rust
|
|
69
69
|
# kernel runs (`react_policies`: its own table, then cross-domain).
|
|
@@ -79,7 +79,7 @@ module Hecks
|
|
|
79
79
|
end
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
-
#
|
|
82
|
+
# The guard, evaluated against the triggering event's own payload —
|
|
83
83
|
# a policy has no aggregate instance of its own to read state from,
|
|
84
84
|
# so `state` is empty and every bare name a `where` resolves comes
|
|
85
85
|
# from `attrs` (Expression::Resolver#fetch checks `attrs` before
|
|
@@ -88,7 +88,7 @@ module Hecks
|
|
|
88
88
|
# policy simply has none of). Called from inside `deliver`'s own
|
|
89
89
|
# rescue-guarded body (both callers, below) — never guarded here —
|
|
90
90
|
# so an EvaluationError (an unresolvable field, a bad comparison) is
|
|
91
|
-
# caught the
|
|
91
|
+
# caught the same way any other reaction defect is, not swallowed as
|
|
92
92
|
# though the policy had merely declined to fire.
|
|
93
93
|
def where_holds?(policy, event)
|
|
94
94
|
return true if policy.where.to_s.empty?
|
|
@@ -97,7 +97,7 @@ module Hecks
|
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
def deliver(policy, event, domain)
|
|
100
|
-
# `record`
|
|
100
|
+
# `record` assigned before any branch that can raise, same reason
|
|
101
101
|
# `deliver_for_each`'s own header gives : both rescue clauses below
|
|
102
102
|
# call `.merge` on it, and a defect raised before it existed would
|
|
103
103
|
# be caught here only to raise a second, different NoMethodError
|
|
@@ -121,7 +121,7 @@ module Hecks
|
|
|
121
121
|
# fatal to the command that emitted the event.
|
|
122
122
|
record.merge(delivered: false, reason: e.message)
|
|
123
123
|
rescue StandardError => e
|
|
124
|
-
# A
|
|
124
|
+
# A defect, not a refusal — a NoMethodError in an interpreter, a
|
|
125
125
|
# NameError from a missing constant, a TypeError from a bad
|
|
126
126
|
# assumption : exactly the class of thing DOMAIN_REFUSALS
|
|
127
127
|
# (errors.rb, see the comment above that constant) deliberately
|
|
@@ -129,19 +129,19 @@ module Hecks
|
|
|
129
129
|
# folding a crash into the same `delivered: false` shape as an
|
|
130
130
|
# ordinary refusal makes a broken runtime read as normal operation
|
|
131
131
|
# in the log. This clause does not reopen that hole: it is a
|
|
132
|
-
#
|
|
132
|
+
# second, narrower rescue, tried only once the first one above has
|
|
133
133
|
# already declined to match, so a legitimate refusal still takes
|
|
134
134
|
# the branch above and a defect always takes this one.
|
|
135
135
|
#
|
|
136
|
-
# Catching it
|
|
136
|
+
# Catching it here is safe for a fact this method's caller cannot
|
|
137
137
|
# see from where it sits: by the time `react` runs, the command
|
|
138
|
-
# that
|
|
138
|
+
# that emitted `event` has already succeeded and persisted —
|
|
139
139
|
# `Dispatcher#dispatch` calls `@policies.react` only after its own
|
|
140
140
|
# `announced` events are already in hand. Letting this exception
|
|
141
141
|
# keep propagating would not undo that command (nothing here is
|
|
142
142
|
# transactional across aggregates) — it would only blow up the
|
|
143
|
-
#
|
|
144
|
-
# a
|
|
143
|
+
# original caller's `dispatch` call for a failure that happened in
|
|
144
|
+
# a different command, one the caller never asked to run and has no
|
|
145
145
|
# way to compensate for. So the defect is recorded, distinguishably
|
|
146
146
|
# (`defect: true`, plus the error's own class — nothing here is
|
|
147
147
|
# allowed to read like an ordinary refusal), warned to STDERR so it
|
|
@@ -152,19 +152,19 @@ module Hecks
|
|
|
152
152
|
record.merge(delivered: false, reason: e.message, defect: true, error_class: e.class.name)
|
|
153
153
|
end
|
|
154
154
|
|
|
155
|
-
#
|
|
155
|
+
# **The fan-out** — `policy.for_each` names a query ; this runs it
|
|
156
156
|
# against the triggering event's own payload (the same source
|
|
157
157
|
# `deliver`'s own ordinary path forwards to `trigger` wholesale) and
|
|
158
158
|
# fires `trigger` once per row, merging each row's own id into the
|
|
159
|
-
# forwarded payload under whichever key
|
|
159
|
+
# forwarded payload under whichever key the target command itself
|
|
160
160
|
# expects to be addressed by (`Behaviour::Command#addressing_key_for`
|
|
161
161
|
# — never a guessed, one-size mint: `Account.Freeze`, addressed by
|
|
162
|
-
# `account` because it is declared
|
|
162
|
+
# `account` because it is declared on Account and self-references
|
|
163
163
|
# it, refused every dispatch for as long as this hardcoded
|
|
164
164
|
# `<aggregate>_id` instead — a real bug, found wiring `for_each`
|
|
165
165
|
# into a real domain for the first time, not a hypothetical). A
|
|
166
166
|
# refusal is recorded per row and the fan-out continues ; a crash
|
|
167
|
-
# resolving the
|
|
167
|
+
# resolving the query itself, or the target command's own inability
|
|
168
168
|
# to address this aggregate at all (`addressing_key_for` answering
|
|
169
169
|
# `nil` — a domain-authoring mistake, not a data problem), is a
|
|
170
170
|
# single top-level defect for the policy, the same shape `deliver`'s
|
|
@@ -174,9 +174,9 @@ module Hecks
|
|
|
174
174
|
|
|
175
175
|
query_domain, aggregate_name, query_name = policy.for_each_route(domain)
|
|
176
176
|
aggregate = resolve_query_aggregate(query_domain, aggregate_name, policy.for_each)
|
|
177
|
-
#
|
|
178
|
-
# what the
|
|
179
|
-
# different question (
|
|
177
|
+
# The query reads the event, never the projection — `with:` says
|
|
178
|
+
# what the trigger is given, and the fan-out's query is asking a
|
|
179
|
+
# different question (which rows) in the event's own vocabulary.
|
|
180
180
|
query_args = for_each_query_args(aggregate.query(query_name), event)
|
|
181
181
|
rows = QueryInterpreter.new(@registry).call(query_domain, aggregate, query_name, query_args)
|
|
182
182
|
reference_key = addressing_key_for(target, aggregate_name)
|
|
@@ -192,20 +192,20 @@ module Hecks
|
|
|
192
192
|
record.merge(delivered: false, reason: e.message, defect: true, error_class: e.class.name)
|
|
193
193
|
end
|
|
194
194
|
|
|
195
|
-
#
|
|
196
|
-
# for_each query commonly filters by the
|
|
195
|
+
# The event's own identity is a fact too, not only its payload. A
|
|
196
|
+
# for_each query commonly filters by the emitting record's own
|
|
197
197
|
# identity (`OpenForCustomer`'s own `reference:`, scoping by the
|
|
198
198
|
# very customer who was just suspended) — which used to arrive for
|
|
199
|
-
# free because
|
|
199
|
+
# free because legacy dispatch left the self-addressing key riding
|
|
200
200
|
# along in `event.payload` unfiltered. Routing separated from
|
|
201
201
|
# payload (`to:`/`with:`, what the facade's own bang-methods always
|
|
202
202
|
# use) correctly stopped carrying it there, which left this query
|
|
203
|
-
# silently seeing
|
|
203
|
+
# silently seeing neither the field it needs nor any error saying
|
|
204
204
|
# why — an empty result read as "nothing to freeze" instead of "the
|
|
205
205
|
# customer" the whole reaction exists to catch.
|
|
206
206
|
#
|
|
207
|
-
# Merged in
|
|
208
|
-
# name
|
|
207
|
+
# Merged in only when the query declares an argument by that exact
|
|
208
|
+
# name and the emitting aggregate's own identity is genuinely what
|
|
209
209
|
# that name means (`construct.identity_heads`) — the same guard
|
|
210
210
|
# `SagaInterpreter::Correlation#self_identified?` uses for the
|
|
211
211
|
# identical shape one call away, so an unrelated aggregate sharing
|
|
@@ -229,22 +229,22 @@ module Hecks
|
|
|
229
229
|
args
|
|
230
230
|
end
|
|
231
231
|
|
|
232
|
-
#
|
|
232
|
+
# **What the trigger is given**. Undeclared, the event's whole payload
|
|
233
233
|
# forwards verbatim — the behaviour every policy had before `with:`
|
|
234
234
|
# existed, and still the right default for a trigger shaped like
|
|
235
235
|
# its event.
|
|
236
236
|
#
|
|
237
237
|
# Declared, it is the same reading a saga's own `dispatch ...,
|
|
238
238
|
# with:` gets (`SagaInterpreter#dispatch_args`): a Symbol names a
|
|
239
|
-
# field on the
|
|
239
|
+
# field on the source below, anything else is a literal the policy
|
|
240
240
|
# supplies itself. A saga additionally resolves against its own
|
|
241
241
|
# memory and correlation key; a policy has neither — it holds
|
|
242
242
|
# nothing between events — so the source is the event, plus:
|
|
243
243
|
#
|
|
244
|
-
# `extra` is a
|
|
244
|
+
# `extra` is a fan-out's row key, merged into the source before the
|
|
245
245
|
# projection rather than after it. That is what lets a `for_each`
|
|
246
246
|
# policy name the row it is acting on — `with: { account: :account }`
|
|
247
|
-
# — and therefore what lets one send the row and
|
|
247
|
+
# — and therefore what lets one send the row and nothing else. A
|
|
248
248
|
# trigger needing only which record to act on is the ordinary case
|
|
249
249
|
# for a fan-out, and before this it could not be written: the whole
|
|
250
250
|
# event rode along, and the target had to declare every field of it
|
|
@@ -261,26 +261,26 @@ module Hecks
|
|
|
261
261
|
label: "#{policy.name}'s trigger"
|
|
262
262
|
)
|
|
263
263
|
|
|
264
|
-
#
|
|
264
|
+
# **The raw inputs `args` was resolved from** — same additive,
|
|
265
265
|
# Ruby-only shape SagaInterpreter#deliver_saga_dispatch's own
|
|
266
266
|
# saga_dispatch_log gets, for Properties.dispatch_binding_
|
|
267
267
|
# fidelity's own independent re-derivation of Policy#with_spec's
|
|
268
268
|
# 2-branch resolution (Symbol → payload lookup, anything else →
|
|
269
269
|
# literal — a policy holds no correlation and no memory, so
|
|
270
270
|
# `payload` — the merged event-payload-plus-fan-out-row source —
|
|
271
|
-
# is the
|
|
271
|
+
# is the whole source, unlike a saga's own 4-branch one).
|
|
272
272
|
@registry.policy_dispatch_log << { policy: policy.name, on: event.name, payload: payload,
|
|
273
273
|
with_spec: policy.with_spec, args: args }
|
|
274
274
|
args
|
|
275
275
|
end
|
|
276
276
|
|
|
277
|
-
#
|
|
277
|
+
# The emitting record's own identity is a fact a projection may read
|
|
278
278
|
# — the same reasoning `for_each_query_args` gives one method up,
|
|
279
|
-
# extended from the fan-out's
|
|
279
|
+
# extended from the fan-out's query to the trigger's own `with:`.
|
|
280
280
|
# Routing separated from payload (`to:`/`with:`) stopped carrying
|
|
281
281
|
# the emitting aggregate's identity in the payload, which is right
|
|
282
282
|
# for the event (a KnightCaptured is a fact about a knight, not a
|
|
283
|
-
# re-statement of which game) but left a
|
|
283
|
+
# re-statement of which game) but left a cross-aggregate reaction
|
|
284
284
|
# with no way to say which record to address: chess's Graveyard is
|
|
285
285
|
# one-per-game, fed by policy from every piece's own Captured
|
|
286
286
|
# event, and its burials had no way to name the game — the
|
|
@@ -288,10 +288,10 @@ module Hecks
|
|
|
288
288
|
# graveyard's identity and refused every one ("no Graveyard with
|
|
289
289
|
# label.value \"bb\""). Same-aggregate targets were already covered
|
|
290
290
|
# (`ReactionInvocation.source_receiver_for` lifts Event.id), so
|
|
291
|
-
# this is the
|
|
291
|
+
# this is the other aggregate's half of that.
|
|
292
292
|
#
|
|
293
293
|
# Offered under the emitting aggregate's own identity heads, only
|
|
294
|
-
# to an
|
|
294
|
+
# to an explicit projection (a legacy wholesale forward keeps its
|
|
295
295
|
# exact old payload), and never over a value the payload itself
|
|
296
296
|
# carries. `event.id` is the scalar the identity resolves to, so a
|
|
297
297
|
# projection naming it as the target's own identity head routes it
|
|
@@ -309,9 +309,9 @@ module Hecks
|
|
|
309
309
|
construct.identity_heads.to_h { |head| [head.to_sym, event.id] }
|
|
310
310
|
end
|
|
311
311
|
|
|
312
|
-
#
|
|
313
|
-
# `deliver`'s own caller already built) back to its
|
|
314
|
-
# command, then asks
|
|
312
|
+
# Resolves `target` ("Domain::Aggregate.Command", the same shape
|
|
313
|
+
# `deliver`'s own caller already built) back to its own declared
|
|
314
|
+
# command, then asks it how it expects to be addressed by a row of
|
|
315
315
|
# `aggregate_name` — see `Behaviour::Command#addressing_key_for`'s
|
|
316
316
|
# own comment for the two shapes that answers. Raises (caught by
|
|
317
317
|
# `deliver_for_each`'s own outer `rescue StandardError`, the same
|
|
@@ -342,8 +342,8 @@ module Hecks
|
|
|
342
342
|
reason: "reaction depth #{@door.max_reaction_depth} reached")
|
|
343
343
|
end
|
|
344
344
|
|
|
345
|
-
#
|
|
346
|
-
# source a `with:` projection reads
|
|
345
|
+
# Already merged, by `trigger_args` — the row key belongs in the
|
|
346
|
+
# source a `with:` projection reads from, not bolted onto its
|
|
347
347
|
# result, or a projection could never name the row it acts on.
|
|
348
348
|
@door.reenter(target, **reaction_invocation(target, args, policy, event))
|
|
349
349
|
row_record.merge(delivered: true)
|
|
@@ -361,20 +361,20 @@ module Hecks
|
|
|
361
361
|
)
|
|
362
362
|
end
|
|
363
363
|
|
|
364
|
-
# `for_each`'s own
|
|
364
|
+
# `for_each`'s own query route moved onto the Policy itself
|
|
365
365
|
# (Behaviour::Policy#for_each_route) — one reading the interpreter
|
|
366
366
|
# and the fuzzer's fan-out property both call, rather than the
|
|
367
|
-
# same split spelled twice. The reference-
|
|
368
|
-
# uses is `addressing_key_for`, above — a property of the
|
|
369
|
-
#
|
|
367
|
+
# same split spelled twice. The reference-key the dispatch itself
|
|
368
|
+
# uses is `addressing_key_for`, above — a property of the target
|
|
369
|
+
# command, not of the policy, so it lives on `Behaviour::Command`
|
|
370
370
|
# instead.
|
|
371
371
|
|
|
372
372
|
def resolve_query_aggregate(domain, aggregate_name, verb)
|
|
373
373
|
bluebook = @registry.bluebook(domain) ||
|
|
374
|
-
raise(UnknownVerb, RefusalWording.
|
|
374
|
+
raise(UnknownVerb, RefusalWording.render_site("UnknownVerb", "no_domain", domain: domain, verb: verb))
|
|
375
375
|
bluebook.aggregate(aggregate_name) ||
|
|
376
|
-
raise(UnknownVerb, RefusalWording.
|
|
377
|
-
|
|
376
|
+
raise(UnknownVerb, RefusalWording.render_site("UnknownVerb", "no_aggregate",
|
|
377
|
+
domain: domain, aggregate: aggregate_name))
|
|
378
378
|
end
|
|
379
379
|
end
|
|
380
380
|
end
|