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
@@ -14,14 +14,31 @@ module Hecks
14
14
 
15
15
  attr_reader :root, :discovery, :register
16
16
 
17
+ # Discovers and loads every domain under `root` in one call.
18
+ #
19
+ # @param root [String] the directory to search under
20
+ # @return [Bluebook::ProjectLoader] the loader, with every discovered domain
21
+ # booted and registered
17
22
  def self.load(root) = new(root).load
18
23
 
24
+ # @param root [String] the directory to search under
25
+ # @param discovery [Bluebook::ProjectDiscovery] the discovery this loader walks
26
+ # `root` with
27
+ # @param register [Bluebook::ProjectRegister] the register booted domains are
28
+ # fed into
19
29
  def initialize(root, discovery: ProjectDiscovery.new(root), register: ProjectRegister.new)
20
30
  @root = File.expand_path(root)
21
31
  @discovery = discovery
22
32
  @register = register
23
33
  end
24
34
 
35
+ # Boots every domain `discovery` finds under `root` and registers its declarations.
36
+ #
37
+ # @return [Bluebook::ProjectLoader] self
38
+ # @raise [Bluebook::ProjectRegister::MissingRealm] see `ProjectRegister#register`
39
+ # @raise [Bluebook::ProjectRegister::LatestMismatch] see `ProjectRegister#register`
40
+ # @raise [Bluebook::ProjectRegister::DuplicateFqn] see `ProjectRegister#register`
41
+ # @raise [Runtime::WiringError] see `ProjectRegister#register`
25
42
  def load
26
43
  discovery.bluebook_directories.each do |directory|
27
44
  runtime = Runtime.boot(directory)
@@ -30,10 +47,33 @@ module Hecks
30
47
  self
31
48
  end
32
49
 
50
+ # Lists every registered entry.
51
+ #
52
+ # @return [Hash{String => Bluebook::ProjectRegister::Entry}] every registered
53
+ # entry, keyed by its FQN
33
54
  def entries = register.entries
55
+
56
+ # Finds a registered entry by its fully-qualified verb.
57
+ #
58
+ # @param address [String, #to_s] the FQN to look up
59
+ # @return [Bluebook::ProjectRegister::Entry] the entry registered under `address`
60
+ # @raise [KeyError] if no entry is registered under `address`
34
61
  def fetch(address) = register.fetch(address)
62
+
63
+ # Says whether an entry is registered under an address.
64
+ #
65
+ # @param address [String, #to_s] the FQN to check
66
+ # @return [Boolean] whether an entry is registered under `address`
35
67
  def include?(address) = register.include?(address)
68
+
69
+ # Lists every registered command entry.
70
+ #
71
+ # @return [Array<Bluebook::ProjectRegister::Entry>] every registered command entry
36
72
  def commands = register.commands
73
+
74
+ # Lists every registered query entry.
75
+ #
76
+ # @return [Array<Bluebook::ProjectRegister::Entry>] every registered query entry
37
77
  def queries = register.queries
38
78
  end
39
79
  end
@@ -4,7 +4,14 @@ module Hecks
4
4
  # declarations become public FQNs, but never searches or boots folders.
5
5
  class ProjectRegister
6
6
  Entry = Struct.new(:fqn, :source_directory, :dispatcher, :declared_verb, :domain_version, keyword_init: true) do
7
+ # Says whether this entry addresses a command.
8
+ #
9
+ # @return [Boolean] whether this entry's `fqn` addresses a command
7
10
  def command? = fqn.command?
11
+
12
+ # Says whether this entry addresses a query.
13
+ #
14
+ # @return [Boolean] whether this entry's `fqn` addresses a query
8
15
  def query? = fqn.query?
9
16
  end
10
17
 
@@ -14,16 +21,53 @@ module Hecks
14
21
 
15
22
  attr_reader :entries
16
23
 
24
+ # Starts an empty register, with no domains loaded yet.
17
25
  def initialize
18
26
  @entries = {}
19
27
  @tenant_directories = Hash.new { |hash, key| hash[key] = [] }
20
28
  end
21
29
 
30
+ # Finds a registered entry by its fully-qualified verb.
31
+ #
32
+ # @param address [String, #to_s] the FQN to look up, such as `"Pizzas.Pizza.Order"`
33
+ # @return [Bluebook::ProjectRegister::Entry] the entry registered under `address`
34
+ # @raise [KeyError] if no entry is registered under `address`
22
35
  def fetch(address) = entries.fetch(address.to_s)
36
+
37
+ # Says whether an entry is registered under an address.
38
+ #
39
+ # @param address [String, #to_s] the FQN to check
40
+ # @return [Boolean] whether an entry is registered under `address`
23
41
  def include?(address) = entries.key?(address.to_s)
42
+
43
+ # Lists every registered command entry.
44
+ #
45
+ # @return [Array<Bluebook::ProjectRegister::Entry>] every registered command entry
24
46
  def commands = entries.values.select(&:command?)
47
+
48
+ # Lists every registered query entry.
49
+ #
50
+ # @return [Array<Bluebook::ProjectRegister::Entry>] every registered query entry
25
51
  def queries = entries.values.select(&:query?)
26
52
 
53
+ # Registers every aggregate command/query and read model query a booted chapter
54
+ # declares, under its versioned and (if current) unversioned FQNs.
55
+ #
56
+ # @param bluebooks [Array<Bluebook::Chapter>] the booted chapters to register
57
+ # @param registry [Runtime::Registry] the registry `bluebooks` booted into
58
+ # @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted
59
+ # dispatcher entries dispatch through
60
+ # @param directory [String] the source directory `bluebooks` was discovered under
61
+ # @return [Bluebook::ProjectRegister] self
62
+ # @raise [Bluebook::ProjectRegister::MissingRealm] if a bluebook's world declares no
63
+ # realm
64
+ # @raise [Bluebook::ProjectRegister::LatestMismatch] if a world's `latest` disagrees
65
+ # with its bluebook's own version
66
+ # @raise [Bluebook::ProjectRegister::DuplicateFqn] if an FQN this call would register
67
+ # is already registered
68
+ # @raise [Runtime::WiringError] if a second tenant registers the same on-disk
69
+ # directory while any of its aggregates binds to an adapter that is not
70
+ # `tenant_capable?`
27
71
  def register(bluebooks, registry, dispatcher, directory)
28
72
  bluebooks.each do |bluebook|
29
73
  refuse_unless_safe_for_second_tenant!(bluebook, registry, directory)
@@ -6,6 +6,11 @@ module Hecks
6
6
  # throughout this chapter reach for. See lib/hecks/bluebook.rb's own
7
7
  # header for what `Bluebook` is as a whole.
8
8
  module Bluebook
9
+ # Renders a captured Ruby value to its self-describing wire spelling.
10
+ #
11
+ # @param value [Object] any value an `emits_ir` field carries, such as a Symbol,
12
+ # String, Hash, Array, or literal
13
+ # @return [String] the value rendered through `Hecks::Literal.render`
9
14
  def self.render_value(value) = Literal.render(value)
10
15
 
11
16
  # A query — an ask, declared on an aggregate or on one of its entities.
@@ -40,6 +45,25 @@ module Hecks
40
45
 
41
46
  attr_reader :name, :description, :attributes
42
47
 
48
+ # @param name [String, Symbol] the query's declared name
49
+ # @param description [String, nil] the query's declared prose description
50
+ # @param attributes [Array<Bluebook::Attribute>] the query's declared result fields
51
+ # @param wheres [Array<QuerySpecification::Common::WhereClause>] the declared filter
52
+ # conditions
53
+ # @param order_by [QuerySpecification::Common::OrderBy, nil] the declared sort field
54
+ # and direction, or `nil` for no explicit ordering
55
+ # @param limit [QuerySpecification::Common::LimitSpec, nil] the declared row limit,
56
+ # or `nil` for none
57
+ # @param offset [QuerySpecification::Common::OffsetSpec, nil] the declared row offset,
58
+ # or `nil` for none
59
+ # @param cursor [QuerySpecification::Common::CursorSpec, nil] the declared pagination
60
+ # cursor, or `nil` for none
61
+ # @param authorization [QuerySpecification::Common::AuthorizationSpec, nil] the declared
62
+ # authorization policy, or `nil` for none
63
+ # @param null_semantics [QuerySpecification::Common::NullSemantics, nil] how this query
64
+ # orders `nil` values; defaults to `NullSemantics.default` when omitted
65
+ # @param inspection [QuerySpecification::Common::InspectionSpec, nil] the declared
66
+ # inspection mode, or `nil` for none
43
67
  def initialize(name:, description: nil, attributes: [], wheres: [],
44
68
  order_by: nil, limit: nil, offset: nil, cursor: nil,
45
69
  authorization: nil, null_semantics: nil,
@@ -59,6 +83,9 @@ module Hecks
59
83
  # group_by, scope_to). Declared emission covers the settled part
60
84
  # and `super` hands it over; the tail stays dynamic, which is the
61
85
  # honest description of it.
86
+ #
87
+ # @return [Hash] the declared emission, merged with whatever `extra_options_to_h`
88
+ # the specification layer currently carries
62
89
  def to_h = super.merge(extra_options_to_h)
63
90
  end
64
91
  end
@@ -31,9 +31,25 @@ module Hecks
31
31
 
32
32
  # `reference_name:`/`reference_target:` are nil for a rootless read
33
33
  # model (no `reference_to` declared) — `&.` throughout, rather than
34
- # the `.to_s`/`.to_sym` this used to require unconditionally, so
34
+ # calling `.to_s`/`.to_sym` unconditionally, so
35
35
  # `reference_target.nil?` stays a real, checkable fact for the
36
36
  # interpreter instead of silently becoming `""`.
37
+ #
38
+ # @param name [String, Symbol] the read model's declared name
39
+ # @param description [String, nil] the read model's declared prose description
40
+ # @param reference_name [Symbol, String, nil] the local reference attribute this
41
+ # read model roots at, or `nil` for a rootless read model
42
+ # @param reference_target [String, Symbol, nil] the rooted aggregate's name, or
43
+ # `nil` for a rootless read model
44
+ # @param aggregate_heads [Array<Hash{Symbol => Object}>] the included aggregates, each
45
+ # a row with `:aggregate` (the aggregate's name), `:as` (the output field name) and
46
+ # `:many` (whether this head is a many-side join)
47
+ # @param group_by [Array<Hash{field: Symbol}>] the declared group-by fields, one row
48
+ # per field
49
+ # @param count [Boolean, nil] whether this read model reduces to a row count; `nil`
50
+ # when undeclared
51
+ # @param median_field [Symbol, nil] the field this read model reduces to the median
52
+ # of, or `nil` when undeclared
37
53
  def initialize(name:, description: nil, reference_name: nil, reference_target: nil, aggregate_heads: [],
38
54
  group_by: [], count: nil, median_field: nil, **)
39
55
  super(joins: aggregate_heads, **)
@@ -94,6 +110,10 @@ module Hecks
94
110
  # `WholeBluebook`, ...) for a fact nothing about them changed —
95
111
  # the exact `extra_options_to_h` reads for `cursor`/`offset`/etc,
96
112
  # applied here for the same reason.
113
+ #
114
+ # @return [Hash] the declared emission, with `aggregate_heads`/`group_by` rows
115
+ # stringified, `count`/`median_field` merged in only when declared, and
116
+ # `extra_options_to_h`'s own dynamic tail merged last
97
117
  def to_h
98
118
  reductions = {}
99
119
  reductions[:count] = true if @count
@@ -2,15 +2,16 @@ module Hecks
2
2
  module Bluebook
3
3
  # An attribute that points at another aggregate's head.
4
4
  #
5
- # This used to be the string `"Reference<Customer>"`, minted by
6
- # `AggregateBuilder#reference_to` and `CommandBuilder#cross_reference` from
7
- # a real constant that had just been handed in, and then parsed back apart
8
- # by a regex in the command interpreter, by string equality in the read-model
9
- # interpreter and the SQLite adapter, and by `delete_prefix` in the bluebook
10
- # builder. Five readers of a spelling one writer invented.
5
+ # Holds the target directly, and answers `resolve` with the target's
6
+ # Aggregate, rather than being spelled as the string `"Reference<Customer>"`
7
+ # that `AggregateBuilder#reference_to` and `CommandBuilder#cross_reference`
8
+ # would otherwise mint from the real constant just handed in. That string
9
+ # would need five separate readers to parse back apart — a regex in the
10
+ # command interpreter, string equality in the read-model interpreter and
11
+ # the SQLite adapter, and `delete_prefix` in the bluebook builder — one
12
+ # writer inventing a spelling, five readers reparsing it.
11
13
  #
12
- # It holds the target instead, and answers `resolve` with the target's
13
- # Aggregate. Resolution is lazy and deliberately so: `reference_to
14
+ # Resolution is lazy and deliberately so: `reference_to
14
15
  # Customer` may name an aggregate declared lower in the file — banking's
15
16
  # Account points at Customer and survives only because Customer happens to
16
17
  # be written above — so the edge cannot be resolved at declaration time.
@@ -29,6 +30,8 @@ module Hecks
29
30
  # up to the chapter, stamped once every sibling has been read.
30
31
  attr_accessor :declared_in
31
32
 
33
+ # @param target_name [Module, String, Symbol] the aggregate constant this
34
+ # reference points at, or its already-spelled name
32
35
  def initialize(target_name)
33
36
  @target_name = Naming.demodulise(target_name).to_s
34
37
  end
@@ -36,6 +39,11 @@ module Hecks
36
39
  # The Aggregate this points at, or nil when the target belongs to
37
40
  # another domain — a cross-domain target may legitimately not be loaded,
38
41
  # the same reading `across` policies get.
42
+ #
43
+ # @return [Bluebook::Aggregate, nil] the target aggregate, or `nil` when
44
+ # `declared_in`'s own chapter does not declare one by this name
45
+ # @raise [DSL::Malformed] if `declared_in` is unset, so there is no
46
+ # chapter to resolve the target against
39
47
  def resolve
40
48
  unless declared_in
41
49
  raise DSL::Malformed,
@@ -48,6 +56,8 @@ module Hecks
48
56
  end
49
57
 
50
58
  # The IR spelling, the one the export carries.
59
+ #
60
+ # @return [String] `"Reference<TargetName>"`
51
61
  def to_s = "Reference<#{@target_name}>"
52
62
  def inspect = "#<Reference #{@target_name}>"
53
63
 
@@ -66,6 +76,9 @@ module Hecks
66
76
  # (if same-shaped) owning aggregates, and `declared_in` is a
67
77
  # cross-reference for `resolve`, not part of what this attribute
68
78
  # itself is.
79
+ #
80
+ # @param other [Object] the value to compare against
81
+ # @return [Boolean] whether `other` is a `Reference` to the same target
69
82
  def ==(other) = other.is_a?(Reference) && target_name == other.target_name
70
83
  alias eql? ==
71
84
  def hash = [self.class, target_name].hash
@@ -22,11 +22,15 @@ module Hecks
22
22
  # report, per created root; every failure is collected and reported
23
23
  # together rather than stopping at the first.
24
24
  #
25
- # **What this cannot catch**: a call that dispatches cleanly but answers
26
- # wrong — the read-model join bug this exact tool's own first user
27
- # hit is the textbook case, a query that silently returned an empty
28
- # array rather than raising. Smoke-testing proves nothing crashes;
29
- # it does not prove the answer is the right one.
25
+ # ## What this cannot catch
26
+ #
27
+ # A call that dispatches cleanly but answers wrong — the read-model join
28
+ # bug this exact tool's own first user hit is the textbook case, a query
29
+ # that silently returned an empty array rather than raising.
30
+ # Smoke-testing proves nothing crashes; it does not prove the answer is
31
+ # the right one.
32
+ #
33
+ # ## False positives from a naive synthesizer
30
34
  #
31
35
  # A failure here is not necessarily a domain bug, either — it may
32
36
  # just be `Synthesizer`'s own naive values (0, an empty list,
@@ -82,6 +86,11 @@ module Hecks
82
86
  # measured, not hypothetical: it once left a stale `Widget` constant
83
87
  # that corrupted an unrelated spec's own unrelated use of the same
84
88
  # bare name.
89
+ #
90
+ # @param dir [String] path to a bootable hecks app directory — a saved
91
+ # domain, or a throwaway one rendered into a temp dir for this purpose
92
+ # @return [Array<Failure>] every dispatch or query failure collected,
93
+ # empty when `dir` declares no bluebook or nothing failed
85
94
  def call(dir)
86
95
  Dir.mktmpdir("hecks-smoke-") do |scratch|
87
96
  isolate!(dir, scratch)
@@ -105,6 +114,11 @@ module Hecks
105
114
  # excluded — those are earlier, superseded versions of the same
106
115
  # domain; smoke-testing the current declaration is the point, not
107
116
  # its whole history.
117
+ #
118
+ # @param dir [String] path to the source hecks app directory to isolate
119
+ # @param scratch [String] path to the throwaway directory to copy the
120
+ # `.bluebook` files into
121
+ # @return [void]
108
122
  def isolate!(dir, scratch)
109
123
  source = Adapters::Folder.new.bluebook_directory(dir)
110
124
  target = File.join(scratch, "bluebook")
@@ -123,6 +137,14 @@ module Hecks
123
137
  # documents; splitting would turn that ordering into an implicit contract on
124
138
  # parameter/return passing instead of one visible sequence.
125
139
  # rubocop:disable-next Metrics/AbcSize
140
+ #
141
+ # @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the
142
+ # booted dispatcher to dispatch synthesized commands and queries through
143
+ # @param domain [String] the domain name to smoke-test, a key of
144
+ # `dispatcher.registry.bluebooks`
145
+ # @return [Array<Failure>] every dispatch or query failure collected, in
146
+ # declaration order; empty when `domain` names no loaded chapter or
147
+ # nothing failed
126
148
  def smoke_domain(dispatcher, domain)
127
149
  chapter = dispatcher.registry.bluebook(domain)
128
150
  return [] unless chapter
@@ -156,7 +178,8 @@ module Hecks
156
178
 
157
179
  chapter.read_models.each do |model|
158
180
  root_id = created[model.reference_target]
159
- next unless root_id # nothing was created for this root — nothing to smoke-test yet, not a failure
181
+ # Nothing was created for this root — nothing to smoke-test yet, not a failure.
182
+ next unless root_id
160
183
 
161
184
  dispatcher.query("#{domain}.#{model.query_name}", model.reference_name => root_id)
162
185
  rescue StandardError => e
@@ -42,6 +42,17 @@ module Hecks
42
42
  # the same run; a reference whose target isn't in there yet gets
43
43
  # a placeholder instead of failing outright, since the caller may
44
44
  # not care about that particular argument's real value.
45
+ #
46
+ # @param chapter [Bluebook::Chapter] the chapter the command's aggregate
47
+ # belongs to, needed to resolve a value object type name back to its
48
+ # own declared shape
49
+ # @param aggregate [Bluebook::Aggregate] the aggregate `command` belongs to
50
+ # @param command [Class] the command class (a `Bluebook::Command`
51
+ # subclass) to synthesize arguments for
52
+ # @param created [Hash{String => Object}] aggregate name mapped to a real
53
+ # id already minted for it earlier in this same run
54
+ # @return [Hash{Symbol => Object}] one synthesized value per declared
55
+ # attribute, keyed by attribute name
45
56
  def args_for(chapter, aggregate, command, created = {})
46
57
  command.attributes.to_h do |attribute|
47
58
  if attribute.reference?
@@ -59,6 +70,16 @@ module Hecks
59
70
  # same tolerance `bin/interview shape`'s own lookup already
60
71
  # needed: a value object referenced by name doesn't have to be
61
72
  # declared on the same aggregate using it.
73
+ #
74
+ # @param chapter [Bluebook::Chapter] the chapter to search when
75
+ # `type_name` is not declared on `aggregate` itself
76
+ # @param aggregate [Bluebook::Aggregate] the aggregate `type_name` is
77
+ # looked up on first
78
+ # @param type_name [String] the value object's declared type name
79
+ # @return [String, Hash{Symbol => Object}] the string `"smoke-test"` when
80
+ # no such value object is declared; otherwise a Hash of one value per
81
+ # field — the closed set's own first admitted member's fields, or one
82
+ # freshly synthesized scalar per declared field
62
83
  def value_for(chapter, aggregate, type_name)
63
84
  value_object = aggregate.value_object(type_name) ||
64
85
  chapter.aggregates.filter_map { |a| a.value_object(type_name) }.first
@@ -75,6 +96,13 @@ module Hecks
75
96
  # another value object, resolved the identical way `value_for`
76
97
  # resolves any other one (its own closed set or its own nested
77
98
  # fields, however deep that nesting actually goes).
99
+ #
100
+ # @param chapter [Bluebook::Chapter] see `value_for`
101
+ # @param aggregate [Bluebook::Aggregate] see `value_for`
102
+ # @param type_name [String] the field's declared type name
103
+ # @return [Integer, Float, true, false, String, Hash{Symbol => Object}] a
104
+ # bare scalar when `type_name` is a true primitive (see `scalar_for`),
105
+ # otherwise `value_for`'s own result for the nested value object it names
78
106
  def field_value_for(chapter, aggregate, type_name)
79
107
  PRIMITIVES.include?(type_name.to_s) ? scalar_for(type_name) : value_for(chapter, aggregate, type_name)
80
108
  end
@@ -83,6 +111,12 @@ module Hecks
83
111
  # closed set's own field (that's `value_for`'s job, reading the
84
112
  # set's real first member) — only for a plain scalar field with
85
113
  # no declared vocabulary to respect.
114
+ #
115
+ # @param primitive [String, Symbol] the primitive type name, such as
116
+ # `"Integer"` or `"String"`
117
+ # @return [Integer, Float, false, String] `0` for `"Integer"`, `0.0` for
118
+ # `"Float"`, `false` for `"TrueClass"`/`"FalseClass"`, or the string
119
+ # `"smoke-test"` for anything else
86
120
  def scalar_for(primitive)
87
121
  case primitive.to_s
88
122
  when "Integer" then 0
@@ -19,7 +19,7 @@ module Hecks
19
19
  # A value object's or entity's own type name changed with its member
20
20
  # structure unchanged — the one drift `rename`/`move` cannot express,
21
21
  # because the attribute kept its name and only the type it points at
22
- # was renamed. Mirrors `was:` one level deeper.
22
+ # changed name. Mirrors `was:` one level deeper.
23
23
  TranslationRetype = Struct.new(:from, :to)
24
24
 
25
25
  # A computed transform — rescale, reformat, split, merge — whose only
@@ -57,6 +57,23 @@ module Hecks
57
57
  class TranslationAggregate
58
58
  attr_reader :name, :was, :renames, :moves, :converts, :drops, :retypes, :computes, :rekeys, :backfills
59
59
 
60
+ # @param name [String, Symbol] the aggregate's name in the destination era
61
+ # @param was [String, Symbol, nil] the aggregate's name in the origin era, or `nil`
62
+ # if it was not renamed
63
+ # @param renames [Hash{Symbol => Symbol}] each renamed field, old name to new name
64
+ # @param moves [Array<Bluebook::TranslationMove>] fields crossing a value-object
65
+ # boundary
66
+ # @param converts [Array<Bluebook::TranslationConvert>] fields whose value is
67
+ # remapped through a declared lookup table
68
+ # @param drops [Array<Symbol>] fields deliberately not carried forward
69
+ # @param retypes [Array<Bluebook::TranslationRetype>] value object or entity type
70
+ # renames with their member structure unchanged
71
+ # @param computes [Array<Bluebook::TranslationCompute>] fields computed by a SQL
72
+ # expression evaluated only inside the compiled Postgres head
73
+ # @param rekeys [Array<Bluebook::TranslationRekey>] SQL expressions that recompute
74
+ # the aggregate's own identity
75
+ # @param backfills [Array<Bluebook::TranslationBackfill>] newly added required
76
+ # fields with no source in old data
60
77
  def initialize(name:, was: nil, renames: {}, moves: [], converts: [], drops: [], retypes: [],
61
78
  computes: [], rekeys: [], backfills: [])
62
79
  @name = name.to_s
@@ -81,6 +98,13 @@ module Hecks
81
98
  class Translation
82
99
  attr_reader :domain, :from, :to, :aggregates, :retired
83
100
 
101
+ # @param domain [String, Symbol] the domain this translation carries forward
102
+ # @param from [String, Symbol] the origin era
103
+ # @param to [String, Symbol] the destination era
104
+ # @param aggregates [Array<Bluebook::TranslationAggregate>] each aggregate's own
105
+ # translation rules
106
+ # @param retired [Array<String>] the names of aggregates gone outright in the
107
+ # destination era, rather than renamed
84
108
  def initialize(domain:, from:, to:, aggregates: [], retired: [])
85
109
  @domain = domain.to_s
86
110
  @from = from
@@ -89,6 +113,11 @@ module Hecks
89
113
  @retired = retired
90
114
  end
91
115
 
116
+ # Finds one aggregate's own translation rules by its destination-era name.
117
+ #
118
+ # @param name [String, Symbol] the aggregate's name in the destination era
119
+ # @return [Bluebook::TranslationAggregate, nil] the aggregate's translation, or
120
+ # `nil` if `name` carries no translation rules
92
121
  def for_aggregate(name) = @aggregates.find { |aggregate| aggregate.name == name.to_s }
93
122
  end
94
123
  end
@@ -42,11 +42,11 @@ module Hecks
42
42
  # The field name is stringified, never the value. A `member` row can
43
43
  # hold any of the scalar types an attribute declares — `Integer 84`
44
44
  # (`StatementFrequency#retention_months`, statements.bluebook), not
45
- # only `String` — and `value.to_s` used to erase that on the way
46
- # out, so `84` and `"84"` (a member some other row might
47
- # legitimately spell as text) became indistinguishable once they
48
- # reached `to_h`. The declared name still moves (`field.to_s`) —
49
- # that half was never a Ruby object with a type to lose.
45
+ # only `String` — and stringifying it on the way out would erase
46
+ # that: `84` and `"84"` (a member some other row might legitimately
47
+ # spell as text) would become indistinguishable once they reached
48
+ # `to_h`. The declared name still moves (`field.to_s`) — that half
49
+ # was never a Ruby object with a type to lose.
50
50
  members: -> { members.map { |member| member.map { |field, value| [field.to_s, value] } } }
51
51
  )
52
52
 
@@ -55,6 +55,17 @@ module Hecks
55
55
 
56
56
  # One declared shape — a subclass rather than an instance, so the thing
57
57
  # the bluebook declares and the thing Ruby holds are one object.
58
+ #
59
+ # @param name [String, Symbol] the value object's declared type name
60
+ # @param attributes [Array<Bluebook::Attribute>] the value object's declared
61
+ # fields
62
+ # @param invariants [Array<Bluebook::Invariant>] the rules checked against every
63
+ # instance of this value object
64
+ # @param members [Array<Hash{Symbol => Object}>] the declared `one_of` members, one
65
+ # row of field values per member
66
+ # @param closed_set [Boolean] whether a `one_of` was declared, even with no
67
+ # members; defaults to whether `members` is non-empty
68
+ # @return [Class] the minted shape class (a `Bluebook::ValueObject` subclass)
58
69
  def declare(name:, attributes: [], invariants: [], members: [], closed_set: !members.empty?)
59
70
  shape = Class.new(self)
60
71
  shape.hecks_name = name.to_s
@@ -63,6 +74,13 @@ module Hecks
63
74
  shape
64
75
  end
65
76
 
77
+ # Assigns what the language declares onto this shape class.
78
+ #
79
+ # @param attributes [Array<Bluebook::Attribute>] see `declare`
80
+ # @param invariants [Array<Bluebook::Invariant>] see `declare`
81
+ # @param members [Array<Hash{Symbol => Object}>] see `declare`
82
+ # @param closed_set [Boolean] see `declare`
83
+ # @return [void]
66
84
  def absorb(attributes:, invariants:, members:, closed_set:)
67
85
  @attributes = attributes
68
86
  @invariants = invariants
@@ -7,10 +7,9 @@
7
7
  # DSL (dsl/), and the meta-validator that judges a chapter against the
8
8
  # language's own grammar (meta_validator.rb + meta_validator/).
9
9
  #
10
- # The require order below preserves the boot order the flat list in
11
- # lib/hecks.rb used to spell: expression and IR first (pure
12
- # declarations), assembly's collaborators before its face, the DSL before
13
- # the meta-validator that its builders call at build time.
10
+ # The require order below matters: expression and IR load first (pure
11
+ # declarations), assembly's collaborators load before its face, and the DSL
12
+ # loads before the meta-validator that its builders call at build time.
14
13
 
15
14
  module Hecks
16
15
  # Declared as a class, not a module — `Hecks::Bluebook` is a