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.
Files changed (275) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +65 -0
  3. data/lib/hecks/adapters/driven/folder.rb +73 -0
  4. data/lib/hecks/adapters/driven/google_authentication.rb +25 -4
  5. data/lib/hecks/adapters/driven/governance_authorization.rb +57 -6
  6. data/lib/hecks/adapters/driven/heki/journal.rb +8 -0
  7. data/lib/hecks/adapters/driven/heki/saga_store.rb +53 -7
  8. data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
  9. data/lib/hecks/adapters/driven/heki.rb +93 -2
  10. data/lib/hecks/adapters/driven/identity_registry.rb +10 -0
  11. data/lib/hecks/adapters/driven/in_memory_ordering.rb +22 -0
  12. data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
  13. data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
  14. data/lib/hecks/adapters/driven/lambda/client.rb +35 -7
  15. data/lib/hecks/adapters/driven/lambda.rb +43 -2
  16. data/lib/hecks/adapters/driven/local_storage.rb +67 -1
  17. data/lib/hecks/adapters/driven/memory.rb +13 -13
  18. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +20 -0
  19. data/lib/hecks/adapters/driven/prism.rb +46 -7
  20. data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
  21. data/lib/hecks/adapters/driven/system_clock.rb +3 -0
  22. data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
  23. data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
  24. data/lib/hecks/adapters/driven.rb +2 -0
  25. data/lib/hecks/adapters/driving/github_webhook.rb +14 -1
  26. data/lib/hecks/behaviors/dsl.rb +58 -0
  27. data/lib/hecks/behaviors/expectations.rb +168 -7
  28. data/lib/hecks/behaviors/ir.rb +11 -0
  29. data/lib/hecks/behaviors/rspec.rb +8 -0
  30. data/lib/hecks/behaviors/runner.rb +19 -0
  31. data/lib/hecks/behaviors.rb +8 -0
  32. data/lib/hecks/bluebook/aggregate.rb +30 -0
  33. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +7 -0
  34. data/lib/hecks/bluebook/assembly/build.rb +25 -4
  35. data/lib/hecks/bluebook/assembly/contract.rb +66 -14
  36. data/lib/hecks/bluebook/assembly/contracts.rb +24 -19
  37. data/lib/hecks/bluebook/assembly/marks.rb +143 -14
  38. data/lib/hecks/bluebook/assembly/specializer.rb +19 -0
  39. data/lib/hecks/bluebook/assembly.rb +25 -7
  40. data/lib/hecks/bluebook/attribute.rb +17 -3
  41. data/lib/hecks/bluebook/behaviour/aggregate.rb +32 -0
  42. data/lib/hecks/bluebook/behaviour/attribute.rb +13 -0
  43. data/lib/hecks/bluebook/behaviour/chapter.rb +55 -2
  44. data/lib/hecks/bluebook/behaviour/command.rb +33 -3
  45. data/lib/hecks/bluebook/behaviour/domain_port.rb +20 -0
  46. data/lib/hecks/bluebook/behaviour/entity.rb +12 -0
  47. data/lib/hecks/bluebook/behaviour/hexagon.rb +26 -0
  48. data/lib/hecks/bluebook/behaviour/lifecycle.rb +23 -2
  49. data/lib/hecks/bluebook/behaviour/policy.rb +34 -9
  50. data/lib/hecks/bluebook/behaviour/process_manager.rb +32 -1
  51. data/lib/hecks/bluebook/behaviour/query.rb +5 -0
  52. data/lib/hecks/bluebook/behaviour/read_model.rb +21 -0
  53. data/lib/hecks/bluebook/behaviour/traits.rb +36 -0
  54. data/lib/hecks/bluebook/behaviour/value_object.rb +17 -5
  55. data/lib/hecks/bluebook/chapter.rb +23 -0
  56. data/lib/hecks/bluebook/command.rb +53 -8
  57. data/lib/hecks/bluebook/domain_port.rb +25 -0
  58. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +196 -37
  59. data/lib/hecks/bluebook/dsl/attribute_collector.rb +79 -42
  60. data/lib/hecks/bluebook/dsl/binding_proxy.rb +59 -0
  61. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +385 -53
  62. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +135 -21
  63. data/lib/hecks/bluebook/dsl/command_builder.rb +189 -27
  64. data/lib/hecks/bluebook/dsl/entity_builder.rb +139 -9
  65. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +41 -0
  66. data/lib/hecks/bluebook/dsl/identity_declaration.rb +23 -6
  67. data/lib/hecks/bluebook/dsl/policy_builder.rb +60 -19
  68. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +79 -15
  69. data/lib/hecks/bluebook/dsl/query_builder.rb +33 -4
  70. data/lib/hecks/bluebook/dsl/read_model_builder.rb +102 -27
  71. data/lib/hecks/bluebook/dsl/rule_reference.rb +60 -8
  72. data/lib/hecks/bluebook/dsl/translation_builder.rb +146 -40
  73. data/lib/hecks/bluebook/dsl/value_object_builder.rb +55 -7
  74. data/lib/hecks/bluebook/entity.rb +29 -0
  75. data/lib/hecks/bluebook/expression/ast_json.rb +115 -23
  76. data/lib/hecks/bluebook/expression/ast_reader.rb +29 -0
  77. data/lib/hecks/bluebook/expression/canonical_form.rb +48 -9
  78. data/lib/hecks/bluebook/expression/evaluator.rb +207 -29
  79. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +36 -0
  80. data/lib/hecks/bluebook/expression/resolver.rb +320 -77
  81. data/lib/hecks/bluebook/hexagon.rb +34 -0
  82. data/lib/hecks/bluebook/lifecycle.rb +11 -0
  83. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -0
  84. data/lib/hecks/bluebook/meta_validator/judge.rb +30 -26
  85. data/lib/hecks/bluebook/meta_validator/plan.rb +43 -8
  86. data/lib/hecks/bluebook/meta_validator/port_judge.rb +1 -0
  87. data/lib/hecks/bluebook/meta_validator/readings.rb +157 -7
  88. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -4
  89. data/lib/hecks/bluebook/meta_validator/shapes.rb +141 -0
  90. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +149 -23
  91. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +5 -4
  92. data/lib/hecks/bluebook/meta_validator/world_judge.rb +1 -0
  93. data/lib/hecks/bluebook/meta_validator.rb +180 -84
  94. data/lib/hecks/bluebook/model_check.rb +268 -24
  95. data/lib/hecks/bluebook/pattern_subset.rb +23 -1
  96. data/lib/hecks/bluebook/process_manager.rb +13 -0
  97. data/lib/hecks/bluebook/project_discovery.rb +5 -0
  98. data/lib/hecks/bluebook/project_loader.rb +40 -0
  99. data/lib/hecks/bluebook/project_register.rb +44 -0
  100. data/lib/hecks/bluebook/query.rb +27 -0
  101. data/lib/hecks/bluebook/read_model.rb +21 -1
  102. data/lib/hecks/bluebook/reference.rb +21 -8
  103. data/lib/hecks/bluebook/smoke_test.rb +29 -6
  104. data/lib/hecks/bluebook/synthesizer.rb +34 -0
  105. data/lib/hecks/bluebook/translation.rb +30 -1
  106. data/lib/hecks/bluebook/value_object.rb +23 -5
  107. data/lib/hecks/bluebook.rb +3 -4
  108. data/lib/hecks/codemod.rb +107 -20
  109. data/lib/hecks/construct.rb +15 -1
  110. data/lib/hecks/corpus.rb +146 -25
  111. data/lib/hecks/deploy/bluebook/deploy.bluebook +105 -0
  112. data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
  113. data/lib/hecks/deploy/oidc.json +5 -0
  114. data/lib/hecks/doc/reference.rb +185 -16
  115. data/lib/hecks/embryonaut_bluebook.rb +32 -9
  116. data/lib/hecks/facade/handle.rb +76 -3
  117. data/lib/hecks/facade/surface/aggregate_door.rb +8 -0
  118. data/lib/hecks/forms/field_shape.rb +3 -0
  119. data/lib/hecks/forms/page.rb +14 -0
  120. data/lib/hecks/forms/port_argument.rb +12 -0
  121. data/lib/hecks/forms/query_form_renderer.rb +63 -0
  122. data/lib/hecks/forms/record_renderer.rb +58 -0
  123. data/lib/hecks/forms/record_table.rb +27 -0
  124. data/lib/hecks/forms/reference_options.rb +24 -0
  125. data/lib/hecks/forms/value_object_shape.rb +10 -0
  126. data/lib/hecks/fqn.rb +58 -0
  127. data/lib/hecks/framework/bluebook/compliance.bluebook +221 -0
  128. data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
  129. data/lib/hecks/framework/oidc.json +15 -0
  130. data/lib/hecks/framework.rb +43 -20
  131. data/lib/hecks/freezer.rb +17 -1
  132. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +159 -24
  133. data/lib/hecks/fuzzing/combination_miner.rb +59 -0
  134. data/lib/hecks/fuzzing/concurrent_dispatch.rb +109 -8
  135. data/lib/hecks/fuzzing/coverage_campaign.rb +56 -13
  136. data/lib/hecks/fuzzing/differential.rb +34 -0
  137. data/lib/hecks/fuzzing/domain_generator.rb +188 -11
  138. data/lib/hecks/fuzzing/era_boundary.rb +45 -15
  139. data/lib/hecks/fuzzing/form_census.rb +86 -0
  140. data/lib/hecks/fuzzing/generated_domain_check.rb +76 -0
  141. data/lib/hecks/fuzzing/invalid_value_generator.rb +39 -0
  142. data/lib/hecks/fuzzing/isolated_boot.rb +79 -22
  143. data/lib/hecks/fuzzing/nondeterministic.rb +13 -1
  144. data/lib/hecks/fuzzing/persistence_parity.rb +95 -3
  145. data/lib/hecks/fuzzing/properties/corrections.rb +25 -0
  146. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +158 -14
  147. data/lib/hecks/fuzzing/properties/guards.rb +44 -0
  148. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +48 -0
  149. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +18 -0
  150. data/lib/hecks/fuzzing/properties/outbox.rb +49 -11
  151. data/lib/hecks/fuzzing/properties/querying.rb +68 -14
  152. data/lib/hecks/fuzzing/properties.rb +24 -15
  153. data/lib/hecks/fuzzing/qa_settings.rb +12 -0
  154. data/lib/hecks/fuzzing/replay.rb +137 -29
  155. data/lib/hecks/fuzzing/rotation_priority.rb +41 -21
  156. data/lib/hecks/fuzzing/rust_gap_manifest.rb +46 -20
  157. data/lib/hecks/fuzzing/self_consistency.rb +189 -40
  158. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +12 -6
  159. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +1 -1
  160. data/lib/hecks/fuzzing/sequence_generator.rb +47 -0
  161. data/lib/hecks/fuzzing/shrinker.rb +114 -2
  162. data/lib/hecks/fuzzing/structural_skips.rb +18 -4
  163. data/lib/hecks/fuzzing/sweep_depth.rb +8 -0
  164. data/lib/hecks/fuzzing/target_capabilities.rb +61 -14
  165. data/lib/hecks/fuzzing/value_generator.rb +98 -10
  166. data/lib/hecks/grammar/evolve.rb +178 -2
  167. data/lib/hecks/grammar.rb +46 -0
  168. data/lib/hecks/ir.rb +38 -7
  169. data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
  170. data/lib/hecks/literal.rb +32 -0
  171. data/lib/hecks/naming.rb +88 -7
  172. data/lib/hecks/ports/access_control.rb +5 -10
  173. data/lib/hecks/ports/authorization.rb +3 -6
  174. data/lib/hecks/ports/identity_assignment.rb +1 -2
  175. data/lib/hecks/ports/identity_resolution.rb +1 -2
  176. data/lib/hecks/ports/key_vault.port +6 -0
  177. data/lib/hecks/ports/key_vault.rb +85 -0
  178. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +6 -0
  179. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +38 -2
  180. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -0
  181. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +40 -0
  182. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +92 -1
  183. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
  184. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +8 -0
  185. data/lib/hecks/ports/query/in_memory.rb +39 -6
  186. data/lib/hecks/ports/query/ordering.rb +15 -0
  187. data/lib/hecks/ports.rb +1 -0
  188. data/lib/hecks/projections/bootstrap_table.rb +43 -8
  189. data/lib/hecks/projections/deploy/fargate.rb +666 -0
  190. data/lib/hecks/projections/deploy/lambda.rb +2423 -0
  191. data/lib/hecks/projections/deploy/shared.rb +624 -0
  192. data/lib/hecks/projections/diagrams.rb +243 -7
  193. data/lib/hecks/projections/glossary/html.rb +88 -0
  194. data/lib/hecks/projections/glossary/markdown.rb +47 -0
  195. data/lib/hecks/projections/glossary/mermaid.rb +48 -0
  196. data/lib/hecks/projections/glossary/sentences.rb +105 -0
  197. data/lib/hecks/projections/glossary.rb +161 -19
  198. data/lib/hecks/projections/model/deviations.rb +44 -0
  199. data/lib/hecks/projections/model.rb +51 -1
  200. data/lib/hecks/projections/oidc.rb +40 -3
  201. data/lib/hecks/projections/parser_table.rb +49 -8
  202. data/lib/hecks/projections/reference.rb +12 -0
  203. data/lib/hecks/projections/rust_vocabulary.rb +219 -16
  204. data/lib/hecks/projections/shape.rb +8 -0
  205. data/lib/hecks/projections/statements.rb +63 -16
  206. data/lib/hecks/projections/vocabulary.rb +17 -0
  207. data/lib/hecks/projections.rb +3 -0
  208. data/lib/hecks/projector/cli_projector.rb +218 -10
  209. data/lib/hecks/projector/docs_projector.rb +145 -19
  210. data/lib/hecks/projector/exporter.rb +65 -11
  211. data/lib/hecks/projector/ir_projector.rb +6 -0
  212. data/lib/hecks/projector/narrate_projector.rb +136 -15
  213. data/lib/hecks/projector/target.rb +47 -10
  214. data/lib/hecks/projector.rb +101 -19
  215. data/lib/hecks/query_ir.rb +47 -0
  216. data/lib/hecks/query_specification/common/null_policy.rb +5 -3
  217. data/lib/hecks/rendering.rb +6 -0
  218. data/lib/hecks/router/namespace_installer.rb +13 -0
  219. data/lib/hecks/router.rb +55 -0
  220. data/lib/hecks/runtime/aggregate_lock.rb +9 -0
  221. data/lib/hecks/runtime/boot_gates.rb +18 -0
  222. data/lib/hecks/runtime/caller.rb +32 -0
  223. data/lib/hecks/runtime/capability_graph.rb +11 -0
  224. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +23 -21
  225. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +14 -15
  226. data/lib/hecks/runtime/command_interpreter.rb +42 -17
  227. data/lib/hecks/runtime/command_rules/admissibility.rb +165 -14
  228. data/lib/hecks/runtime/command_rules/arithmetic.rb +17 -5
  229. data/lib/hecks/runtime/command_rules/references.rb +118 -28
  230. data/lib/hecks/runtime/dependency_planning.rb +45 -0
  231. data/lib/hecks/runtime/dispatcher.rb +28 -50
  232. data/lib/hecks/runtime/entity_element.rb +161 -8
  233. data/lib/hecks/runtime/entity_interpreter.rb +44 -9
  234. data/lib/hecks/runtime/errors.rb +18 -4
  235. data/lib/hecks/runtime/event.rb +10 -5
  236. data/lib/hecks/runtime/identity.rb +71 -3
  237. data/lib/hecks/runtime/instance.rb +67 -7
  238. data/lib/hecks/runtime/interpreting.rb +13 -5
  239. data/lib/hecks/runtime/invocation.rb +118 -36
  240. data/lib/hecks/runtime/loader.rb +94 -8
  241. data/lib/hecks/runtime/outbox.rb +145 -7
  242. data/lib/hecks/runtime/policy_interpreter.rb +22 -9
  243. data/lib/hecks/runtime/port_operation_interpreter.rb +20 -0
  244. data/lib/hecks/runtime/query_interpreter.rb +40 -12
  245. data/lib/hecks/runtime/reaction_invocation.rb +53 -8
  246. data/lib/hecks/runtime/read_model_interpreter.rb +23 -7
  247. data/lib/hecks/runtime/rebuild_sweep.rb +28 -0
  248. data/lib/hecks/runtime/reference_hop.rb +42 -0
  249. data/lib/hecks/runtime/refusal_wording.rb +50 -0
  250. data/lib/hecks/runtime/registry/saga_persistence.rb +11 -0
  251. data/lib/hecks/runtime/registry/verification.rb +119 -4
  252. data/lib/hecks/runtime/registry.rb +157 -4
  253. data/lib/hecks/runtime/remote_dispatcher.rb +92 -6
  254. data/lib/hecks/runtime/routing.rb +27 -2
  255. data/lib/hecks/runtime/saga_interpreter/correlation.rb +10 -12
  256. data/lib/hecks/runtime/saga_interpreter.rb +27 -13
  257. data/lib/hecks/runtime/tenant_check.rb +26 -6
  258. data/lib/hecks/runtime/tenant_scope.rb +18 -0
  259. data/lib/hecks/runtime/value/coercion.rb +255 -33
  260. data/lib/hecks/runtime/value/entity_list_coercion.rb +102 -30
  261. data/lib/hecks/runtime/value.rb +50 -0
  262. data/lib/hecks/runtime.rb +32 -0
  263. data/lib/hecks/storehouse.rb +305 -9
  264. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
  265. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
  266. data/lib/hecks/version.rb +1 -1
  267. data/lib/hecks.rb +79 -1
  268. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -0
  269. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +12 -2
  270. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +29 -5
  271. metadata +14 -5
  272. data/lib/hecks/codemod/legacy_dispatch_args.rb +0 -299
  273. data/lib/hecks/codemod/legacy_dispatch_recorder.rb +0 -186
  274. data/lib/hecks/deprecation.rb +0 -95
  275. 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. This used to regex the target's
88
- # name out of "Reference<Customer>" and then search
89
- # `registry.bluebook(domain).aggregates` for it — and later reached the
90
- # target through Ruby's constant tree, a class thrown away for its `.ir`
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). Before
188
- # this, plain `value.to_s` on that compound case's own coerced
189
- # Value hit Ruby's default `Object#to_s` (a raw, run-to-run-random
190
- # memory address) instead of joining the record's real id — found
191
- # live via bin/fuzz on the self-hosted "translation" domain
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
- # A command argument's own related record, reachable by name from
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` used to
217
- # be the other half of this method's job — a live query against
218
- # another aggregate's own repository, every time a `given`/
219
- # `ensures`/`invariant` ran. That half is gone; a cross-aggregate
220
- # fact a rule needs now has to be a `projects`-maintained local
221
- # field (`AggregateBuilder#projects_impl`'s own comment), already
222
- # present in `subject`'s own state, no hydration needed. A
223
- # reference-typed command argument stays in bounds, though — the
224
- # ADR's own boundary list names "its command arguments" as
225
- # readable, and nothing is stored yet for a fresh argument to
226
- # project from; resolving it once here, synchronous with this
227
- # command's own admission, is a different shape from a live query
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 before this comment was written, not
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
- # Loose keyword facts are deprecated (roadmap I3) — `dispatch(verb,
125
- # amount: 5)` still works, and warns once per call site; pass
126
- # `with: { amount: 5 }` (and the receiver in `to:`) instead. The
127
- # keyword door closes in `LEGACY_ARGS_REMOVAL`. `bin/codemod_legacy_
128
- # dispatch_args` rewrites existing callers.
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
- # @raise [Deprecation::Error] if loose keyword facts are given where
149
- # `Deprecation.raise_on!(:legacy_dispatch_args)` applies
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, and not
175
- # deprecated: the shape `spec/corpus/*.json` steps, the Rust kernel's
176
- # `cli.rs` contract, a reaction without a `with:` projection, and the
177
- # self-hosted meta-domain all carry. Routes exactly as
178
- # `dispatch(verb, **args)` does: a Symbol `:to`, `:with` or
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, legacy_args: facts)
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:, legacy_args:)
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, legacy: legacy_args,
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, legacy: legacy_args,
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, legacy: legacy_args) { command }
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, legacy: args, receiver: :entity) do
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, legacy: args) { command }
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 legacy_args [Hash{Symbol => Object}] deprecated loose keyword facts
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
- # @raise [Deprecation::Error] if loose keyword facts are given where
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, legacy: legacy_args,
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