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
@@ -8,6 +8,8 @@ module Hecks
8
8
  # IR it stores must equal the IR the DSL builder produces". The judge is the
9
9
  # first half. This reads the records back and assembles `to_h`.
10
10
  #
11
+ # ## One table, both directions
12
+ #
11
13
  # It is the inverse of the walk and shares its plan: the walk reads a node's
12
14
  # lists through the command that appends to each, and this reads them back out
13
15
  # of the rows those commands wrote. The retired `experiment/replay.rb` needed
@@ -17,6 +19,12 @@ module Hecks
17
19
  # This file is the traversal. The hashes at its tips, and the encodings they
18
20
  # undo, are in Shapes.
19
21
  #
22
+ # The parent key of every level is read from the language's own plan, the same
23
+ # Plan the walk dispatches from — so the two directions really are one table,
24
+ # which is what the header above has always claimed.
25
+ #
26
+ # ## Reads level by level, not through the read model
27
+ #
20
28
  # It reads level by level, through `DeclaredIn`, and not through the read
21
29
  # model — which is the difference between a reconstruction that can be the
22
30
  # source and one that can only be a check.
@@ -31,9 +39,7 @@ module Hecks
31
39
  # `DeclaredIn` preserves it (spec/executes_spec says so), so this asks each
32
40
  # level for its own children rather than filtering one sorted gather.
33
41
  #
34
- # The parent key of every level is read from the language's own plan, the same
35
- # Plan the walk dispatches from — so the two directions really are one table,
36
- # which is what the header above has always claimed.
42
+ # ## What it cannot rebuild
37
43
  #
38
44
  # What it cannot rebuild matters as much as what it can, and
39
45
  # spec/round_trip_spec pins the difference as an exact set: a field the language
@@ -43,13 +49,27 @@ module Hecks
43
49
  include Readings
44
50
  include Shapes
45
51
 
52
+ # Reads one judged chapter back out of `runtime` and assembles it.
53
+ #
54
+ # @param runtime [Runtime::Dispatcher] the dispatcher holding the
55
+ # judge's own records
56
+ # @param chapter [String] the chapter's own `hecks_name`
57
+ # @return [Hash{Symbol => Object}] the reconstructed IR, in the
58
+ # shape the DSL builder's own `to_h` produces
59
+ # @raise [Runtime::NotFound] if `runtime` holds no bluebook named
60
+ # `chapter`
46
61
  def self.of(runtime, chapter) = new(runtime, chapter).to_h
47
62
 
63
+ # @param runtime [Runtime::Dispatcher] the dispatcher holding the
64
+ # judge's own records
65
+ # @param chapter [String] the chapter's own `hecks_name`
66
+ # @raise [Runtime::NotFound] if `runtime` holds no bluebook named
67
+ # `chapter`
48
68
  def initialize(runtime, chapter)
49
69
  @runtime = runtime
50
70
  @plan = Plan.for(MetaValidator.grammar_registry)
51
71
  @chapter = runtime.query("Bluebook::Bluebook.Called", name: { value: chapter }).first or
52
- raise NotFound, "the meta-domain holds no bluebook called #{chapter.inspect}"
72
+ raise Runtime::NotFound, "the meta-domain holds no bluebook called #{chapter.inspect}"
53
73
  end
54
74
 
55
75
  def to_h
@@ -14,6 +14,14 @@ module Hecks
14
14
  module Shapes
15
15
  # The type came in as the id of what it names, so it goes back out as the
16
16
  # name — or, for another aggregate's head, as the encoding the IR spells.
17
+ #
18
+ # @param field [Hash{Symbol => Object}] one dispatched attribute row
19
+ # @param aggregate_id [String] the owning aggregate's own id, to
20
+ # strip from an owned value-object type
21
+ # @return [Hash{Symbol => Object}] `:name` (`Symbol`, `nil`),
22
+ # `:type` (`String`), `:list`/`:optional` (`Boolean`), `:default`
23
+ # (decoded literal, `nil` when absent), `:pattern`/`:admits`/
24
+ # `:relationship` (`String`, `nil` when absent)
17
25
  def attribute(field, aggregate_id)
18
26
  type = text(field[:type]).to_s
19
27
 
@@ -39,6 +47,12 @@ module Hecks
39
47
  # A type this aggregate owns, offered as its id and read back as its name.
40
48
  # Prefixed with the owner and the identity join, because that is the value
41
49
  # object's identity — the aggregate it belongs to, then its name.
50
+ #
51
+ # @param type [String] the attribute's own dispatched `type` cell
52
+ # @param aggregate_id [String] the owning aggregate's own id
53
+ # @return [String, nil] `type` with the owner's own identity prefix
54
+ # stripped, or `nil` if `type` does not carry that prefix (an
55
+ # ordinary type name, or a cross-aggregate reference)
42
56
  def owned_type(type, aggregate_id)
43
57
  prefix = Naming.identity([aggregate_id, ""])
44
58
  return nil unless type.start_with?(prefix)
@@ -49,6 +63,9 @@ module Hecks
49
63
  # One part of an identity, read back as the path it went in as. The inverse
50
64
  # of `Marks#identity_path`, and named the same so the two directions read
51
65
  # as one table.
66
+ #
67
+ # @param part [Hash{Symbol => Object}] one dispatched identity-part row
68
+ # @return [String] the identity part's own path text
52
69
  def identity_path(part) = text(part[:value]).to_s
53
70
 
54
71
  # An argument, a parameter, a piece's attribute — the three places an
@@ -76,6 +93,15 @@ module Hecks
76
93
  # ordinary type names something declared beside it (`Money`,
77
94
  # `AccountNumber`) and never carries a chapter, while a head's id
78
95
  # always does.
96
+ #
97
+ # @param field [Hash{Symbol => Object}] one dispatched attribute,
98
+ # argument, or parameter row
99
+ # @param aggregate_id [String, nil] the piece's own owning
100
+ # aggregate id, passed only for a piece's own attribute
101
+ # @return [Hash{Symbol => Object}] `:name` (`Symbol`, `nil`),
102
+ # `:type` (`String`), `:list`/`:optional` (`Boolean`), `:default`
103
+ # (decoded literal, `nil` when absent), `:pattern`/`:admits`/
104
+ # `:relationship` (`String`, `nil` when absent)
79
105
  def shape_field(field, aggregate_id = nil)
80
106
  return attribute(field, aggregate_id) if aggregate_id
81
107
 
@@ -102,6 +128,11 @@ module Hecks
102
128
  # is a real regex (it matches everything), so keeping it would turn
103
129
  # "no pattern" into "a pattern that always passes" and quietly cost the
104
130
  # IR its round trip.
131
+ #
132
+ # @param text [String, nil] a dispatched cell's own already-unwrapped
133
+ # text
134
+ # @return [String, nil] `text`, or `nil` when `text` is `nil` or
135
+ # empty
105
136
  def presence(text)
106
137
  value = text.to_s
107
138
  value.empty? ? nil : value
@@ -113,13 +144,29 @@ module Hecks
113
144
  # String, Integer, Float, TrueClass, FalseClass — plus a symbol, and an
114
145
  # object literal, which is what `to: { value: "good" }` is: a value object's
115
146
  # fields written inline.
147
+ #
148
+ # @param text [String, nil] the self-describing wire spelling
149
+ # `Readings#encode_literal` wrote
150
+ # @return [Object] `nil`, `true`, `false`, `Integer`, `Float`,
151
+ # `Symbol`, `Hecks::StateRef`, `String`, `Hash`, or `Array` — or
152
+ # `text` itself, stripped, when it matches no known spelling
116
153
  def decode_literal(text) = Literal.read(text)
117
154
 
155
+ # Reads a rule's plain description/canonical pair, for a
156
+ # `given`/`invariant`/`ensures`.
157
+ #
158
+ # @param row [Hash{Symbol => Object}] one dispatched rule row
159
+ # @return [Hash{Symbol => String}] `:description` and `:canonical`
118
160
  def rule(row) = { description: text(row[:description]), canonical: text(row[:canonical]) }
119
161
 
120
162
  # S12, ADR 0025 — `projects :name, from: :"reference.remote_field"`,
121
163
  # read back the same three plain identifiers `rule` above reads
122
164
  # description/canonical as.
165
+ #
166
+ # @param row [Hash{Symbol => Object}] one dispatched projected-field
167
+ # row
168
+ # @return [Hash{Symbol => String}] `:name`, `:reference`, and
169
+ # `:remote_field`
123
170
  def projected_field(row)
124
171
  { name: text(row[:name]), reference: text(row[:reference]), remote_field: text(row[:remote_field]) }
125
172
  end
@@ -128,16 +175,30 @@ module Hecks
128
175
  # does — an object literal, self-describing via Hecks::Literal — one
129
176
  # level up: a whole keyword's argument rather than an attribute's
130
177
  # `default:`.
178
+ #
179
+ # @param row [Hash{Symbol => Object}] a dispatched row carrying a
180
+ # `:provenance` cell
181
+ # @return [Object] the decoded `from:` object literal, or `nil` when
182
+ # absent
131
183
  def provenance(row) = decode_literal(text(row[:provenance]))
132
184
 
133
185
  # `command "Debit", from: "open"` — the same literal encoding
134
186
  # `provenance`/`default:` already ride (S10, ADR 0025), one
135
187
  # state or an array of them, or nil for a command with no
136
188
  # lifecycle guard.
189
+ #
190
+ # @param row [Hash{Symbol => Object}] a dispatched row carrying a
191
+ # `:from` cell
192
+ # @return [String, Array<String>, nil] the decoded lifecycle guard
193
+ # state(s), or `nil` for a command with no lifecycle guard
137
194
  def from(row) = decode_literal(text(row[:from]))
138
195
 
139
196
  # A flag is held as text ("true"/"false") and emitted as a boolean —
140
197
  # `Policy#expect_undelivered` on the wire.
198
+ #
199
+ # @param row [Hash{Symbol => Object}] a dispatched row carrying an
200
+ # `:expect_undelivered` cell
201
+ # @return [Boolean] whether the flag reads `"true"`
141
202
  def expect_undelivered?(row) = text(row[:expect_undelivered]).to_s == "true"
142
203
 
143
204
  # The option rows, gathered back into the shapes `extra_options_to_h` spells.
@@ -146,12 +207,25 @@ module Hecks
146
207
  # several groups told apart by `at`. Grouping by option name and then by `at`
147
208
  # rebuilds both without either knowing which options exist — the whole point
148
209
  # of holding them as an open map.
210
+ #
211
+ # @param row [Hash{Symbol => Object}] a dispatched row carrying an
212
+ # `:options` list
213
+ # @return [Hash{Symbol => Object}] one entry per option name; each
214
+ # value is `gathered`'s own return for that option's own parts
149
215
  def options_of(row)
150
216
  Array(row[:options])
151
217
  .group_by { |part| text(part[:option]) }
152
218
  .to_h { |option, parts| [option.to_sym, gathered(parts)] }
153
219
  end
154
220
 
221
+ # Groups one option's own dispatched parts back into a single
222
+ # binding, or several `at`-keyed groups for a repeated option.
223
+ #
224
+ # @param parts [Array<Hash{Symbol => Object}>] one option's own
225
+ # dispatched key/value/at rows
226
+ # @return [Hash{Symbol => String}, Array<Hash{Symbol => String}>]
227
+ # a single `key => value` hash when no part carries `at`;
228
+ # otherwise an array of such hashes, one per distinct `at`
155
229
  def gathered(parts)
156
230
  repeated, single = parts.partition { |part| !text(part[:at]).to_s.empty? }
157
231
  return single.to_h { |part| [text(part[:key]).to_sym, text(part[:value])] } if repeated.empty?
@@ -170,11 +244,20 @@ module Hecks
170
244
  # back out as the plain string "ceiling", indistinguishable from a
171
245
  # literal of the same name. One decode, at the one place that builds
172
246
  # the object every comparator actually reads.
247
+ #
248
+ # @param row [Hash{Symbol => Object}] one dispatched where-clause row
249
+ # @return [Hash{Symbol => String}] `:field`, `:op`, and `:value`
250
+ # (raw text, undecoded)
173
251
  def where_clause(row)
174
252
  { field: text(row[:field]), op: text(row[:op]), value: text(row[:value]) }
175
253
  end
176
254
 
177
255
  # One object in the IR, two fields in the language.
256
+ #
257
+ # @param row [Hash{Symbol => Object}] a dispatched row carrying
258
+ # `:order_field`/`:order_way` cells
259
+ # @return [Hash{Symbol => String}, nil] `:field` and `:direction`,
260
+ # or `nil` when no `order_field` was declared
178
261
  def order_by(row)
179
262
  field = text(row[:order_field])
180
263
  return nil if field.to_s.empty?
@@ -182,6 +265,12 @@ module Hecks
182
265
  { field: field, direction: text(row[:order_way]) }
183
266
  end
184
267
 
268
+ # Reads a read model's own declared row limit.
269
+ #
270
+ # @param row [Hash{Symbol => Object}] a dispatched row carrying a
271
+ # `:limit` cell
272
+ # @return [Hash{Symbol => String}, nil] `{value: ceiling}`, or `nil`
273
+ # when no limit was declared
185
274
  def limit(row)
186
275
  ceiling = text(row[:limit])
187
276
  return nil if ceiling.to_s.empty?
@@ -189,6 +278,12 @@ module Hecks
189
278
  { value: ceiling }
190
279
  end
191
280
 
281
+ # Reads one lifecycle transition row.
282
+ #
283
+ # @param row [Hash{Symbol => Object}] one dispatched lifecycle
284
+ # transition row
285
+ # @return [Hash{Symbol => String}] `:command`, `:from_state`, and
286
+ # `:to_state`
192
287
  def transition(row)
193
288
  {
194
289
  command: text(row[:command]),
@@ -197,6 +292,13 @@ module Hecks
197
292
  }
198
293
  end
199
294
 
295
+ # Reads one reference head — a process manager handler's own
296
+ # aggregate binding.
297
+ #
298
+ # @param row [Hash{Symbol => Object}] one dispatched reference-head
299
+ # row
300
+ # @return [Hash{Symbol => Object}] `:aggregate`/`:as` (`String`) and
301
+ # `:many` (`Boolean`)
200
302
  def head(row)
201
303
  {
202
304
  aggregate: text(row[:aggregate]),
@@ -207,6 +309,11 @@ module Hecks
207
309
  }
208
310
  end
209
311
 
312
+ # Reads a read model's own `group_by` field name.
313
+ #
314
+ # @param row [Hash{Symbol => Object}] a dispatched row carrying a
315
+ # `:field` cell
316
+ # @return [Hash{Symbol => String}] `{field: ...}`
210
317
  def group_by_field(row) = { field: text(row[:field]) }
211
318
 
212
319
  # `count`'s own boolean, read back the same way `head`'s own
@@ -219,6 +326,11 @@ module Hecks
219
326
  # `true`/`nil` pair (never `false`) exactly, rather than the
220
327
  # unconditional `true`/`false` `head`'s own `many` needs (every
221
328
  # head does get a `Gather` dispatch, declared or derived).
329
+ #
330
+ # @param row [Hash{Symbol => Object}] a dispatched row carrying a
331
+ # `:count` cell
332
+ # @return [Boolean, nil] `true` when the flag reads `"true"`, `nil`
333
+ # for an undeclared read model's own `count`
222
334
  def read_model_count(row) = (true if text(row[:count]).to_s == "true")
223
335
 
224
336
  # The append flattening, in reverse.
@@ -227,6 +339,11 @@ module Hecks
227
339
  # field/kind/source triple, so the walk offers an append once per binding.
228
340
  # Rebuilding groups those rows back into the single mutation the IR keeps —
229
341
  # the only place here that undoes something rather than simply reading it.
342
+ #
343
+ # @param row [Hash{Symbol => Object}] a dispatched row carrying a
344
+ # `:mutations` list
345
+ # @return [Array<Hash{Symbol => Object}>] one hash per distinct
346
+ # `target`/`op` pair, each `mutation`'s own return
230
347
  def mutations(row)
231
348
  Array(row[:mutations])
232
349
  .group_by { |change| [text(change[:target]), text(change[:op])] }
@@ -242,6 +359,16 @@ module Hecks
242
359
  # Mutation's own `to_h` lambda does, rather than leaving the key
243
360
  # silently absent (spec/round_trip_spec's whole point: a field the
244
361
  # language does not hold is a named gap, not a byte-for-byte one).
362
+ #
363
+ # @param target [String] the mutated field's own name
364
+ # @param oper [String] the mutation's operation name, such as
365
+ # `"set"`, `"append"`, `"delegate"`, or `"corrects"`
366
+ # @param bindings [Array<Hash{Symbol => Object}>] this target/op
367
+ # pair's own dispatched binding rows
368
+ # @return [Hash{Symbol => Object}] `:target`/`:op` (`Symbol`),
369
+ # `:sign` (`String`), plus `:fields` (`appended`'s own return) for
370
+ # `"append"`/`"delegate"`/`"corrects"`, or `:source`
371
+ # (`classified`'s own return) for anything else
245
372
  def mutation(target, oper, bindings)
246
373
  base = { target: target.to_sym, op: oper.to_sym, sign: Hecks::Bluebook::Mutation.sign_for(oper) }
247
374
  # `:delegate`/`:corrects` (CommandBuilder#delegates_to's and
@@ -252,10 +379,24 @@ module Hecks
252
379
  base.merge(source: classified(bindings.first))
253
380
  end
254
381
 
382
+ # Rebuilds an append's own field -> source map from its flattened
383
+ # per-binding rows.
384
+ #
385
+ # @param bindings [Array<Hash{Symbol => Object}>] one append's own
386
+ # dispatched binding rows
387
+ # @return [Hash{Symbol => String}] each bound field name mapped to
388
+ # its own source text
255
389
  def appended(bindings)
256
390
  bindings.to_h { |binding| [text(binding[:field]).to_sym, text(binding[:source])] }
257
391
  end
258
392
 
393
+ # Classifies a single-binding mutation's own source.
394
+ #
395
+ # @param binding [Hash{Symbol => Object}] one dispatched binding row
396
+ # @return [Hash{Symbol => Object}] `{kind: "argument"|"state",
397
+ # name: String}` for an argument or state reference, or
398
+ # `{kind: "literal", value: Object}` (the decoded literal) for
399
+ # anything else
259
400
  def classified(binding)
260
401
  kind = text(binding[:kind])
261
402
  value = text(binding[:source])
@@ -10,23 +10,27 @@ module Hecks
10
10
  # coercion/lifecycle-guard door every other command goes through),
11
11
  # not merely declared and never exercised.
12
12
  #
13
- # **The source stays static**. Aggregate-local `KeywordSeed`/
14
- # `ArgumentSeed` value objects (still hand-written `member` rows —
15
- # now beside the concepts they spell) are what gets written ; this
16
- # is what turns them into what gets read. `Judge`/`Reconstruction`
17
- # already draw exactly this line everywhere else in the meta-domain
18
- # (a chapter's own declarations versus what gets dispatched from
19
- # them) — this runs the same distinction one level further out, for
20
- # the language's own grammar table.
13
+ # ## The source stays static
21
14
  #
22
- # A dedicated runtime, not `MetaValidator.fresh_runtime`. That one
23
- # is reserved for `Judge`'s own bootstrap (dispatching a chapter's
24
- # declarations into the meta-domain's grammar) — a different act
25
- # from this one (dispatching the meta-domain's own grammar table
26
- # data into a live "Bluebook" domain instance). Sharing the runtime
27
- # would let one boot's own repository state leak into the other's.
15
+ # Aggregate-local `KeywordSeed`/`ArgumentSeed` value objects (still
16
+ # hand-written `member` rows — now beside the concepts they spell)
17
+ # are what gets written ; this is what turns them into what gets
18
+ # read. `Judge`/`Reconstruction` already draw exactly this line
19
+ # everywhere else in the meta-domain (a chapter's own declarations
20
+ # versus what gets dispatched from them) — this runs the same
21
+ # distinction one level further out, for the language's own grammar
22
+ # table.
28
23
  #
29
- # Usage:
24
+ # ## A dedicated runtime
25
+ #
26
+ # Not `MetaValidator.fresh_runtime`. That one is reserved for
27
+ # `Judge`'s own bootstrap (dispatching a chapter's declarations into
28
+ # the meta-domain's grammar) — a different act from this one
29
+ # (dispatching the meta-domain's own grammar table data into a live
30
+ # "Bluebook" domain instance). Sharing the runtime would let one
31
+ # boot's own repository state leak into the other's.
32
+ #
33
+ # ## Usage
30
34
  #
31
35
  # MetaValidator.syntax_table # => { keywords: [...], arguments: [...] }
32
36
  #
@@ -76,6 +80,9 @@ module Hecks
76
80
  # tracked. Holding the chapter objects themselves (not their ids)
77
81
  # in the key also means a collected chapter can never hand its id
78
82
  # to a newcomer behind this cache's back.
83
+ # @return [Hash{Symbol => Array<Hash{Symbol => String}>}] `:keywords`
84
+ # and `:arguments`, each an array of plain, string-valued row
85
+ # hashes (`status` included)
79
86
  def call
80
87
  chapters = MetaValidator.grammar_registry.bluebooks.to_a
81
88
  return @call if @call && same_chapters?(@call_chapters, chapters)
@@ -91,6 +98,15 @@ module Hecks
91
98
  result
92
99
  end
93
100
 
101
+ # Compares the in-process cache key against the registry's current
102
+ # chapters.
103
+ #
104
+ # @param cached [Array<Array(String, Object)>] the `[name, chapter]`
105
+ # pairs `call` last cached against
106
+ # @param current [Array<Array(String, Object)>] the registry's
107
+ # current `[name, chapter]` pairs
108
+ # @return [Boolean] whether `cached` and `current` name the same
109
+ # chapters, by identity, in the same order
94
110
  def same_chapters?(cached, current)
95
111
  cached.size == current.size &&
96
112
  cached.zip(current).all? do |(cached_name, cached_chapter), (name, chapter)|
@@ -152,8 +168,19 @@ module Hecks
152
168
  # be ruled out while debugging something else entirely.
153
169
  CACHE_DIR = File.expand_path("../../../../tmp/hecks_syntax_boot_cache", __dir__).freeze
154
170
 
171
+ # Reports whether the cross-process disk cache is turned on.
172
+ #
173
+ # @return [Boolean] whether the cross-process disk cache is turned
174
+ # on (`HECKS_SYNTAX_BOOT_CACHE=off` turns it off)
155
175
  def disk_cache_enabled? = ENV["HECKS_SYNTAX_BOOT_CACHE"] != "off"
156
176
 
177
+ # Reads an already-written cache entry for `chapters`, if any.
178
+ #
179
+ # @param chapters [Array<Array(String, Object)>] the `[name,
180
+ # chapter]` pairs to key the cache entry on
181
+ # @return [Hash{Symbol => Array<Hash{Symbol => String}>}, nil] the
182
+ # cached syntax table, or `nil` if the cache is disabled, no entry
183
+ # exists yet, or the entry could not be read
157
184
  def read_disk_cache(chapters)
158
185
  return nil unless disk_cache_enabled?
159
186
 
@@ -165,6 +192,13 @@ module Hecks
165
192
  nil
166
193
  end
167
194
 
195
+ # Writes `result` to the disk cache for `chapters`, atomically.
196
+ #
197
+ # @param chapters [Array<Array(String, Object)>] the `[name,
198
+ # chapter]` pairs to key the cache entry on
199
+ # @param result [Hash{Symbol => Array<Hash{Symbol => String}>}] the
200
+ # syntax table to persist
201
+ # @return [void]
168
202
  def write_disk_cache(chapters, result)
169
203
  return unless disk_cache_enabled?
170
204
 
@@ -177,10 +211,21 @@ module Hecks
177
211
  nil
178
212
  end
179
213
 
214
+ # Builds the disk cache file path for `chapters`.
215
+ #
216
+ # @param chapters [Array<Array(String, Object)>] the `[name,
217
+ # chapter]` pairs to key the cache entry on
218
+ # @return [String] the disk cache file path for `chapters`
180
219
  def disk_cache_path(chapters)
181
220
  File.join(CACHE_DIR, "#{disk_cache_key(chapters)}.marshal")
182
221
  end
183
222
 
223
+ # Builds the cache key for `chapters`.
224
+ #
225
+ # @param chapters [Array<Array(String, Object)>] the `[name,
226
+ # chapter]` pairs to key the cache entry on
227
+ # @return [String] a SHA-256 digest of the chapter names and the
228
+ # current grammar source
184
229
  def disk_cache_key(chapters)
185
230
  names = chapters.map { |name, _chapter| name }
186
231
  Digest::SHA256.hexdigest("#{names.join(',')}:#{grammar_content_digest}")
@@ -196,12 +241,20 @@ module Hecks
196
241
  # chapter it belongs to) — deliberately, since under-covering this
197
242
  # set is a correctness bug (a stale table survives a real grammar
198
243
  # edit) and over-covering it is only ever a wasted cache miss.
244
+ # @return [String] a SHA-256 digest of every grammar file's own
245
+ # current content, sorted for a stable digest
199
246
  def grammar_content_digest
200
247
  files = (MetaValidator::GRAMMAR_FILES + MetaValidator::WORLD_GRAMMAR + MetaValidator::HECKSAGON_GRAMMAR +
201
248
  Dir.glob(File.join(MetaValidator::ATTACHED_GRAMMAR_DIR, "*.bluebook"))).sort
202
249
  Digest::SHA256.hexdigest(files.map { |file| File.read(file) }.join("\0"))
203
250
  end
204
251
 
252
+ # Dispatches every seed row into a fresh "Bluebook" instance and
253
+ # reads the result back.
254
+ #
255
+ # @return [Hash{Symbol => Array<Hash{Symbol => String}>}] `:keywords`
256
+ # and `:arguments`, each an array of plain, string-valued row
257
+ # hashes (`status` included)
205
258
  def boot
206
259
  bluebook = MetaValidator.grammar_registry.bluebook("Bluebook")
207
260
  # `MetaValidator.fresh_runtime`, not a brand-new `Runtime::
@@ -222,21 +275,35 @@ module Hecks
222
275
  read_back(runtime, bluebook)
223
276
  end
224
277
 
278
+ # Wraps `text` as a single-field value-object payload, for a
279
+ # dispatch's own `with:`.
280
+ #
225
281
  # An Integer stays an Integer — `position` is `Position`-typed
226
282
  # (`attribute :value, Integer`), so stringifying it fails the type
227
283
  # gate rather than feeding it, the same reading `Judge#v` gives.
284
+ #
285
+ # @param text [String, Integer] the scalar to wrap
286
+ # @return [Hash{Symbol => String, Integer}] `{value: text}`,
287
+ # stringified unless `text` is already an Integer
228
288
  def v(text)
229
289
  return { value: text } if text.is_a?(Integer)
230
290
 
231
291
  { value: text.to_s }
232
292
  end
233
293
 
294
+ # Wraps `text` as an `optional: true` command argument's own
295
+ # payload, or omits it.
296
+ #
234
297
  # Absent stays absent. A seed row's own optional columns ("was",
235
298
  # "at", "named", ...) are empty strings, not nil — `Literal`/CSV-
236
299
  # shaped grammar data has no `nil` to write — so this is the one
237
300
  # place that decides "" means "not given" for the purpose of an
238
301
  # `optional: true` command argument, the same reading `Judge#v`
239
302
  # makes for the meta-domain's own dispatches.
303
+ #
304
+ # @param text [String, nil] the seed row's own column value
305
+ # @return [Hash{Symbol => String}, nil] `v(text)`, or `nil` when
306
+ # `text` is `nil` or empty
240
307
  def optional(text)
241
308
  return nil if text.nil? || text.to_s.empty?
242
309
 
@@ -251,6 +318,13 @@ module Hecks
251
318
  # declared first, named after the real chapter, purely to satisfy
252
319
  # the reference — its own vision/classification are never read
253
320
  # by anything this boot does.
321
+ #
322
+ # @param runtime [Runtime::Dispatcher] the fresh runtime to
323
+ # dispatch into
324
+ # @param bluebook [Bluebook::Chapter] the language's own assembled
325
+ # "Bluebook" chapter, read for its `hecks_name` and `"Syntax"`
326
+ # aggregate
327
+ # @return [void]
254
328
  def declare_syntax(runtime, bluebook)
255
329
  syntax = bluebook.aggregate("Syntax")
256
330
  runtime.dispatch("Bluebook::Bluebook.Declare",
@@ -264,14 +338,19 @@ module Hecks
264
338
 
265
339
  # `to: "Syntax"` names the record already opened by `declare_syntax`
266
340
  # above — an append onto an existing aggregate, not a second creation
267
- # of it. This used to smuggle `name: v("Syntax")` into the payload
268
- # instead, the pre-routing convention `Judge#appends` (the same
269
- # append shape, for `ValueObject.Member`/`ProcessManager.Handler`)
270
- # already left behind for `to:`/`with:` — carrying the receiver in
271
- # the payload made `Syntax.Keyword`'s own `command.creates?` (true:
272
- # it declares no `reference_to`) look like a fresh identity to mint,
273
- # which collided with the very "Syntax" row `declare_syntax` had
274
- # just opened.
341
+ # of it. Smuggling `name: v("Syntax")` into the payload instead — the
342
+ # pre-routing convention `Judge#appends` (the same append shape, for
343
+ # `ValueObject.Member`/`ProcessManager.Handler`) already left behind
344
+ # for `to:`/`with:` — would carry the receiver in the payload,
345
+ # making `Syntax.Keyword`'s own `command.creates?` (true: it
346
+ # declares no `reference_to`) look like a fresh identity to mint,
347
+ # colliding with the very "Syntax" row `declare_syntax` just opened.
348
+ #
349
+ # @param runtime [Runtime::Dispatcher] the fresh runtime to
350
+ # dispatch into
351
+ # @param bluebook [Bluebook::Chapter] the language's own assembled
352
+ # "Bluebook" chapter, read for its `KeywordSeed` rows
353
+ # @return [void]
275
354
  def admit_keywords(runtime, bluebook)
276
355
  all_rows(bluebook, "KeywordSeed").each_with_index do |row, index|
277
356
  runtime.dispatch("Bluebook::Syntax.Keyword", to: "Syntax",
@@ -289,6 +368,14 @@ module Hecks
289
368
  end
290
369
  end
291
370
 
371
+ # Admits every `ArgumentSeed` row as a `Syntax.Argument`, the same
372
+ # `to: "Syntax"` append shape `admit_keywords` uses one type over.
373
+ #
374
+ # @param runtime [Runtime::Dispatcher] the fresh runtime to
375
+ # dispatch into
376
+ # @param bluebook [Bluebook::Chapter] the language's own assembled
377
+ # "Bluebook" chapter, read for its `ArgumentSeed` rows
378
+ # @return [void]
292
379
  def admit_arguments(runtime, bluebook)
293
380
  all_rows(bluebook, "ArgumentSeed").each_with_index do |row, index|
294
381
  runtime.dispatch("Bluebook::Syntax.Argument", to: "Syntax",
@@ -315,6 +402,14 @@ module Hecks
315
402
  # Port, Adapter, Translation), and attached sub-languages alike.
316
403
  # Concatenated into one sequence because `position` is minted from the
317
404
  # walk index and must not collide across concepts.
405
+ #
406
+ # @param bluebook [Bluebook::Chapter] the language's own assembled
407
+ # "Bluebook" chapter, read first, before every other loaded
408
+ # chapter
409
+ # @param name [String] the local value-object name to collect rows
410
+ # of, such as `"KeywordSeed"`
411
+ # @return [Array<Hash{Symbol => String}>] every matching row, across
412
+ # every loaded chapter, in chapter order
318
413
  def all_rows(bluebook, name)
319
414
  seed_chapters(bluebook).flat_map { |chapter| rows(chapter, name) }
320
415
  end
@@ -323,6 +418,11 @@ module Hecks
323
418
  # sibling/extension. The name rejection avoids reading the same core
324
419
  # object twice without relying on object identity across fixpoint
325
420
  # assembly.
421
+ #
422
+ # @param bluebook [Bluebook::Chapter] the language's own assembled
423
+ # "Bluebook" chapter
424
+ # @return [Array<Bluebook::Chapter>] `bluebook` first, then every
425
+ # other chapter the grammar registry holds
326
426
  def seed_chapters(bluebook)
327
427
  [bluebook] + MetaValidator.grammar_registry.bluebooks.values.reject { |chapter| chapter.name == bluebook.name }
328
428
  end
@@ -331,6 +431,13 @@ module Hecks
331
431
  # every aggregate that owns a same-named local value object. Repeating
332
432
  # the value-object shape is deliberate: each concept remains readable
333
433
  # by itself, while this discovery is the only grouping mechanism.
434
+ #
435
+ # @param bluebook [Bluebook::Chapter] the chapter to search
436
+ # @param name [String] the local value-object name to collect rows
437
+ # of, such as `"KeywordSeed"`
438
+ # @return [Array<Hash{Symbol => String}>] one hash per member row,
439
+ # `[]` if no aggregate in `bluebook` owns a value object named
440
+ # `name`
334
441
  def rows(bluebook, name)
335
442
  bluebook.aggregates.flat_map do |aggregate|
336
443
  value_object = aggregate.value_objects.find { |vo| vo.hecks_name == name }
@@ -344,6 +451,14 @@ module Hecks
344
451
  # above hands the seed data in as — plain hashes, string values,
345
452
  # `status` included — so `ParserTable`/`syntax_conformance_spec`
346
453
  # need not know or care that a real dispatch happened in between.
454
+ #
455
+ # @param runtime [Runtime::Dispatcher] the runtime `boot` dispatched
456
+ # every seed row into
457
+ # @param bluebook [Bluebook::Chapter] the language's own assembled
458
+ # "Bluebook" chapter, read for its `"Syntax"` aggregate
459
+ # @return [Hash{Symbol => Array<Hash{Symbol => String}>}] `:keywords`
460
+ # and `:arguments`, each an array of plain, string-valued row
461
+ # hashes (`status` included)
347
462
  def read_back(runtime, bluebook)
348
463
  syntax = bluebook.aggregate("Syntax")
349
464
  repository = runtime.registry.repository("Bluebook", syntax)
@@ -355,12 +470,23 @@ module Hecks
355
470
  }
356
471
  end
357
472
 
473
+ # Flattens one dispatched record into a plain, string-valued row.
474
+ #
475
+ # @param row [Object] one dispatched keyword or argument record,
476
+ # answering `to_h`
477
+ # @return [Hash{Symbol => String}] `row`'s own fields, each value
478
+ # unwrapped and stringified
358
479
  def stringify(row)
359
480
  row.to_h.transform_values do |cell|
360
481
  scalar(cell).to_s
361
482
  end
362
483
  end
363
484
 
485
+ # Unwraps one field's stored value to its bare scalar.
486
+ #
487
+ # @param cell [Object] one field's stored value
488
+ # @return [Object] `cell`'s sole wrapped value, if `cell` is a
489
+ # to_h-able non-String; `cell` unchanged otherwise
364
490
  def scalar(cell)
365
491
  return cell.to_h.values.first if cell.respond_to?(:to_h) && !cell.is_a?(String)
366
492