hecks 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (373) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +72 -7
  3. data/lib/hecks/adapters/driven/d1.rb +187 -23
  4. data/lib/hecks/adapters/driven/folder.rb +83 -10
  5. data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
  6. data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
  7. data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
  8. data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
  9. data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
  10. data/lib/hecks/adapters/driven/heki.rb +106 -9
  11. data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
  12. data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
  13. data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
  15. data/lib/hecks/adapters/driven/lambda/client.rb +67 -14
  16. data/lib/hecks/adapters/driven/lambda.rb +82 -35
  17. data/lib/hecks/adapters/driven/local_storage.rb +83 -10
  18. data/lib/hecks/adapters/driven/memory.rb +205 -9
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -1
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
  24. data/lib/hecks/adapters/driven/postgres.rb +175 -28
  25. data/lib/hecks/adapters/driven/prism.rb +50 -11
  26. data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
  27. data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
  28. data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
  29. data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
  30. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
  31. data/lib/hecks/adapters/driven/sqlite.rb +181 -21
  32. data/lib/hecks/adapters/driven/system_clock.rb +3 -0
  33. data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
  34. data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
  35. data/lib/hecks/adapters/driven.rb +6 -4
  36. data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
  37. data/lib/hecks/behaviors/dsl.rb +60 -2
  38. data/lib/hecks/behaviors/expectations.rb +190 -29
  39. data/lib/hecks/behaviors/ir.rb +12 -1
  40. data/lib/hecks/behaviors/rspec.rb +9 -1
  41. data/lib/hecks/behaviors/runner.rb +21 -2
  42. data/lib/hecks/behaviors.rb +9 -1
  43. data/lib/hecks/bluebook/aggregate.rb +43 -13
  44. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
  45. data/lib/hecks/bluebook/assembly/build.rb +26 -5
  46. data/lib/hecks/bluebook/assembly/contract.rb +98 -23
  47. data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
  48. data/lib/hecks/bluebook/assembly/marks.rb +159 -30
  49. data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
  50. data/lib/hecks/bluebook/assembly.rb +32 -14
  51. data/lib/hecks/bluebook/attribute.rb +26 -12
  52. data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
  53. data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
  54. data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
  55. data/lib/hecks/bluebook/behaviour/command.rb +55 -25
  56. data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
  57. data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
  58. data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
  59. data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
  60. data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
  61. data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
  62. data/lib/hecks/bluebook/behaviour/query.rb +6 -1
  63. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
  64. data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
  65. data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
  66. data/lib/hecks/bluebook/capabilities.rb +27 -0
  67. data/lib/hecks/bluebook/chapter.rb +51 -9
  68. data/lib/hecks/bluebook/command.rb +62 -17
  69. data/lib/hecks/bluebook/domain_port.rb +34 -9
  70. data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
  71. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
  72. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +282 -123
  73. data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
  74. data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
  75. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
  76. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
  77. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
  78. data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
  79. data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
  80. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
  81. data/lib/hecks/bluebook/dsl/entity_builder.rb +191 -61
  82. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
  83. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
  84. data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
  85. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
  86. data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
  87. data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
  88. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
  89. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +111 -47
  90. data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
  91. data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
  92. data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
  93. data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
  94. data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
  95. data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
  96. data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
  97. data/lib/hecks/bluebook/entity.rb +40 -11
  98. data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
  99. data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
  100. data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
  101. data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
  102. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
  103. data/lib/hecks/bluebook/expression/resolver.rb +369 -128
  104. data/lib/hecks/bluebook/hexagon.rb +35 -1
  105. data/lib/hecks/bluebook/lifecycle.rb +12 -1
  106. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
  107. data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
  108. data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
  109. data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
  110. data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
  111. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
  112. data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
  113. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
  114. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
  115. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
  116. data/lib/hecks/bluebook/meta_validator.rb +235 -139
  117. data/lib/hecks/bluebook/model_check.rb +434 -104
  118. data/lib/hecks/bluebook/pattern_subset.rb +32 -10
  119. data/lib/hecks/bluebook/policy.rb +15 -13
  120. data/lib/hecks/bluebook/process_manager.rb +27 -14
  121. data/lib/hecks/bluebook/project_discovery.rb +5 -0
  122. data/lib/hecks/bluebook/project_loader.rb +40 -0
  123. data/lib/hecks/bluebook/project_register.rb +50 -6
  124. data/lib/hecks/bluebook/query.rb +31 -4
  125. data/lib/hecks/bluebook/read_model.rb +35 -15
  126. data/lib/hecks/bluebook/reference.rb +26 -13
  127. data/lib/hecks/bluebook/smoke_test.rb +46 -23
  128. data/lib/hecks/bluebook/synthesizer.rb +46 -12
  129. data/lib/hecks/bluebook/translation.rb +34 -5
  130. data/lib/hecks/bluebook/value_object.rb +29 -11
  131. data/lib/hecks/bluebook.rb +5 -6
  132. data/lib/hecks/codemod.rb +138 -50
  133. data/lib/hecks/construct.rb +21 -7
  134. data/lib/hecks/corpus.rb +438 -0
  135. data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
  136. data/lib/hecks/doc/reference.rb +200 -31
  137. data/lib/hecks/embryonaut_bluebook.rb +38 -15
  138. data/lib/hecks/facade/cli_door.rb +69 -10
  139. data/lib/hecks/facade/cli_runner.rb +105 -24
  140. data/lib/hecks/facade/command_request.rb +23 -0
  141. data/lib/hecks/facade/handle.rb +155 -35
  142. data/lib/hecks/facade/json_door.rb +106 -25
  143. data/lib/hecks/facade/surface/aggregate_door.rb +50 -27
  144. data/lib/hecks/facade/surface/chapter.rb +26 -17
  145. data/lib/hecks/facade/surface.rb +16 -3
  146. data/lib/hecks/facade.rb +15 -4
  147. data/lib/hecks/forms/app.rb +46 -30
  148. data/lib/hecks/forms/command_form_renderer.rb +70 -9
  149. data/lib/hecks/forms/field_renderer.rb +142 -6
  150. data/lib/hecks/forms/field_shape.rb +183 -19
  151. data/lib/hecks/forms/html.rb +51 -7
  152. data/lib/hecks/forms/index_renderer.rb +14 -2
  153. data/lib/hecks/forms/page.rb +14 -0
  154. data/lib/hecks/forms/params.rb +120 -23
  155. data/lib/hecks/forms/port_argument.rb +14 -2
  156. data/lib/hecks/forms/query_form_renderer.rb +65 -2
  157. data/lib/hecks/forms/record_renderer.rb +60 -2
  158. data/lib/hecks/forms/record_table.rb +28 -1
  159. data/lib/hecks/forms/reference_options.rb +24 -0
  160. data/lib/hecks/forms/value_object_shape.rb +13 -3
  161. data/lib/hecks/forms.rb +24 -4
  162. data/lib/hecks/fqn.rb +59 -1
  163. data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
  164. data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
  165. data/lib/hecks/framework/oidc.json +15 -0
  166. data/lib/hecks/framework.rb +79 -25
  167. data/lib/hecks/freezer.rb +27 -11
  168. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
  169. data/lib/hecks/fuzzing/combination_miner.rb +178 -0
  170. data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
  171. data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
  172. data/lib/hecks/fuzzing/differential.rb +192 -0
  173. data/lib/hecks/fuzzing/domain_generator.rb +871 -0
  174. data/lib/hecks/fuzzing/era_boundary.rb +68 -19
  175. data/lib/hecks/fuzzing/form_census.rb +121 -20
  176. data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
  177. data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
  178. data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
  179. data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
  180. data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
  181. data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
  182. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
  183. data/lib/hecks/fuzzing/properties/guards.rb +86 -42
  184. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
  185. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
  186. data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
  187. data/lib/hecks/fuzzing/properties/querying.rb +82 -28
  188. data/lib/hecks/fuzzing/properties.rb +84 -51
  189. data/lib/hecks/fuzzing/qa_settings.rb +164 -0
  190. data/lib/hecks/fuzzing/replay.rb +241 -133
  191. data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
  192. data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
  193. data/lib/hecks/fuzzing/self_consistency.rb +315 -137
  194. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
  195. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
  196. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
  197. data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
  198. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +59 -32
  199. data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
  200. data/lib/hecks/fuzzing/shrinker.rb +309 -0
  201. data/lib/hecks/fuzzing/structural_skips.rb +37 -130
  202. data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
  203. data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
  204. data/lib/hecks/fuzzing/value_generator.rb +110 -22
  205. data/lib/hecks/fuzzing.rb +5 -0
  206. data/lib/hecks/grammar/evolve.rb +188 -12
  207. data/lib/hecks/grammar.rb +53 -7
  208. data/lib/hecks/ir.rb +51 -20
  209. data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
  210. data/lib/hecks/language/bluebook/policy.bluebook +11 -1
  211. data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
  212. data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
  213. data/lib/hecks/language/oidc.json +5 -0
  214. data/lib/hecks/literal.rb +41 -9
  215. data/lib/hecks/naming.rb +112 -31
  216. data/lib/hecks/ports/access_control.rb +53 -2
  217. data/lib/hecks/ports/agent/answers.rb +83 -6
  218. data/lib/hecks/ports/agent.rb +119 -35
  219. data/lib/hecks/ports/authentication.rb +44 -4
  220. data/lib/hecks/ports/authorization.rb +50 -11
  221. data/lib/hecks/ports/clock.rb +42 -23
  222. data/lib/hecks/ports/extraction.rb +16 -0
  223. data/lib/hecks/ports/identity_assignment.rb +23 -2
  224. data/lib/hecks/ports/identity_generation.rb +17 -3
  225. data/lib/hecks/ports/identity_resolution.rb +17 -1
  226. data/lib/hecks/ports/key_vault.port +6 -0
  227. data/lib/hecks/ports/key_vault.rb +58 -0
  228. data/lib/hecks/ports/loading.rb +4 -0
  229. data/lib/hecks/ports/persistence/append_only.rb +172 -8
  230. data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
  231. data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
  232. data/lib/hecks/ports/persistence/execution.rb +4 -0
  233. data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
  234. data/lib/hecks/ports/persistence/plugin.rb +42 -4
  235. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
  236. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
  237. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
  238. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
  239. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
  242. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
  243. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
  244. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
  245. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
  246. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +34 -5
  247. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
  248. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
  249. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
  250. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
  251. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
  252. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +95 -6
  253. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
  254. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
  255. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
  256. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
  257. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
  258. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +48 -8
  259. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
  260. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
  261. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
  262. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +58 -19
  263. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
  264. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
  265. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -1
  266. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
  267. data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
  268. data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
  269. data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
  270. data/lib/hecks/ports/persistence/state_codec.rb +319 -0
  271. data/lib/hecks/ports/persistence.rb +36 -1
  272. data/lib/hecks/ports/projection.rb +61 -7
  273. data/lib/hecks/ports/query/in_memory.rb +41 -8
  274. data/lib/hecks/ports/query/ordering.rb +21 -6
  275. data/lib/hecks/ports/query.rb +35 -0
  276. data/lib/hecks/ports.rb +1 -0
  277. data/lib/hecks/projections/bootstrap_table.rb +147 -0
  278. data/lib/hecks/projections/diagrams.rb +315 -79
  279. data/lib/hecks/projections/glossary/html.rb +91 -3
  280. data/lib/hecks/projections/glossary/markdown.rb +50 -3
  281. data/lib/hecks/projections/glossary/mermaid.rb +49 -1
  282. data/lib/hecks/projections/glossary/sections.rb +1 -1
  283. data/lib/hecks/projections/glossary/sentences.rb +110 -5
  284. data/lib/hecks/projections/glossary.rb +173 -31
  285. data/lib/hecks/projections/ir.rb +1 -1
  286. data/lib/hecks/projections/model/deviations.rb +62 -17
  287. data/lib/hecks/projections/model.rb +75 -21
  288. data/lib/hecks/projections/oidc.rb +45 -8
  289. data/lib/hecks/projections/parser_table.rb +53 -12
  290. data/lib/hecks/projections/reference.rb +15 -3
  291. data/lib/hecks/projections/rust_vocabulary.rb +646 -0
  292. data/lib/hecks/projections/shape.rb +10 -2
  293. data/lib/hecks/projections/statements.rb +69 -22
  294. data/lib/hecks/projections/vocabulary.rb +26 -9
  295. data/lib/hecks/projections.rb +5 -3
  296. data/lib/hecks/projector/cli_projector.rb +245 -37
  297. data/lib/hecks/projector/docs_projector.rb +154 -28
  298. data/lib/hecks/projector/exporter.rb +104 -29
  299. data/lib/hecks/projector/ir_projector.rb +7 -1
  300. data/lib/hecks/projector/narrate_projector.rb +144 -23
  301. data/lib/hecks/projector/target.rb +42 -18
  302. data/lib/hecks/projector.rb +86 -18
  303. data/lib/hecks/query_ir.rb +94 -47
  304. data/lib/hecks/query_specification/common/comparators.rb +19 -3
  305. data/lib/hecks/query_specification/common/comparison.rb +109 -27
  306. data/lib/hecks/query_specification/common/dsl.rb +65 -9
  307. data/lib/hecks/query_specification/common/null_policy.rb +61 -15
  308. data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
  309. data/lib/hecks/query_specification/common/options.rb +25 -0
  310. data/lib/hecks/query_specification/field_path.rb +69 -15
  311. data/lib/hecks/query_specification/hop_path.rb +57 -20
  312. data/lib/hecks/query_specification/read_model/specification.rb +4 -0
  313. data/lib/hecks/rendering.rb +9 -3
  314. data/lib/hecks/router/namespace_installer.rb +16 -3
  315. data/lib/hecks/router.rb +56 -1
  316. data/lib/hecks/runtime/aggregate_lock.rb +20 -11
  317. data/lib/hecks/runtime/boot_gates.rb +21 -3
  318. data/lib/hecks/runtime/caller.rb +40 -8
  319. data/lib/hecks/runtime/capability_graph.rb +13 -2
  320. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
  321. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
  322. data/lib/hecks/runtime/command_interpreter.rb +195 -113
  323. data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
  324. data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
  325. data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
  326. data/lib/hecks/runtime/command_rules/emission.rb +18 -1
  327. data/lib/hecks/runtime/command_rules/references.rb +145 -55
  328. data/lib/hecks/runtime/command_rules.rb +3 -0
  329. data/lib/hecks/runtime/dependency_planning.rb +56 -11
  330. data/lib/hecks/runtime/dispatcher.rb +263 -92
  331. data/lib/hecks/runtime/entity_element.rb +304 -67
  332. data/lib/hecks/runtime/entity_interpreter.rb +149 -88
  333. data/lib/hecks/runtime/errors.rb +37 -23
  334. data/lib/hecks/runtime/event.rb +15 -10
  335. data/lib/hecks/runtime/identity.rb +91 -23
  336. data/lib/hecks/runtime/instance.rb +103 -18
  337. data/lib/hecks/runtime/interpreting.rb +24 -16
  338. data/lib/hecks/runtime/invocation.rb +358 -0
  339. data/lib/hecks/runtime/loader.rb +105 -19
  340. data/lib/hecks/runtime/outbox.rb +164 -26
  341. data/lib/hecks/runtime/policy_interpreter.rb +73 -60
  342. data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
  343. data/lib/hecks/runtime/query_interpreter.rb +93 -74
  344. data/lib/hecks/runtime/reaction_invocation.rb +73 -28
  345. data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
  346. data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
  347. data/lib/hecks/runtime/reference_hop.rb +48 -6
  348. data/lib/hecks/runtime/refusal_wording.rb +142 -115
  349. data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
  350. data/lib/hecks/runtime/registry/verification.rb +153 -28
  351. data/lib/hecks/runtime/registry.rb +202 -32
  352. data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
  353. data/lib/hecks/runtime/routing.rb +36 -154
  354. data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
  355. data/lib/hecks/runtime/saga_interpreter.rb +90 -76
  356. data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
  357. data/lib/hecks/runtime/tenant_check.rb +33 -13
  358. data/lib/hecks/runtime/tenant_scope.rb +23 -5
  359. data/lib/hecks/runtime/value/admission.rb +75 -30
  360. data/lib/hecks/runtime/value/coercion.rb +555 -142
  361. data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
  362. data/lib/hecks/runtime/value.rb +71 -21
  363. data/lib/hecks/runtime.rb +39 -7
  364. data/lib/hecks/storehouse.rb +368 -72
  365. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
  366. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
  367. data/lib/hecks/version.rb +3 -3
  368. data/lib/hecks/vocabulary.rb +205 -4
  369. data/lib/hecks.rb +91 -11
  370. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
  371. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
  372. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
  373. metadata +28 -2
@@ -4,7 +4,7 @@ require_relative "../statements"
4
4
  module Hecks
5
5
  module Projections
6
6
  module Glossary
7
- # EVERY SENTENCE THE GLOSSARY SAYS, AND THE RULE EACH ONE OBEYS.
7
+ # Every sentence the glossary says, and the rule each one obeys.
8
8
  #
9
9
  # Authored text is verbatim: an aggregate's `description`, a
10
10
  # command's `goal`, a query's `description`, an invariant's own
@@ -30,6 +30,11 @@ module Hecks
30
30
 
31
31
  # The paragraphs under a term's headword — the definition first,
32
32
  # then, when the term carries rules, one "Always true: …" line.
33
+ #
34
+ # @param entry [Glossary::Entry] the term to render
35
+ # @param index [Glossary::Index] the document's link index
36
+ # @return [Array<String>] one or more paragraphs, compacted of any that
37
+ # have nothing to say (such as an entity with no lifecycle)
33
38
  def paragraphs(entry, index)
34
39
  facts = entry.facts
35
40
  case entry.kind
@@ -47,10 +52,18 @@ module Hecks
47
52
 
48
53
  # An aggregate's lede or an entity's entry: what it is, then the
49
54
  # states it can be in.
55
+ #
56
+ # @param holder [Bluebook::Aggregate, Bluebook::Entity] the holder to describe
57
+ # @return [Array<String>] `holder`'s description, then its lifecycle sentence;
58
+ # either or both may be absent
50
59
  def holder_paragraphs(holder)
51
60
  [holder.description, holder.lifecycle && lifecycle_sentence(holder.lifecycle)].compact
52
61
  end
53
62
 
63
+ # Describes a lifecycle's starting state and every state it can reach.
64
+ #
65
+ # @param lifecycle [Bluebook::Lifecycle] the state machine to describe
66
+ # @return [String] what state it starts in and every state it can reach
54
67
  def lifecycle_sentence(lifecycle)
55
68
  states = ([lifecycle.default] + lifecycle.transitions.map { |_name, transition| transition.target }).uniq
56
69
  "Starts out #{spoken(lifecycle.default)}. " \
@@ -58,15 +71,31 @@ module Hecks
58
71
  end
59
72
 
60
73
  # A state name is spelled `awaiting_credit`; said, it is "awaiting credit".
74
+ #
75
+ # @param state [String, Symbol] a state name
76
+ # @return [String] `state` with underscores turned to spaces
61
77
  def spoken(state) = state.to_s.tr("_", " ")
62
78
 
79
+ # Renders a value object's definition, and its rules if it has any.
80
+ #
81
+ # @param value_object [Bluebook::ValueObject] the value object to describe
82
+ # @param index [Glossary::Index] the document's link index
83
+ # @param within [String, nil] the value object's own holder's name, for
84
+ # resolving a nested value object's own type link
85
+ # @return [Array<String>] the value object's definition sentence, then its
86
+ # "Always true: …" rules line; the rules line is absent when it has none
63
87
  def value_object_paragraphs(value_object, index, within)
64
88
  rules = value_object.invariants.map { |invariant| Statements.invariant_statement(invariant) }
65
89
  [value_object_sentence(value_object, index, within), rules_line(rules)].compact
66
90
  end
67
91
 
68
- # A one-field object whose field is just "value" IS its type —
92
+ # A one-field object whose field is just "value" is its type —
69
93
  # "Text.", "A whole number." — the field name would add nothing.
94
+ #
95
+ # @param value_object [Bluebook::ValueObject] the value object to describe
96
+ # @param index [Glossary::Index] the document's link index
97
+ # @param within [String, nil] the value object's own holder's name
98
+ # @return [String] the value object's definition sentence
70
99
  def value_object_sentence(value_object, index, within)
71
100
  return closed_set_sentence(value_object.members) if value_object.closed_set?
72
101
  return "A marker with no details of its own." if value_object.attributes.empty?
@@ -82,20 +111,35 @@ module Hecks
82
111
 
83
112
  # "amount (a whole number)" — the field as the author named it,
84
113
  # then what kind of thing goes in it.
114
+ #
115
+ # @param field [Bluebook::Attribute] the field to describe
116
+ # @param index [Glossary::Index] the document's link index
117
+ # @param within [String, nil] the field's own holder's name
118
+ # @return [String] the field's name, spoken, with its type in parentheses
85
119
  def field_phrase(field, index, within)
86
120
  "#{Naming.words(field.name).downcase} (#{type_words(field, index, within)})"
87
121
  end
88
122
 
123
+ # Describes one field's type in plain words.
124
+ #
125
+ # @param field [Bluebook::Attribute] the field to describe
126
+ # @param index [Glossary::Index] the document's link index
127
+ # @param within [String, nil] the field's own holder's name
128
+ # @return [String] the field's type in plain words, or a link to its value
129
+ # object's own headword; "a list of …" when the field is a list
89
130
  def type_words(field, index, within)
90
131
  type = field.type.to_s
91
132
  inner = TYPE_WORDS[type] || index.link(:value_object, type, within: within)
92
133
  field.list? ? "a list of #{inner}" : inner
93
134
  end
94
135
 
95
- # A CLOSED SET'S ROWS — a one-field set is its values; a set
136
+ # A closed set's rows — a one-field set is its values; a set
96
137
  # whose rows carry more (StatementFrequency's cadence plus a
97
138
  # retention and a fee) leads with the first field and keeps the
98
139
  # rest beside it, so no row loses what makes it distinct.
140
+ #
141
+ # @param members [Array<Hash{Symbol => Object}>] the closed set's member rows
142
+ # @return [String] "One of: …" (multi-field rows) or "One of …" (single-field)
99
143
  def closed_set_sentence(members)
100
144
  if members.first && members.first.size > 1
101
145
  rows = members.map do |row|
@@ -112,6 +156,10 @@ module Hecks
112
156
  # code." — the rules as their author wrote them, kept out of the
113
157
  # definition sentence (a rule hidden inside a definition is a rule
114
158
  # a reader misses).
159
+ #
160
+ # @param rules [Array<String>] rendered invariant sentences
161
+ # @return [String, nil] "Always true: …" joining every rule, or nil if
162
+ # `rules` is empty
115
163
  def rules_line(rules)
116
164
  return nil if rules.empty?
117
165
 
@@ -119,6 +167,11 @@ module Hecks
119
167
  "Always true: #{clauses.join('; ')}."
120
168
  end
121
169
 
170
+ # Describes a command's goal and who does it.
171
+ #
172
+ # @param command [Bluebook::Command] the command to describe
173
+ # @return [String, nil] the command's goal and/or its role sentence, or nil
174
+ # if it declares neither
122
175
  def command_sentence(command)
123
176
  parts = []
124
177
  parts << with_period(command.goal) if command.goal
@@ -126,6 +179,14 @@ module Hecks
126
179
  parts.empty? ? nil : parts.join(" ")
127
180
  end
128
181
 
182
+ # Describes when an event is recorded and what it prompts.
183
+ #
184
+ # @param facts [Hash{Symbol => Object}] an `:event`-kind entry's facts:
185
+ # `:raised_by` (`Array<Array(Bluebook::Aggregate, Bluebook::Command)>`) and
186
+ # `:policies` (`Array<Bluebook::Policy>`)
187
+ # @param index [Glossary::Index] the document's link index
188
+ # @return [String] which commands record the event, and, when any policy
189
+ # reacts to it, which ones it prompts
129
190
  def event_sentence(facts, index)
130
191
  raisers = command_links(facts[:raised_by], index)
131
192
  sentence = "Recorded after #{Naming.to_sentence_list(raisers, conj: 'or')}."
@@ -138,6 +199,10 @@ module Hecks
138
199
  # account, once for each row of Open for customer." — a
139
200
  # cross-domain trigger (`across "Compliance"`) is spoken as words
140
201
  # with the domain named, since nothing here to link to exists.
202
+ #
203
+ # @param policy [Bluebook::Policy] the policy to describe
204
+ # @param index [Glossary::Index] the document's link index
205
+ # @return [String] when the policy fires and what it asks for
141
206
  def policy_sentence(policy, index)
142
207
  holder, command = split_trigger(policy.trigger_command)
143
208
  asked = if policy.target_domain
@@ -154,6 +219,13 @@ module Hecks
154
219
  "#{sentence}."
155
220
  end
156
221
 
222
+ # Describes when a saga begins and ends, and its states along the way.
223
+ #
224
+ # @param shape [Hash{Symbol => Object}] a process manager's `to_h`, read for
225
+ # `:starts_on`, `:ends_on`, and `:states`
226
+ # @param index [Glossary::Index] the document's link index
227
+ # @return [String] when the saga begins and ends, and, when it declares any,
228
+ # the states it can be in along the way
157
229
  def saga_sentence(shape, index)
158
230
  sentence = "Begins when #{index.link(:event, bare(shape[:starts_on]))} happens " \
159
231
  "and ends when #{index.link(:event, bare(shape[:ends_on]))} happens."
@@ -162,18 +234,29 @@ module Hecks
162
234
  sentence
163
235
  end
164
236
 
165
- # A NOUN LIST, DELIBERATELY — "Responsible for Credit and Debit",
237
+ # A noun list, deliberately — "Responsible for Credit and Debit",
166
238
  # never "Can credit and debit": banking's System role raises
167
239
  # `Debited` and `Credited`, and "can … debited" is a wrong
168
240
  # sentence. The headwords are already what people say.
241
+ #
242
+ # @param issues [Array<Array(Bluebook::Aggregate, Bluebook::Command)>] the
243
+ # role's own `[holder, command]` pairs
244
+ # @param index [Glossary::Index] the document's link index
245
+ # @return [String] "Responsible for …", naming every command
169
246
  def role_sentence(issues, index)
170
247
  "Responsible for #{Naming.to_sentence_list(command_links(issues, index))}."
171
248
  end
172
249
 
173
- # THE SAME WORD FOR TWO DIFFERENT THINGS gets its holder beside it
250
+ # The same word for two different things gets its holder beside it
174
251
  # — a role responsible for CardPayment's Reverse and Transfer's
175
252
  # Reverse is responsible for "Reverse (card payment)" and
176
253
  # "Reverse (transfer)", not for "Reverse" twice.
254
+ #
255
+ # @param issues [Array<Array(Bluebook::Aggregate, Bluebook::Command)>] each
256
+ # `[holder, command]` pair to link
257
+ # @param index [Glossary::Index] the document's link index
258
+ # @return [Array<String>] one Markdown link per distinct `[holder, command]`
259
+ # pair, qualified with the holder's name when the command name repeats
177
260
  def command_links(issues, index)
178
261
  issues = issues.uniq { |holder, command| [holder.hecks_name, command.hecks_name] }
179
262
  repeated = issues.map { |_holder, command| command.hecks_name }.tally.select { |_name, count| count > 1 }
@@ -187,17 +270,39 @@ module Hecks
187
270
 
188
271
  # ── small carpentry ─────────────────────────────────────────────
189
272
 
273
+ # Splits a dotted `Holder.command` trigger into its two parts.
274
+ #
275
+ # @param dotted [String, Symbol] a `"Holder.command"` reference
276
+ # @return [Array(String, String)] `[holder, command]`
190
277
  def split_trigger(dotted)
191
278
  holder, _dot, command = dotted.to_s.rpartition(".")
192
279
  [holder, command]
193
280
  end
194
281
 
282
+ # Strips a dotted name down to its last segment.
283
+ #
284
+ # @param qualified [String, Symbol] a dotted or bare name, such as
285
+ # `"Handler.Dispatch"` or `"Freeze"`
286
+ # @return [String] the name after the last `.`, or the whole name if it has none
195
287
  def bare(qualified) = qualified.to_s.split(".").last
196
288
 
289
+ # Ensures text ends in sentence-ending punctuation.
290
+ #
291
+ # @param text [String, Symbol, nil] the text to punctuate
292
+ # @return [String] `text`, stripped, with a trailing `.` added if it has no
293
+ # sentence-ending punctuation already
197
294
  def with_period(text) = text.to_s.strip.end_with?(".", "!", "?") ? text.to_s.strip : "#{text.to_s.strip}."
198
295
 
296
+ # Lowercases a leading capital letter.
297
+ #
298
+ # @param text [String] the text to adjust
299
+ # @return [String] `text` with a leading uppercase letter lowercased
199
300
  def lower_first(text) = text.sub(/\A[[:upper:]]/, &:downcase)
200
301
 
302
+ # Uppercases a leading lowercase letter.
303
+ #
304
+ # @param text [String] the text to adjust
305
+ # @return [String] `text` with a leading lowercase letter uppercased
201
306
  def upper_first(text) = text.sub(/\A[[:lower:]]/, &:upcase)
202
307
  end
203
308
  end
@@ -9,33 +9,37 @@ require_relative "glossary/html"
9
9
 
10
10
  module Hecks
11
11
  module Projections
12
- # A CHAPTER, PROJECTED AS ITS UBIQUITOUS LANGUAGE — a glossary for the
13
- # whole team, in the sense Evans meant (DDD ch. 2): ONE language,
12
+ # A chapter, projected as its ubiquitous language — a glossary for the
13
+ # whole team, in the sense Evans meant (DDD ch. 2): one language,
14
14
  # shared by domain experts and developers, written the same way in
15
15
  # conversation, diagrams, documents and code, so that a subject-matter
16
16
  # expert can read the model and say "yes, that's how it works" or
17
17
  # "no, that's wrong". A banker, a support rep, the CEO and an engineer
18
18
  # read the same page.
19
19
  #
20
- # THREE THINGS FOLLOW FROM THAT AUDIENCE. First, no type jargon: a
21
- # term is a term, not "an Aggregate" or "a Value Object", and every
22
- # identifier is spelled as a person says it (`Naming.words`: `ATMCard`
23
- # is "ATM card"). Second, nothing invented: every sentence is either
24
- # the domain author's own words (a `description`, a `goal`, an
25
- # invariant) or built mechanically from a declared fact ("Recorded
26
- # after Freeze account") — the same discipline `DocsProjector` holds
27
- # to; a wrong sentence is worse than a missing one. Third, grouped
28
- # under the aggregate each term belongs to, A to Z within it — a
29
- # reader thinks "what does Account mean" before "what starts with A",
30
- # and once there reads the way a dictionary reads.
20
+ # ## Three things follow from that audience
31
21
  #
32
- # TWO FILES FROM ONE SOURCE. `glossary.md` is the document — every
33
- # construct in it renders on GitHub as-is (headings, blockquotes,
34
- # ```mermaid fences, lists, in-page links). `html/index.html` is that
35
- # exact Markdown string rendered into a page with a navigation rail;
36
- # it is built FROM the Markdown, not beside it, so the two cannot
37
- # drift. `bin/project_glossary` writes both to `<domain>/glossary/` beside
38
- # the bluebook (examples/banking, examples/pizzas, and the QA ledger in qa/).
22
+ # First, no type jargon: a term is a term, not "an Aggregate" or "a
23
+ # Value Object", and every identifier is spelled as a person says it
24
+ # (`Naming.words`: `ATMCard` is "ATM card"). Second, nothing invented:
25
+ # every sentence is either the domain author's own words (a
26
+ # `description`, a `goal`, an invariant) or built mechanically from a
27
+ # declared fact ("Recorded after Freeze account") — the same
28
+ # discipline `DocsProjector` holds to; a wrong sentence is worse than
29
+ # a missing one. Third, grouped under the aggregate each term belongs
30
+ # to, A to Z within it — a reader thinks "what does Account mean"
31
+ # before "what starts with A", and once there reads the way a
32
+ # dictionary reads.
33
+ #
34
+ # ## Two files from one source
35
+ #
36
+ # `glossary.md` is the document — every construct in it renders on
37
+ # GitHub as-is (headings, blockquotes, ```mermaid fences, lists,
38
+ # in-page links). `html/index.html` is that exact Markdown string
39
+ # rendered into a page with a navigation rail; it is built from the
40
+ # Markdown, not beside it, so the two cannot drift. `bin/project_glossary`
41
+ # writes both to `<domain>/glossary/` beside the bluebook (examples/banking,
42
+ # examples/pizzas, and the QA ledger in qa/).
39
43
  #
40
44
  # Projector.call(:glossary, bluebook: <the Bluebook chapter>)
41
45
  # # => { "glossary.md" => "...", "html/index.html" => "..." }
@@ -59,6 +63,14 @@ module Hecks
59
63
 
60
64
  module_function
61
65
 
66
+ # Projects `bluebook`'s glossary as a Markdown document and the HTML
67
+ # page built from it.
68
+ #
69
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter to render a
70
+ # glossary for
71
+ # @param options [Hash] unused; accepted to satisfy the registry's call shape
72
+ # @return [Hash{String => String}] `"glossary.md"` and `"html/index.html"`,
73
+ # each mapped to its rendered content
62
74
  def call(bluebook:, options: {})
63
75
  markdown = Markdown.render(document(bluebook))
64
76
  { "glossary.md" => markdown, "html/index.html" => Html.render(markdown) }
@@ -66,14 +78,25 @@ module Hecks
66
78
 
67
79
  # ── the document ─────────────────────────────────────────────────
68
80
 
81
+ # Gathers every term, groups it into sections, assigns headings and
82
+ # slugs, and builds the index links resolve through.
83
+ #
84
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter to render
85
+ # @return [Document] the fully assembled document, ready for `Markdown.render`
69
86
  def document(bluebook)
70
87
  sections = sections(bluebook, entries(bluebook))
71
88
  Slugs.assign!(bluebook, sections)
72
89
  Document.new(bluebook: bluebook, sections: sections, index: Index.new(sections))
73
90
  end
74
91
 
75
- # AGGREGATES FIRST, A TO Z BY THEIR SPOKEN NAME ("Account" before
92
+ # Aggregates first, a to Z by their spoken name ("Account" before
76
93
  # "ATM card"), then the three groups nothing homes to one aggregate.
94
+ #
95
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter declaring the
96
+ # aggregates to group by
97
+ # @param entries [Array<Entry>] every gathered term
98
+ # @return [Array<Section>] one section per aggregate, sorted, followed by
99
+ # any of the Roles/Read models/Reactions sections that have entries
77
100
  def sections(bluebook, entries)
78
101
  grouped = entries.group_by(&:section)
79
102
  list = bluebook.aggregates.sort_by { |aggregate| Naming.words(aggregate.hecks_name).downcase }.map do |aggregate|
@@ -87,10 +110,16 @@ module Hecks
87
110
  list
88
111
  end
89
112
 
90
- # A HEADWORD IS QUALIFIED ONLY WHEN IT WOULD REPEAT within its own
113
+ # A headword is qualified only when it would repeat within its own
91
114
  # section — "Open" the action and "Open (the list)" the question;
92
115
  # "Return (key issuance)" beside another Return — never numbered
93
116
  # (Chicago 18.9, MDN's disambiguation pages: qualify the headword).
117
+ #
118
+ # @param entries [Array<Entry>] the section's own entries
119
+ # @param section_name [String] the entries' own section name, for telling
120
+ # an entry's own section apart from a borrowed one when qualifying
121
+ # @return [Array<Entry>] `entries`, headword-assigned, sorted by headword
122
+ # then kind
94
123
  def with_headwords(entries, section_name)
95
124
  entries.each { |entry| entry.headword = Naming.words(entry.name) }
96
125
  entries.group_by(&:headword).each_value do |group|
@@ -101,6 +130,14 @@ module Hecks
101
130
  entries.sort_by { |entry| [entry.headword.downcase, entry.kind.to_s] }
102
131
  end
103
132
 
133
+ # Disambiguates one entry's headword from the rest of its group.
134
+ #
135
+ # @param entry [Entry] the entry to qualify
136
+ # @param group [Array<Entry>] every entry sharing `entry`'s plain headword
137
+ # @param section_name [String] the section `entry` and `group` are being
138
+ # rendered under
139
+ # @return [String] `entry`'s headword, qualified with "(the list)" or its
140
+ # own holder's name when needed to tell it apart from the rest of `group`
104
141
  def qualified(entry, group, section_name)
105
142
  base = Naming.words(entry.name)
106
143
  return "#{base} (the list)" if entry.kind == :query && group.any? { |other| other.kind == :command }
@@ -111,16 +148,24 @@ module Hecks
111
148
 
112
149
  # ── gathering ────────────────────────────────────────────────────
113
150
 
114
- # AN ENTITY CAN CARRY ITS OWN COMMANDS AND QUERIES TOO — walked the
151
+ # An entity can carry its own commands and queries too — walked the
115
152
  # same one level down `DocsProjector` and `Projections::Diagrams`
116
153
  # already walk it.
154
+ #
155
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter to walk
156
+ # @return [Array<Bluebook::Aggregate, Bluebook::Entity>] every aggregate, each
157
+ # immediately followed by its own nested entities
117
158
  def holders(bluebook)
118
159
  bluebook.aggregates.flat_map { |aggregate| [aggregate, *aggregate.entities] }
119
160
  end
120
161
 
121
- # EVERY HOLDER'S OWN AGGREGATE, ONE HOP OR ZERO — an aggregate maps
162
+ # Every holder's own aggregate, one hop or zero — an aggregate maps
122
163
  # to itself, an entity to whichever aggregate declared it. The one
123
164
  # fact the grouping is built on.
165
+ #
166
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter to walk
167
+ # @return [Hash{String => String}] every aggregate and entity name, mapped to
168
+ # its own owning aggregate's name
124
169
  def holder_aggregate(bluebook)
125
170
  bluebook.aggregates.each_with_object({}) do |aggregate, map|
126
171
  map[aggregate.hecks_name] = aggregate.hecks_name
@@ -128,17 +173,32 @@ module Hecks
128
173
  end
129
174
  end
130
175
 
131
- # EVERY EVENT'S RAISERS — an event is never declared, only emitted,
132
- # so its home is whichever aggregate the FIRST command that raises
176
+ # Every event's raisers — an event is never declared, only emitted,
177
+ # so its home is whichever aggregate the first command that raises
133
178
  # it belongs to; a policy or saga reacting to it inherits that home.
179
+ #
180
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter to walk
181
+ # @return [Hash{String => Array<Array(Bluebook::Aggregate, Bluebook::Command)>}]
182
+ # every emitted event name, mapped to `[holder, command]` pairs for every
183
+ # command that raises it; a name absent from the Hash raises nothing
134
184
  def event_raisers(bluebook)
135
185
  holders(bluebook).each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |holder, map|
136
186
  holder.commands.each { |command| command.emits.each { |event| map[event] << [holder, command] } }
137
187
  end
138
188
  end
139
189
 
190
+ # Strips a dotted name down to its last segment.
191
+ #
192
+ # @param qualified [String, Symbol] a dotted or bare name, such as
193
+ # `"Handler.Dispatch"` or `"Freeze"`
194
+ # @return [String] the name after the last `.`, or the whole name if it has none
140
195
  def bare(qualified) = qualified.to_s.split(".").last
141
196
 
197
+ # Gathers every term the glossary carries, ungrouped and unordered.
198
+ #
199
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather terms from
200
+ # @return [Array<Entry>] one entry per entity, value object, command, query,
201
+ # event, policy, saga, role, and read model
142
202
  def entries(bluebook)
143
203
  homes = holder_aggregate(bluebook)
144
204
  raisers = event_raisers(bluebook)
@@ -156,6 +216,10 @@ module Hecks
156
216
  entries
157
217
  end
158
218
 
219
+ # Gathers one entry per entity.
220
+ #
221
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather entities from
222
+ # @return [Array<Entry>] one `:entity`-kind entry per entity
159
223
  def entity_entries(bluebook)
160
224
  bluebook.aggregates.flat_map do |aggregate|
161
225
  aggregate.entities.map do |entity|
@@ -165,8 +229,13 @@ module Hecks
165
229
  end
166
230
  end
167
231
 
168
- # AGGREGATES ONLY — an entity declares no value objects of its own
232
+ # Aggregates only — an entity declares no value objects of its own
169
233
  # (`Bluebook::Entity` deliberately does not answer `value_objects`).
234
+ #
235
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather value
236
+ # objects from
237
+ # @return [Array<Entry>] one `:value_object`-kind entry per aggregate-declared
238
+ # value object
170
239
  def value_object_entries(bluebook)
171
240
  bluebook.aggregates.flat_map do |aggregate|
172
241
  aggregate.value_objects.map do |value_object|
@@ -176,6 +245,14 @@ module Hecks
176
245
  end
177
246
  end
178
247
 
248
+ # Gathers one entry per command and query.
249
+ #
250
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather commands
251
+ # and queries from
252
+ # @param homes [Hash{String => String}] every holder's name, mapped to its
253
+ # owning aggregate's name (as `holder_aggregate` builds)
254
+ # @return [Array<Entry>] one `:command`-kind or `:query`-kind entry per command
255
+ # and query, commands first
179
256
  def verb_entries(bluebook, homes)
180
257
  holders(bluebook).flat_map do |holder|
181
258
  commands = holder.commands.map do |command|
@@ -190,6 +267,15 @@ module Hecks
190
267
  end
191
268
  end
192
269
 
270
+ # Gathers one entry per raised event.
271
+ #
272
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter declaring the
273
+ # policies checked for reactions
274
+ # @param raisers [Hash{String => Array<Array(Bluebook::Aggregate, Bluebook::Command)>}]
275
+ # every event name, mapped to its raising `[holder, command]` pairs (as
276
+ # `event_raisers` builds)
277
+ # @param home_of [Proc, #call] answers an event name's owning aggregate name, or nil
278
+ # @return [Array<Entry>] one `:event`-kind entry per raised event
193
279
  def event_entries(bluebook, raisers, home_of)
194
280
  raisers.map do |event, raised_by|
195
281
  reactions = bluebook.policies.select { |policy| bare(policy.on_event) == event }
@@ -198,6 +284,11 @@ module Hecks
198
284
  end
199
285
  end
200
286
 
287
+ # Gathers one entry per declared policy.
288
+ #
289
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather policies from
290
+ # @param home_of [Proc, #call] answers an event name's owning aggregate name, or nil
291
+ # @return [Array<Entry>] one `:policy`-kind entry per declared policy
201
292
  def policy_entries(bluebook, home_of)
202
293
  bluebook.policies.map do |policy|
203
294
  Entry.new(name: policy.name, kind: :policy, section: home_of.call(bare(policy.on_event)),
@@ -205,6 +296,11 @@ module Hecks
205
296
  end
206
297
  end
207
298
 
299
+ # Gathers one entry per declared process manager.
300
+ #
301
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather sagas from
302
+ # @param home_of [Proc, #call] answers an event name's owning aggregate name, or nil
303
+ # @return [Array<Entry>] one `:saga`-kind entry per declared process manager
208
304
  def saga_entries(bluebook, home_of)
209
305
  bluebook.process_managers.map do |saga|
210
306
  shape = saga.to_h
@@ -213,9 +309,12 @@ module Hecks
213
309
  end
214
310
  end
215
311
 
216
- # CROSS-CUTTING BY NATURE — `System` and `Customer` issue commands
312
+ # Cross-cutting by nature — `System` and `Customer` issue commands
217
313
  # across half the aggregates here — so a role belongs to no single
218
314
  # one and gets its own section.
315
+ #
316
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather roles from
317
+ # @return [Array<Entry>] one `:role`-kind entry per distinct role named by a command
219
318
  def role_entries(bluebook)
220
319
  by_role = Hash.new { |hash, key| hash[key] = [] }
221
320
  holders(bluebook).each do |holder|
@@ -228,6 +327,10 @@ module Hecks
228
327
 
229
328
  # A read model joins heads from more than one aggregate — its own
230
329
  # header says so — so it belongs to none of them.
330
+ #
331
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter to gather read
332
+ # models from
333
+ # @return [Array<Entry>] one `:read_model`-kind entry per declared read model
231
334
  def read_model_entries(bluebook)
232
335
  bluebook.read_models.map do |read_model|
233
336
  Entry.new(name: read_model.name, kind: :read_model, section: READ_MODELS, facts: { read_model: read_model })
@@ -236,7 +339,7 @@ module Hecks
236
339
 
237
340
  # ── anchors ──────────────────────────────────────────────────────
238
341
 
239
- # GITHUB'S OWN HEADING SLUGS, REPRODUCED — lowercase, punctuation
342
+ # **GitHub's own heading slugs, reproduced** — lowercase, punctuation
240
343
  # dropped, spaces to hyphens, a repeat gets "-1", "-2" in document
241
344
  # order. Computed here, once, in the order the headings will appear,
242
345
  # so a link written into the Markdown lands on the same heading
@@ -244,8 +347,20 @@ module Hecks
244
347
  module Slugs
245
348
  module_function
246
349
 
350
+ # Reproduces GitHub's own heading-to-slug transform.
351
+ #
352
+ # @param text [String] the heading text to slugify
353
+ # @return [String] `text` lowercased, stripped of punctuation, spaces turned
354
+ # to hyphens
247
355
  def github(text) = text.to_s.downcase.gsub(/[^\p{Word}\- ]/, "").tr(" ", "-")
248
356
 
357
+ # Assigns every section's and every entry's own slug, in document order.
358
+ #
359
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter the document
360
+ # titles itself after
361
+ # @param sections [Array<Section>] the sections to assign slugs to, in the
362
+ # order they render
363
+ # @return [void]
249
364
  def assign!(bluebook, sections)
250
365
  seen = Hash.new(0)
251
366
  take = lambda do |text|
@@ -261,12 +376,16 @@ module Hecks
261
376
  end
262
377
  end
263
378
 
264
- # WHERE A DECLARED REFERENCE POINTS — keyed structurally (a command
265
- # by its holder AND name, an event by its bare name), never by
379
+ # **Where a declared reference points** — keyed structurally (a command
380
+ # by its holder and name, an event by its bare name), never by
266
381
  # searching prose for a matching word. Anything not declared here
267
382
  # (a cross-domain command like `Notifications.Send`) answers nil,
268
383
  # and the sentence says it in words with no link.
269
384
  class Index
385
+ # Builds the lookup table every `link` call resolves through.
386
+ #
387
+ # @param sections [Array<Section>] the document's own sections, already
388
+ # built and slugged
270
389
  def initialize(sections)
271
390
  @by_key = {}
272
391
  sections.each do |section|
@@ -275,6 +394,12 @@ module Hecks
275
394
  end
276
395
  end
277
396
 
397
+ # Builds one entry's lookup key.
398
+ #
399
+ # @param entry [Entry] the entry to key
400
+ # @return [Array(Symbol, String, String), Array(Symbol, String)] `[kind, within,
401
+ # name]` for a command, query, or value object (which can share a bare name
402
+ # across holders); `[kind, name]` otherwise
278
403
  def key_of(entry)
279
404
  case entry.kind
280
405
  when :command, :query, :value_object then [entry.kind, entry.within, entry.name]
@@ -282,6 +407,14 @@ module Hecks
282
407
  end
283
408
  end
284
409
 
410
+ # Looks up one entry or aggregate section by its key.
411
+ #
412
+ # @param kind [Symbol] the entry's kind, or `:aggregate` for a section
413
+ # @param name [String] the entry's or aggregate's name
414
+ # @param within [String, nil] the holder's name, for a command, query, or
415
+ # value object; nil otherwise
416
+ # @return [Entry, Section, nil] the matching entry or aggregate section, or
417
+ # nil if the chapter declares none
285
418
  def [](kind, name, within: nil)
286
419
  @by_key[within ? [kind, within, name] : [kind, name]]
287
420
  end
@@ -289,6 +422,15 @@ module Hecks
289
422
  # A Markdown link to a term's own heading, or the plain words when
290
423
  # the chapter declares no such term. `label:` overrides the link
291
424
  # text for a sentence that has to tell two same-named terms apart.
425
+ #
426
+ # @param kind [Symbol] the target's kind, or `:aggregate` for a section
427
+ # @param name [String] the target's or aggregate's name
428
+ # @param within [String, nil] the holder's name, for a command, query, or
429
+ # value object; nil otherwise
430
+ # @param label [String, nil] link text to use instead of the target's own
431
+ # headword or title
432
+ # @return [String] a `[text](#slug)` Markdown link, or the plain words when
433
+ # the chapter declares no such term
292
434
  def link(kind, name, within: nil, label: nil)
293
435
  target = self[kind, name, within: within]
294
436
  return label || Naming.words(name) unless target
@@ -7,7 +7,7 @@ module Hecks
7
7
  # The canonical IR, as a constant. The implementation already existed
8
8
  # and is already golden-tested (`Projector::IRProjector`, registered
9
9
  # as `:ir`) — this only gives it the constant spelling every other
10
- # target has, by re-registering the SAME module under the same key.
10
+ # target has, by re-registering the same module under the same key.
11
11
  #
12
12
  # Deliberately not a new implementation: two things named `IR` that
13
13
  # each rendered IR their own way is exactly the drift this namespace