hecks 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +65 -0
  3. data/lib/hecks/adapters/driven/folder.rb +73 -0
  4. data/lib/hecks/adapters/driven/google_authentication.rb +25 -4
  5. data/lib/hecks/adapters/driven/governance_authorization.rb +57 -6
  6. data/lib/hecks/adapters/driven/heki/journal.rb +8 -0
  7. data/lib/hecks/adapters/driven/heki/saga_store.rb +53 -7
  8. data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
  9. data/lib/hecks/adapters/driven/heki.rb +93 -2
  10. data/lib/hecks/adapters/driven/identity_registry.rb +10 -0
  11. data/lib/hecks/adapters/driven/in_memory_ordering.rb +22 -0
  12. data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
  13. data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
  14. data/lib/hecks/adapters/driven/lambda/client.rb +35 -7
  15. data/lib/hecks/adapters/driven/lambda.rb +43 -2
  16. data/lib/hecks/adapters/driven/local_storage.rb +67 -1
  17. data/lib/hecks/adapters/driven/memory.rb +13 -13
  18. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +20 -0
  19. data/lib/hecks/adapters/driven/prism.rb +46 -7
  20. data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
  21. data/lib/hecks/adapters/driven/system_clock.rb +3 -0
  22. data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
  23. data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
  24. data/lib/hecks/adapters/driven.rb +2 -0
  25. data/lib/hecks/adapters/driving/github_webhook.rb +14 -1
  26. data/lib/hecks/behaviors/dsl.rb +58 -0
  27. data/lib/hecks/behaviors/expectations.rb +168 -7
  28. data/lib/hecks/behaviors/ir.rb +11 -0
  29. data/lib/hecks/behaviors/rspec.rb +8 -0
  30. data/lib/hecks/behaviors/runner.rb +19 -0
  31. data/lib/hecks/behaviors.rb +8 -0
  32. data/lib/hecks/bluebook/aggregate.rb +30 -0
  33. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +7 -0
  34. data/lib/hecks/bluebook/assembly/build.rb +25 -4
  35. data/lib/hecks/bluebook/assembly/contract.rb +66 -14
  36. data/lib/hecks/bluebook/assembly/contracts.rb +24 -19
  37. data/lib/hecks/bluebook/assembly/marks.rb +143 -14
  38. data/lib/hecks/bluebook/assembly/specializer.rb +19 -0
  39. data/lib/hecks/bluebook/assembly.rb +25 -7
  40. data/lib/hecks/bluebook/attribute.rb +17 -3
  41. data/lib/hecks/bluebook/behaviour/aggregate.rb +32 -0
  42. data/lib/hecks/bluebook/behaviour/attribute.rb +13 -0
  43. data/lib/hecks/bluebook/behaviour/chapter.rb +55 -2
  44. data/lib/hecks/bluebook/behaviour/command.rb +33 -3
  45. data/lib/hecks/bluebook/behaviour/domain_port.rb +20 -0
  46. data/lib/hecks/bluebook/behaviour/entity.rb +12 -0
  47. data/lib/hecks/bluebook/behaviour/hexagon.rb +26 -0
  48. data/lib/hecks/bluebook/behaviour/lifecycle.rb +23 -2
  49. data/lib/hecks/bluebook/behaviour/policy.rb +34 -9
  50. data/lib/hecks/bluebook/behaviour/process_manager.rb +32 -1
  51. data/lib/hecks/bluebook/behaviour/query.rb +5 -0
  52. data/lib/hecks/bluebook/behaviour/read_model.rb +21 -0
  53. data/lib/hecks/bluebook/behaviour/traits.rb +36 -0
  54. data/lib/hecks/bluebook/behaviour/value_object.rb +17 -5
  55. data/lib/hecks/bluebook/chapter.rb +23 -0
  56. data/lib/hecks/bluebook/command.rb +53 -8
  57. data/lib/hecks/bluebook/domain_port.rb +25 -0
  58. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +196 -37
  59. data/lib/hecks/bluebook/dsl/attribute_collector.rb +79 -42
  60. data/lib/hecks/bluebook/dsl/binding_proxy.rb +59 -0
  61. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +385 -53
  62. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +135 -21
  63. data/lib/hecks/bluebook/dsl/command_builder.rb +189 -27
  64. data/lib/hecks/bluebook/dsl/entity_builder.rb +139 -9
  65. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +41 -0
  66. data/lib/hecks/bluebook/dsl/identity_declaration.rb +23 -6
  67. data/lib/hecks/bluebook/dsl/policy_builder.rb +60 -19
  68. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +79 -15
  69. data/lib/hecks/bluebook/dsl/query_builder.rb +33 -4
  70. data/lib/hecks/bluebook/dsl/read_model_builder.rb +102 -27
  71. data/lib/hecks/bluebook/dsl/rule_reference.rb +60 -8
  72. data/lib/hecks/bluebook/dsl/translation_builder.rb +146 -40
  73. data/lib/hecks/bluebook/dsl/value_object_builder.rb +55 -7
  74. data/lib/hecks/bluebook/entity.rb +29 -0
  75. data/lib/hecks/bluebook/expression/ast_json.rb +115 -23
  76. data/lib/hecks/bluebook/expression/ast_reader.rb +29 -0
  77. data/lib/hecks/bluebook/expression/canonical_form.rb +48 -9
  78. data/lib/hecks/bluebook/expression/evaluator.rb +207 -29
  79. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +36 -0
  80. data/lib/hecks/bluebook/expression/resolver.rb +320 -77
  81. data/lib/hecks/bluebook/hexagon.rb +34 -0
  82. data/lib/hecks/bluebook/lifecycle.rb +11 -0
  83. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -0
  84. data/lib/hecks/bluebook/meta_validator/judge.rb +30 -26
  85. data/lib/hecks/bluebook/meta_validator/plan.rb +43 -8
  86. data/lib/hecks/bluebook/meta_validator/port_judge.rb +1 -0
  87. data/lib/hecks/bluebook/meta_validator/readings.rb +157 -7
  88. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -4
  89. data/lib/hecks/bluebook/meta_validator/shapes.rb +141 -0
  90. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +149 -23
  91. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +5 -4
  92. data/lib/hecks/bluebook/meta_validator/world_judge.rb +1 -0
  93. data/lib/hecks/bluebook/meta_validator.rb +180 -84
  94. data/lib/hecks/bluebook/model_check.rb +268 -24
  95. data/lib/hecks/bluebook/pattern_subset.rb +23 -1
  96. data/lib/hecks/bluebook/process_manager.rb +13 -0
  97. data/lib/hecks/bluebook/project_discovery.rb +5 -0
  98. data/lib/hecks/bluebook/project_loader.rb +40 -0
  99. data/lib/hecks/bluebook/project_register.rb +44 -0
  100. data/lib/hecks/bluebook/query.rb +27 -0
  101. data/lib/hecks/bluebook/read_model.rb +21 -1
  102. data/lib/hecks/bluebook/reference.rb +21 -8
  103. data/lib/hecks/bluebook/smoke_test.rb +29 -6
  104. data/lib/hecks/bluebook/synthesizer.rb +34 -0
  105. data/lib/hecks/bluebook/translation.rb +30 -1
  106. data/lib/hecks/bluebook/value_object.rb +23 -5
  107. data/lib/hecks/bluebook.rb +3 -4
  108. data/lib/hecks/codemod.rb +107 -20
  109. data/lib/hecks/construct.rb +15 -1
  110. data/lib/hecks/corpus.rb +146 -25
  111. data/lib/hecks/deploy/bluebook/deploy.bluebook +105 -0
  112. data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
  113. data/lib/hecks/deploy/oidc.json +5 -0
  114. data/lib/hecks/doc/reference.rb +185 -16
  115. data/lib/hecks/embryonaut_bluebook.rb +32 -9
  116. data/lib/hecks/facade/handle.rb +76 -3
  117. data/lib/hecks/facade/surface/aggregate_door.rb +8 -0
  118. data/lib/hecks/forms/field_shape.rb +3 -0
  119. data/lib/hecks/forms/page.rb +14 -0
  120. data/lib/hecks/forms/port_argument.rb +12 -0
  121. data/lib/hecks/forms/query_form_renderer.rb +63 -0
  122. data/lib/hecks/forms/record_renderer.rb +58 -0
  123. data/lib/hecks/forms/record_table.rb +27 -0
  124. data/lib/hecks/forms/reference_options.rb +24 -0
  125. data/lib/hecks/forms/value_object_shape.rb +10 -0
  126. data/lib/hecks/fqn.rb +58 -0
  127. data/lib/hecks/framework/bluebook/compliance.bluebook +221 -0
  128. data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
  129. data/lib/hecks/framework/oidc.json +15 -0
  130. data/lib/hecks/framework.rb +43 -20
  131. data/lib/hecks/freezer.rb +17 -1
  132. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +159 -24
  133. data/lib/hecks/fuzzing/combination_miner.rb +59 -0
  134. data/lib/hecks/fuzzing/concurrent_dispatch.rb +109 -8
  135. data/lib/hecks/fuzzing/coverage_campaign.rb +56 -13
  136. data/lib/hecks/fuzzing/differential.rb +34 -0
  137. data/lib/hecks/fuzzing/domain_generator.rb +188 -11
  138. data/lib/hecks/fuzzing/era_boundary.rb +45 -15
  139. data/lib/hecks/fuzzing/form_census.rb +86 -0
  140. data/lib/hecks/fuzzing/generated_domain_check.rb +76 -0
  141. data/lib/hecks/fuzzing/invalid_value_generator.rb +39 -0
  142. data/lib/hecks/fuzzing/isolated_boot.rb +79 -22
  143. data/lib/hecks/fuzzing/nondeterministic.rb +13 -1
  144. data/lib/hecks/fuzzing/persistence_parity.rb +95 -3
  145. data/lib/hecks/fuzzing/properties/corrections.rb +25 -0
  146. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +158 -14
  147. data/lib/hecks/fuzzing/properties/guards.rb +44 -0
  148. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +48 -0
  149. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +18 -0
  150. data/lib/hecks/fuzzing/properties/outbox.rb +49 -11
  151. data/lib/hecks/fuzzing/properties/querying.rb +68 -14
  152. data/lib/hecks/fuzzing/properties.rb +24 -15
  153. data/lib/hecks/fuzzing/qa_settings.rb +12 -0
  154. data/lib/hecks/fuzzing/replay.rb +137 -29
  155. data/lib/hecks/fuzzing/rotation_priority.rb +41 -21
  156. data/lib/hecks/fuzzing/rust_gap_manifest.rb +46 -20
  157. data/lib/hecks/fuzzing/self_consistency.rb +189 -40
  158. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +12 -6
  159. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +1 -1
  160. data/lib/hecks/fuzzing/sequence_generator.rb +47 -0
  161. data/lib/hecks/fuzzing/shrinker.rb +114 -2
  162. data/lib/hecks/fuzzing/structural_skips.rb +18 -4
  163. data/lib/hecks/fuzzing/sweep_depth.rb +8 -0
  164. data/lib/hecks/fuzzing/target_capabilities.rb +61 -14
  165. data/lib/hecks/fuzzing/value_generator.rb +98 -10
  166. data/lib/hecks/grammar/evolve.rb +178 -2
  167. data/lib/hecks/grammar.rb +46 -0
  168. data/lib/hecks/ir.rb +38 -7
  169. data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
  170. data/lib/hecks/literal.rb +32 -0
  171. data/lib/hecks/naming.rb +88 -7
  172. data/lib/hecks/ports/access_control.rb +5 -10
  173. data/lib/hecks/ports/authorization.rb +3 -6
  174. data/lib/hecks/ports/identity_assignment.rb +1 -2
  175. data/lib/hecks/ports/identity_resolution.rb +1 -2
  176. data/lib/hecks/ports/key_vault.port +6 -0
  177. data/lib/hecks/ports/key_vault.rb +85 -0
  178. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +6 -0
  179. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +38 -2
  180. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -0
  181. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +40 -0
  182. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +92 -1
  183. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
  184. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +8 -0
  185. data/lib/hecks/ports/query/in_memory.rb +39 -6
  186. data/lib/hecks/ports/query/ordering.rb +15 -0
  187. data/lib/hecks/ports.rb +1 -0
  188. data/lib/hecks/projections/bootstrap_table.rb +43 -8
  189. data/lib/hecks/projections/deploy/fargate.rb +666 -0
  190. data/lib/hecks/projections/deploy/lambda.rb +2423 -0
  191. data/lib/hecks/projections/deploy/shared.rb +624 -0
  192. data/lib/hecks/projections/diagrams.rb +243 -7
  193. data/lib/hecks/projections/glossary/html.rb +88 -0
  194. data/lib/hecks/projections/glossary/markdown.rb +47 -0
  195. data/lib/hecks/projections/glossary/mermaid.rb +48 -0
  196. data/lib/hecks/projections/glossary/sentences.rb +105 -0
  197. data/lib/hecks/projections/glossary.rb +161 -19
  198. data/lib/hecks/projections/model/deviations.rb +44 -0
  199. data/lib/hecks/projections/model.rb +51 -1
  200. data/lib/hecks/projections/oidc.rb +40 -3
  201. data/lib/hecks/projections/parser_table.rb +49 -8
  202. data/lib/hecks/projections/reference.rb +12 -0
  203. data/lib/hecks/projections/rust_vocabulary.rb +219 -16
  204. data/lib/hecks/projections/shape.rb +8 -0
  205. data/lib/hecks/projections/statements.rb +63 -16
  206. data/lib/hecks/projections/vocabulary.rb +17 -0
  207. data/lib/hecks/projections.rb +3 -0
  208. data/lib/hecks/projector/cli_projector.rb +218 -10
  209. data/lib/hecks/projector/docs_projector.rb +145 -19
  210. data/lib/hecks/projector/exporter.rb +65 -11
  211. data/lib/hecks/projector/ir_projector.rb +6 -0
  212. data/lib/hecks/projector/narrate_projector.rb +136 -15
  213. data/lib/hecks/projector/target.rb +47 -10
  214. data/lib/hecks/projector.rb +101 -19
  215. data/lib/hecks/query_ir.rb +47 -0
  216. data/lib/hecks/query_specification/common/null_policy.rb +5 -3
  217. data/lib/hecks/rendering.rb +6 -0
  218. data/lib/hecks/router/namespace_installer.rb +13 -0
  219. data/lib/hecks/router.rb +55 -0
  220. data/lib/hecks/runtime/aggregate_lock.rb +9 -0
  221. data/lib/hecks/runtime/boot_gates.rb +18 -0
  222. data/lib/hecks/runtime/caller.rb +32 -0
  223. data/lib/hecks/runtime/capability_graph.rb +11 -0
  224. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +23 -21
  225. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +14 -15
  226. data/lib/hecks/runtime/command_interpreter.rb +42 -17
  227. data/lib/hecks/runtime/command_rules/admissibility.rb +165 -14
  228. data/lib/hecks/runtime/command_rules/arithmetic.rb +17 -5
  229. data/lib/hecks/runtime/command_rules/references.rb +118 -28
  230. data/lib/hecks/runtime/dependency_planning.rb +45 -0
  231. data/lib/hecks/runtime/dispatcher.rb +28 -50
  232. data/lib/hecks/runtime/entity_element.rb +161 -8
  233. data/lib/hecks/runtime/entity_interpreter.rb +44 -9
  234. data/lib/hecks/runtime/errors.rb +18 -4
  235. data/lib/hecks/runtime/event.rb +10 -5
  236. data/lib/hecks/runtime/identity.rb +71 -3
  237. data/lib/hecks/runtime/instance.rb +67 -7
  238. data/lib/hecks/runtime/interpreting.rb +13 -5
  239. data/lib/hecks/runtime/invocation.rb +118 -36
  240. data/lib/hecks/runtime/loader.rb +94 -8
  241. data/lib/hecks/runtime/outbox.rb +145 -7
  242. data/lib/hecks/runtime/policy_interpreter.rb +22 -9
  243. data/lib/hecks/runtime/port_operation_interpreter.rb +20 -0
  244. data/lib/hecks/runtime/query_interpreter.rb +40 -12
  245. data/lib/hecks/runtime/reaction_invocation.rb +53 -8
  246. data/lib/hecks/runtime/read_model_interpreter.rb +23 -7
  247. data/lib/hecks/runtime/rebuild_sweep.rb +28 -0
  248. data/lib/hecks/runtime/reference_hop.rb +42 -0
  249. data/lib/hecks/runtime/refusal_wording.rb +50 -0
  250. data/lib/hecks/runtime/registry/saga_persistence.rb +11 -0
  251. data/lib/hecks/runtime/registry/verification.rb +119 -4
  252. data/lib/hecks/runtime/registry.rb +157 -4
  253. data/lib/hecks/runtime/remote_dispatcher.rb +92 -6
  254. data/lib/hecks/runtime/routing.rb +27 -2
  255. data/lib/hecks/runtime/saga_interpreter/correlation.rb +10 -12
  256. data/lib/hecks/runtime/saga_interpreter.rb +27 -13
  257. data/lib/hecks/runtime/tenant_check.rb +26 -6
  258. data/lib/hecks/runtime/tenant_scope.rb +18 -0
  259. data/lib/hecks/runtime/value/coercion.rb +255 -33
  260. data/lib/hecks/runtime/value/entity_list_coercion.rb +102 -30
  261. data/lib/hecks/runtime/value.rb +50 -0
  262. data/lib/hecks/runtime.rb +32 -0
  263. data/lib/hecks/storehouse.rb +305 -9
  264. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
  265. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
  266. data/lib/hecks/version.rb +1 -1
  267. data/lib/hecks.rb +79 -1
  268. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -0
  269. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +12 -2
  270. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +29 -5
  271. metadata +14 -5
  272. data/lib/hecks/codemod/legacy_dispatch_args.rb +0 -299
  273. data/lib/hecks/codemod/legacy_dispatch_recorder.rb +0 -186
  274. data/lib/hecks/deprecation.rb +0 -95
  275. data/lib/hecks/framework/bluebook/compliance.bluebook +0 -1
@@ -21,6 +21,15 @@ module Hecks
21
21
  include WordGate
22
22
  include Sealing
23
23
 
24
+ # @param name [String] the aggregate's name, as written after `aggregate`
25
+ # @param chapter_named_givens [Hash{String => Hash{String => Bluebook::Given}}] the
26
+ # chapter-wide given pool, shared and written through by `given_impl`
27
+ # @param chapter_pending_givens [Array<Hash>] unresolved chapter-wide bare given
28
+ # references, appended to when this aggregate's own reference cannot resolve yet
29
+ # @param chapter_entity_named_givens [Hash{String => Hash{String => Bluebook::Given}}]
30
+ # the chapter-wide, entity-scoped given pool, threaded unchanged to every entity
31
+ # @param chapter_entity_pending_givens [Array<Hash>] unresolved chapter-wide,
32
+ # entity-scoped bare given references, threaded unchanged to every entity
24
33
  def initialize(name, chapter_named_givens: {}, chapter_pending_givens: [],
25
34
  chapter_entity_named_givens: {}, chapter_entity_pending_givens: [])
26
35
  @name = name
@@ -66,33 +75,45 @@ module Hecks
66
75
  @pending_queries = []
67
76
  end
68
77
 
78
+ # Sets the human-readable description shown for this aggregate.
79
+ #
80
+ # @param value [String] the description text
81
+ # @return [String] the description as stored
69
82
  def description(value)
70
83
  # moved to the language: Description invariant, on Root.Declare
71
84
 
72
85
  @description = value
73
86
  end
74
87
 
88
+ # Names where a concept adopted from a canonical source came from.
89
+ #
75
90
  # Origin, not runtime identity — a concept adopted from a canonical
76
91
  # source (§28) names where it came from without that fact ever
77
92
  # touching `hecks_fqn`/dispatch. Captured raw, the same way
78
93
  # `attribute ..., default: { value: "small" }` captures a literal
79
94
  # Hash untouched — no re-parsing, no structure imposed beyond
80
95
  # "whatever the author wrote."
81
- # Renamed from `provenance`/`projects`/`lifecycle`/`entity`/
82
- # `query`/`policy`/`command` (all below) — item #13's full
96
+ #
97
+ # Answers the `provenance` word (and, via the same table rows,
98
+ # its siblings `projects`/`lifecycle`/`entity`/
99
+ # `query`/`policy`/`command` below) through the table's `calls:`
100
+ # column — item #13's full
83
101
  # metaprogrammed dispatch (slice 4c). All bootstrap-reachable
84
102
  # (used throughout the core/attached chapters), all in
85
- # GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.
103
+ # `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
104
+ #
105
+ # @param from [Object] the canonical source, captured exactly as written
106
+ # @return [Object] `from` as stored
86
107
  def provenance_impl(from:)
87
108
  @provenance = from
88
109
  end
89
110
 
111
+ # Declares a reference from this aggregate's own head to another aggregate's identity.
112
+ #
90
113
  # `optional:` — matching `CommandBuilder#reference_to`'s own
91
- # signature, which already had it; this one used to never
92
- # forward it to `attribute_impl()`/`relationship_attribute`
93
- # even though those already accept it — closed in the same
94
- # commit that added this comment (`optional: optional`, below).
95
- # It was a real gap because an aggregate that can point at one
114
+ # signature, which already had it, and forwarded here to
115
+ # `attribute_impl()`/`relationship_attribute` (`optional: optional`, below). A real
116
+ # need: an aggregate that can point at one
96
117
  # of several targets (Item's own `personal_list_id`/
97
118
  # `camping_list_id`, never both) needs each reference optional
98
119
  # on the aggregate's own persisted schema, not just as a
@@ -100,10 +121,19 @@ module Hecks
100
121
  # `spec/fixtures/hop_chain.bluebook`'s own `Proposal` aggregate
101
122
  # declares `reference_to Engagement, optional: true` at the
102
123
  # aggregate head.
103
- # Renamed from `reference_to` — item #13's full metaprogrammed
124
+ #
125
+ # Answers the `reference_to` word through the table's `calls:`
126
+ # column — item #13's full metaprogrammed
104
127
  # dispatch (slice 4b). Bootstrap-reachable (every core/attached
105
128
  # grammar chapter uses reference_to to describe itself), so also
106
- # named in GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.
129
+ # named in `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
130
+ #
131
+ # @param type [Module, Symbol, String] the referenced aggregate, written as a bare
132
+ # constant
133
+ # @param as [Symbol, nil] the attribute's name; nil derives it from `type`
134
+ # @param optional [Boolean] whether the reference may be absent
135
+ # @return [void]
136
+ # @raise [Bluebook::DSL::Malformed] if `as` (or the derived name) is already declared
107
137
  def reference_to_impl(type, as: nil, optional: false)
108
138
  target = Naming.demodulise(type)
109
139
  @reference_targets << target
@@ -111,13 +141,15 @@ module Hecks
111
141
  as || default_reference_name(target), optional: optional)
112
142
  end
113
143
 
144
+ # Declares that this aggregate holds its own kept-fresh copy of a field reached through
145
+ # a reference, so a rule can read it locally instead of reaching across the boundary.
146
+ #
114
147
  # A rule may only read within its own aggregate boundary (S12,
115
- # ADR 0025 — "Consistency across aggregate boundaries"). A
116
- # `given`/`ensures`/`invariant` used to reach through a
148
+ # ADR 0025 — "Consistency across aggregate boundaries"). Reaching through a
117
149
  # `reference_to` at rule-evaluation time (`References#
118
150
  # dereference`, a live query against another aggregate's own
119
- # repository, unbounded and inconsistent with the "a rule reads
120
- # only this record" model everywhere else) — `projects` is what
151
+ # repository) would be unbounded and inconsistent with the "a rule reads
152
+ # only this record" model everywhere else — `projects` is what
121
153
  # replaces that: `projects :customer_status, from: :"customer.
122
154
  # status"` declares that this aggregate holds its own copy of
123
155
  # `Customer`'s own `:status`, kept fresh by a rebuild sweep
@@ -137,6 +169,14 @@ module Hecks
137
169
  # aggregate in the chapter is real, not by `AggregateBuilder`
138
170
  # itself) — `validate_projected_fields!` is where that half
139
171
  # happens.
172
+ #
173
+ # @param name [Symbol, String] the local field's name this aggregate projects the
174
+ # remote value into
175
+ # @param from [Symbol, String] the local reference and remote field, dotted, such as
176
+ # `:"customer.status"`
177
+ # @return [Array<Bluebook::ProjectedField>] every projected field declared so far, this
178
+ # one last
179
+ # @raise [Bluebook::DSL::Malformed] if `from` is not `reference.field` shaped
140
180
  def projects_impl(name, from:)
141
181
  reference, _, remote_field = from.to_s.rpartition(".")
142
182
 
@@ -165,12 +205,27 @@ module Hecks
165
205
  # did when it was written — real, if rare corpus: "Combined corpus
166
206
  # uses: one."
167
207
  #
168
- # Renamed from `has_many`/`has_one`/`belongs_to` — item #13's full
169
- # metaprogrammed dispatch (slice 4). Each Keyword row's own
170
- # `calls:` names the matching `_impl`; not bootstrap-reachable
171
- # (no core/attached chapter uses one of these to describe itself),
172
- # so no BOOTSTRAP_CALLS_FALLBACK entry is needed, unlike
173
- # `attribute`/`role`.
208
+ # Declares a list-typed relationship to another aggregate, referenced by its plural name.
209
+ #
210
+ # Answers the `has_many` word (and, via the same table rows, its
211
+ # siblings `has_one`/`belongs_to` below) through the table's
212
+ # `calls:` column — item #13's full metaprogrammed dispatch
213
+ # (slice 4). Each Keyword row's own `calls:` names the matching
214
+ # `_impl`, so all three are carried in
215
+ # `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK` like every other
216
+ # `calls:`-routed word, but never actually exercised during real
217
+ # bootstrap (no core/attached chapter uses one of these to
218
+ # describe itself), unlike `attribute`/`role`.
219
+ #
220
+ # @param type [Module, Symbol, String] the related aggregate's plural name, a bare
221
+ # constant
222
+ # @param as [Symbol, nil] the attribute's name; nil derives it from `type`
223
+ # @param legacy_options [Hash] must be empty outside shadow-parsing; under
224
+ # shadow-parsing, `:optional` is read for the legacy single-reference form
225
+ # @return [void]
226
+ # @raise [Bluebook::DSL::Malformed] outside shadow-parsing, if `legacy_options` is
227
+ # non-empty; may also raise from `relationship_attribute` if the derived name is
228
+ # already declared
174
229
  def has_many_impl(type, as: nil, **legacy_options)
175
230
  return legacy_has_many(type, as: as, optional: legacy_options.fetch(:optional, false)) if MetaValidator.shadow_parsing?
176
231
 
@@ -186,6 +241,12 @@ module Hecks
186
241
  list: true)
187
242
  end
188
243
 
244
+ # Declares a single-valued relationship this aggregate holds toward another.
245
+ #
246
+ # @param type [Module, Symbol, String] the related aggregate, a bare constant
247
+ # @param as [Symbol, nil] the attribute's name; nil derives it from `type`
248
+ # @param optional [Boolean] whether the relationship may be absent
249
+ # @return [void]
189
250
  def has_one_impl(type, as: nil, optional: false)
190
251
  return legacy_has_one(type, as: as, optional: optional) if MetaValidator.shadow_parsing?
191
252
 
@@ -195,6 +256,12 @@ module Hecks
195
256
  optional: optional)
196
257
  end
197
258
 
259
+ # Declares a single-valued relationship toward the aggregate that owns this one.
260
+ #
261
+ # @param type [Module, Symbol, String] the owning aggregate, a bare constant
262
+ # @param as [Symbol, nil] the attribute's name; nil derives it from `type`
263
+ # @param optional [Boolean] whether the relationship may be absent
264
+ # @return [void]
198
265
  def belongs_to_impl(type, as: nil, optional: false)
199
266
  return legacy_has_one(type, as: as, optional: optional) if MetaValidator.shadow_parsing?
200
267
 
@@ -204,10 +271,19 @@ module Hecks
204
271
  optional: optional)
205
272
  end
206
273
 
274
+ # Declares this aggregate's own state machine.
275
+ #
276
+ # @param field [Symbol, String] the attribute the state machine lives on
277
+ # @param default [String, Symbol] the state a new record starts in
278
+ # @yield the lifecycle body of `transition` rows, evaluated against a `LifecycleBuilder`
279
+ # @return [Bluebook::Lifecycle] the built state machine
280
+ # @raise [Bluebook::DSL::Malformed] if two transitions for one command overlap
207
281
  def lifecycle_impl(field, default:, &)
208
282
  @lifecycle = LifecycleBuilder.build(field, default: default, &)
209
283
  end
210
284
 
285
+ # Queues a piece nested in this aggregate, built later once every sibling has been seen.
286
+ #
211
287
  # A piece is declared in this aggregate — its owner is stamped by
212
288
  # `Aggregate#initialize`, once the aggregate exists. Its own
213
289
  # commands were given the piece as their owner when it was declared,
@@ -230,25 +306,42 @@ module Hecks
230
306
  # different canonical — bare `customer.status`, not
231
307
  # `parent.customer.status`, wrong scope for a piece's own command
232
308
  # to evaluate) nor round 4's single-piece `given` could reach.
309
+ # @param name [String] the nested piece's name
310
+ # @yield the piece body, evaluated against an `EntityBuilder` once drained
311
+ # @return [Array<Array>] every pending piece queued so far, this one last
233
312
  def entity_impl(name, &block)
234
313
  @pending_entities << [name, block]
235
314
  end
236
315
 
316
+ # Queues a query declared on this aggregate, built later once every sibling has been seen.
317
+ #
318
+ # @param name [String] the query's name
319
+ # @yield the query body, evaluated against a `QueryBuilder` once drained
320
+ # @return [Array<Array>] every pending query queued so far, this one last
237
321
  def query_impl(name, &block)
238
322
  @pending_queries << [name, block]
239
323
  end
240
324
 
325
+ # Declares a policy scoped to this aggregate, stamping it with the aggregate's own name.
326
+ #
327
+ # @param name [String] the policy's name
328
+ # @yield the policy body, evaluated against a `PolicyBuilder`
329
+ # @return [Array<Bluebook::Policy>] every policy declared so far, this one last
330
+ # @raise [Bluebook::DSL::Malformed] if the body fails any check the policy builder raises
241
331
  def policy_impl(name, &)
242
332
  reaction = PolicyBuilder.build(name, &)
243
333
  reaction.aggregate = @name
244
334
  @policies << reaction
245
335
  end
246
336
 
247
- # `builder.closed_sets` too, not only `builder.build` — a real,
248
- # previously-unreachable gap this exact fix exposed: a
249
- # value_object's own inline `attribute :x, one_of(...)` (now legal
250
- # — S3, ADR 0025 removed the wrong-arity collision that used to
251
- # make this crash before it could ever matter) synthesises its own
337
+ # Declares a value object on this aggregate, either as a block of `attribute` lines or
338
+ # (the `type` shorthand) as a single `:value` attribute.
339
+ #
340
+ # `builder.closed_sets` too, not only `builder.build` — a real
341
+ # gap this exact fix closes: a
342
+ # value_object's own inline `attribute :x, one_of(...)` (legal
343
+ # since S3, ADR 0025 removed the wrong-arity collision that would
344
+ # otherwise crash it) synthesises its own
252
345
  # anonymous value object via the same `AttributeCollector#closed_
253
346
  # sets` mechanism an aggregate's own attributes already use — and
254
347
  # nothing installed it anywhere. `Box.attributes` said `size:
@@ -278,6 +371,16 @@ module Hecks
278
371
  # Neither type nor block keeps its historical behavior untouched
279
372
  # (an empty attribute list — judged, or not, by the language
280
373
  # downstream, the same as before this parameter existed).
374
+ #
375
+ # @param name [String] the value object's name
376
+ # @param type [Module, nil] the bare shorthand's single attribute type; mutually
377
+ # exclusive with `block`
378
+ # @yield the value object body of `attribute`/`invariant`/`one_of` lines; mutually
379
+ # exclusive with `type`
380
+ # @return [Array<Bluebook::ValueObject>] this aggregate's own value objects, including
381
+ # this one and any closed sets its attributes synthesised
382
+ # @raise [Bluebook::DSL::Malformed] if both `type` and a block are given, or the body
383
+ # fails any check the value object language or its builder raises
281
384
  def value_object(name, type = nil, &block)
282
385
  if type && block
283
386
  raise Malformed,
@@ -293,6 +396,9 @@ module Hecks
293
396
  @value_objects.concat(builder.closed_sets)
294
397
  end
295
398
 
399
+ # Queues a command declared on this aggregate, built later once every sibling has been
400
+ # seen.
401
+ #
296
402
  # `from:` — lifecycle state becomes a command guard (S10, ADR
297
403
  # 0025) — `command "Debit", from: "open"` replaces `given
298
404
  # ("account is open") { status == "open" }`, written 35 times
@@ -302,6 +408,12 @@ module Hecks
302
408
  # the lifecycle already declares which states exist, so naming
303
409
  # the legal ones is checkable against it, where a free-text
304
410
  # given could drift out of sync with the state machine and did.
411
+ #
412
+ # @param name [String] the command's name
413
+ # @param from [String, Symbol, Array<String, Symbol>, nil] the lifecycle state(s) this
414
+ # command guards from; nil admits from any state
415
+ # @yield the command body, evaluated against a `CommandBuilder` once drained
416
+ # @return [Array<Array>] every pending command queued so far, this one last
305
417
  def command_impl(name, from: nil, &block)
306
418
  # The verb is declared on this aggregate — the owner `acts_on` answers
307
419
  # with — stamped by `Aggregate#initialize` once the aggregate
@@ -312,6 +424,9 @@ module Hecks
312
424
  @pending_commands << [name, from, block]
313
425
  end
314
426
 
427
+ # Declares a rule this aggregate's own commands must satisfy, or references one a
428
+ # sibling aggregate in the chapter already declared.
429
+ #
315
430
  # A precondition shared across commands, declared once (S10, ADR
316
431
  # 0025) — an aggregate-level `given`, block required, stored by
317
432
  # its own description rather than appended anywhere: a command
@@ -354,9 +469,23 @@ module Hecks
354
469
  # textually-different canonical registers under the same
355
470
  # description; see `reference_named_chapter_given`'s own
356
471
  # ambiguity error for how that surfaces.
357
- # Renamed from `given` — item #13's full metaprogrammed dispatch
358
- # (slice 4b), same reasoning as reference_to_impl above:
359
- # bootstrap-reachable, in BOOTSTRAP_CALLS_FALLBACK.
472
+ # Answers the `given` word through the table's `calls:` column —
473
+ # item #13's full metaprogrammed dispatch
474
+ # (slice 4b), same reasoning as `reference_to_impl` above:
475
+ # bootstrap-reachable, in `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
476
+ #
477
+ # @param description [String] the rule's description; also the name a sibling aggregate
478
+ # references it by when no block is given
479
+ # @param declared_by [Module, Symbol, String, nil] disambiguates which aggregate's own
480
+ # rule to reference, a bare constant, when more than one shares `description`; only
481
+ # meaningful with no block
482
+ # @yield the predicate body; evaluated for its extracted source, never called directly
483
+ # @return [void]
484
+ # @raise [Bluebook::DSL::Malformed] if given a block whose source cannot be extracted;
485
+ # given no block, the description is immediately ambiguous between more than one
486
+ # already-loaded aggregate with no `declared_by` to disambiguate; an unresolved
487
+ # reference defers instead, and may still raise once the whole chapter has loaded, if
488
+ # it then resolves to none or more than one candidate
360
489
  def given_impl(description, declared_by: nil, &predicate)
361
490
  return reference_named_chapter_given(description, declared_by: declared_by) unless predicate
362
491
 
@@ -448,23 +577,39 @@ module Hecks
448
577
 
449
578
  public
450
579
 
580
+ # Declares a rule the whole aggregate must satisfy, checked after every command, before
581
+ # save.
582
+ #
451
583
  # The aggregate boundary is what an invariant defines (S10, ADR
452
- # 0025 — "Rules") — checked after every command, before save,
453
- # the same way a value object's already is
584
+ # 0025 — "Rules") — the same check a value object's already gets
454
585
  # (`ValueObjectBuilder#invariant`, whose own shape this mirrors
455
- # exactly). Today `invariant` lived only inside `value_object`;
456
- # an aggregate-level rule had nowhere to live, so "the balance
586
+ # exactly). Without an aggregate-level rule, "the balance
457
587
  # never goes negative" was three different `given`/`ensures`
458
588
  # texts across banking's six balance-moving commands, and the
459
589
  # four that only increase it said nothing at all — completeness
460
590
  # depended on someone noticing which commands could decrease it.
461
- # Renamed from `invariant` — item #13's full metaprogrammed
462
- # dispatch (slice 4b), same reasoning as given_impl above.
591
+ #
592
+ # Answers the `invariant` word through the table's `calls:`
593
+ # column — item #13's full metaprogrammed
594
+ # dispatch (slice 4b), same reasoning as `given_impl` above.
595
+ #
596
+ # @param description [String] the rule's description
597
+ # @yield the predicate body; evaluated for its extracted source, never called directly
598
+ # @return [void]
599
+ # @raise [Bluebook::DSL::Malformed] if the block's source could not be extracted
463
600
  def invariant_impl(description, &predicate)
464
601
  @invariants << build_rule(Invariant, description, predicate, owner_name: @name, word: "invariant",
465
602
  extraction_failure: "it would be a rule the IR cannot carry")
466
603
  end
467
604
 
605
+ # Assembles every declared attribute, construct and rule into an `Aggregate`, after
606
+ # draining pending commands/queries/entities and running every `seal_*` check.
607
+ #
608
+ # @return [Bluebook::Aggregate] the built aggregate
609
+ # @raise [Bluebook::DSL::Malformed] if identity resolution or any `seal_*` check fails —
610
+ # a mutation or query naming an undeclared field, an inconsistent default, a lifecycle
611
+ # guard with no lifecycle, a lifecycle-field mutation outside a transition, a projected
612
+ # field naming an undeclared reference, or a correction targeting an unreferenced field
468
613
  def build
469
614
  drain_pending!
470
615
  resolve_pending_identity!
@@ -500,6 +645,20 @@ module Hecks
500
645
  ir
501
646
  end
502
647
 
648
+ # Evaluates an `aggregate` block against a fresh builder and returns the built aggregate.
649
+ #
650
+ # @param name [String] the aggregate's name
651
+ # @param chapter_named_givens [Hash{String => Hash{String => Bluebook::Given}}] the
652
+ # chapter-wide given pool
653
+ # @param chapter_pending_givens [Array<Hash>] unresolved chapter-wide bare given
654
+ # references
655
+ # @param chapter_entity_named_givens [Hash{String => Hash{String => Bluebook::Given}}]
656
+ # the chapter-wide, entity-scoped given pool
657
+ # @param chapter_entity_pending_givens [Array<Hash>] unresolved chapter-wide,
658
+ # entity-scoped bare given references
659
+ # @yield the aggregate body, evaluated with the builder as `self`; may be omitted
660
+ # @return [Bluebook::Aggregate] the built aggregate
661
+ # @raise [Bluebook::DSL::Malformed] if the body fails any check `#build` raises
503
662
  def self.build(name, chapter_named_givens: {}, chapter_pending_givens: [],
504
663
  chapter_entity_named_givens: {}, chapter_entity_pending_givens: [], &block)
505
664
  builder = new(name, chapter_named_givens: chapter_named_givens, chapter_pending_givens: chapter_pending_givens,
@@ -511,9 +670,9 @@ module Hecks
511
670
 
512
671
  private
513
672
 
514
- # **Deferred construction** — `entity`/`command`/`query` used to build
515
- # immediately, inline, the moment their own DSL line ran during
516
- # `instance_eval` — meaning a command's own resolution (`sets
673
+ # Deferred construction — `entity`/`command`/`query` queue a descriptor rather than
674
+ # building immediately, inline, the moment their own DSL line runs during
675
+ # `instance_eval`. Building immediately would mean a command's own resolution (`sets
517
676
  # :field` importing the owner's own attribute, `given("desc")`
518
677
  # referencing an aggregate-level precondition, a query's own
519
678
  # positional-param resolution) only ever saw whatever `@entities`/
@@ -20,12 +20,20 @@ module Hecks
20
20
  UNSET = Object.new.freeze
21
21
  private_constant :UNSET
22
22
 
23
+ # Returns the attributes declared so far, minting the accumulating list on first use.
24
+ #
25
+ # @return [Array<Bluebook::Attribute>] the attributes declared so far, in declaration order
23
26
  def attributes = @attributes ||= []
24
27
 
25
28
  # Value objects synthesised from inline closed sets, collected here and
26
29
  # installed by whoever owns value objects (the aggregate).
30
+ #
31
+ # @return [Array<Bluebook::ValueObject>] the value objects synthesised so far
27
32
  def closed_sets = @closed_sets ||= []
28
33
 
34
+ # Declares one field on the owning construct, with its type and, optionally, a default,
35
+ # an optionality flag, a pattern, a closed-set reference, or an inline closed set.
36
+ #
29
37
  # `admits:` names a closed set that is already declared elsewhere —
30
38
  #
31
39
  # attribute :op, String, admits: "Vocabulary::QueryComparator"
@@ -54,22 +62,39 @@ module Hecks
54
62
  # (S0b) already resolves a bare, not-yet-declared constant to the
55
63
  # same forward reference the quoted form existed for — a bareword
56
64
  # `Name` reaches a value object named "Name" declared later in the
57
- # same block exactly as `"Name"` used to, `spell`'s own `to_s`
58
- # renders either one identically. Neither form appears in any frozen
59
- # era text (checked directly), so both are refused unconditionally —
60
- # nothing for `MetaValidator.shadow_parsing?` to answer for.
61
- # Renamed from `attribute` — item #13's full metaprogrammed
62
- # dispatch (slice 3, whole-project table-unification survey).
63
- # The word `attribute` itself is no longer a real method any
64
- # builder answers directly: every (context, word) Keyword row
65
- # for it carries `calls: "attribute_impl"`, and `GenericDispatch`
66
- # forwards the whole call here untouched — this method's own
67
- # body is exactly what `attribute` always was, unchanged, just
68
- # reached generically now rather than by Ruby's own direct
69
- # method lookup. `attribute_collector_spec.rb` (`AttributeCollector
70
- # has no method without a test` — dsl_coverage_spec.rb) and the
71
- # bootstrap fallback (`GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`) both
72
- # name this same string; they must never drift apart.
65
+ # same block, exactly what the quoted form `"Name"` would have
66
+ # reached — `spell`'s own `to_s` renders either one identically.
67
+ # Neither form appears in any frozen era text (checked directly), so
68
+ # both are refused unconditionally — nothing for
69
+ # `MetaValidator.shadow_parsing?` to answer for.
70
+ #
71
+ # The word `attribute` itself is not a real method any builder answers
72
+ # directly: every (context, word) Keyword row for it carries
73
+ # `calls: "attribute_impl"`, and `GenericDispatch` forwards the whole
74
+ # call here untouched. `attribute_collector_spec.rb`
75
+ # (`AttributeCollector has no method without a test` —
76
+ # dsl_coverage_spec.rb) and the bootstrap fallback
77
+ # (`GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`) both name this same
78
+ # string; they must never drift apart.
79
+ #
80
+ # @param name [Symbol] the attribute's name
81
+ # @param type [Module, Symbol, ListOf, OneOf] the attribute's type: a bare constant
82
+ # (resolved by `ConstShim`), or the `ListOf`/`OneOf` wrapper `list_of`/`one_of` return
83
+ # when called in this same type position; quoted text is refused
84
+ # @param default [Object, nil] the value an omitted attribute defaults to; not type-checked
85
+ # here
86
+ # @param optional [Boolean] whether the attribute may be omitted entirely
87
+ # @param pattern [Regexp, String, nil] a pattern the attribute's value must match; refused
88
+ # if it uses a construct `PatternSubset` disallows
89
+ # @param admits [String, nil] the qualified name of an already-declared closed set this
90
+ # attribute's value may come from, such as `"Vocabulary::QueryComparator"`
91
+ # @param one_of [Array<String, Symbol>, nil] permitted values for a field-shaped closed
92
+ # set, declared inline via `one_of: [...]`; meaningful only inside a `value_object`
93
+ # (`ValueObjectBuilder` overrides `install_inline_closed_set`) — refused everywhere else
94
+ # @return [void]
95
+ # @raise [Bluebook::DSL::Malformed] if `name` is already declared, `type` is omitted or
96
+ # quoted text, `pattern` uses a disallowed construct, or `one_of:` is given outside a
97
+ # `value_object`
73
98
  def attribute_impl(name, type = UNSET, default: nil, optional: false, pattern: nil,
74
99
  admits: nil, one_of: nil)
75
100
  # moved to the language: FieldName invariant, on Root.Attribute
@@ -109,17 +134,22 @@ module Hecks
109
134
  install_inline_closed_set(name, one_of) if one_of
110
135
  end
111
136
 
112
- # Renamed from `list_of` — item #13's full metaprogrammed
113
- # dispatch (slice 5). Called in an attribute's own type
114
- # position (`attribute :x, list_of(Y)`), never through a `def
115
- # list_of` any one builder answers as its own word — reached
116
- # via `WordGate#word_gate_dispatch`'s new "Type"-context
117
- # fallback, the same one `one_of_impl` below uses. Bootstrap-
118
- # reachable (every core chapter's own list-typed attributes use
119
- # it), so `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK` carries a
120
- # single `["Type", "list_of"]` entry rather than one per calling
121
- # context — the bootstrap branch checks that key too now, same
122
- # reasoning as the ordinary fallback.
137
+ # Wraps a type so `attribute_impl` records it as a list-valued attribute.
138
+ #
139
+ # Called in an attribute's own type position (`attribute :x,
140
+ # list_of(Y)`), never through a `def list_of` any one builder
141
+ # answers as its own word — reached via
142
+ # `WordGate#word_gate_dispatch`'s "Type"-context fallback, the same
143
+ # one `one_of_impl` below uses. Bootstrap-reachable (every core
144
+ # chapter's own list-typed attributes use it), so
145
+ # `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK` carries a single
146
+ # `["Type", "list_of"]` entry rather than one per calling context —
147
+ # the bootstrap branch checks that key too, same reasoning as the
148
+ # ordinary fallback.
149
+ #
150
+ # @param type [Module, Symbol] the list's element type, a bare constant
151
+ # @return [Bluebook::DSL::AttributeCollector::ListOf] the wrapper `attribute_impl` reads
152
+ # to mark the attribute list-valued
123
153
  def list_of_impl(type) = ListOf.new(type)
124
154
 
125
155
  # `reference_to Account` mints `:account` — no `_id` — the default
@@ -155,16 +185,22 @@ module Hecks
155
185
  # attribute's type is still a declared value object, which is now a
156
186
  # structural rule rather than a predicate.
157
187
  #
158
- # An earlier reading of this spelling parsed it and threw the values
159
- # away: the attribute became a plain String and the closed set meant
160
- # nothing, in a construct that looked supported. The desugaring is
161
- # pinned now — the same bluebook must always yield the same IR.
162
- # Renamed from `one_of` — item #13's full metaprogrammed dispatch
163
- # (slice 5), same reasoning as list_of_impl above. Same name as
188
+ # Wraps a list of permitted values so `attribute_impl` synthesises a closed-set value
189
+ # object for them.
190
+ #
191
+ # The desugaring is pinned — the same bluebook must always yield the
192
+ # same IR, so a plain String attribute silently discarding the
193
+ # values is not an outcome this spelling can produce.
194
+ #
195
+ # Reached the same way `list_of_impl` above is. Same name as
164
196
  # `ValueObjectBuilder#one_of_impl`'s own override on purpose —
165
197
  # that method's own `super(*values)` call (the no-block, bare
166
198
  # type-position case) resolves by method name up the ancestor
167
199
  # chain, and renaming only one side would silently break it.
200
+ #
201
+ # @param values [Array<String, Symbol>] the permitted values
202
+ # @return [Bluebook::DSL::AttributeCollector::OneOf] the wrapper `attribute_impl` reads to
203
+ # synthesise the closed-set value object
168
204
  def one_of_impl(*values) = OneOf.new(values)
169
205
 
170
206
  private
@@ -204,9 +240,9 @@ module Hecks
204
240
  # (`seal_mutation_targets`, `seal_query_field`, `projects`'s own
205
241
  # local check, `Instance#[]`, ...) uses `Array#find`/`any?`, which
206
242
  # silently answers whichever declaration happens to come first and
207
- # discards the second. Used to boot clean and stay that way : both
208
- # declarations survived into the IR, one of them permanently
209
- # unreachable by name. Refused here, at the one place every owner
243
+ # discards the second — left unrefused, both declarations would
244
+ # survive into the IR, one of them permanently unreachable by name.
245
+ # Refused here, at the one place every owner
210
246
  # (Aggregate/Entity/Command/Query/PortOperation/ValueObject, each
211
247
  # `include AttributeCollector`) mints an attribute through, rather
212
248
  # than taught to each of those readers individually.
@@ -302,9 +338,9 @@ module Hecks
302
338
  # regardless of where `identified_by` was actually written.
303
339
  # Most real bluebooks write it first (insert_at 0); one (a
304
340
  # ScheduledPayment corpus member) writes it after a reference_to
305
- # and an attribute — this matches either, and whatever a person
306
- # hand-writing `attribute field, Type` at that exact point,
307
- # the way this used to be required, would have produced.
341
+ # and an attribute — this matches either, producing the same
342
+ # result a person hand-writing `attribute field, Type` at that
343
+ # exact point would.
308
344
  attributes.insert(insert_at, attributes.pop)
309
345
  vo.attributes.flat_map do |attribute|
310
346
  identity_paths_for_attribute(attribute, value_objects, context_name,
@@ -336,9 +372,10 @@ module Hecks
336
372
  # of a compound `identified_by :a, :b`) names a single field, and
337
373
  # deriving its path only makes sense while every value object along
338
374
  # the way wraps exactly one field itself — the same "single-field
339
- # value object" ADR 0025 names this shape after. A multi-field
340
- # value object here used to expand silently into every one of its
341
- # own fields, minting an unannounced compound key nothing declared.
375
+ # value object" ADR 0025 names this shape after. Left unrefused, a
376
+ # multi-field value object here would expand silently into every
377
+ # one of its own fields, minting an unannounced compound key
378
+ # nothing declared.
342
379
  if nested.attributes.size != 1
343
380
  candidates = nested.attributes.map(&:name).join(", ")
344
381
  raise Malformed,