hecks 1.4.0 → 1.5.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.
Files changed (267) 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.hecksagon +19 -0
  112. data/lib/hecks/doc/reference.rb +185 -16
  113. data/lib/hecks/embryonaut_bluebook.rb +32 -9
  114. data/lib/hecks/facade/handle.rb +76 -3
  115. data/lib/hecks/facade/surface/aggregate_door.rb +8 -0
  116. data/lib/hecks/forms/field_shape.rb +3 -0
  117. data/lib/hecks/forms/page.rb +14 -0
  118. data/lib/hecks/forms/port_argument.rb +12 -0
  119. data/lib/hecks/forms/query_form_renderer.rb +63 -0
  120. data/lib/hecks/forms/record_renderer.rb +58 -0
  121. data/lib/hecks/forms/record_table.rb +27 -0
  122. data/lib/hecks/forms/reference_options.rb +24 -0
  123. data/lib/hecks/forms/value_object_shape.rb +10 -0
  124. data/lib/hecks/fqn.rb +58 -0
  125. data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
  126. data/lib/hecks/framework/oidc.json +15 -0
  127. data/lib/hecks/framework.rb +43 -20
  128. data/lib/hecks/freezer.rb +17 -1
  129. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +159 -24
  130. data/lib/hecks/fuzzing/combination_miner.rb +59 -0
  131. data/lib/hecks/fuzzing/concurrent_dispatch.rb +109 -8
  132. data/lib/hecks/fuzzing/coverage_campaign.rb +56 -13
  133. data/lib/hecks/fuzzing/differential.rb +34 -0
  134. data/lib/hecks/fuzzing/domain_generator.rb +188 -11
  135. data/lib/hecks/fuzzing/era_boundary.rb +45 -15
  136. data/lib/hecks/fuzzing/form_census.rb +86 -0
  137. data/lib/hecks/fuzzing/generated_domain_check.rb +76 -0
  138. data/lib/hecks/fuzzing/invalid_value_generator.rb +39 -0
  139. data/lib/hecks/fuzzing/isolated_boot.rb +79 -22
  140. data/lib/hecks/fuzzing/nondeterministic.rb +13 -1
  141. data/lib/hecks/fuzzing/persistence_parity.rb +95 -3
  142. data/lib/hecks/fuzzing/properties/corrections.rb +25 -0
  143. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +158 -14
  144. data/lib/hecks/fuzzing/properties/guards.rb +44 -0
  145. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +48 -0
  146. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +18 -0
  147. data/lib/hecks/fuzzing/properties/outbox.rb +49 -11
  148. data/lib/hecks/fuzzing/properties/querying.rb +68 -14
  149. data/lib/hecks/fuzzing/properties.rb +24 -15
  150. data/lib/hecks/fuzzing/qa_settings.rb +12 -0
  151. data/lib/hecks/fuzzing/replay.rb +137 -29
  152. data/lib/hecks/fuzzing/rotation_priority.rb +41 -21
  153. data/lib/hecks/fuzzing/rust_gap_manifest.rb +46 -20
  154. data/lib/hecks/fuzzing/self_consistency.rb +189 -40
  155. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +12 -6
  156. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +1 -1
  157. data/lib/hecks/fuzzing/sequence_generator.rb +47 -0
  158. data/lib/hecks/fuzzing/shrinker.rb +114 -2
  159. data/lib/hecks/fuzzing/structural_skips.rb +18 -4
  160. data/lib/hecks/fuzzing/sweep_depth.rb +8 -0
  161. data/lib/hecks/fuzzing/target_capabilities.rb +61 -14
  162. data/lib/hecks/fuzzing/value_generator.rb +98 -10
  163. data/lib/hecks/grammar/evolve.rb +178 -2
  164. data/lib/hecks/grammar.rb +46 -0
  165. data/lib/hecks/ir.rb +38 -7
  166. data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
  167. data/lib/hecks/literal.rb +32 -0
  168. data/lib/hecks/naming.rb +88 -7
  169. data/lib/hecks/ports/access_control.rb +5 -10
  170. data/lib/hecks/ports/authorization.rb +3 -6
  171. data/lib/hecks/ports/identity_assignment.rb +1 -2
  172. data/lib/hecks/ports/identity_resolution.rb +1 -2
  173. data/lib/hecks/ports/key_vault.port +6 -0
  174. data/lib/hecks/ports/key_vault.rb +58 -0
  175. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +6 -0
  176. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +38 -2
  177. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -0
  178. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +40 -0
  179. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +92 -1
  180. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
  181. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +8 -0
  182. data/lib/hecks/ports/query/in_memory.rb +39 -6
  183. data/lib/hecks/ports/query/ordering.rb +15 -0
  184. data/lib/hecks/ports.rb +1 -0
  185. data/lib/hecks/projections/bootstrap_table.rb +43 -8
  186. data/lib/hecks/projections/diagrams.rb +243 -7
  187. data/lib/hecks/projections/glossary/html.rb +88 -0
  188. data/lib/hecks/projections/glossary/markdown.rb +47 -0
  189. data/lib/hecks/projections/glossary/mermaid.rb +48 -0
  190. data/lib/hecks/projections/glossary/sentences.rb +105 -0
  191. data/lib/hecks/projections/glossary.rb +161 -19
  192. data/lib/hecks/projections/model/deviations.rb +44 -0
  193. data/lib/hecks/projections/model.rb +51 -1
  194. data/lib/hecks/projections/oidc.rb +40 -3
  195. data/lib/hecks/projections/parser_table.rb +49 -8
  196. data/lib/hecks/projections/reference.rb +12 -0
  197. data/lib/hecks/projections/rust_vocabulary.rb +219 -16
  198. data/lib/hecks/projections/shape.rb +8 -0
  199. data/lib/hecks/projections/statements.rb +63 -16
  200. data/lib/hecks/projections/vocabulary.rb +17 -0
  201. data/lib/hecks/projector/cli_projector.rb +218 -10
  202. data/lib/hecks/projector/docs_projector.rb +145 -19
  203. data/lib/hecks/projector/exporter.rb +65 -11
  204. data/lib/hecks/projector/ir_projector.rb +6 -0
  205. data/lib/hecks/projector/narrate_projector.rb +136 -15
  206. data/lib/hecks/projector/target.rb +29 -5
  207. data/lib/hecks/projector.rb +74 -6
  208. data/lib/hecks/query_ir.rb +47 -0
  209. data/lib/hecks/query_specification/common/null_policy.rb +5 -3
  210. data/lib/hecks/rendering.rb +6 -0
  211. data/lib/hecks/router/namespace_installer.rb +13 -0
  212. data/lib/hecks/router.rb +55 -0
  213. data/lib/hecks/runtime/aggregate_lock.rb +9 -0
  214. data/lib/hecks/runtime/boot_gates.rb +18 -0
  215. data/lib/hecks/runtime/caller.rb +32 -0
  216. data/lib/hecks/runtime/capability_graph.rb +11 -0
  217. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +23 -21
  218. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +14 -15
  219. data/lib/hecks/runtime/command_interpreter.rb +42 -17
  220. data/lib/hecks/runtime/command_rules/admissibility.rb +165 -14
  221. data/lib/hecks/runtime/command_rules/arithmetic.rb +17 -5
  222. data/lib/hecks/runtime/command_rules/references.rb +118 -28
  223. data/lib/hecks/runtime/dependency_planning.rb +45 -0
  224. data/lib/hecks/runtime/dispatcher.rb +28 -50
  225. data/lib/hecks/runtime/entity_element.rb +161 -8
  226. data/lib/hecks/runtime/entity_interpreter.rb +44 -9
  227. data/lib/hecks/runtime/errors.rb +18 -4
  228. data/lib/hecks/runtime/event.rb +10 -5
  229. data/lib/hecks/runtime/identity.rb +71 -3
  230. data/lib/hecks/runtime/instance.rb +67 -7
  231. data/lib/hecks/runtime/interpreting.rb +13 -5
  232. data/lib/hecks/runtime/invocation.rb +118 -36
  233. data/lib/hecks/runtime/loader.rb +94 -8
  234. data/lib/hecks/runtime/outbox.rb +145 -7
  235. data/lib/hecks/runtime/policy_interpreter.rb +22 -9
  236. data/lib/hecks/runtime/port_operation_interpreter.rb +20 -0
  237. data/lib/hecks/runtime/query_interpreter.rb +40 -12
  238. data/lib/hecks/runtime/reaction_invocation.rb +53 -8
  239. data/lib/hecks/runtime/read_model_interpreter.rb +23 -7
  240. data/lib/hecks/runtime/rebuild_sweep.rb +28 -0
  241. data/lib/hecks/runtime/reference_hop.rb +42 -0
  242. data/lib/hecks/runtime/refusal_wording.rb +50 -0
  243. data/lib/hecks/runtime/registry/saga_persistence.rb +11 -0
  244. data/lib/hecks/runtime/registry/verification.rb +119 -4
  245. data/lib/hecks/runtime/registry.rb +157 -4
  246. data/lib/hecks/runtime/remote_dispatcher.rb +92 -6
  247. data/lib/hecks/runtime/routing.rb +27 -2
  248. data/lib/hecks/runtime/saga_interpreter/correlation.rb +10 -12
  249. data/lib/hecks/runtime/saga_interpreter.rb +27 -13
  250. data/lib/hecks/runtime/tenant_check.rb +26 -6
  251. data/lib/hecks/runtime/tenant_scope.rb +18 -0
  252. data/lib/hecks/runtime/value/coercion.rb +255 -33
  253. data/lib/hecks/runtime/value/entity_list_coercion.rb +102 -30
  254. data/lib/hecks/runtime/value.rb +50 -0
  255. data/lib/hecks/runtime.rb +32 -0
  256. data/lib/hecks/storehouse.rb +305 -9
  257. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
  258. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
  259. data/lib/hecks/version.rb +1 -1
  260. data/lib/hecks.rb +79 -1
  261. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -0
  262. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +12 -2
  263. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +29 -5
  264. metadata +11 -5
  265. data/lib/hecks/codemod/legacy_dispatch_args.rb +0 -299
  266. data/lib/hecks/codemod/legacy_dispatch_recorder.rb +0 -186
  267. data/lib/hecks/deprecation.rb +0 -95
@@ -37,6 +37,20 @@ module Hecks
37
37
  # its own env var name and passes the resulting string straight
38
38
  # through, e.g. `Hecks.boot(path, environment:
39
39
  # ENV.fetch("MYAPP_ENV", "development"))`.
40
+ #
41
+ # @param path [String] a domain directory to boot, resolved through
42
+ # `Ports::Loading#bluebook_directory`
43
+ # @param shared [String, nil] an explicit shared ports/adapters root override; nil
44
+ # resolves it by walking up from the domain directory
45
+ # @param install_facade [Boolean] whether to install the `Widget::Item.Add(...)`
46
+ # facade sugar
47
+ # @param environment [String, nil] the environment overlay to load after the
48
+ # domain's own files (e.g. `"production"`); nil loads none
49
+ # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted dispatcher
50
+ # @raise [Errno::ENOENT] if `path` names neither a domain directory nor one holding
51
+ # a `bluebook/` subdirectory
52
+ # @raise [Runtime::WiringError] if a boot gate finds a wiring problem (an undeclared
53
+ # bind, a compute/rekey rule with no persistence plugin loaded, …)
40
54
  def self.boot(path, shared: nil, install_facade: true, environment: nil)
41
55
  loading = Ports::Loading.bootstrap
42
56
  directory = loading.bluebook_directory(path)
@@ -52,6 +66,7 @@ module Hecks
52
66
  run_boot_gates!(registry, directory)
53
67
  dispatcher = dispatcher_for(registry)
54
68
  redrive_outbox!(dispatcher)
69
+ seed_privacy_markings!(dispatcher, registry)
55
70
  install_facade ? bind_runtime(dispatcher) : dispatcher
56
71
  end
57
72
 
@@ -63,12 +78,43 @@ module Hecks
63
78
  # provably never started; `claimed` rows are surfaced, never
64
79
  # auto-redriven — see `Runtime::Outbox`. A remote dispatcher has
65
80
  # no local stores to scan.
81
+ #
82
+ # @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the
83
+ # just-booted dispatcher; a no-op unless it exposes an `outbox`
84
+ # @return [void]
66
85
  def self.redrive_outbox!(dispatcher)
67
86
  return unless dispatcher.respond_to?(:outbox)
68
87
 
69
88
  dispatcher.outbox.redrive!
70
89
  end
71
90
 
91
+ # Turns every `.hecksagon`-declared `AggregateDoor#mark_sensitive` fact into a real
92
+ # `Privacy::Marking.Mark` — after the dispatcher exists (dispatch needs a bound
93
+ # repository), idempotent across reboots (a marking already present is never
94
+ # re-dispatched, the same "provably never started" restraint `redrive_outbox!`
95
+ # holds itself to above). A no-op when nothing declared one, or when the domain
96
+ # never attached Privacy at all.
97
+ #
98
+ # @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the just-booted
99
+ # dispatcher `Privacy::Marking.Mark` dispatches through
100
+ # @param registry [Runtime::Registry] the just-booted registry `pending_privacy_markings`
101
+ # was recorded on
102
+ # @return [void]
103
+ def self.seed_privacy_markings!(dispatcher, registry)
104
+ return if registry.pending_privacy_markings.empty?
105
+ return unless registry.bluebook("Privacy")
106
+
107
+ already_marked_by_domain = registry.pending_privacy_markings.map { |marking| marking[:domain] }.uniq.to_h do |domain|
108
+ [domain, dispatcher.query("Privacy::Marking.ForDomain", domain: domain).map { |row| row[:attribute_path][:value] }]
109
+ end
110
+
111
+ registry.pending_privacy_markings.each do |marking|
112
+ next if already_marked_by_domain[marking[:domain]].include?(marking[:attribute_path])
113
+
114
+ dispatcher.dispatch_flat("Privacy::Marking.Mark", marking)
115
+ end
116
+ end
117
+
72
118
  # The explicit-file form — `paths` names the exact bluebook/hecksagon/
73
119
  # world files to boot, in place, wherever they actually live. `boot`
74
120
  # above only ever takes a directory and globs it; that is the right
@@ -91,6 +137,18 @@ module Hecks
91
137
  # first real path in `paths` — genuinely on disk, not a copy — so
92
138
  # every downstream path (`EraCheck`, `persisted_by`, `shared_root`)
93
139
  # resolves exactly as an ordinary directory boot's would.
140
+ #
141
+ # @param paths [Array<String>, String] the exact bluebook/hecksagon/world file paths
142
+ # to boot, in the order they should load
143
+ # @param shared [String, nil] an explicit shared ports/adapters root override; nil
144
+ # resolves it by walking up from `File.dirname` of the first path
145
+ # @param install_facade [Boolean] whether to install the `Widget::Item.Add(...)`
146
+ # facade sugar
147
+ # @param environment [String, nil] the environment overlay to load after the
148
+ # named files (e.g. `"production"`); nil loads none
149
+ # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted dispatcher
150
+ # @raise [Runtime::WiringError] if a boot gate finds a wiring problem (an undeclared
151
+ # bind, a compute/rekey rule with no persistence plugin loaded, …)
94
152
  def self.boot_files(paths, shared: nil, install_facade: true, environment: nil)
95
153
  loading = Ports::Loading.bootstrap
96
154
  files = Array(paths).map { |path| File.expand_path(path) }
@@ -109,9 +167,11 @@ module Hecks
109
167
  install_facade ? bind_runtime(dispatcher) : dispatcher
110
168
  end
111
169
 
112
- # ADR 0031 — replaces two previously-hardcoded, unconditional calls
113
- # with a per-boot `BootGates` instance holding exactly the gates this
114
- # registry's own bound adapters have a capability for. Ordering is
170
+ # Runs every registered boot gate against `registry`, in order.
171
+ #
172
+ # ADR 0031 — a per-boot `BootGates` instance holds exactly the gates this
173
+ # registry's own bound adapters have a capability for, rather than two
174
+ # hardcoded, unconditional calls. Ordering is
115
175
  # preserved: era-checking (when a persistence plugin contributes one)
116
176
  # still runs before `verify!`, saga rehydration still runs after
117
177
  # (conservative — see `SagaPersistence#rehydrate_sagas!`'s own
@@ -124,6 +184,13 @@ module Hecks
124
184
  # `:post_verify` gates generically; `:saga_rehydration` is the one
125
185
  # gate core still registers directly, because ADR 0031 already
126
186
  # proved it's not era-specific.
187
+ #
188
+ # @param registry [Runtime::Registry] the registry mid-boot, with its bluebooks,
189
+ # hecksagons, ports, adapters and worlds already loaded
190
+ # @param directory [String] the domain directory the gates report against in a refusal
191
+ # @return [Runtime::BootGates] the gates instance built and run for this boot
192
+ # @raise [Runtime::WiringError] if `registry.verify!` or any contributed gate finds
193
+ # a wiring problem
127
194
  def self.run_boot_gates!(registry, directory)
128
195
  gates = BootGates.new
129
196
  Ports::Persistence.each_plugin { |plugin| plugin.contribute_boot_gates(registry, gates) }
@@ -149,6 +216,12 @@ module Hecks
149
216
  # the real, adapter-aware version of this check and refuses by name
150
217
  # ("...is bound to Memory") long before this ever would; this only
151
218
  # fires when nothing did, because nothing was loaded to.
219
+ #
220
+ # @param registry [Runtime::Registry] the registry mid-boot, whose declared
221
+ # `translations` are checked
222
+ # @return [void]
223
+ # @raise [Runtime::WiringError] if any translation declares a `computes`/`rekeys`
224
+ # rule while no persistence plugin is loaded to interpret it
152
225
  def self.check_compute_rules_backstop!(registry)
153
226
  return if Ports::Persistence.plugins_loaded?
154
227
 
@@ -183,6 +256,12 @@ module Hecks
183
256
  # this landed. A domain opts in explicitly, the same way
184
257
  # `persisted_by("PostgresEra")` is never inferred from anything
185
258
  # else either.
259
+ #
260
+ # @param registry [Runtime::Registry] the just-booted registry, whose just-booted
261
+ # domain's `.world` is checked for `dispatched_by("Lambda")`
262
+ # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] a `RemoteDispatcher`
263
+ # bound to `registry` if the domain declares `dispatched_by("Lambda")`, a plain
264
+ # `Dispatcher` otherwise
186
265
  def self.dispatcher_for(registry)
187
266
  domain = registry.bluebooks.keys.first
188
267
  settings = registry.world(domain)&.for_verb("dispatched_by") || {}
@@ -191,11 +270,18 @@ module Hecks
191
270
  RemoteDispatcher.new(registry, region: settings.fetch(:region, "us-east-1"), function: settings[:function])
192
271
  end
193
272
 
194
- # The door is installed here, not stamped. This used to write the
195
- # dispatcher onto every aggregate's class (`ruby_class.runtime =`) — the
196
- # class-level global that made two boots in one process share one
197
- # name. The facade's modules close over this dispatcher instead, so the
198
- # binding lives in the surface a boot installs, not on anything shared.
273
+ # Installs the facade sugar (`Widget::Item.Add(...)`), closed over
274
+ # `dispatcher`, and hands `dispatcher` back.
275
+ #
276
+ # The door is installed here, not stamped: the facade's modules close
277
+ # over this dispatcher, so the binding lives in the surface a boot
278
+ # installs rather than in a class-level global (`ruby_class.runtime =`
279
+ # on every aggregate's class) that would make two boots in one process
280
+ # share one name.
281
+ #
282
+ # @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the
283
+ # just-booted dispatcher the facade should call into
284
+ # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] `dispatcher`, unchanged
199
285
  def self.bind_runtime(dispatcher)
200
286
  Facade::Surface.install(dispatcher)
201
287
  dispatcher
@@ -13,7 +13,9 @@ module Hecks
13
13
  # outbound port operation) the external effects those reactions
14
14
  # cause. `future-features.md` item 8, built.
15
15
  #
16
- # **The shape**. One row per (event, consumer). A consumer is a named
16
+ # ## The shape
17
+ #
18
+ # One row per (event, consumer). A consumer is a named
17
19
  # policy or process manager that would react to the event — resolved
18
20
  # at enqueue time from the registry (`Fanout`), so the outbox records
19
21
  # who was owed what, not just that an event happened. Rows move
@@ -33,18 +35,22 @@ module Hecks
33
35
  # tracks delivery, not the domain's answer.
34
36
  # failed the consumer raised a defect (non-refusal error).
35
37
  #
36
- # **Delivery is inline by default** — the dispatcher drains the rows it
38
+ # ## Delivery is inline by default
39
+ #
40
+ # The dispatcher drains the rows it
37
41
  # just wrote, in the same call, in the order C10.2 fixes (per event
38
42
  # in `emits` order: that event's policy rows, then its saga rows —
39
43
  # the emitting domain's own policies before other domains'). Nothing about the
40
44
  # happy path is deferred or asynchronous; a caller still sees every
41
45
  # reaction settled when `dispatch` returns. What changes is the
42
- # crash window: a process that dies between commit and reaction
43
- # used to lose the reaction silently. Now the row survives, and
44
- # `Relay#redrive!` — run at boot by `Loader.run_boot_gates!` —
46
+ # crash window: without a durable row, a process that dies between
47
+ # commit and reaction loses the reaction silently. The row survives,
48
+ # and `Relay#redrive!` — run at boot by `Loader.run_boot_gates!` —
45
49
  # finds it.
46
50
  #
47
- # **What redrive does, and deliberately doesn't**. A `pending` row is
51
+ # ## What redrive does, and deliberately doesn't
52
+ #
53
+ # A `pending` row is
48
54
  # redriven: its consumer provably never started (claiming is the
49
55
  # first thing delivery does), so running it now is exactly-once by
50
56
  # construction. A `claimed` row is not auto-redriven: the consumer
@@ -57,7 +63,9 @@ module Hecks
57
63
  # unique per store, so a re-enqueue of the same fact to the same
58
64
  # consumer is a no-op rather than a second row.
59
65
  #
60
- # **Which adapters**. Memory (in-process rows — visible to specs,
66
+ # ## Which adapters
67
+ #
68
+ # Memory (in-process rows — visible to specs,
61
69
  # gone with the process, exactly like everything else Memory holds),
62
70
  # Sqlite and Postgres (a `hecks_outbox` table in the aggregate's own
63
71
  # database — the only way the enqueue can share the save's
@@ -71,13 +79,30 @@ module Hecks
71
79
 
72
80
  Row = Struct.new(:id, :delivery_id, :event_uid, :aggregate, :domain, :kind, :consumer, :event,
73
81
  :status, :attempts, :error, keyword_init: true) do
82
+ # Reports whether this row is still waiting to be claimed.
83
+ #
84
+ # @return [Boolean] true if this row's status is `"pending"`
74
85
  def pending? = status == "pending"
86
+
87
+ # Reports whether this row's consumer is currently running.
88
+ #
89
+ # @return [Boolean] true if this row's status is `"claimed"`
75
90
  def claimed? = status == "claimed"
91
+
92
+ # Reports whether this row's consumer ran to completion.
93
+ #
94
+ # @return [Boolean] true if this row's status is `"delivered"`
76
95
  def delivered? = status == "delivered"
96
+
97
+ # Reports whether this row's consumer raised a defect.
98
+ #
99
+ # @return [Boolean] true if this row's status is `"failed"`
77
100
  def failed? = status == "failed"
78
101
 
79
102
  # Wire-shaped — what an adapter persists. `event` is the event's
80
103
  # own `to_h` plus correlation; `Row.event_from` reverses it.
104
+ #
105
+ # @return [Hash{Symbol => Object}] this row's own fields, keyed by name
81
106
  def to_h
82
107
  { id: id, delivery_id: delivery_id, event_uid: event_uid, aggregate: aggregate, domain: domain,
83
108
  kind: kind, consumer: consumer, event: event, status: status, attempts: attempts, error: error }
@@ -89,6 +114,12 @@ module Hecks
89
114
 
90
115
  module_function
91
116
 
117
+ # Renders an event as the wire-shaped Hash an outbox row's own `event`
118
+ # field stores.
119
+ #
120
+ # @param event [Runtime::Event] the event to serialize
121
+ # @return [Hash{Symbol => Object}] `event`'s own `to_h`, with `correlation`
122
+ # merged in
92
123
  def serialize_event(event)
93
124
  event.to_h.merge(correlation: event.correlation)
94
125
  end
@@ -96,11 +127,21 @@ module Hecks
96
127
  # The emitting domain's own bluebook first, then the rest in load
97
128
  # order (C10.2) — the one policy ordering both `PolicyInterpreter#
98
129
  # policies_for` and `Fanout.policies` read.
130
+ #
131
+ # @param registry [Runtime::Registry] the booted registry whose loaded
132
+ # bluebooks are ordered
133
+ # @param domain [String, Symbol] the emitting domain, sorted first
134
+ # @return [Array<Bluebook::Chapter>] every loaded chapter, `domain`'s own first
99
135
  def bluebooks_home_first(registry, domain)
100
136
  home, others = registry.bluebooks.each_value.partition { |bluebook| bluebook.name == domain }
101
137
  home + others
102
138
  end
103
139
 
140
+ # Rebuilds a frozen event from an outbox row's own stored `event` field.
141
+ #
142
+ # @param hash [Hash{String, Symbol => Object}] the wire-shaped event Hash,
143
+ # as `serialize_event` built it (String or Symbol keys either way)
144
+ # @return [Runtime::Event] the rebuilt, frozen event
104
145
  def event_from(hash)
105
146
  hash = hash.transform_keys(&:to_sym)
106
147
  Event.new(
@@ -113,6 +154,12 @@ module Hecks
113
154
  ).emit!
114
155
  end
115
156
 
157
+ # Recursively symbolizes every Hash key reachable from `value`.
158
+ #
159
+ # @param value [Object] the value to symbolize; typically a Hash or Array,
160
+ # possibly nested
161
+ # @return [Object] `value` with every Hash key (at any depth) turned into a
162
+ # Symbol; a non-Hash, non-Array value passes through unchanged
116
163
  def deep_symbolize(value)
117
164
  case value
118
165
  when Hash then value.to_h { |k, v| [k.to_sym, deep_symbolize(v)] }
@@ -137,6 +184,12 @@ module Hecks
137
184
  # this consumer".
138
185
  # Row order is delivery order (C10.2): per event, in `emits`
139
186
  # order — that event's policy rows, then its saga rows.
187
+ #
188
+ # @param registry [Runtime::Registry] the booted registry every candidate
189
+ # consumer is resolved against
190
+ # @param events [Array<Runtime::Event>] the just-emitted events to build rows for
191
+ # @param domain [String, Symbol] the emitting domain
192
+ # @return [Array<Runtime::Outbox::Row>] one pending row per (event, consumer)
140
193
  def rows_for(registry, events, domain)
141
194
  uids = events.to_h { |event| [event, SecureRandom.uuid] }
142
195
  events.flat_map do |event|
@@ -144,6 +197,16 @@ module Hecks
144
197
  end
145
198
  end
146
199
 
200
+ # Builds one pending row per policy `event` triggers.
201
+ #
202
+ # @param registry [Runtime::Registry] the booted registry policies are
203
+ # resolved against
204
+ # @param event [Runtime::Event] the just-emitted event
205
+ # @param domain [String, Symbol] the emitting domain
206
+ # @param uid [String] this event's own enqueue-time UID, shared by every row
207
+ # built for it
208
+ # @return [Array<Runtime::Outbox::Row>] one pending row per matching policy,
209
+ # emitting domain's own bluebook first
147
210
  def policies(registry, event, domain, uid)
148
211
  emitting = Naming.demodulise(event.aggregate)
149
212
  Outbox.bluebooks_home_first(registry, domain).flat_map do |bluebook|
@@ -159,6 +222,18 @@ module Hecks
159
222
  end
160
223
  end
161
224
 
225
+ # Builds one pending row per process manager `event` advances, in `domain`
226
+ # only (a saga never reacts across domains).
227
+ #
228
+ # @param registry [Runtime::Registry] the booted registry process managers
229
+ # are resolved against
230
+ # @param event [Runtime::Event] the just-emitted event
231
+ # @param domain [String, Symbol] the domain whose declared process managers
232
+ # are checked
233
+ # @param uid [String] this event's own enqueue-time UID, shared by every row
234
+ # built for it
235
+ # @return [Array<Runtime::Outbox::Row>] one pending row per process manager
236
+ # `event` starts, ends, or advances
162
237
  def sagas(registry, event, domain, uid)
163
238
  bluebook = registry.bluebook(domain)
164
239
  return [] unless bluebook
@@ -170,6 +245,13 @@ module Hecks
170
245
  end
171
246
  end
172
247
 
248
+ # Reports whether `event` starts, ends, or advances `process_manager`.
249
+ #
250
+ # @param process_manager [Bluebook::ProcessManager] the declared process
251
+ # manager to check
252
+ # @param event [Runtime::Event] the just-emitted event
253
+ # @return [Boolean] true if `event` names `process_manager`'s own
254
+ # `starts_on`, `ends_on`, or a declared handler
173
255
  def listens?(process_manager, event)
174
256
  process_manager.starts_on == event.name || process_manager.ends_on == event.name ||
175
257
  !process_manager.handler_for(event.name).nil?
@@ -179,6 +261,14 @@ module Hecks
179
261
  # operation — the row is the durable record that an external
180
262
  # call was owed, claimed right before the adapter is asked and
181
263
  # settled right after. Everything else is a plain "reaction".
264
+ #
265
+ # @param registry [Runtime::Registry] the booted registry the trigger's own
266
+ # aggregate/port are resolved against
267
+ # @param policy [Bluebook::Policy] the policy whose trigger is classified
268
+ # @param home_domain [String, Symbol] the domain `policy` is declared in,
269
+ # used when `policy` declares no `target_domain`
270
+ # @return [String] `"effect"` when the trigger resolves to an outbound port
271
+ # operation, `"reaction"` otherwise
182
272
  def kind_for(registry, policy, home_domain)
183
273
  target = "#{policy.target_domain || home_domain}::#{policy.trigger_command}"
184
274
  parsed = Naming.split_verb(target)
@@ -204,11 +294,16 @@ module Hecks
204
294
  # by the Rust kernel (`spec/rust_conformance_spec.rb`); this is
205
295
  # an additive, Ruby-only log, the same rule `saga_dispatch_log`
206
296
  # and `policy_dispatch_log` already follow.
297
+ #
298
+ # @param registry [Runtime::Registry] the booted registry this relay drains
299
+ # rows for
207
300
  def initialize(registry)
208
301
  @registry = registry
209
302
  @log = []
210
303
  end
211
304
 
305
+ # Attaches the interpreters a delivered row's own consumer runs through.
306
+ #
212
307
  # A Dispatcher hands over the interpreters a consumer runs
213
308
  # through (`Dispatcher#initialize`). Until then this relay can
214
309
  # enqueue (that needs only the registry) but not deliver — and
@@ -216,20 +311,38 @@ module Hecks
216
311
  # to. The registry holds one relay for its lifetime; a second
217
312
  # dispatcher fronting the same registry re-attaches, which is
218
313
  # fine because both dispatchers share every log and store.
314
+ #
315
+ # @param policies [Runtime::PolicyInterpreter] the interpreter a `"policy:"`
316
+ # consumer reacts through
317
+ # @param sagas [Runtime::SagaInterpreter] the interpreter a `"saga:"` consumer
318
+ # advances through
319
+ # @return [Runtime::Outbox::Relay] self
219
320
  def attach(policies:, sagas:)
220
321
  @policies = policies
221
322
  @sagas = sagas
222
323
  self
223
324
  end
224
325
 
326
+ # Reports whether a dispatcher has attached its interpreters.
327
+ #
328
+ # @return [Boolean] true once `attach` has run
225
329
  def attached? = !@policies.nil?
226
330
 
331
+ # Writes one pending row per (event, consumer) `events` owes a reaction to.
332
+ #
227
333
  # Called inside the save transaction by `Interpreting` for the
228
334
  # command/entity paths, and outside one by `Dispatcher` for port
229
335
  # operations (which save nothing, so there is no transaction to
230
336
  # share). Returns the rows as stored (ids assigned), or nil when
231
337
  # the repository has no outbox — the dispatcher then reacts
232
338
  # directly, exactly as before.
339
+ #
340
+ # @param repository [Persistence::AppendOnly] the repository whose store the
341
+ # rows are enqueued into; a no-op unless it has an outbox
342
+ # @param events [Array<Runtime::Event>] the events this dispatch just emitted
343
+ # @param domain [String, Symbol] the emitting domain
344
+ # @return [Array<Runtime::Outbox::Row>, nil] the stored rows, `id` assigned;
345
+ # `nil` when `repository` has no outbox; `[]` when `events` is empty
233
346
  def enqueue(repository, events, domain)
234
347
  return nil unless repository.outbox?
235
348
  return [] if events.empty?
@@ -241,6 +354,15 @@ module Hecks
241
354
 
242
355
  # Drain the rows a dispatch just committed. `rows` nil means "no
243
356
  # outbox here" — react directly, the pre-outbox path.
357
+ #
358
+ # @param rows [Array<Runtime::Outbox::Row>, nil] the just-enqueued rows to
359
+ # deliver; nil to react directly instead (no outbox on this repository)
360
+ # @param events [Array<Runtime::Event>] the events this dispatch just emitted,
361
+ # read when `rows` is nil
362
+ # @param domain [String, Symbol] the emitting domain
363
+ # @param repository [Persistence::AppendOnly] the repository `rows` were
364
+ # enqueued into
365
+ # @return [void]
244
366
  def deliver(rows, events, domain, repository)
245
367
  if rows.nil?
246
368
  # Per event, in `emits` order — its policies, then its sagas
@@ -258,6 +380,11 @@ module Hecks
258
380
 
259
381
  # One row: claim, run its consumer, settle. A claim that fails
260
382
  # means another relay (or this one, re-entrantly) already has it.
383
+ #
384
+ # @param row [Runtime::Outbox::Row] the row to claim and deliver
385
+ # @param repository [Persistence::AppendOnly] the repository `row` is stored in
386
+ # @return [Boolean] true when this call claimed and delivered `row`; false
387
+ # when the claim failed, or the consumer raised a defect (recorded on `log`)
261
388
  def deliver_row(row, repository)
262
389
  return false unless repository.outbox_claim(row.id)
263
390
 
@@ -283,13 +410,24 @@ module Hecks
283
410
  end
284
411
 
285
412
  # Every row in every bound store, newest last. `status:` narrows.
413
+ #
414
+ # @param status [String, Symbol, nil] keep only rows with this status; nil
415
+ # for every row
416
+ # @return [Array<Runtime::Outbox::Row>] copies of the matching rows, across
417
+ # every store with an outbox
286
418
  def rows(status: nil)
287
419
  stores.flat_map { |repository| repository.outbox_rows(status: status) }
288
420
  end
289
421
 
422
+ # Runs the boot-time reconciliation over every bound store.
423
+ #
290
424
  # **Boot-time reconciliation**. Redrives `pending` rows (never
291
425
  # claimed — safe by construction); surfaces `claimed` rows and
292
426
  # redrives them only when told to (`claimed: true`).
427
+ #
428
+ # @param claimed [Boolean] whether to also redrive `claimed` rows (an
429
+ # explicit operator decision); false leaves them surfaced and untouched
430
+ # @return [Array<Runtime::Outbox::Row>] every row this call actually delivered
293
431
  def redrive!(claimed: false)
294
432
  redriven = []
295
433
  stores.each do |repository|
@@ -16,11 +16,18 @@ module Hecks
16
16
  class PolicyInterpreter
17
17
  attr_reader :registry
18
18
 
19
+ # @param registry [Runtime::Registry] the booted registry whose loaded
20
+ # bluebooks are scanned for candidate policies
21
+ # @param door [Runtime::Dispatcher] the dispatcher reactions re-enter through,
22
+ # and whose reaction-depth guard is checked before each delivery
19
23
  def initialize(registry, door:)
20
24
  @registry = registry
21
25
  @door = door
22
26
  end
23
27
 
28
+ # Fires every declared policy `event` triggers, recording each outcome on
29
+ # the registry's reaction log.
30
+ #
24
31
  # `deliver` returns `nil` for a policy whose `where` did not hold —
25
32
  # silently, the same as a policy `policies_for` never selected at all
26
33
  # (an `event_qualifier` miss carries no reaction_log entry either) —
@@ -32,6 +39,13 @@ module Hecks
32
39
  # way of running exactly the consumer a row names (`Runtime::
33
40
  # Outbox::Relay#run_consumer`) instead of every policy that
34
41
  # matches the event. Selection is otherwise identical.
42
+ #
43
+ # @param event [Runtime::Event] the just-emitted event to react to
44
+ # @param domain [String, Symbol] the domain `event`'s own aggregate belongs
45
+ # to, the emitting domain's policies fire first
46
+ # @param only [Array(Bluebook::Policy, String), nil] one `[policy, home_domain]`
47
+ # pair to run exactly, instead of scanning every loaded bluebook for candidates
48
+ # @return [void]
35
49
  def react(event, domain, only: nil)
36
50
  selected = only ? [only] : policies_for(event, domain)
37
51
  selected.each do |policy, home_domain|
@@ -195,14 +209,13 @@ module Hecks
195
209
  # The event's own identity is a fact too, not only its payload. A
196
210
  # for_each query commonly filters by the emitting record's own
197
211
  # identity (`OpenForCustomer`'s own `reference:`, scoping by the
198
- # very customer who was just suspended) — which used to arrive for
199
- # free because legacy dispatch left the self-addressing key riding
200
- # along in `event.payload` unfiltered. Routing separated from
201
- # payload (`to:`/`with:`, what the facade's own bang-methods always
202
- # use) correctly stopped carrying it there, which left this query
203
- # silently seeing neither the field it needs nor any error saying
204
- # why — an empty result read as "nothing to freeze" instead of "the
205
- # customer" the whole reaction exists to catch.
212
+ # very customer who was just suspended) — a fact routing keeps
213
+ # separate from payload (`to:`/`with:`, what the facade's own
214
+ # bang-methods always use), so `event.payload` alone does not carry
215
+ # it. Without this, the query would silently see neither the field
216
+ # it needs nor any error saying why — an empty result read as
217
+ # "nothing to freeze" instead of "the customer" the whole reaction
218
+ # exists to catch.
206
219
  #
207
220
  # Merged in only when the query declares an argument by that exact
208
221
  # name and the emitting aggregate's own identity is genuinely what
@@ -261,7 +274,7 @@ module Hecks
261
274
  label: "#{policy.name}'s trigger"
262
275
  )
263
276
 
264
- # **The raw inputs `args` was resolved from** — same additive,
277
+ # The raw inputs `args` was resolved from — same additive,
265
278
  # Ruby-only shape SagaInterpreter#deliver_saga_dispatch's own
266
279
  # saga_dispatch_log gets, for Properties.dispatch_binding_
267
280
  # fidelity's own independent re-derivation of Policy#with_spec's
@@ -25,6 +25,10 @@ module Hecks
25
25
  refuse_unknown_arguments refuse_absent_arguments normalize_args resolve_references resolve_route emit
26
26
  ].freeze
27
27
 
28
+ # @param registry [Runtime::Registry] the booted registry this interpreter dispatches
29
+ # against
30
+ # @param rules [Runtime::CommandRules] the shared command-rule checks (references) this
31
+ # interpreter's steps call
28
32
  def initialize(registry, rules:)
29
33
  @registry = registry
30
34
  @rules = rules
@@ -32,6 +36,22 @@ module Hecks
32
36
 
33
37
  # `invocation` — the `Runtime::Invocation` `Dispatcher` built;
34
38
  # `ctx.args` is its `to_args`, `ctx.route` its `target`.
39
+ #
40
+ # @param domain [String] the domain the aggregate belongs to
41
+ # @param aggregate [Bluebook::Aggregate] the aggregate the port operation belongs to
42
+ # @param operation [Bluebook::PortOperation] the port operation to dispatch
43
+ # @param invocation [Runtime::Invocation] the invocation `Dispatcher` built for this call
44
+ # @return [Array<Runtime::Event>] the events recorded: the operation's own `emits` for
45
+ # an inbound operation, or a single `answers`/`refuses` event for an outbound one
46
+ # (an adapter failure is recorded as a `refuses` event, not raised)
47
+ # @raise [Runtime::UnknownArgument] if `invocation` offers an argument the operation
48
+ # does not declare
49
+ # @raise [Runtime::AbsentArgument] if `invocation` omits a non-optional declared
50
+ # argument
51
+ # @raise [Runtime::TypeMismatch] if an offered argument does not coerce to its declared
52
+ # type
53
+ # @raise [Runtime::NotFound] if an offered reference or the operation's own receiving
54
+ # record cannot be found
35
55
  def call(domain, aggregate, operation, invocation)
36
56
  ctx = Context.new(domain, aggregate, operation, invocation.to_args)
37
57
  ctx.invocation = invocation