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
@@ -5,6 +5,10 @@ module Hecks
5
5
  # the states and the handlers; the compensation half — `saga` — is
6
6
  # derived from the handler that answers a refusal, not declared.
7
7
  module ProcessManager
8
+ # The bluebook's name for this construct.
9
+ #
10
+ # @return [String, nil] the process manager's declared name, or `nil` before it
11
+ # is set
8
12
  def hecks_name = @name
9
13
 
10
14
  # The leg that answers — selected by (event, current state), C10.3
@@ -17,12 +21,27 @@ module Hecks
17
21
  # selection). Build refuses two legs on one (event, state) pair
18
22
  # (`ProcessManagerBuilder#validate!`), so a stated lookup is
19
23
  # unambiguous by construction.
24
+ #
25
+ # @param event [String, Symbol] the triggering event's name
26
+ # @param state [String, Symbol, nil] the instance's current state; `nil` asks
27
+ # declaratively, ignoring state
28
+ # @return [Bluebook::ProcessManagerHandler, nil] the matching handler row, or `nil`
29
+ # if no declared leg answers this (event, state) pair
20
30
  def handler_for(event, state = nil)
21
31
  @handlers.find { |h| h.event_type == event.to_s && (state.nil? || h.from_state == state.to_s) }
22
32
  end
23
33
 
34
+ # Finds every declared leg for an event, regardless of source state.
35
+ #
36
+ # @param event [String, Symbol] the triggering event's name
37
+ # @return [Array<Bluebook::ProcessManagerHandler>] every handler row declared for
38
+ # `event`; `[]` if none is declared
24
39
  def handlers_for(event) = @handlers.select { |h| h.event_type == event.to_s }
25
40
 
41
+ # Says whether any declared leg answers an event.
42
+ #
43
+ # @param event [String, Symbol] the triggering event's name
44
+ # @return [Boolean] whether any handler row is declared for `event`
26
45
  def handles?(event) = @handlers.any? { |h| h.event_type == event.to_s }
27
46
 
28
47
  # Whether a state is one this procedure declares — asked of a value
@@ -30,15 +49,24 @@ module Hecks
30
49
  # state), the way `Lifecycle#states` is asked of an aggregate's
31
50
  # resting field. A rehydrated instance in a state no handler could
32
51
  # have reached is corruption the durable round-trip introduced.
52
+ #
53
+ # @param state [String, Symbol] the state to check
54
+ # @return [Boolean] whether `state` is one of this procedure's declared states
33
55
  def declares_state?(state) = @states.map(&:to_s).include?(state.to_s)
34
56
 
57
+ # Names the payload field a triggering event's instances are correlated by.
58
+ #
59
+ # @return [Symbol] the head of `correlates_by` — the part before the first `.`
35
60
  def correlation_head = @correlates_by.to_s.split(".").first.to_sym
36
61
 
37
62
  # The compensation half of a procedure, read off the handler that
38
- # answers REFUSED.
63
+ # answers `REFUSED`.
39
64
  #
40
65
  # nil for a procedure with no answer to a refusal, which is a legitimate
41
66
  # thing to be — a hiring pipeline cannot un-interview anybody.
67
+ #
68
+ # @return [Bluebook::Saga, nil] the derived saga, or `nil` if no handler answers
69
+ # `REFUSED`
42
70
  def saga
43
71
  leg = handler_for(Bluebook::ProcessManager::REFUSED)
44
72
  return nil unless leg
@@ -60,6 +88,9 @@ module Hecks
60
88
  to_state: leg.to_state, compensations: derived + leg.dispatches)
61
89
  end
62
90
 
91
+ # Says whether this procedure declares a handler for `REFUSED`.
92
+ #
93
+ # @return [Boolean] whether this procedure has a derivable saga
63
94
  def saga? = !saga.nil?
64
95
  end
65
96
  end
@@ -3,6 +3,11 @@ module Hecks
3
3
  module Behaviour
4
4
  # What a query does beyond holding its declared shape.
5
5
  module Query
6
+ # Finds a declared result attribute by name.
7
+ #
8
+ # @param named [String, Symbol] the attribute's declared name
9
+ # @return [Bluebook::Attribute, nil] the attribute, or `nil` if none is declared
10
+ # by that name
6
11
  def attribute(named) = @attributes.find { |a| a.name == named.to_sym }
7
12
  end
8
13
  end
@@ -4,14 +4,22 @@ module Hecks
4
4
  # **What a read model does**. Its declared half is the gathered heads
5
5
  # and the query shape; these are readings taken off them.
6
6
  module ReadModel
7
+ # Lists the fields this read model groups rows by.
8
+ #
9
+ # @return [Array<Symbol>] each declared `group_by` field's name, in declaration order
7
10
  def group_by_fields = @group_by.map { |row| row[:field].to_sym }
8
11
 
9
12
  # `!!` rather than a bare `@count` — the DSL/reconstruction
10
13
  # boundary (ReadModel#initialize) already normalises to
11
14
  # `true`/`nil`, so this is belt and braces against a future
12
15
  # caller constructing a ReadModel by hand with `count: false`.
16
+ #
17
+ # @return [Boolean] whether this read model reduces to a row count
13
18
  def count? = !!@count
14
19
 
20
+ # Names the verb `Dispatcher#query` looks this read model up by.
21
+ #
22
+ # @return [String] this read model's name in `snake_case`
15
23
  def query_name = Naming.snake(@name)
16
24
 
17
25
  # Which gathered heads the filtering applies to (ADR 0055) — plural,
@@ -22,6 +30,9 @@ module Hecks
22
30
  # single-head-only — ADR 0055) applies to it, same as before `on:`
23
31
  # existed. With several many-side heads, only the ones actually named
24
32
  # by a targeted option are eligible.
33
+ #
34
+ # @return [Array<Symbol>] the `:as` name of each many-side head that filtering
35
+ # applies to; `[]` if this read model has no many-side head
25
36
  def filtered_head_names
26
37
  many = @aggregate_heads.select { |head| head[:many] }
27
38
  return [] if many.empty?
@@ -37,6 +48,11 @@ module Hecks
37
48
  # `median`, still single-head-only) applies to it — split out only
38
49
  # to keep `filtered_head_names` itself under this file's own
39
50
  # complexity budget, not because the two questions differ in kind.
51
+ #
52
+ # @param many [Array<Hash{Symbol => Object}>] the read model's many-side
53
+ # `aggregate_heads` rows; must hold exactly one
54
+ # @return [Array<Symbol>] `[the one head's :as name]` if any filtering option is
55
+ # declared, else `[]`
40
56
  def single_filtered_head_name(many)
41
57
  declared = wheres.any? || order_by || limit || offset || authorization&.tenant ||
42
58
  @group_by.any? || count? || @median_field
@@ -53,6 +69,11 @@ module Hecks
53
69
  # resolves to `head_as`'s own aggregate.
54
70
  FilteredOptions = Struct.new(:wheres, :order_by, :limit, :offset, :null_semantics)
55
71
 
72
+ # Scopes this read model's filtering options down to one included head.
73
+ #
74
+ # @param head_as [Symbol] the `:as` name of the head to scope filtering to
75
+ # @return [FilteredOptions] the `wheres`/`order_by`/`limit`/`offset` that apply to
76
+ # `head_as`, and this read model's own `null_semantics`
56
77
  def options_for(head_as)
57
78
  many = @aggregate_heads.select { |head| head[:many] }
58
79
  aggregate_name = @aggregate_heads.find { |head| head[:as] == head_as }&.fetch(:aggregate)
@@ -28,6 +28,10 @@ module Hecks
28
28
  # no single head, and answering with the first would be a guess ; the
29
29
  # readers that need all of them ask for `identity_heads`.
30
30
  module Identified
31
+ # Derives `identity_paths`, `identity_heads` and the single-head form of
32
+ # `identified_by` from the declared `identified_by` path(s).
33
+ #
34
+ # @return [void]
31
35
  def derive_identity
32
36
  @identity_paths = Array(@identified_by).map(&:to_s).reject(&:empty?)
33
37
  @identity_heads = @identity_paths.map { |path| path.split(".").first.to_sym }.uniq
@@ -48,6 +52,10 @@ module Hecks
48
52
  module Indexed
49
53
  # Keyed by symbol — an attribute is asked for by its declared
50
54
  # symbol name everywhere in the runtime.
55
+ #
56
+ # @param attributes [Array<Bluebook::Attribute>] the construct's declared
57
+ # attributes
58
+ # @return [void]
51
59
  def index_attributes(attributes)
52
60
  @attributes_by_name = attributes.to_h { |held| [held.name, held] }
53
61
  end
@@ -56,12 +64,34 @@ module Hecks
56
64
  # object is a construct whose Ruby `name` is something else
57
65
  # entirely (a constant path, or nothing at all for an anonymous
58
66
  # class), so the declared name is the only one worth indexing.
67
+ #
68
+ # @param collection [Array<#hecks_name>] declared constructs to index —
69
+ # value objects, commands or queries, each answering `hecks_name`
70
+ # @return [Hash{String => Object}] the collection keyed by each member's
71
+ # `hecks_name`
59
72
  def index_by_hecks_name(collection)
60
73
  collection.to_h { |held| [held.hecks_name, held] }
61
74
  end
62
75
 
76
+ # Finds a declared attribute by its declared name.
77
+ #
78
+ # @param named [String, Symbol] the attribute's declared name
79
+ # @return [Bluebook::Attribute, nil] the attribute named `named`, or `nil`
80
+ # if none is declared under that name
63
81
  def attribute(named) = @attributes_by_name[named.to_sym]
82
+
83
+ # Finds a declared command by its declared name.
84
+ #
85
+ # @param named [String, Symbol] the command's declared name
86
+ # @return [Class, nil] the command class (a `Bluebook::Command` subclass)
87
+ # named `named`, or `nil` if none is declared under that name
64
88
  def command(named) = @commands_by_name[named.to_s]
89
+
90
+ # Finds a declared query by its declared name.
91
+ #
92
+ # @param named [String, Symbol] the query's declared name
93
+ # @return [Bluebook::Query, nil] the query named `named`, or `nil` if none
94
+ # is declared under that name
65
95
  def query(named) = @queries_by_name[named.to_s]
66
96
  end
67
97
 
@@ -72,6 +102,12 @@ module Hecks
72
102
  # is complete is the right stamping point: its declarations are
73
103
  # final, and nothing outside needs to remember to stamp them.
74
104
  module Owns
105
+ # Stamps each of `children` as owned by this construct.
106
+ #
107
+ # @param children [Array<Object>] the construct's own declared children —
108
+ # arrays or bare objects, flattened before stamping — each answering
109
+ # `hecks_owner=`
110
+ # @return [void]
75
111
  def stamp(*children)
76
112
  children.flatten.each { |child| child.hecks_owner = self }
77
113
  end
@@ -6,13 +6,22 @@ module Hecks
6
6
  # behaviour is singleton behaviour, and the holding half's `absorb`
7
7
  # is what a generated constructor would be.
8
8
  module ValueObject
9
- # A one_of declared but left empty used to be indistinguishable from no
10
- # one_of at all — both are `members: []` — so the rule about it could
11
- # only live in the builder. Recording the declaration lets the language
12
- # judge it, the same way an empty attribute name survives into the IR
13
- # and is judged there.
9
+ # Recorded as its own fact (`closed_set`), not left to be inferred from
10
+ # `members: []` alone — an empty `one_of` and no `one_of` at all would
11
+ # otherwise be indistinguishable, which would leave the rule about it
12
+ # only livable in the builder. Recording the declaration lets the
13
+ # language judge it instead, the same way an empty attribute name
14
+ # survives into the IR and is judged there.
15
+ #
16
+ # @return [Boolean] whether this value object declares `one_of`, even
17
+ # if left empty
14
18
  def closed_set? = @closed_set
15
19
 
20
+ # Finds a declared attribute by its declared name.
21
+ #
22
+ # @param named [String, Symbol] the attribute's declared name
23
+ # @return [Bluebook::Attribute, nil] the attribute named `named`, or
24
+ # `nil` if none is declared under that name
16
25
  def attribute(named) = attributes.find { |held| held.name == named.to_sym }
17
26
 
18
27
  # A single-attribute value object (EmailAddress{address},
@@ -30,6 +39,9 @@ module Hecks
30
39
  # `retention_months`/`paper_fee_cents`). `sole_attribute` would
31
40
  # return `nil` for that shape and break the discriminant lookup —
32
41
  # left as `.first` on purpose, not a missed migration.
42
+ #
43
+ # @return [Bluebook::Attribute, nil] this value object's only
44
+ # attribute, or `nil` when it has zero or more than one
33
45
  def sole_attribute
34
46
  attributes.first if attributes.size == 1
35
47
  end
@@ -63,6 +63,12 @@ module Hecks
63
63
  # ("RoleAssignment.Assign"); `Behaviour::Chapter#provided_verb`
64
64
  # qualifies it with the chapter's own name.
65
65
  Provision = Struct.new(:capability, :key, :verb, keyword_init: true) do
66
+ # Coerces one declared or reconstructed `provides` row into a `Provision`.
67
+ #
68
+ # @param row [Bluebook::Chapter::Provision, #to_h] a `Provision` already, or
69
+ # anything answering `to_h` with `capability`/`key`/`verb` entries
70
+ # @return [Bluebook::Chapter::Provision] `row` itself if it already is one, else
71
+ # a new `Provision` built from its fields
66
72
  def self.from(row)
67
73
  return row if row.is_a?(self)
68
74
 
@@ -74,6 +80,23 @@ module Hecks
74
80
  attr_reader :name, :version, :vision, :aggregates, :policies, :process_managers,
75
81
  :classification, :read_models, :ports, :formerly_known_as, :attaches_to, :provides
76
82
 
83
+ # @param name [String, Symbol] the chapter's declared name
84
+ # @param version [String, Symbol, nil] the chapter's declared business version,
85
+ # such as `"v1"`, or `nil` if it declares none
86
+ # @param vision [String, nil] the chapter's declared vision statement
87
+ # @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
88
+ # @param policies [Array<Bluebook::Policy>] every reaction declared across the
89
+ # chapter's own aggregates, hoisted here
90
+ # @param process_managers [Array<Bluebook::ProcessManager>] the chapter's declared
91
+ # process managers
92
+ # @param classification [String, Symbol, nil] whether this chapter is central to
93
+ # its project's own domain model, or `nil` if undeclared
94
+ # @param read_models [Array<Bluebook::ReadModel>] the chapter's declared read models
95
+ # @param formerly_known_as [String, Symbol, nil] an earlier name this domain was
96
+ # known under, or `nil` if it was never renamed
97
+ # @param attaches_to [Array<String, Symbol>] the contexts this chapter attaches to
98
+ # @param provides [Array<Bluebook::Chapter::Provision, Hash>] the capabilities this
99
+ # chapter declares it provides
77
100
  def initialize(name:, version: nil, vision: nil, aggregates: [], policies: [],
78
101
  process_managers: [], classification: nil, read_models: [], formerly_known_as: nil,
79
102
  attaches_to: [], provides: [])
@@ -10,14 +10,12 @@ module Hecks
10
10
  include Hecks::IR
11
11
  include Behaviour::Mutation
12
12
 
13
- # `sign:` — item #5 of the whole-project table-unification survey.
14
- # `increment`/`decrement`'s own +1/-1 used to be re-derived from the
15
- # op name by two independent Rust codegen scripts (rust/project/
16
- # mutations.rb, rust/codegen/src/mutations.rs — a ternary on
17
- # `op == "increment"` in each), even though the fact was already
18
- # table-driven on the Ruby runtime side
19
- # (Runtime::CommandRules::Arithmetic::MUTATION_OPS, itself read off
20
- # the same generated Vocabulary::MutationOp table).
13
+ # `sign:` — item #5 of the whole-project table-unification survey:
14
+ # `increment`/`decrement`'s own +1/-1 comes from `Vocabulary::MutationOp`
15
+ # here, the same table `Runtime::CommandRules::Arithmetic::MUTATION_OPS`
16
+ # already reads on the Ruby runtime side, rather than a ternary on the op
17
+ # name re-deriving it independently in each of two Rust codegen scripts
18
+ # (rust/project/mutations.rb, rust/codegen/src/mutations.rs).
21
19
  # Reads `Vocabulary::MutationOp` directly (plain data, no framework
22
20
  # dependency — safe during parsing, same reason `RuleReference`'s
23
21
  # own bootstrap concerns don't apply here) rather than requiring
@@ -25,6 +23,11 @@ module Hecks
25
23
  # real layering inversion (runtime depends on bluebook, not the
26
24
  # reverse). "" (not nil) for ops with no sign, matching every other
27
25
  # optional IR text field's own absent-is-empty-string convention.
26
+ #
27
+ # @param oper [String, Symbol] the mutation's operation name, such as
28
+ # `"increment"` or `"decrement"`
29
+ # @return [String] the operation's sign from `Vocabulary::MutationOp`
30
+ # (`"+"` or `"-"`), or `""` when the op has none or is not found
28
31
  def self.sign_for(oper)
29
32
  Hecks::Vocabulary.rows("MutationOp").find { |row| row["name"] == oper.to_s }&.fetch("sign", "") || ""
30
33
  end
@@ -39,6 +42,10 @@ module Hecks
39
42
  # emission covers the fixed head; `super` supplies it and this adds
40
43
  # the tail, which is why a construct with a variable shape needs no
41
44
  # new mixin API.
45
+ # @return [Hash{Symbol => Object}] the declared emission — `super`'s fixed
46
+ # head, plus `fields:` (a Hash of bound field values) for an
47
+ # append-shaped op, or `source:` (`classified_source`'s own result) for
48
+ # any other
42
49
  def to_h
43
50
  return super.merge(fields: appended_fields) if [:append, :delegate, :corrects].include?(op)
44
51
 
@@ -95,6 +102,29 @@ module Hecks
95
102
  attr_reader :role, :goal, :attributes, :givens, :ensures, :mutations, :emits, :references,
96
103
  :from, :provenance
97
104
 
105
+ # Mints one command as its own anonymous class, a subclass of whichever
106
+ # `Command`-descended class `declare` is called on.
107
+ #
108
+ # @param name [String, Symbol] the command's declared name
109
+ # @param role [String, nil] the command's declared role text
110
+ # @param goal [String, nil] the command's declared goal text
111
+ # @param attributes [Array<Bluebook::Attribute>] the command's declared
112
+ # arguments
113
+ # @param givens [Array<Bluebook::Given>] the command's declared
114
+ # preconditions
115
+ # @param ensures [Array<Bluebook::Given>] the command's declared
116
+ # postconditions
117
+ # @param mutations [Array<Bluebook::Mutation>] the state changes this
118
+ # command applies
119
+ # @param emits [Array<String>] the event names this command may emit
120
+ # @param references [String, Symbol, nil] the aggregate name this verb's
121
+ # `reference_to` addresses, or `nil` for a verb declared on the
122
+ # aggregate it acts on
123
+ # @param from [String, Array<String>, nil] the lifecycle state(s) this
124
+ # command is admissible from, or `nil` for no such guard
125
+ # @param provenance [Object, nil] the command's declared canonical
126
+ # source, captured exactly as written, or `nil` if it declares none
127
+ # @return [Class] the new command class
98
128
  def declare(name:, role: nil, goal: nil, attributes: [], givens: [], ensures: [],
99
129
  mutations: [], emits: [], references: nil, from: nil, provenance: nil)
100
130
  verb = Class.new(self)
@@ -105,6 +135,21 @@ module Hecks
105
135
  verb
106
136
  end
107
137
 
138
+ # Assigns what the language declares, then hands off to the behaviour's
139
+ # own `settle` — indexing attributes, the one thing not derivable from
140
+ # the declaration.
141
+ #
142
+ # @param role [String, nil] see `declare`
143
+ # @param goal [String, nil] see `declare`
144
+ # @param attributes [Array<Bluebook::Attribute>] see `declare`
145
+ # @param givens [Array<Bluebook::Given>] see `declare`
146
+ # @param ensures [Array<Bluebook::Given>] see `declare`
147
+ # @param mutations [Array<Bluebook::Mutation>] see `declare`
148
+ # @param emits [Array<String>] see `declare`
149
+ # @param references [String, nil] see `declare`
150
+ # @param from [String, Array<String>, nil] see `declare`
151
+ # @param provenance [Object, nil] see `declare`
152
+ # @return [Class] self, once attributes are indexed
108
153
  def absorb(role:, goal:, attributes:, givens:, ensures:, mutations:, emits:, references:,
109
154
  from: nil, provenance: nil)
110
155
  @role = role
@@ -33,6 +33,19 @@ module Hecks
33
33
  # what the adapter came back with, `refuses` for what it said instead.
34
34
  # Naming only the happy one would put the failure somewhere the model
35
35
  # cannot see, which is the whole reason a boundary is worth modelling.
36
+ #
37
+ # @param name [String, Symbol] the operation's declared name
38
+ # @param attributes [Array<Bluebook::Attribute>] the operation's declared payload
39
+ # fields
40
+ # @param emits [Array<String>] the events an inbound operation declares it records
41
+ # @param direction [Symbol, String] `:inbound` for a `tells`/`operation`, `:outbound`
42
+ # for an `asks`
43
+ # @param answers [String, nil] an outbound operation's declared event for what the
44
+ # adapter came back with
45
+ # @param refuses [String, nil] an outbound operation's declared event for what the
46
+ # adapter said instead
47
+ # @param to [String, nil] the aggregate this operation routes to, or `nil` if it
48
+ # declares no routing target
36
49
  def initialize(name:, attributes: [], emits: [], direction: :inbound, answers: nil, refuses: nil, to: nil)
37
50
  @hecks_name = name.to_s
38
51
  @attributes = attributes
@@ -44,7 +57,14 @@ module Hecks
44
57
  @attributes_by_name = attributes.to_h { |attribute| [attribute.name, attribute] }
45
58
  end
46
59
 
60
+ # Says whether this operation is the domain asking something of an adapter.
61
+ #
62
+ # @return [Boolean] whether this operation is an `asks`
47
63
  def outbound? = @direction == :outbound
64
+
65
+ # Says whether this operation is an adapter telling the domain something.
66
+ #
67
+ # @return [Boolean] whether this operation is a `tells`/`operation`
48
68
  def inbound? = @direction == :inbound
49
69
 
50
70
  # No root reference of its own — unlike a command, every attribute
@@ -72,6 +92,9 @@ module Hecks
72
92
  # migrated yet keeps the exact prior IR shape, byte for byte,
73
93
  # instead of an unconditional new key breaking parser_parity_spec
74
94
  # for every domain that never touched this.
95
+ #
96
+ # @return [Hash] the declared emission, plus `direction`/`answers`/`refuses` for an
97
+ # outbound operation and `to` when a routing target is declared
75
98
  def to_h
76
99
  shape = super
77
100
  shape = shape.merge(direction: @direction.to_s, answers: @answers, refuses: @refuses) unless inbound?
@@ -93,6 +116,8 @@ module Hecks
93
116
 
94
117
  attr_reader :name, :operations
95
118
 
119
+ # @param name [String, Symbol] the port's declared name
120
+ # @param operations [Array<Bluebook::PortOperation>] the port's declared operations
96
121
  def initialize(name:, operations: [])
97
122
  @name = name.to_s
98
123
  @operations = operations