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
@@ -4,17 +4,36 @@ require_relative "../ir"
4
4
  module Hecks
5
5
  module Bluebook
6
6
  Port = Struct.new(:name, :verb, :signal, :answers, keyword_init: true) do
7
+ # Says whether this port answers its verb with a return value.
8
+ #
9
+ # @return [Boolean] whether `signal` is `:reply`
7
10
  def reply? = signal == :reply
11
+
12
+ # Says whether this port answers its verb by taking effect, with no return value.
13
+ #
14
+ # @return [Boolean] whether `signal` is `:effect`
8
15
  def effect? = signal == :effect
9
16
  end
10
17
 
11
18
  Adapter = Struct.new(:name, :port, :fields, :secrets, keyword_init: true) do
19
+ # Says whether this adapter declares a field, plain or secret.
20
+ #
21
+ # @param field [Symbol, String] the field to check
22
+ # @return [Boolean] whether `field` is one of this adapter's own `fields` or `secrets`
12
23
  def declares?(field) = all_fields.include?(field.to_sym)
13
24
 
25
+ # Lists every field this adapter declares, plain and secret alike.
26
+ #
27
+ # @return [Array<Symbol>] every field this adapter declares, `fields` and `secrets`
28
+ # combined
14
29
  def all_fields = (fields || []) + (secrets || [])
15
30
  end
16
31
 
17
32
  Bind = Struct.new(:aggregate, :verb, :adapter, :role, keyword_init: true) do
33
+ # Names the aggregate this bind applies to.
34
+ #
35
+ # @return [String] this bind's aggregate name, demodulised, or `""` for a
36
+ # domain-level default bind with no aggregate
18
37
  def aggregate_name = Naming.demodulise(aggregate)
19
38
  end
20
39
 
@@ -37,6 +56,14 @@ module Hecks
37
56
 
38
57
  attr_reader :domain, :binds, :subscriptions, :framework_members, :vendored_bluebooks
39
58
 
59
+ # @param domain [String, Symbol] the domain this hecksagon wires
60
+ # @param binds [Array<Bluebook::Bind>] the declared adapter binds
61
+ # @param subscriptions [Array<String, Symbol>] the external events this domain
62
+ # subscribes to
63
+ # @param framework_members [Array<String, Symbol>] the framework members
64
+ # (`Governance`, `Identity`, ...) this domain attaches
65
+ # @param vendored_bluebooks [Array<String, Symbol>] the vendored embryonaut
66
+ # bluebook package names this domain attaches
40
67
  def initialize(domain:, binds: [], subscriptions: [], framework_members: [], vendored_bluebooks: [])
41
68
  @domain = domain.to_s
42
69
  @binds = binds
@@ -59,6 +86,13 @@ module Hecks
59
86
 
60
87
  attr_reader :domain, :realm, :latest, :settings
61
88
 
89
+ # @param domain [String, Symbol] the domain this world configures
90
+ # @param realm [String, Symbol, nil] the declared realm/version marker, or `nil`
91
+ # if none is declared
92
+ # @param latest [String, Symbol, nil] the declared latest-version marker, or `nil`
93
+ # if none is declared
94
+ # @param settings [Hash] the declared adapter bind settings, keyed by verb and,
95
+ # for a qualified entry, `"verb:adapter"`
62
96
  def initialize(domain:, realm: nil, latest: nil, settings: {})
63
97
  @domain = domain.to_s
64
98
  @realm = realm&.to_s
@@ -9,6 +9,9 @@ module Hecks
9
9
  class StateTransition
10
10
  attr_reader :target, :from
11
11
 
12
+ # @param target [String, Symbol] the state this transition moves the record to
13
+ # @param from [String, Symbol, Array<String, Symbol>, nil] the state, or states, this
14
+ # transition applies from; `nil` means any current state admits it
12
15
  def initialize(target:, from: nil)
13
16
  @target = target.to_s
14
17
  @from = case from
@@ -18,6 +21,10 @@ module Hecks
18
21
  end
19
22
  end
20
23
 
24
+ # Says whether this transition is guarded to specific source states.
25
+ #
26
+ # @return [Boolean] whether this transition is guarded to specific source states,
27
+ # rather than applying from any current state
21
28
  def constrained? = !@from.nil?
22
29
  end
23
30
 
@@ -41,6 +48,10 @@ module Hecks
41
48
 
42
49
  attr_reader :field, :default, :transitions
43
50
 
51
+ # @param field [Symbol, String] the attribute this state machine lives on
52
+ # @param default [String, Symbol] the state a new record starts in
53
+ # @param transitions [Array<Array(String, Bluebook::StateTransition)>] each declared
54
+ # `command name, StateTransition` pair, in declaration order
44
55
  def initialize(field:, default:, transitions: [])
45
56
  @field = field.to_sym
46
57
  @default = default.to_s
@@ -11,6 +11,7 @@ module Hecks
11
11
  class AdapterJudge
12
12
  attr_reader :refusals
13
13
 
14
+ # @param adapter [Bluebook::Adapter] the built adapter to judge
14
15
  def initialize(adapter)
15
16
  @adapter = adapter
16
17
  @refusals = []
@@ -3,18 +3,19 @@ module Hecks
3
3
  module MetaValidator
4
4
  # Offers every declaration in a built bluebook to the meta-domain.
5
5
  #
6
- # This used to be one hand-written branch per category, and the cost of that
7
- # shape was fourteen verbs the language declared and the judge never offered
8
- # — among them `Command.Argument` and `ValueObject.Field`, so a command's own
9
- # arguments and a value object's own fields were never judged. Every rule
10
- # hanging off them was decoration. Nothing went red, because a branch that
11
- # does not exist cannot fail.
6
+ # There are no hand-written branches, one per category: that shape's
7
+ # real cost is any verb the language declares that the judge never
8
+ # offers — a branch nobody wrote for it — going undetected, since a
9
+ # branch that does not exist cannot fail. `Command.Argument` and
10
+ # `ValueObject.Field` were both admitted that way once, undetected;
11
+ # every rule hanging off them was decoration until offered for real.
12
12
  #
13
- # So there are no branches. The judge walks: it reads the plan the language
14
- # makes of itself (Plan), and for each node offers the creating command, then
15
- # each list through the command that appends to it, then each child. A verb
16
- # in the plan with no offer is now impossible — there is no branch left in
17
- # which to forget one, and spec/judge_coverage_spec holds it to that.
13
+ # So instead the judge walks: it reads the plan the language makes of
14
+ # itself (Plan), and for each node offers the creating command, then
15
+ # each list through the command that appends to it, then each child. A
16
+ # verb in the plan with no offer is now impossible — there is no
17
+ # branch left in which to forget one, and spec/judge_coverage_spec
18
+ # holds it to that.
18
19
  #
19
20
  # What is not uniform lives in Readings, and only where the IR's shape
20
21
  # differs from the language's. Naming differences do not appear at all: the
@@ -61,6 +62,8 @@ module Hecks
61
62
  # not a curiosity, so the runtime is simply readable.
62
63
  attr_reader :runtime
63
64
 
65
+ # @param bluebook [Object] a built bluebook chapter graph, as
66
+ # `MetaValidator.call` receives it
64
67
  def initialize(bluebook)
65
68
  @bluebook = bluebook
66
69
  @refusals = []
@@ -160,12 +163,12 @@ module Hecks
160
163
 
161
164
  # Declared before detailed, for every set of siblings.
162
165
  #
163
- # A node used to be offered whole — declared, then its lists, then its
164
- # children — one sibling at a time. Which means an aggregate's attributes
165
- # were offered before its later siblings existed, and an attribute that
166
- # points at another aggregate could only resolve if that aggregate happened
167
- # to be declared earlier in the file. Banking survives on luck: Customer is
168
- # written above Account.
166
+ # Offering a node whole — declared, then its lists, then its children
167
+ # — one sibling at a time would mean an aggregate's attributes are
168
+ # offered before its later siblings exist, so an attribute that
169
+ # points at another aggregate could only resolve if that aggregate
170
+ # happens to be declared earlier in the file: banking would survive
171
+ # on luck, Customer written above Account.
169
172
  #
170
173
  # So siblings are declared in one pass and detailed in a second. It is the
171
174
  # same ordering the walk already used one level down — value objects before
@@ -256,7 +259,7 @@ module Hecks
256
259
  #
257
260
  # the parent link (plan.parent_key) -> `parent_id`, the id
258
261
  # the walk already carries in from one level up
259
- # a walk-minted one (POSITION) -> the walk index itself ;
262
+ # a walk-minted one (`POSITION`) -> the walk index itself ;
260
263
  # never a stored field (Member's own header:
261
264
  # "position is not a mint — it is read straight
262
265
  # out of the source file")
@@ -265,12 +268,12 @@ module Hecks
265
268
  # (Handler's own `event_type`, Dispatch's own
266
269
  # `command_name`)
267
270
  #
268
- # `carried` still decides bare-vs-wrapped the normal way ; POSITION
271
+ # `carried` still decides bare-vs-wrapped the normal way ; `POSITION`
269
272
  # is the one case with no verb to ask `carried` about (`plan.
270
273
  # declare` is always nil for an entity-owned category — Plan#read's
271
274
  # own comment says why), so it is minted straight as a value object,
272
275
  # matching exactly what `declare`'s own field loop already mints a
273
- # POSITION field as.
276
+ # `POSITION` field as.
274
277
  def node_identity(plan, category, node, index, parent_id)
275
278
  plan.identity_paths.each_with_object({}) do |path, fields|
276
279
  head = path.to_s.split(".").first
@@ -384,10 +387,11 @@ module Hecks
384
387
  # Where it sits among its siblings is a fact about the walk, not about the
385
388
  # node : a command does not know it is the third command on its aggregate.
386
389
  # The walk knows, so the walk supplies it, and every other field still
387
- # comes from the node. Declaration order used to survive only because the
388
- # meta store happened to iterate in insertion order — an accident that an
389
- # ask ordered any other way would have taken away, and Reconstruction is
390
- # the one reader that must have the source's order rather than a stable one.
390
+ # comes from the node. Without this, declaration order would survive only
391
+ # because the meta store happens to iterate in insertion order — an
392
+ # accident that an ask ordered any other way would take away, and
393
+ # Reconstruction is the one reader that must have the source's order
394
+ # rather than a stable one.
391
395
  # `private` above has no effect on a constant; kept here anyway,
392
396
  # beside the method that reads it, for the narrative.
393
397
  # rubocop:disable-next Lint/UselessConstantScoping
@@ -654,8 +658,8 @@ module Hecks
654
658
 
655
659
  # Command -> commands, ValueObject -> value_objects, Query -> queries.
656
660
  # Convention, not a table : the IR names a collection after what it holds.
657
- # The pluraliser lives in Naming because there used to be two of them and
658
- # one was wrong — see Naming.plural.
661
+ # The pluraliser lives in Naming, kept to exactly one implementation — a
662
+ # second one risks disagreeing with it — see Naming.plural.
659
663
  def collection_reader(category) = Naming.plural(Naming.snake(category))
660
664
  end
661
665
  end
@@ -3,11 +3,12 @@ module Hecks
3
3
  module MetaValidator
4
4
  # What the language says about itself, read back as something walkable.
5
5
  #
6
- # The judge used to carry one hand-written branch per category, and the
7
- # reason given for keeping it that way was that "which append command
8
- # belongs to which list is not derivable from a name". True — and beside
9
- # the point. It is derivable from the language's own IR, because every
10
- # append command declares its target:
6
+ # ## Why a plan, not a hand-written branch per category
7
+ #
8
+ # "Which append command belongs to which list is not derivable from a
9
+ # name" sounds like a reason to hand-write one branch per category —
10
+ # and is beside the point. It is derivable from the language's own IR,
11
+ # because every append command declares its target:
11
12
  #
12
13
  # command "Argument" do
13
14
  # reference_to Command
@@ -24,7 +25,7 @@ module Hecks
24
25
  # Query / Entity, ValueObject -> Member, ProcessManager -> Handler ->
25
26
  # Dispatch all fall out of the declarations rather than being restated here.
26
27
  #
27
- # Usage:
28
+ # ## Usage
28
29
  #
29
30
  # plan = Plan.for(MetaValidator.grammar_registry)
30
31
  # plan.category("Command").parent # => "Aggregate"
@@ -52,6 +53,10 @@ module Hecks
52
53
  # Every verb this category declares, in declaration order. `alternates`
53
54
  # matters here: two commands can append to one list, and a verb missing
54
55
  # from this is a verb the coverage gate stops watching.
56
+ #
57
+ # @return [Array<String>] every command name this category declares
58
+ # (the creating command, every setter, appender, alternate
59
+ # appender, and sealer), `nil` entries dropped
55
60
  def verbs
56
61
  [declare, *setters.map(&:verb), *appends.values.map(&:verb),
57
62
  *alternates.map(&:verb), *sealers].compact
@@ -61,19 +66,35 @@ module Hecks
61
66
  # an id is a scalar — so the judge offers it bare, where every other
62
67
  # field goes as a one-field value object. The language answers this
63
68
  # about itself, so declaring a new reference needs no change here.
69
+ #
70
+ # @param verb [String] the command name declaring `argument`
71
+ # @param argument [String] the argument name to check
72
+ # @return [Boolean] whether `argument` on `verb` is a reference
64
73
  def references?(verb, argument)
65
74
  Array(references[verb.to_s]).include?(argument.to_s)
66
75
  end
67
76
 
77
+ # Reports whether this category is the root of the containment
78
+ # tree.
79
+ #
80
+ # @return [Boolean] whether this category has no parent — the root
81
+ # of the containment tree
68
82
  def root? = parent.nil?
69
83
  end
70
84
 
85
+ # Reads the language's own self-description off `registry`.
86
+ #
87
+ # @param registry [Runtime::Registry] a registry with the language's
88
+ # own "Bluebook" chapter already judged and assembled
89
+ # @return [Plan] the plan, built from that chapter's own aggregates
71
90
  def self.for(registry)
72
91
  new(registry.bluebook("Bluebook"))
73
92
  end
74
93
 
75
94
  attr_reader :categories
76
95
 
96
+ # @param meta [Bluebook::Chapter] the language's own assembled
97
+ # "Bluebook" chapter
77
98
  def initialize(meta)
78
99
  @categories = {}
79
100
  meta.aggregates.each do |aggregate|
@@ -121,7 +142,17 @@ module Hecks
121
142
  @categories.freeze
122
143
  end
123
144
 
145
+ # Looks up one category by name.
146
+ #
147
+ # @param name [String, Symbol] the category name, such as
148
+ # `"Command"` or `"ValueObject"`
149
+ # @return [Category, nil] the named category, or `nil` if the
150
+ # language declares no such category
124
151
  def category(name) = @categories[name.to_s]
152
+
153
+ # Lists every category this plan holds.
154
+ #
155
+ # @return [Array<String>] every category name this plan holds
125
156
  def names = @categories.keys
126
157
 
127
158
  # Every verb the language declares, spelled as the judge would
@@ -135,6 +166,10 @@ module Hecks
135
166
  # entity-owned category's own parent may itself be entity-
136
167
  # owned), so this stays what it already is: the judge's own
137
168
  # coverage promise, not a second guess at it.
169
+ #
170
+ # @return [Array<String>] every verb the language declares, dotted
171
+ # and prefixed with `"Bluebook::"`, such as `"Bluebook::Aggregate.
172
+ # Command.Argument"`
138
173
  def verbs
139
174
  @categories.flat_map do |name, category|
140
175
  category.verbs.map { |verb| "Bluebook::#{dotted_prefix(name)}.#{verb}" }
@@ -218,8 +253,8 @@ module Hecks
218
253
  # than restated. The judge has to know a record's id before it
219
254
  # dispatches, because the children it walks next carry it as their
220
255
  # parent — so it derives the same join the runtime will, off the same
221
- # declaration. It used to be a branch per category, and a branch that
222
- # disagreed with the runtime by one separator was a broken reference.
256
+ # declaration: a hand-written branch per category, disagreeing with
257
+ # the runtime by even one separator, would be a broken reference.
223
258
  identity_paths: aggregate.identity_paths,
224
259
  entity_owned: entity_owned
225
260
  )
@@ -11,6 +11,7 @@ module Hecks
11
11
  class PortJudge
12
12
  attr_reader :refusals
13
13
 
14
+ # @param port [Bluebook::Port] the built port to judge
14
15
  def initialize(port)
15
16
  @port = port
16
17
  @refusals = []
@@ -26,6 +26,13 @@ module Hecks
26
26
  # every one of them was a fact `Assembly::Contracts` is the right place to
27
27
  # keep: which shaper turns this list into rows. A list with no shaper reads
28
28
  # straight off the node, which is most of them.
29
+ #
30
+ # @param category [String] the node's own construct category, such
31
+ # as `"Command"` or `"ValueObject"`
32
+ # @param list_name [String] the appendable list's own field name
33
+ # @param node [Object] the built IR node being judged
34
+ # @return [Array<Hash>] one row per element of `node`'s own
35
+ # `list_name`, shaped by the category's own shaper if it has one
29
36
  def rows_for(category, list_name, node)
30
37
  shaper = Assembly.contract(category).shaper(list_name)
31
38
  return Array(node.public_send(list_name)) unless shaper
@@ -37,20 +44,39 @@ module Hecks
37
44
  # symbol argument as ":ceiling". Reading the object instead lost the colon,
38
45
  # and nothing downstream could tell an argument from a literal of the same
39
46
  # name.
47
+ #
48
+ # @param node [Object] the built IR node being judged (a `Query` or
49
+ # `ReadModel`)
50
+ # @return [Array<Hash{Symbol => Object}>] each declared `where`
51
+ # clause's own fields
40
52
  def where_rows(node) = Array(node.wheres).map(&:to_h)
41
53
 
42
54
  # The language holds a value object's name here ; the IR holds the object.
55
+ #
56
+ # @param node [Object] the built IR node being judged (an
57
+ # `Aggregate`)
58
+ # @return [Array<Hash{Symbol => String}>] `{name: ...}` for every
59
+ # declared value object
43
60
  def value_object_names(node) = node.value_objects.map { |shape| { name: shape.hecks_name } }
44
61
 
45
62
  # An identity is a list of parts, so it is offered one part at a time — the
46
63
  # same way attributes and transitions are. The IR holds the paths ; the
47
64
  # language holds a row per path, and the order between them is the whole
48
65
  # meaning, because the identity is their join.
66
+ #
67
+ # @param node [Object] the built IR node being judged (an
68
+ # `Aggregate` or `Entity`)
69
+ # @return [Array<Hash{Symbol => String}>] `{value: ...}` for every
70
+ # identity path part, in order
49
71
  def identity_rows(node) = node.identity_paths.map { |path| { value: path } }
50
72
 
51
73
  # Through to_h, which is where Bluebook.render_value spells a symbol argument as
52
74
  # ":source". The raw with_spec lost the colon, and a binding that reads an
53
75
  # argument became indistinguishable from one carrying a literal string.
76
+ #
77
+ # @param node [Object] the built IR node being judged (a `Dispatch`)
78
+ # @return [Array<Hash{Symbol => Object}>] `pair_rows`' own return
79
+ # for `node`'s own `with_spec`
54
80
  def with_spec_rows(node) = pair_rows(node.to_h[:with_spec])
55
81
 
56
82
  # The same read, one level in — `compensates` folds `DispatchSpec`
@@ -59,19 +85,40 @@ module Hecks
59
85
  # "Dispatch"), so its own with_spec pairs live nested one hash
60
86
  # down from where `with_spec_rows` looks. `&.dig(...)` — no
61
87
  # compensation at all is not an error, it is `pair_rows(nil)`, empty.
88
+ #
89
+ # @param node [Object] the built IR node being judged (a `Dispatch`)
90
+ # @return [Array<Hash{Symbol => Object}>] `pair_rows`' own return
91
+ # for `node`'s own `compensates.with_spec`, `[]` when `node` has
92
+ # no compensation
62
93
  def compensates_with_spec_rows(node) = pair_rows(node.to_h[:compensates]&.dig(:with_spec))
63
94
 
64
95
  # A read model carries the same options an ask does, plus its filters — see
65
96
  # option_rows.
97
+ #
98
+ # @param node [Object] the built IR node being judged (a
99
+ # `ReadModel`)
100
+ # @return [Array<Hash{Symbol => Object}>] `option_rows`' own return,
101
+ # with filters included
66
102
  def read_model_option_rows(node) = option_rows(node, filters: true)
67
103
 
68
104
  # The canonical-form table is the expression grammar's, not this chapter's, so
69
105
  # the node is not consulted at all.
106
+ #
107
+ # @param _node [Object] unused; every node shares the one table
108
+ # @return [Array<Hash{Symbol => Object}>] `normalisation_rows`' own
109
+ # return
70
110
  def normalisation_table(_node) = normalisation_rows
71
111
 
72
112
  # `lifecycle :status do transition "Retire" => "retired", from: ["issued", "active"] end`
73
113
  # is one declaration and two transitions. Offering it once would leave the
74
114
  # second unjudged, which is the whole failure this judge exists to avoid.
115
+ #
116
+ # @param node [Object] the built IR node being judged (an
117
+ # `Aggregate` or `Entity`)
118
+ # @return [Array<Hash{Symbol => Object}>] `:command`, `:from_state`,
119
+ # and `:to_state`, one row per declared `from` state (or one row
120
+ # with a `nil` `:from_state` for an unconstrained transition);
121
+ # `[]` if `node` declares no lifecycle
75
122
  def transition_rows(node)
76
123
  lifecycle = node.respond_to?(:lifecycle) ? node.lifecycle : nil
77
124
  return [] unless lifecycle
@@ -87,6 +134,10 @@ module Hecks
87
134
  # An open map — a member's fields, a dispatch's argument bindings — has no
88
135
  # value object that can hold it, so each entry becomes its own row. This is
89
136
  # why Member and Dispatch are roots in the language rather than lists.
137
+ #
138
+ # @param map [Hash, nil] the open map to flatten, or `nil`
139
+ # @return [Array<Hash{Symbol => Object}>] `{key:, value:}` for every
140
+ # entry, `[]` when `map` is `nil`
90
141
  def pair_rows(map)
91
142
  Array(map&.to_h).map { |key, value| { key: key, value: value } }
92
143
  end
@@ -125,6 +176,14 @@ module Hecks
125
176
  #
126
177
  # Named `wheres`, `order_by` and `limit` so they gather back into exactly the
127
178
  # declaration keys the assembly already reads.
179
+ #
180
+ # @param node [Object] the built IR node being judged (a `Query` or
181
+ # `ReadModel`)
182
+ # @param filters [Boolean] whether to also flatten `node`'s own
183
+ # `wheres`/`order_by`/`limit`
184
+ # @return [Array<Hash{Symbol => Object}>] `[]` if `node` answers no
185
+ # `extra_options_to_h`; otherwise `parts`' own rows for every
186
+ # option `node` carries
128
187
  def option_rows(node, filters: false)
129
188
  return [] unless node.respond_to?(:extra_options_to_h)
130
189
 
@@ -139,6 +198,14 @@ module Hecks
139
198
  end
140
199
  end
141
200
 
201
+ # Reads a read model's own filtering fields, in the shape
202
+ # `option_rows` expects to flatten alongside its ordinary options.
203
+ #
204
+ # @param node [Object] the built IR node being judged (a
205
+ # `ReadModel`)
206
+ # @return [Hash{Symbol => Object}] `:wheres` (`Array<Hash>`),
207
+ # `:order_by`, and `:limit` (each `Hash` or absent), absent keys
208
+ # dropped
142
209
  def filter_options(node)
143
210
  {
144
211
  wheres: Array(node.wheres).map(&:to_h),
@@ -147,6 +214,17 @@ module Hecks
147
214
  }.reject { |_, held| held.nil? || held == [] }
148
215
  end
149
216
 
217
+ # Flattens one option's own value (or one element of a repeated
218
+ # option) into key/value rows.
219
+ #
220
+ # @param option [Symbol] the option's own name
221
+ # @param held [Object] the option's own value (a Hash-coercible
222
+ # object) — one repetition's worth, for a repeated option
223
+ # @param at [Integer, nil] the repetition index, for a repeated
224
+ # option; `nil` otherwise
225
+ # @return [Array<Hash{Symbol => String, Object}>] `:option`, `:key`,
226
+ # `:value`, and `:at` (stringified, `nil` when `at` is `nil`), one
227
+ # per key of `held`
150
228
  def parts(option, held, at)
151
229
  Hash(held).map do |key, value|
152
230
  { option: option.to_s, key: key.to_s, value: value, at: at&.to_s }
@@ -158,9 +236,14 @@ module Hecks
158
236
  # (`append: { name: :name, amount: :amount }`). So an append is offered
159
237
  # once per binding, and each one is judged.
160
238
  #
161
- # The judge used to send `field: v(""), kind: v("argument"), source: v("")`
162
- # here — three stubbed values, so every rule about what a mutation reads
163
- # was being handed a blank and could never refuse.
239
+ # Sending `field: v(""), kind: v("argument"), source: v("")` here
240
+ # instead — three stubbed values — would hand every rule about what
241
+ # a mutation reads a blank, so it could never refuse.
242
+ #
243
+ # @param node [Object] the built IR node being judged (a `Command`)
244
+ # @return [Array<Hash{Symbol => Object}>] one row per binding for an
245
+ # append/delegate/corrects mutation, or `set_row`'s own single-row
246
+ # array for a set/increment/decrement
164
247
  def mutation_rows(node)
165
248
  Array(node.mutations).flat_map do |mutation|
166
249
  # `:delegate`/`:corrects` (CommandBuilder#delegates_to's and
@@ -185,6 +268,11 @@ module Hecks
185
268
 
186
269
  # A set/increment/decrement reads one thing: a command argument, or a
187
270
  # literal written into the bluebook.
271
+ #
272
+ # @param mutation [Bluebook::Mutation] a set/increment/decrement
273
+ # mutation
274
+ # @return [Array(Hash{Symbol => Object})] a single-element array
275
+ # holding `:target`, `:op`, `:field`, `:kind`, and `:source`
188
276
  def set_row(mutation)
189
277
  classified = mutation.to_h[:source] || {}
190
278
 
@@ -196,6 +284,10 @@ module Hecks
196
284
  # The normalisation table belongs to the expression grammar, not to any one
197
285
  # bluebook — it is how the canonical form of a rule is spelled. The language
198
286
  # models it because a bluebook's rules are canonicalised on the way in.
287
+ #
288
+ # @return [Array<Hash{Symbol => Object}>] one row per admitted
289
+ # normalisation rule (`:strategy`, `:source_token`, `:replacement`,
290
+ # `:boundary`, `:position`), `[]` if the table cannot be read
199
291
  def normalisation_rows
200
292
  table = Expression::CanonicalForm.table
201
293
  return [] unless table
@@ -217,10 +309,12 @@ module Hecks
217
309
 
218
310
  # What the bluebook calls a node, whichever kind of thing the node is.
219
311
  #
220
- # This used to sniff — `respond_to?(:hecks_name) ? … : node.name` — because
221
- # only value objects had crossed over. Every construct answers now, so
222
- # there is nothing to choose between. It disappears entirely when the DSL
223
- # stops handing the judge nodes at all.
312
+ # Every construct answers `hecks_name` now, so there is nothing to sniff
313
+ # (`respond_to?(:hecks_name) ? … : node.name`) or choose between. This
314
+ # disappears entirely when the DSL stops handing the judge nodes at all.
315
+ #
316
+ # @param node [Object] the built IR node being judged
317
+ # @return [String] the node's own declared name
224
318
  def declared_name(node) = node.hecks_name
225
319
 
226
320
  # One field of a Declare payload. Mostly a reader of the same name — the
@@ -233,6 +327,18 @@ module Hecks
233
327
  # `:parent` there ; a folded field names the object and member it lives in.
234
328
  # So the exceptions are looked up rather than repeated, and a new fold is one
235
329
  # line in one file instead of two lines in two.
330
+ #
331
+ # @param category [String] the node's own construct category, such
332
+ # as `"Command"` or `"ValueObject"`
333
+ # @param node [Object] the built IR node being judged
334
+ # @param field [Symbol] the Declare payload field to read
335
+ # @param parent_id [String] the id the containment walk carries in
336
+ # from one level up, offered when `field` is the parent pointer
337
+ # @return [Object] `field`'s own value: `node`'s declared name,
338
+ # `parent_id`, a folded member's value (`through`), the read
339
+ # model's own limit value, an encoded literal (`provenance`), or
340
+ # `node.public_send(field)`; `nil` if `node` does not respond to
341
+ # `field`
236
342
  def field_value(category, node, field, parent_id)
237
343
  return declared_name(node) if field == :name
238
344
 
@@ -271,6 +377,14 @@ module Hecks
271
377
  # member names are the ones the IR spells — a Lifecycle's `default`, an
272
378
  # OrderBy's `direction` — and reading the object raw is how a colon or a type
273
379
  # goes missing.
380
+ #
381
+ # @param node [Object] the built IR node being judged
382
+ # @param object [Symbol] the member object's own field name on
383
+ # `node`, such as `:lifecycle`
384
+ # @param member [Symbol] the field to read off that object, such as
385
+ # `:default`; `:transitions` returns the object itself
386
+ # @return [Object, nil] the member's own value, or `nil` if `node`
387
+ # does not respond to `object` or that object is absent
274
388
  def through(node, object, member)
275
389
  held = node.respond_to?(object) ? node.public_send(object) : nil
276
390
  return nil unless held
@@ -281,6 +395,13 @@ module Hecks
281
395
  # What a setting command writes. A setter whose source is absent is not
282
396
  # dispatched at all — absent is not empty, and offering "" would turn every
283
397
  # "if you declare it, declare something" rule into "you must declare it".
398
+ #
399
+ # @param category [String] the node's own construct category
400
+ # @param node [Object] the built IR node being judged
401
+ # @param target [Symbol] the setter's own target field
402
+ # @return [Object, nil] the target's own value: the closed set's own
403
+ # row count, a folded member's value (`through`), or
404
+ # `node.public_send(target)`; `nil` if absent
284
405
  def setter_value(category, node, target)
285
406
  # `rows` folds into `closed_set` and `members` between them, with no single
286
407
  # member to name, so it keeps its own reading — see Contract#folded.
@@ -294,6 +415,11 @@ module Hecks
294
415
 
295
416
  # Only a declared closed set has a row count. An empty one is the defect,
296
417
  # so `rows` must stay absent rather than arrive as zero.
418
+ #
419
+ # @param node [Object] the built IR node being judged (a
420
+ # `ValueObject`)
421
+ # @return [Integer, nil] the closed set's own row count, or `nil` if
422
+ # `node` is not a declared closed set
297
423
  def closed_set_size(node)
298
424
  return nil unless node.respond_to?(:closed_set?) && node.closed_set?
299
425
 
@@ -305,6 +431,12 @@ module Hecks
305
431
  # that head's ID, and resolution does the rest. Encoding and decoding both
306
432
  # live here, because this is where the IR's shape differs from the
307
433
  # language's and nowhere else should know the spelling.
434
+ #
435
+ # @param row [Bluebook::Attribute] the attribute being offered
436
+ # @param aggregate_id [String] the owning head's own id, read for
437
+ # its chapter prefix
438
+ # @return [String, nil] the target head's own id, or `nil` if `row`
439
+ # is not a `reference_to` attribute
308
440
  def points_at(row, aggregate_id)
309
441
  return nil unless row.reference?
310
442
 
@@ -331,15 +463,33 @@ module Hecks
331
463
  #
332
464
  # nil stays nil rather than becoming "nil": absent is a real answer here,
333
465
  # and the language's own field is optional.
466
+ #
467
+ # @param value [Object] the value to encode: `nil`, `Symbol`,
468
+ # `String`, `Hecks::StateRef`, `true`, `false`, `Integer`, `Float`,
469
+ # `Hash`, or `Array` (recursively)
470
+ # @return [String, nil] the self-describing wire spelling, or `nil`
471
+ # when `value` is `nil`
334
472
  def encode_literal(value) = value.nil? ? nil : Literal.render(value)
335
473
 
336
474
  # The way back out: an aggregate id becomes the type the IR spells. The
337
475
  # id is a join of chapter + name (Naming::IDENTITY_JOIN, the same join
338
476
  # `points_at` built it with, not the "::" a real bluebook's own type
339
477
  # names never carry) ; the wire format wants only the bare name.
478
+ #
479
+ # @param points_at_id [String] a target head's own id, as `points_at`
480
+ # built it
481
+ # @return [String] `"Reference<Name>"`, the bare target name
340
482
  def reference_type(points_at_id) = "Reference<#{points_at_id.to_s.split(Naming::IDENTITY_JOIN).last}>"
341
483
 
342
484
  # One value out of a row, named by the value object's field.
485
+ #
486
+ # @param row [Hash, Object] the row to read: a Hash, an object
487
+ # answering `field` directly, an object answering `to_h`, or a
488
+ # bare scalar
489
+ # @param field [Symbol] the field name to read
490
+ # @return [Object] `row[field]` for a Hash, `row.public_send(field)`
491
+ # when `row` answers `field` directly, `row.to_h[field]` for
492
+ # anything else to_h-able, or `row` itself for a bare scalar
343
493
  def row_value(row, field)
344
494
  # A Hash first. Hash answers to `key` (Hash#key(value)) and to `value` on
345
495
  # some rows, so asking respond_to? before checking for a Hash reads a