hecks 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +65 -0
- data/lib/hecks/adapters/driven/folder.rb +73 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +25 -4
- data/lib/hecks/adapters/driven/governance_authorization.rb +57 -6
- data/lib/hecks/adapters/driven/heki/journal.rb +8 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +53 -7
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +93 -2
- data/lib/hecks/adapters/driven/identity_registry.rb +10 -0
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +22 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +35 -7
- data/lib/hecks/adapters/driven/lambda.rb +43 -2
- data/lib/hecks/adapters/driven/local_storage.rb +67 -1
- data/lib/hecks/adapters/driven/memory.rb +13 -13
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +20 -0
- data/lib/hecks/adapters/driven/prism.rb +46 -7
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- data/lib/hecks/adapters/driven/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +2 -0
- data/lib/hecks/adapters/driving/github_webhook.rb +14 -1
- data/lib/hecks/behaviors/dsl.rb +58 -0
- data/lib/hecks/behaviors/expectations.rb +168 -7
- data/lib/hecks/behaviors/ir.rb +11 -0
- data/lib/hecks/behaviors/rspec.rb +8 -0
- data/lib/hecks/behaviors/runner.rb +19 -0
- data/lib/hecks/behaviors.rb +8 -0
- data/lib/hecks/bluebook/aggregate.rb +30 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +7 -0
- data/lib/hecks/bluebook/assembly/build.rb +25 -4
- data/lib/hecks/bluebook/assembly/contract.rb +66 -14
- data/lib/hecks/bluebook/assembly/contracts.rb +24 -19
- data/lib/hecks/bluebook/assembly/marks.rb +143 -14
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -0
- data/lib/hecks/bluebook/assembly.rb +25 -7
- data/lib/hecks/bluebook/attribute.rb +17 -3
- data/lib/hecks/bluebook/behaviour/aggregate.rb +32 -0
- data/lib/hecks/bluebook/behaviour/attribute.rb +13 -0
- data/lib/hecks/bluebook/behaviour/chapter.rb +55 -2
- data/lib/hecks/bluebook/behaviour/command.rb +33 -3
- data/lib/hecks/bluebook/behaviour/domain_port.rb +20 -0
- data/lib/hecks/bluebook/behaviour/entity.rb +12 -0
- data/lib/hecks/bluebook/behaviour/hexagon.rb +26 -0
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +23 -2
- data/lib/hecks/bluebook/behaviour/policy.rb +34 -9
- data/lib/hecks/bluebook/behaviour/process_manager.rb +32 -1
- data/lib/hecks/bluebook/behaviour/query.rb +5 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +21 -0
- data/lib/hecks/bluebook/behaviour/traits.rb +36 -0
- data/lib/hecks/bluebook/behaviour/value_object.rb +17 -5
- data/lib/hecks/bluebook/chapter.rb +23 -0
- data/lib/hecks/bluebook/command.rb +53 -8
- data/lib/hecks/bluebook/domain_port.rb +25 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +196 -37
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +79 -42
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +59 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +385 -53
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +135 -21
- data/lib/hecks/bluebook/dsl/command_builder.rb +189 -27
- data/lib/hecks/bluebook/dsl/entity_builder.rb +139 -9
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +41 -0
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +23 -6
- data/lib/hecks/bluebook/dsl/policy_builder.rb +60 -19
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +79 -15
- data/lib/hecks/bluebook/dsl/query_builder.rb +33 -4
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +102 -27
- data/lib/hecks/bluebook/dsl/rule_reference.rb +60 -8
- data/lib/hecks/bluebook/dsl/translation_builder.rb +146 -40
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +55 -7
- data/lib/hecks/bluebook/entity.rb +29 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +115 -23
- data/lib/hecks/bluebook/expression/ast_reader.rb +29 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +48 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +207 -29
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +36 -0
- data/lib/hecks/bluebook/expression/resolver.rb +320 -77
- data/lib/hecks/bluebook/hexagon.rb +34 -0
- data/lib/hecks/bluebook/lifecycle.rb +11 -0
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +30 -26
- data/lib/hecks/bluebook/meta_validator/plan.rb +43 -8
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +157 -7
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -4
- data/lib/hecks/bluebook/meta_validator/shapes.rb +141 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +149 -23
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +5 -4
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator.rb +180 -84
- data/lib/hecks/bluebook/model_check.rb +268 -24
- data/lib/hecks/bluebook/pattern_subset.rb +23 -1
- data/lib/hecks/bluebook/process_manager.rb +13 -0
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +44 -0
- data/lib/hecks/bluebook/query.rb +27 -0
- data/lib/hecks/bluebook/read_model.rb +21 -1
- data/lib/hecks/bluebook/reference.rb +21 -8
- data/lib/hecks/bluebook/smoke_test.rb +29 -6
- data/lib/hecks/bluebook/synthesizer.rb +34 -0
- data/lib/hecks/bluebook/translation.rb +30 -1
- data/lib/hecks/bluebook/value_object.rb +23 -5
- data/lib/hecks/bluebook.rb +3 -4
- data/lib/hecks/codemod.rb +107 -20
- data/lib/hecks/construct.rb +15 -1
- data/lib/hecks/corpus.rb +146 -25
- data/lib/hecks/deploy/bluebook/deploy.bluebook +105 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/deploy/oidc.json +5 -0
- data/lib/hecks/doc/reference.rb +185 -16
- data/lib/hecks/embryonaut_bluebook.rb +32 -9
- data/lib/hecks/facade/handle.rb +76 -3
- data/lib/hecks/facade/surface/aggregate_door.rb +8 -0
- data/lib/hecks/forms/field_shape.rb +3 -0
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/port_argument.rb +12 -0
- data/lib/hecks/forms/query_form_renderer.rb +63 -0
- data/lib/hecks/forms/record_renderer.rb +58 -0
- data/lib/hecks/forms/record_table.rb +27 -0
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +10 -0
- data/lib/hecks/fqn.rb +58 -0
- data/lib/hecks/framework/bluebook/compliance.bluebook +221 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +43 -20
- data/lib/hecks/freezer.rb +17 -1
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +159 -24
- data/lib/hecks/fuzzing/combination_miner.rb +59 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +109 -8
- data/lib/hecks/fuzzing/coverage_campaign.rb +56 -13
- data/lib/hecks/fuzzing/differential.rb +34 -0
- data/lib/hecks/fuzzing/domain_generator.rb +188 -11
- data/lib/hecks/fuzzing/era_boundary.rb +45 -15
- data/lib/hecks/fuzzing/form_census.rb +86 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +76 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +39 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +79 -22
- data/lib/hecks/fuzzing/nondeterministic.rb +13 -1
- data/lib/hecks/fuzzing/persistence_parity.rb +95 -3
- data/lib/hecks/fuzzing/properties/corrections.rb +25 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +158 -14
- data/lib/hecks/fuzzing/properties/guards.rb +44 -0
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +48 -0
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +18 -0
- data/lib/hecks/fuzzing/properties/outbox.rb +49 -11
- data/lib/hecks/fuzzing/properties/querying.rb +68 -14
- data/lib/hecks/fuzzing/properties.rb +24 -15
- data/lib/hecks/fuzzing/qa_settings.rb +12 -0
- data/lib/hecks/fuzzing/replay.rb +137 -29
- data/lib/hecks/fuzzing/rotation_priority.rb +41 -21
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +46 -20
- data/lib/hecks/fuzzing/self_consistency.rb +189 -40
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +12 -6
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +1 -1
- data/lib/hecks/fuzzing/sequence_generator.rb +47 -0
- data/lib/hecks/fuzzing/shrinker.rb +114 -2
- data/lib/hecks/fuzzing/structural_skips.rb +18 -4
- data/lib/hecks/fuzzing/sweep_depth.rb +8 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +61 -14
- data/lib/hecks/fuzzing/value_generator.rb +98 -10
- data/lib/hecks/grammar/evolve.rb +178 -2
- data/lib/hecks/grammar.rb +46 -0
- data/lib/hecks/ir.rb +38 -7
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/literal.rb +32 -0
- data/lib/hecks/naming.rb +88 -7
- data/lib/hecks/ports/access_control.rb +5 -10
- data/lib/hecks/ports/authorization.rb +3 -6
- data/lib/hecks/ports/identity_assignment.rb +1 -2
- data/lib/hecks/ports/identity_resolution.rb +1 -2
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +85 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +6 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +38 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +40 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +92 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +8 -0
- data/lib/hecks/ports/query/in_memory.rb +39 -6
- data/lib/hecks/ports/query/ordering.rb +15 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +43 -8
- data/lib/hecks/projections/deploy/fargate.rb +666 -0
- data/lib/hecks/projections/deploy/lambda.rb +2423 -0
- data/lib/hecks/projections/deploy/shared.rb +624 -0
- data/lib/hecks/projections/diagrams.rb +243 -7
- data/lib/hecks/projections/glossary/html.rb +88 -0
- data/lib/hecks/projections/glossary/markdown.rb +47 -0
- data/lib/hecks/projections/glossary/mermaid.rb +48 -0
- data/lib/hecks/projections/glossary/sentences.rb +105 -0
- data/lib/hecks/projections/glossary.rb +161 -19
- data/lib/hecks/projections/model/deviations.rb +44 -0
- data/lib/hecks/projections/model.rb +51 -1
- data/lib/hecks/projections/oidc.rb +40 -3
- data/lib/hecks/projections/parser_table.rb +49 -8
- data/lib/hecks/projections/reference.rb +12 -0
- data/lib/hecks/projections/rust_vocabulary.rb +219 -16
- data/lib/hecks/projections/shape.rb +8 -0
- data/lib/hecks/projections/statements.rb +63 -16
- data/lib/hecks/projections/vocabulary.rb +17 -0
- data/lib/hecks/projections.rb +3 -0
- data/lib/hecks/projector/cli_projector.rb +218 -10
- data/lib/hecks/projector/docs_projector.rb +145 -19
- data/lib/hecks/projector/exporter.rb +65 -11
- data/lib/hecks/projector/ir_projector.rb +6 -0
- data/lib/hecks/projector/narrate_projector.rb +136 -15
- data/lib/hecks/projector/target.rb +47 -10
- data/lib/hecks/projector.rb +101 -19
- data/lib/hecks/query_ir.rb +47 -0
- data/lib/hecks/query_specification/common/null_policy.rb +5 -3
- data/lib/hecks/rendering.rb +6 -0
- data/lib/hecks/router/namespace_installer.rb +13 -0
- data/lib/hecks/router.rb +55 -0
- data/lib/hecks/runtime/aggregate_lock.rb +9 -0
- data/lib/hecks/runtime/boot_gates.rb +18 -0
- data/lib/hecks/runtime/caller.rb +32 -0
- data/lib/hecks/runtime/capability_graph.rb +11 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +23 -21
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +14 -15
- data/lib/hecks/runtime/command_interpreter.rb +42 -17
- data/lib/hecks/runtime/command_rules/admissibility.rb +165 -14
- data/lib/hecks/runtime/command_rules/arithmetic.rb +17 -5
- data/lib/hecks/runtime/command_rules/references.rb +118 -28
- data/lib/hecks/runtime/dependency_planning.rb +45 -0
- data/lib/hecks/runtime/dispatcher.rb +28 -50
- data/lib/hecks/runtime/entity_element.rb +161 -8
- data/lib/hecks/runtime/entity_interpreter.rb +44 -9
- data/lib/hecks/runtime/errors.rb +18 -4
- data/lib/hecks/runtime/event.rb +10 -5
- data/lib/hecks/runtime/identity.rb +71 -3
- data/lib/hecks/runtime/instance.rb +67 -7
- data/lib/hecks/runtime/interpreting.rb +13 -5
- data/lib/hecks/runtime/invocation.rb +118 -36
- data/lib/hecks/runtime/loader.rb +94 -8
- data/lib/hecks/runtime/outbox.rb +145 -7
- data/lib/hecks/runtime/policy_interpreter.rb +22 -9
- data/lib/hecks/runtime/port_operation_interpreter.rb +20 -0
- data/lib/hecks/runtime/query_interpreter.rb +40 -12
- data/lib/hecks/runtime/reaction_invocation.rb +53 -8
- data/lib/hecks/runtime/read_model_interpreter.rb +23 -7
- data/lib/hecks/runtime/rebuild_sweep.rb +28 -0
- data/lib/hecks/runtime/reference_hop.rb +42 -0
- data/lib/hecks/runtime/refusal_wording.rb +50 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +11 -0
- data/lib/hecks/runtime/registry/verification.rb +119 -4
- data/lib/hecks/runtime/registry.rb +157 -4
- data/lib/hecks/runtime/remote_dispatcher.rb +92 -6
- data/lib/hecks/runtime/routing.rb +27 -2
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +10 -12
- data/lib/hecks/runtime/saga_interpreter.rb +27 -13
- data/lib/hecks/runtime/tenant_check.rb +26 -6
- data/lib/hecks/runtime/tenant_scope.rb +18 -0
- data/lib/hecks/runtime/value/coercion.rb +255 -33
- data/lib/hecks/runtime/value/entity_list_coercion.rb +102 -30
- data/lib/hecks/runtime/value.rb +50 -0
- data/lib/hecks/runtime.rb +32 -0
- data/lib/hecks/storehouse.rb +305 -9
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks.rb +79 -1
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -0
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +12 -2
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +29 -5
- metadata +14 -5
- data/lib/hecks/codemod/legacy_dispatch_args.rb +0 -299
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +0 -186
- data/lib/hecks/deprecation.rb +0 -95
- data/lib/hecks/framework/bluebook/compliance.bluebook +0 -1
|
@@ -23,6 +23,15 @@ module Hecks
|
|
|
23
23
|
# Shared by CommandInterpreter and EntityInterpreter — an entity command
|
|
24
24
|
# can declare a reference-typed attribute the same way an aggregate
|
|
25
25
|
# command can, even though nothing in the real corpus does yet.
|
|
26
|
+
#
|
|
27
|
+
# @param domain [String, Symbol] the domain the referenced aggregate is
|
|
28
|
+
# resolved in
|
|
29
|
+
# @param command [Class] the command or port-operation class (`Bluebook::Command`
|
|
30
|
+
# or `PortOperation` subclass) whose reference-typed attributes are checked
|
|
31
|
+
# @param args [Hash{Symbol => Object}] the offered, already-coerced argument values
|
|
32
|
+
# @return [void]
|
|
33
|
+
# @raise [Runtime::NotFound] if a reference-typed argument names an identity that
|
|
34
|
+
# does not exist in its target aggregate's repository
|
|
26
35
|
def resolve_references(domain, command, args)
|
|
27
36
|
command.attributes.each do |attribute|
|
|
28
37
|
next unless attribute.reference?
|
|
@@ -41,6 +50,20 @@ module Hecks
|
|
|
41
50
|
# Structural references are checked again against the settled state.
|
|
42
51
|
# This is what makes a `has_many` declared on an aggregate honest even
|
|
43
52
|
# when a command supplies its list through an ordinary typed argument.
|
|
53
|
+
#
|
|
54
|
+
# @param domain [String, Symbol] the domain the referenced aggregate is
|
|
55
|
+
# resolved in
|
|
56
|
+
# @param construct [Bluebook::Aggregate, Bluebook::Entity] the construct
|
|
57
|
+
# whose reference-typed attributes are checked against `state`
|
|
58
|
+
# @param state [Hash{Symbol => Object}] the settled, post-mutation state to
|
|
59
|
+
# check every reference and relationship cardinality against
|
|
60
|
+
# @return [void]
|
|
61
|
+
# @raise [Runtime::TypeMismatch] if a required `has_one`/`belongs_to`
|
|
62
|
+
# relationship holds nil
|
|
63
|
+
# @raise [Runtime::NotFound] if a reference-typed field names an identity
|
|
64
|
+
# that does not exist in its target aggregate's repository
|
|
65
|
+
# @raise [Runtime::Unauthorized] if a referenced record belongs to a
|
|
66
|
+
# different tenant than `construct`'s own record
|
|
44
67
|
def resolve_state_references(domain, construct, state)
|
|
45
68
|
own_tenant_field = tenant_field_for(construct)
|
|
46
69
|
|
|
@@ -73,6 +96,14 @@ module Hecks
|
|
|
73
96
|
# checking only command arguments would let a required relationship be
|
|
74
97
|
# persisted as nil. `has_many` admits zero members, so its empty list is
|
|
75
98
|
# already a valid cardinality and needs no presence refusal.
|
|
99
|
+
#
|
|
100
|
+
# @param construct [Bluebook::Aggregate, Bluebook::Entity] the construct
|
|
101
|
+
# `attribute` is declared on, named in a refusal
|
|
102
|
+
# @param attribute [Bluebook::Attribute] the reference-typed attribute to check
|
|
103
|
+
# @param held [Object, nil] the field's settled value
|
|
104
|
+
# @return [void]
|
|
105
|
+
# @raise [Runtime::TypeMismatch] if `attribute` declares a required (non-optional,
|
|
106
|
+
# non-list) `has_one`/`belongs_to` relationship and `held` is nil
|
|
76
107
|
def validate_relationship_cardinality(construct, attribute, held)
|
|
77
108
|
return if attribute.relationship.nil? || attribute.list?
|
|
78
109
|
return unless held.nil? && !attribute.optional?
|
|
@@ -84,15 +115,31 @@ module Hecks
|
|
|
84
115
|
end
|
|
85
116
|
|
|
86
117
|
# The reference resolves itself — through the chapter's own IR, so the
|
|
87
|
-
# bluebook's declared heads are the index
|
|
88
|
-
# name out of "Reference<Customer>" and
|
|
89
|
-
# `registry.bluebook(domain).aggregates` for it
|
|
90
|
-
# target through Ruby's constant tree, a class thrown away for its
|
|
91
|
-
# the moment it was found.
|
|
118
|
+
# bluebook's declared heads are the index, rather than regexing the
|
|
119
|
+
# target's name out of "Reference<Customer>" and searching
|
|
120
|
+
# `registry.bluebook(domain).aggregates` for it, or reaching the
|
|
121
|
+
# target through Ruby's constant tree, a class thrown away for its
|
|
122
|
+
# `.ir` the moment it was found.
|
|
123
|
+
#
|
|
124
|
+
# @param attribute [Bluebook::Attribute] the reference-typed attribute to resolve
|
|
125
|
+
# @return [Bluebook::Aggregate, nil] the target aggregate the reference names,
|
|
126
|
+
# or nil if it cannot be resolved
|
|
127
|
+
# @raise [Bluebook::DSL::Malformed] if the reference does not know which
|
|
128
|
+
# aggregate declares it
|
|
92
129
|
def referenced_aggregate(attribute)
|
|
93
130
|
attribute.type.resolve
|
|
94
131
|
end
|
|
95
132
|
|
|
133
|
+
# Refuses a reference-typed value whose target identity does not exist.
|
|
134
|
+
#
|
|
135
|
+
# @param domain [String, Symbol] the domain `target` is resolved in
|
|
136
|
+
# @param target [Bluebook::Aggregate] the referenced aggregate
|
|
137
|
+
# @param held [Object, Array<Object>] the offered value: a single reference,
|
|
138
|
+
# or, when `list:` is true, an Array of them
|
|
139
|
+
# @param list [Boolean] whether `held` is list-shaped
|
|
140
|
+
# @return [void]
|
|
141
|
+
# @raise [Runtime::NotFound] if any referenced identity does not exist in
|
|
142
|
+
# `target`'s repository
|
|
96
143
|
def validate_reference_values(domain, target, held, list:)
|
|
97
144
|
values = list ? Array(held) : [held]
|
|
98
145
|
values.each do |value|
|
|
@@ -132,6 +179,22 @@ module Hecks
|
|
|
132
179
|
# — one change, both interpreters covered, no new vocabulary step
|
|
133
180
|
# to keep in sync with `Vocabulary::AggregateDispatchOrder`/
|
|
134
181
|
# `EntityDispatchOrder`.
|
|
182
|
+
#
|
|
183
|
+
# @param domain [String, Symbol] the domain `target` is resolved in
|
|
184
|
+
# @param construct [Bluebook::Aggregate, Bluebook::Entity] the construct
|
|
185
|
+
# `attribute` is declared on, named in a refusal
|
|
186
|
+
# @param attribute [Bluebook::Attribute] the reference-typed attribute being
|
|
187
|
+
# checked
|
|
188
|
+
# @param target [Bluebook::Aggregate] the referenced aggregate
|
|
189
|
+
# @param held [Object, Array<Object>] the offered value: a single reference,
|
|
190
|
+
# or, when `attribute.list?` is true, an Array of them
|
|
191
|
+
# @param state [Hash{Symbol => Object}] `construct`'s own settled state, read
|
|
192
|
+
# for its own tenant field's value
|
|
193
|
+
# @param own_tenant_field [Symbol, nil] `construct`'s own declared tenant field;
|
|
194
|
+
# a no-op if nil or absent from `state`
|
|
195
|
+
# @return [void]
|
|
196
|
+
# @raise [Runtime::Unauthorized] if a referenced record's own tenant field
|
|
197
|
+
# disagrees with `construct`'s
|
|
135
198
|
def enforce_tenant_boundary(domain, construct, attribute, target, held, state, own_tenant_field)
|
|
136
199
|
return unless own_tenant_field && state.key?(own_tenant_field)
|
|
137
200
|
|
|
@@ -172,11 +235,19 @@ module Hecks
|
|
|
172
235
|
# (`Entity.queries` is always empty in the real corpus), so this
|
|
173
236
|
# answers `nil` for every entity without needing to special-case
|
|
174
237
|
# one.
|
|
238
|
+
#
|
|
239
|
+
# @param construct [Bluebook::Aggregate, Bluebook::Entity] the construct
|
|
240
|
+
# whose declared queries are searched
|
|
241
|
+
# @return [Symbol, nil] the tenant-scoping field name, or nil if `construct`
|
|
242
|
+
# declares no query with `authorize policy, tenant: :field`
|
|
175
243
|
def tenant_field_for(construct)
|
|
176
244
|
authorization = construct.queries.filter_map(&:authorization).find(&:tenant)
|
|
177
245
|
authorization&.tenant&.to_sym
|
|
178
246
|
end
|
|
179
247
|
|
|
248
|
+
# Renders a reference-typed value as the plain string key its target
|
|
249
|
+
# record is looked up by.
|
|
250
|
+
#
|
|
180
251
|
# `value` is the referenced record's own id, exactly as `Identity.of`
|
|
181
252
|
# would build it for that record — a bare scalar for a single-field
|
|
182
253
|
# identity (the overwhelming common case; Banking's own plain
|
|
@@ -184,18 +255,23 @@ module Hecks
|
|
|
184
255
|
# materialize_unwrapped` is a no-op passthrough here), or a
|
|
185
256
|
# `Naming.identity`-joined string for a compound one
|
|
186
257
|
# (`belongs_to Translation, as: :translation_ref` — Translation's
|
|
187
|
-
# own `identified_by :domain, :from, :to`, three fields).
|
|
188
|
-
#
|
|
189
|
-
# Value hit Ruby's default `Object#to_s` (a raw,
|
|
190
|
-
# memory address) instead of joining the record's
|
|
191
|
-
# live via bin/fuzz on the self-hosted "translation"
|
|
192
|
-
# (replay_is_deterministic), the same class of gap `Identity.from`
|
|
258
|
+
# own `identified_by :domain, :from, :to`, three fields). Without
|
|
259
|
+
# `materialize_unwrapped`, plain `value.to_s` on that compound case's
|
|
260
|
+
# own coerced Value would hit Ruby's default `Object#to_s` (a raw,
|
|
261
|
+
# run-to-run-random memory address) instead of joining the record's
|
|
262
|
+
# real id — found live via bin/fuzz on the self-hosted "translation"
|
|
263
|
+
# domain (replay_is_deterministic), the same class of gap `Identity.from`
|
|
193
264
|
# already had for a compound `identified_by`'s own bare (undotted)
|
|
194
265
|
# attribute paths. `materialize_unwrapped` recurses a multi-
|
|
195
266
|
# attribute value object to a plain Hash keyed by attribute name,
|
|
196
267
|
# in declaration order — `Naming.identity` on `.values` reproduces
|
|
197
268
|
# the identical join `Identity.of` itself would produce for the
|
|
198
269
|
# same fields.
|
|
270
|
+
#
|
|
271
|
+
# @param value [Object] the offered reference value: a `Runtime::Value`, a
|
|
272
|
+
# scalar, or anything `Value.materialize_unwrapped` can open
|
|
273
|
+
# @return [String] the key to look the referenced record up by; empty when
|
|
274
|
+
# `value` names no id
|
|
199
275
|
def reference_key(value)
|
|
200
276
|
unwrapped = Value.materialize_unwrapped(value)
|
|
201
277
|
return Naming.identity(unwrapped.values).to_s if unwrapped.is_a?(Hash)
|
|
@@ -203,8 +279,8 @@ module Hecks
|
|
|
203
279
|
unwrapped.to_s
|
|
204
280
|
end
|
|
205
281
|
|
|
206
|
-
#
|
|
207
|
-
# `given`/`ensures` — `disputed_by.status`, say, `CardPayment
|
|
282
|
+
# Hydrates a command argument's own related record, reachable by name
|
|
283
|
+
# from `given`/`ensures` — `disputed_by.status`, say, `CardPayment
|
|
208
284
|
# .Dispute`'s own fresh `Reference<Customer>` argument — without
|
|
209
285
|
# teaching the pure expression evaluator anything about
|
|
210
286
|
# repositories. The lookup happens here, once, before evaluation;
|
|
@@ -213,23 +289,21 @@ module Hecks
|
|
|
213
289
|
#
|
|
214
290
|
# `owner` narrowed to `command` only (S12, ADR 0025 — "rules
|
|
215
291
|
# confined to their own aggregate boundary"): dereferencing the
|
|
216
|
-
# declaring aggregate/entity's own stored `reference_to`
|
|
217
|
-
#
|
|
218
|
-
#
|
|
219
|
-
# `
|
|
220
|
-
#
|
|
221
|
-
#
|
|
222
|
-
#
|
|
223
|
-
#
|
|
224
|
-
#
|
|
225
|
-
#
|
|
226
|
-
#
|
|
227
|
-
#
|
|
228
|
-
# against an already-persisted reference. `enforce_givens`/
|
|
292
|
+
# declaring aggregate/entity's own stored `reference_to` is not part
|
|
293
|
+
# of this method's job — a cross-aggregate fact a rule needs has to
|
|
294
|
+
# be a `projects`-maintained local field (`AggregateBuilder#
|
|
295
|
+
# projects_impl`'s own comment), already present in `subject`'s own
|
|
296
|
+
# state, no hydration needed, rather than a live query against
|
|
297
|
+
# another aggregate's own repository on every `given`/`ensures`/
|
|
298
|
+
# `invariant` run. A reference-typed command argument stays in
|
|
299
|
+
# bounds, though — the ADR's own boundary list names "its command
|
|
300
|
+
# arguments" as readable, and nothing is stored yet for a fresh
|
|
301
|
+
# argument to project from; resolving it once here, synchronous
|
|
302
|
+
# with this command's own admission, is a different shape from a
|
|
303
|
+
# live query against an already-persisted reference. `enforce_givens`/
|
|
229
304
|
# `enforce_ensures` are this method's only two remaining callers,
|
|
230
305
|
# both passing `command`/`args`, never a `subject`'s own
|
|
231
|
-
# aggregate — verified
|
|
232
|
-
# assumed.
|
|
306
|
+
# aggregate — verified by grep, not assumed.
|
|
233
307
|
#
|
|
234
308
|
# Recurses into what it finds, so a chain deeper than one hop
|
|
235
309
|
# still resolves in one pass. Depth-bounded rather than cycle-
|
|
@@ -239,6 +313,22 @@ module Hecks
|
|
|
239
313
|
DEREFERENCE_DEPTH = 4
|
|
240
314
|
private_constant :DEREFERENCE_DEPTH
|
|
241
315
|
|
|
316
|
+
# Hydrates `owner`'s own reference-typed attributes into the records they name.
|
|
317
|
+
#
|
|
318
|
+
# @param domain [String, Symbol] the domain a referenced aggregate is
|
|
319
|
+
# resolved in
|
|
320
|
+
# @param owner [Class, Bluebook::Aggregate, Bluebook::Entity, nil] the
|
|
321
|
+
# construct whose reference-typed attributes are dereferenced; nil (or a
|
|
322
|
+
# depth of zero) short-circuits to an empty Hash
|
|
323
|
+
# @param source [Hash{Symbol => Object}] the offered payload to read each
|
|
324
|
+
# reference-typed attribute's raw id from
|
|
325
|
+
# @param depth [Integer] how many more hops to recurse into a resolved
|
|
326
|
+
# reference's own reference-typed fields
|
|
327
|
+
# @return [Hash{Symbol => Object}] one entry per resolved reference-typed
|
|
328
|
+
# attribute, keyed by the attribute name with a trailing `_id` stripped,
|
|
329
|
+
# valued at the referenced record's state merged with its own dereferenced
|
|
330
|
+
# references; an attribute with no id, an unresolvable target, or no
|
|
331
|
+
# matching record contributes nothing
|
|
242
332
|
def dereference(domain, owner, source, depth: DEREFERENCE_DEPTH)
|
|
243
333
|
return {} if depth <= 0 || owner.nil?
|
|
244
334
|
|
|
@@ -17,12 +17,28 @@ module Hecks
|
|
|
17
17
|
:unresolved_dependencies,
|
|
18
18
|
keyword_init: true
|
|
19
19
|
) do
|
|
20
|
+
# Reports whether every owner field this command could touch is a known,
|
|
21
|
+
# deterministic write.
|
|
22
|
+
#
|
|
23
|
+
# @return [Boolean] `complete_state`
|
|
20
24
|
def complete_state? = complete_state
|
|
25
|
+
|
|
26
|
+
# Reports whether the command needs no prior state at all (implies `complete_state?`).
|
|
27
|
+
#
|
|
28
|
+
# @return [Boolean] `state_independent`
|
|
21
29
|
def state_independent? = state_independent
|
|
22
30
|
|
|
31
|
+
# Chooses the dispatch strategy this plan's proof and the adapter's own
|
|
32
|
+
# capabilities together allow.
|
|
33
|
+
#
|
|
23
34
|
# Capability negotiation is correctness-first: an optimization is
|
|
24
35
|
# selected only when both the semantic proof and adapter capability
|
|
25
36
|
# are present. This is planning data only; no runtime path calls it yet.
|
|
37
|
+
#
|
|
38
|
+
# @param capabilities [Array<String, Symbol>] the repository's declared capabilities
|
|
39
|
+
# @return [Symbol] `DependencyPlanning::ATOMIC_PUT` when the plan is complete,
|
|
40
|
+
# state-independent, and the adapter declares that capability;
|
|
41
|
+
# `DependencyPlanning::TRANSACTIONAL_FALLBACK` otherwise
|
|
26
42
|
def strategy_for(capabilities: [])
|
|
27
43
|
return TRANSACTIONAL_FALLBACK unless complete_state? && state_independent?
|
|
28
44
|
return TRANSACTIONAL_FALLBACK unless capabilities.map(&:to_sym).include?(ATOMIC_PUT)
|
|
@@ -38,13 +54,27 @@ module Hecks
|
|
|
38
54
|
module ExpressionReads
|
|
39
55
|
module_function
|
|
40
56
|
|
|
57
|
+
# Finds every dotted path a canonical expression reads, without evaluating it.
|
|
58
|
+
#
|
|
41
59
|
# Read the same parsed canonical-expression nodes the evaluator uses.
|
|
42
60
|
# A generic Struct walk keeps this additive when the expression grammar
|
|
43
61
|
# gains a composed node; only Lookup nodes carry domain dependencies.
|
|
62
|
+
#
|
|
63
|
+
# @param canonical [String] the canonical expression text, such as a rule's `canonical`
|
|
64
|
+
# @return [Array<String>] the dotted paths the expression reads
|
|
44
65
|
def paths(canonical)
|
|
45
66
|
collect(Bluebook::Expression::Evaluator.parse(canonical), Set.new)
|
|
46
67
|
end
|
|
47
68
|
|
|
69
|
+
# Walks one parsed expression node, collecting the dotted paths it reads.
|
|
70
|
+
#
|
|
71
|
+
# @param node [Object] a node from `Bluebook::Expression::Evaluator.parse` — a
|
|
72
|
+
# `Bluebook::Expression::Resolver::Lookup`, a `Bluebook::Expression::Resolver::
|
|
73
|
+
# BlockPredicate`, a `Struct` composed of further nodes, an `Array` of them, or any
|
|
74
|
+
# other value (a literal), which contributes no paths
|
|
75
|
+
# @param bound_names [Set<String>] names locally bound by an enclosing block
|
|
76
|
+
# predicate's own parameter, excluded from the result rather than reported as reads
|
|
77
|
+
# @return [Array<String>] the dotted paths read under `node`, excluding `bound_names`
|
|
48
78
|
def collect(node, bound_names)
|
|
49
79
|
case node
|
|
50
80
|
when Bluebook::Expression::Resolver::Lookup
|
|
@@ -87,8 +117,20 @@ module Hecks
|
|
|
87
117
|
# unresolved regardless of correctness. Defaults to `aggregate`
|
|
88
118
|
# (a no-op) for the plain-aggregate case — `CommandInterpreter`'s
|
|
89
119
|
# own call site never needed to change.
|
|
120
|
+
#
|
|
121
|
+
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the construct the command
|
|
122
|
+
# is dispatched against; an entity for an entity-owned command
|
|
123
|
+
# @param command [Bluebook::Command] the command to analyze
|
|
124
|
+
# @param root_aggregate [Bluebook::Aggregate] the owning aggregate a `parent.*` read
|
|
125
|
+
# resolves against; defaults to `aggregate` for a plain-aggregate command
|
|
126
|
+
# @return [Hecks::Runtime::DependencyPlanning::Plan] the derived, frozen plan
|
|
90
127
|
def self.call(aggregate:, command:, root_aggregate: aggregate) = new(aggregate, command, root_aggregate).call
|
|
91
128
|
|
|
129
|
+
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the construct the command
|
|
130
|
+
# is dispatched against
|
|
131
|
+
# @param command [Bluebook::Command] the command to analyze
|
|
132
|
+
# @param root_aggregate [Bluebook::Aggregate] the owning aggregate `parent.*` reads
|
|
133
|
+
# resolve against; defaults to `aggregate`
|
|
92
134
|
def initialize(aggregate, command, root_aggregate = aggregate)
|
|
93
135
|
@aggregate = aggregate
|
|
94
136
|
@command = command
|
|
@@ -126,6 +168,9 @@ module Hecks
|
|
|
126
168
|
@unresolved = Set.new
|
|
127
169
|
end
|
|
128
170
|
|
|
171
|
+
# Runs the analysis and derives the command's dependency plan.
|
|
172
|
+
#
|
|
173
|
+
# @return [Hecks::Runtime::DependencyPlanning::Plan] the derived, frozen plan
|
|
129
174
|
def call
|
|
130
175
|
analyze_initial_state
|
|
131
176
|
analyze_mutations
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
require_relative "errors"
|
|
2
|
-
require_relative "../deprecation"
|
|
3
2
|
require_relative "refusal_wording"
|
|
4
3
|
require_relative "caller"
|
|
5
4
|
require_relative "invocation"
|
|
@@ -121,22 +120,21 @@ module Hecks
|
|
|
121
120
|
# Runs one command, entity command or port operation, then every policy and saga reaction
|
|
122
121
|
# its events are owed.
|
|
123
122
|
#
|
|
124
|
-
#
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
123
|
+
# The receiver in `to:`, the facts in `with:`, and nothing else.
|
|
124
|
+
# Loose keyword facts — `dispatch(verb, amount: 5)`, one bag holding
|
|
125
|
+
# both the route and the payload — were deprecated in 1.3.x and are
|
|
126
|
+
# gone: Ruby now refuses them itself, by name ("unknown keyword:
|
|
127
|
+
# :amount"). Code holding a bag of data rather than written keywords
|
|
128
|
+
# calls `dispatch_flat` below; that door is not going anywhere.
|
|
129
129
|
#
|
|
130
130
|
# @param verb [String] the fully qualified verb: `"Domain::Aggregate.Command"`,
|
|
131
131
|
# `"Domain::Aggregate.Entity.Command"` or `"Domain::Aggregate.Port.Operation"`
|
|
132
132
|
# @param to [String, Hash, nil] the receiver: an aggregate identity, or an entity route
|
|
133
133
|
# Hash with `:aggregate` and one of `:entity`/`:entities`; nil when the facts carry
|
|
134
134
|
# the identity themselves
|
|
135
|
-
# @param with [Hash, nil] the command's facts, keyed by argument name (String or Symbol)
|
|
136
|
-
# may not be combined with loose keyword facts
|
|
135
|
+
# @param with [Hash, nil] the command's facts, keyed by argument name (String or Symbol)
|
|
137
136
|
# @param saga_correlation [Hash, nil] correlation head => value, stamped on every
|
|
138
137
|
# emitted event when a saga leg causes this dispatch; nil otherwise
|
|
139
|
-
# @param legacy_args [Hash{Symbol => Object}] deprecated loose keyword facts
|
|
140
138
|
# @return [Runtime::Dispatcher::Result] the verb, settled instance (nil for a port
|
|
141
139
|
# operation), emitted events, execution plan and persistence outcome
|
|
142
140
|
# @raise [Runtime::UnknownVerb] if the verb is not fully qualified, or names a domain,
|
|
@@ -145,39 +143,20 @@ module Hecks
|
|
|
145
143
|
# the call (`GivenNotMet`, `TypeMismatch`, `Unauthorized`, `NotFound`, …)
|
|
146
144
|
# @raise [Runtime::StaleWrite] if concurrent writers beat this one through every retry
|
|
147
145
|
# @raise [Runtime::WiringError] if the aggregate's repository cannot be resolved
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def dispatch(verb, to: nil, with: nil, saga_correlation: nil, **legacy_args)
|
|
151
|
-
Dispatcher.deprecate_loose_facts(legacy_args)
|
|
152
|
-
dispatch_invocation(verb, to: to, with: with, saga_correlation: saga_correlation, legacy_args: legacy_args)
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
LEGACY_ARGS_REMOVAL = "1.5.0".freeze
|
|
156
|
-
LEGACY_ARGS_WARNING =
|
|
157
|
-
"passing command facts to dispatch as loose keyword arguments is deprecated and will be removed in " \
|
|
158
|
-
"hecks #{LEGACY_ARGS_REMOVAL} — pass them as `with: { ... }`, with the receiver identity in `to:` " \
|
|
159
|
-
"(bin/codemod_legacy_dispatch_args rewrites existing callers)".freeze
|
|
160
|
-
|
|
161
|
-
# Warns, once per call site, that a dispatch passed its facts as loose keyword arguments.
|
|
162
|
-
#
|
|
163
|
-
# @param legacy_args [Hash] the loose keyword facts a dispatch received; empty means
|
|
164
|
-
# nothing deprecated was used, and nothing is warned
|
|
165
|
-
# @return [nil] always, whether or not a warning was issued
|
|
166
|
-
# @raise [Deprecation::Error] if `legacy_args` is not empty where
|
|
167
|
-
# `Deprecation.raise_on!(:legacy_dispatch_args)` applies
|
|
168
|
-
def self.deprecate_loose_facts(legacy_args)
|
|
169
|
-
Deprecation.call(:legacy_dispatch_args, LEGACY_ARGS_WARNING) unless legacy_args.empty?
|
|
146
|
+
def dispatch(verb, to: nil, with: nil, saga_correlation: nil)
|
|
147
|
+
dispatch_invocation(verb, to: to, with: with, saga_correlation: saga_correlation, flat: {})
|
|
170
148
|
end
|
|
171
149
|
|
|
172
150
|
# Dispatches a verb whose receiver and facts arrive together in one flat Hash.
|
|
173
151
|
#
|
|
174
|
-
# **The flat-facts wire form** — one Hash, not keywords
|
|
175
|
-
#
|
|
176
|
-
#
|
|
177
|
-
#
|
|
178
|
-
#
|
|
152
|
+
# **The flat-facts wire form** — one Hash, not keywords: the shape
|
|
153
|
+
# `spec/corpus/*.json` steps, the Rust kernel's `cli.rs` contract, a
|
|
154
|
+
# reaction without a `with:` projection, and the self-hosted
|
|
155
|
+
# meta-domain all carry, where the receiver's identity is one of the
|
|
156
|
+
# keys because that is how the wire spells it. Routes exactly as the
|
|
157
|
+
# removed keyword door did: a Symbol `:to`, `:with` or
|
|
179
158
|
# `:saga_correlation` key is lifted out as that keyword, everything
|
|
180
|
-
# else is a fact (so a String "to" key stays a fact).
|
|
159
|
+
# else is a fact (so a String "to" key stays a fact, as it did).
|
|
181
160
|
# Framework code that replays data calls this; application code
|
|
182
161
|
# calls `dispatch(verb, to:, with:)`.
|
|
183
162
|
#
|
|
@@ -196,10 +175,10 @@ module Hecks
|
|
|
196
175
|
to = facts.delete(:to)
|
|
197
176
|
with = facts.delete(:with)
|
|
198
177
|
saga_correlation = facts.delete(:saga_correlation)
|
|
199
|
-
dispatch_invocation(verb, to: to, with: with, saga_correlation: saga_correlation,
|
|
178
|
+
dispatch_invocation(verb, to: to, with: with, saga_correlation: saga_correlation, flat: facts)
|
|
200
179
|
end
|
|
201
180
|
|
|
202
|
-
def dispatch_invocation(verb, to:, with:, saga_correlation:,
|
|
181
|
+
def dispatch_invocation(verb, to:, with:, saga_correlation:, flat:)
|
|
203
182
|
domain, aggregate_name, command_name = parse(verb)
|
|
204
183
|
aggregate = resolve_aggregate(domain, aggregate_name, verb)
|
|
205
184
|
|
|
@@ -222,12 +201,12 @@ module Hecks
|
|
|
222
201
|
operation = port.operation(sub) ||
|
|
223
202
|
raise(UnknownVerb, RefusalWording.render_site("UnknownVerb", "port_no_operation",
|
|
224
203
|
port: head, operation: sub))
|
|
225
|
-
invocation = Invocation.from_call(verb, to: to, with: with,
|
|
204
|
+
invocation = Invocation.from_call(verb, to: to, with: with, flat: flat,
|
|
226
205
|
receiver: :port, aggregate: aggregate) { operation }
|
|
227
206
|
[nil, @port_ops.call(domain, aggregate, operation, invocation), nil, nil, :enqueue]
|
|
228
207
|
else
|
|
229
208
|
resolution = nil
|
|
230
|
-
invocation = Invocation.from_call(verb, to: to, with: with,
|
|
209
|
+
invocation = Invocation.from_call(verb, to: to, with: with, flat: flat,
|
|
231
210
|
receiver: :entity, entity_depth: command_name.count(".")) do
|
|
232
211
|
(resolution = EntityInterpreter::Resolution.of(aggregate, command_name)).command
|
|
233
212
|
end
|
|
@@ -235,7 +214,7 @@ module Hecks
|
|
|
235
214
|
end
|
|
236
215
|
else
|
|
237
216
|
command = command_of(aggregate, aggregate_name, command_name)
|
|
238
|
-
invocation = Invocation.from_call(verb, to: to, with: with,
|
|
217
|
+
invocation = Invocation.from_call(verb, to: to, with: with, flat: flat) { command }
|
|
239
218
|
@commands.call(domain, aggregate, command, invocation, saga_correlation)
|
|
240
219
|
end
|
|
241
220
|
|
|
@@ -328,13 +307,13 @@ module Hecks
|
|
|
328
307
|
# `to:`/`with:` are not keywords of this method — a key named
|
|
329
308
|
# either is an ordinary fact here (BUG#131), so both go in as nil.
|
|
330
309
|
resolution = nil
|
|
331
|
-
invocation = Invocation.from_call(verb, to: nil, with: nil,
|
|
310
|
+
invocation = Invocation.from_call(verb, to: nil, with: nil, flat: args, receiver: :entity) do
|
|
332
311
|
(resolution = EntityInterpreter::Resolution.of(aggregate, command_name)).command
|
|
333
312
|
end
|
|
334
313
|
@entities.call(domain, aggregate, resolution, invocation, dry_run: true)
|
|
335
314
|
else
|
|
336
315
|
command = command_of(aggregate, aggregate_name, command_name)
|
|
337
|
-
invocation = Invocation.from_call(verb, to: nil, with: nil,
|
|
316
|
+
invocation = Invocation.from_call(verb, to: nil, with: nil, flat: args) { command }
|
|
338
317
|
@commands.call(domain, aggregate, command, invocation, dry_run: true)
|
|
339
318
|
end
|
|
340
319
|
|
|
@@ -360,7 +339,9 @@ module Hecks
|
|
|
360
339
|
# @param to [String, Hash, nil] the receiving aggregate's identity; when nil it is read
|
|
361
340
|
# from the facts, by the operation's reference or identity attribute
|
|
362
341
|
# @param with [Hash, nil] the operation's facts, keyed by argument name
|
|
363
|
-
# @param
|
|
342
|
+
# @param flat [Hash] the wire form `dispatch_flat` takes, for the driving adapter
|
|
343
|
+
# holding a decoded webhook rather than written keywords; it is where the operation's
|
|
344
|
+
# own reference attribute is read from and lifted into `to:` when `to:` is nil
|
|
364
345
|
# @return [Array<Runtime::Event>] the events the operation announced: one per declared
|
|
365
346
|
# `emits` for an inbound operation, the one answering or refusing event for an
|
|
366
347
|
# outbound one
|
|
@@ -369,10 +350,7 @@ module Hecks
|
|
|
369
350
|
# is malformed
|
|
370
351
|
# @raise [Runtime::NotFound] if the receiving aggregate record does not exist
|
|
371
352
|
# @raise [Runtime::WiringError] if the aggregate's repository cannot be resolved
|
|
372
|
-
|
|
373
|
-
# `Deprecation.raise_on!(:legacy_dispatch_args)` applies
|
|
374
|
-
def dispatch_port(domain, aggregate_name, port_name, operation_name, to: nil, with: nil, **legacy_args)
|
|
375
|
-
Dispatcher.deprecate_loose_facts(legacy_args)
|
|
353
|
+
def dispatch_port(domain, aggregate_name, port_name, operation_name, to: nil, with: nil, flat: {})
|
|
376
354
|
aggregate = resolve_aggregate(domain, aggregate_name, "#{domain}::#{aggregate_name}.#{port_name}.#{operation_name}")
|
|
377
355
|
port = aggregate.port(port_name) ||
|
|
378
356
|
raise(UnknownVerb, "#{aggregate_name} has no port #{port_name.inspect}")
|
|
@@ -380,7 +358,7 @@ module Hecks
|
|
|
380
358
|
raise(UnknownVerb, "#{port_name} has no operation #{operation_name.inspect}")
|
|
381
359
|
|
|
382
360
|
invocation = Invocation.from_call("#{domain}::#{aggregate_name}.#{port_name}.#{operation_name}",
|
|
383
|
-
to: to, with: with,
|
|
361
|
+
to: to, with: with, flat: flat,
|
|
384
362
|
receiver: :port, aggregate: aggregate) { operation }
|
|
385
363
|
announced = @port_ops.call(domain, aggregate, operation, invocation)
|
|
386
364
|
|