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
@@ -1,50 +1,49 @@
1
1
  module Hecks
2
2
  module Bluebook
3
3
  class Assembly
4
- # THE FIRST SPECIALIZER — a projection of `contracts.rb`'s `fields:` table,
4
+ # **The first specializer** — a projection of `contracts.rb`'s `fields:` table,
5
5
  # derived from the language's own description of a category instead of
6
6
  # hand-written beside it.
7
7
  #
8
8
  # `Plan` already reads `grammar_registry` to build the judge's walk ; this
9
- # reads the same chapter to build the OTHER table this arc's own header
10
- # names as duplication — "SPELLED AS THE IR SPELLS THEM," field for field,
9
+ # reads the same chapter to build the other table this arc's own header
10
+ # names as duplication — "spelled as the IR spells them," field for field,
11
11
  # for every category simple enough to say so.
12
12
  #
13
- # ONE CASE, PROVEN, NOT THE WHOLE TABLE. A field this can speak for is
13
+ # One case, proven, not the whole table. A field this can speak for is
14
14
  # scalar and not a reference — every other field (a list, a reference, a
15
15
  # fold like Lifecycle) is exactly what `contracts.rb`'s `reads:`/`derived:`
16
16
  # exist to say, and stays hand-written until a later projection learns to
17
17
  # derive readers and folds too. Restricting the claim to what can be
18
- # PROVEN CORRECT — checked in spec/specializer_spec.rb against two
18
+ # proven correct — checked in spec/specializer_spec.rb against two
19
19
  # independent categories — is the same discipline `derived:` itself
20
20
  # enforces : a claim needs a kind, and this one's kind is "plain, checked."
21
21
  module Specializer
22
22
  module_function
23
23
 
24
- # `position` IS THE FIRST FOLD THIS RUNS INTO, and it is a universal
25
- # one : every category declares `attribute :position, Position` — for
26
- # the JUDGE's own walk, `order_by :position` on its `DeclaredIn` ask —
27
- # but no `*` constructor takes it as an argument. `contracts.rb`
28
- # already says so, in the language every other derived field speaks :
29
- # `derived: { position: :walk }`. The language says a category HAS a
30
- # position ; it does not say a category's OWN constructor is handed
31
- # one, and that second fact is exactly what `fields:` needs to answer.
32
- # So this is not silently special-cased — it is the one fold named
33
- # here because it is the one fold that is not a lucky accident of
34
- # Policy or Handler, but a fact true of every category this arc will
35
- # ever reach.
36
- DERIVED_EVERYWHERE = %i[position].freeze
37
-
38
24
  # S17, ADR 0026 — `Handler` is a genuine entity now, nested under
39
25
  # `ProcessManager`, so `.aggregate` alone no longer finds it —
40
26
  # it hangs off some aggregate's own `.entities` instead
41
27
  # (searched recursively, the same reason `Value::Coercion#find_
42
- # entity` does: a NESTED entity, like `Dispatch` inside
28
+ # entity` does: a nested entity, like `Dispatch` inside
43
29
  # `Handler`, is not a direct child of any aggregate either).
30
+ #
31
+ # @param chapter [Bluebook::Chapter] the chapter to search
32
+ # @param name [String, Symbol] the construct's declared name
33
+ # @return [Bluebook::Aggregate, Class, nil] the aggregate, or the entity class
34
+ # (a `Bluebook::Entity` subclass) nested anywhere under one, or `nil` if
35
+ # `chapter` declares no construct by that name
44
36
  def construct_for(chapter, name)
45
37
  chapter.aggregate(name) || chapter.aggregates.filter_map { |a| find_entity(a, name) }.first
46
38
  end
47
39
 
40
+ # Searches a construct's own entities, recursively, for one by name.
41
+ #
42
+ # @param construct [Bluebook::Aggregate, Class] the aggregate, or entity class,
43
+ # to search under
44
+ # @param name [String, Symbol] the entity's declared name
45
+ # @return [Class, nil] the entity class (a `Bluebook::Entity` subclass), or `nil`
46
+ # if none of `construct`'s nested entities has that name
48
47
  def find_entity(construct, name)
49
48
  construct.entities.each do |candidate|
50
49
  return candidate if candidate.hecks_name == name
@@ -55,11 +54,29 @@ module Hecks
55
54
  nil
56
55
  end
57
56
 
57
+ # `position` is the first fold this runs into : a category declares
58
+ # `attribute :position, Position` — for the judge's own walk,
59
+ # `order_by :position` on its `DeclaredIn` ask — but no `*`
60
+ # constructor takes it as an argument. `contracts.rb` already says so,
61
+ # in the language every other derived field speaks :
62
+ # `derived: { position: :walk }`. The language says a category has a
63
+ # position ; it does not say a category's own constructor is handed
64
+ # one, and that second fact is exactly what `fields:` needs to answer.
65
+ # So the skip reads the category's own walk claims (`Contract#walked`)
66
+ # rather than restating `position` here — Handler, which has no
67
+ # walk-minted position, skips nothing.
68
+ #
69
+ # @param category [String, Symbol] the construct category's name, such as
70
+ # `"Aggregate"` or `"Command"`
71
+ # @return [Hash{Symbol => Array(Symbol, Symbol)}] a `contracts.rb`-shaped
72
+ # `fields:` table, one `field => [field, :plain]` entry per scalar,
73
+ # non-reference, non-walked attribute the language declares for `category`
58
74
  def fields_for(category)
59
75
  language = construct_for(MetaValidator.grammar_registry.bluebook("Bluebook"), category.to_s)
76
+ walked = Assembly.contract(category).walked
60
77
  language.attributes.each_with_object({}) do |attribute, fields|
61
78
  next if attribute.list? || attribute.reference?
62
- next if DERIVED_EVERYWHERE.include?(attribute.name)
79
+ next if walked.include?(attribute.name)
63
80
 
64
81
  fields[attribute.name] = [attribute.name, :plain]
65
82
  end
@@ -1,6 +1,6 @@
1
1
  module Hecks
2
2
  module Bluebook
3
- # A GRAPH BUILT FROM DECLARATIONS, rather than from DSL calls.
3
+ # A graph built from declarations, rather than from DSL calls.
4
4
  #
5
5
  # This is the half that lets the language orchestrate. `Reconstruction` reads a
6
6
  # chapter back out of the meta-domain, in declaration order, as plain
@@ -8,32 +8,50 @@ module Hecks
8
8
  # into the graph the runtime runs: IR aggregates with their verbs, value
9
9
  # objects, entities and asks, owned by the chapter that declares them.
10
10
  #
11
- # It takes a HASH, not a runtime, on purpose. That makes it a pure inverse of
11
+ # ## Why a hash, not a runtime
12
+ #
13
+ # It takes a hash, not a runtime, on purpose. That makes it a pure inverse of
12
14
  # `to_h` and testable without the meta-domain in the picture at all:
13
15
  #
14
16
  # Assembly.call(built.to_h).to_h == built.to_h
15
17
  #
16
18
  # which is the check `spec/assembly_spec` makes for every chapter in the tree.
17
19
  # Feed it the reconstruction instead and the same code assembles what the
18
- # LANGUAGE holds — the only difference being where the declarations came from.
20
+ # language holds — the only difference being where the declarations came from.
21
+ #
22
+ # ## One table, not a method per category
19
23
  #
20
- # EVERY FIELD IS READ FROM ONE TABLE. There is no method per category here:
24
+ # Every field is read from one table. There is no method per category here:
21
25
  # `Contracts` names what the language cannot say about a construct, `Build`
22
- # reads it, and the coverage gate holds the table to the language. The first
23
- # draft of this file did have a method each, which is the shape the judge used
24
- # to have — and the price of that shape was fourteen verbs the language declared
25
- # and nothing ever offered.
26
+ # reads it, and the coverage gate holds the table to the language. A method
27
+ # per category can decorate a verb the language declares without ever
28
+ # offering it; the table cannot, because `spec/assembly_spec` checks every
29
+ # field the language declares against it.
30
+ #
31
+ # ## What stays hand-written
26
32
  #
27
- # What stays hand-written is the CONTAINMENT: which construct holds which.
28
- # That is not a field table. (The runtime SURFACE is no longer built here at
29
- # all — the door is a per-boot projection, facade/surface.rb.)
33
+ # The containment — which construct holds which — stays hand-written; it is
34
+ # not a field table. The runtime surface is no longer built here at all — the
35
+ # door is a per-boot projection, facade/surface.rb.
30
36
  class Assembly
37
+ # Builds the graph the runtime runs from one chapter's declared hash.
38
+ #
39
+ # @param declaration [Hash{Symbol => Object}] a chapter's declarations, in the
40
+ # shape `Chapter#to_h` spells
41
+ # @return [Bluebook::Chapter] the assembled chapter, with its aggregates, read
42
+ # models and process managers built and wired
31
43
  def self.call(declaration) = new(declaration).bluebook
32
44
 
45
+ # @param declaration [Hash{Symbol => Object}] a chapter's declarations, in the
46
+ # shape `Chapter#to_h` spells
33
47
  def initialize(declaration)
34
48
  @declaration = declaration
35
49
  end
36
50
 
51
+ # Assembles this instance's declared hash into the chapter's runtime graph.
52
+ #
53
+ # @return [Bluebook::Chapter] the assembled chapter, with its aggregates, read
54
+ # models and process managers built and wired
37
55
  def bluebook
38
56
  aggregates = Array(@declaration[:aggregates]).map { |row| AggregateAssembly.new(row).aggregate }
39
57
  models = Array(@declaration[:read_models]).map { |row| Build.call("ReadModel", row) }
@@ -50,7 +68,7 @@ module Hecks
50
68
  private
51
69
 
52
70
  # Every reaction the chapter holds, and each one also handed back to the head
53
- # that declared it — the builder keeps a policy in BOTH places, hoisting it onto
71
+ # that declared it — the builder keeps a policy in both places, hoisting it onto
54
72
  # the chapter where the runtime reads it while the head keeps its own list. The
55
73
  # language records which head, so the assembly can put it back.
56
74
  def reactions(aggregates)
@@ -72,10 +90,10 @@ module Hecks
72
90
  dispatches: Array(row[:dispatches]).map { |leg| dispatch(leg) })
73
91
  end
74
92
 
75
- # `compensates` — a PLAIN HASH on the declaration (`Reconstruction#
93
+ # `compensates` — a plain hash on the declaration (`Reconstruction#
76
94
  # dispatch`'s own comment for why), built into the real
77
95
  # `DispatchSpec` its own field actually is by recursing through
78
- # THIS SAME method, one level in — the identical move `handler`
96
+ # this same method, one level in — the identical move `handler`
79
97
  # itself takes into `dispatches`, one level up. `nil` when there
80
98
  # is nothing to compensate; `Build.call` never sees a
81
99
  # `compensates:` key it does not know how to read either way, the
@@ -28,15 +28,15 @@ module Hecks
28
28
 
29
29
  attr_reader :name, :type, :default, :pattern, :admits, :relationship
30
30
 
31
- # A Reference is kept AS ITSELF. Every other type is still a name, and
31
+ # A Reference is kept as itself. Every other type is still a name, and
32
32
  # crosses over as its construct does.
33
33
  #
34
- # `admits` names an ALREADY-DECLARED closed set the value must belong
34
+ # `admits` names an already-declared closed set the value must belong
35
35
  # to — `Vocabulary::QueryComparator` — spelled aggregate-qualified
36
- # because the set is a value object INSIDE an aggregate, and the
36
+ # because the set is a value object inside an aggregate, and the
37
37
  # aggregate is the only thing `reference_to` can reach.
38
38
  #
39
- # ON THE WIRE, because it is a RULE and not only a typing hint.
39
+ # On the wire, because it is a rule and not only a typing hint.
40
40
  #
41
41
  # It began as neither. The link existed so a generator could type
42
42
  # `WhereClause.op` as `WhereOp` — a typing convenience, not worth
@@ -48,9 +48,24 @@ module Hecks
48
48
  # refused "burnt" through one reading and emitted the event through
49
49
  # another.
50
50
  #
51
- # The wire carries the NAME, not the members. A reader resolves it
51
+ # The wire carries the name, not the members. A reader resolves it
52
52
  # against the IR it holds, so the members are declared once and
53
53
  # copied nowhere — which is the same reason `admits` exists at all.
54
+ #
55
+ # @param name [Symbol, String] the attribute's name
56
+ # @param type [Module, Bluebook::Reference, String, Symbol] the bare constant naming
57
+ # a primitive or a value object, a `reference_to`-built Reference, or already-spelled
58
+ # text
59
+ # @param list [Boolean] whether this attribute holds a list of values rather than one
60
+ # @param default [Object, nil] the value a new record starts with when none is given
61
+ # @param optional [Boolean] whether a command may omit this attribute
62
+ # @param pattern [String, nil] a regex source the value must match; shared-engine
63
+ # subset only (`PatternSubset`)
64
+ # @param admits [String, nil] an already-declared closed set's aggregate-qualified
65
+ # name, such as `"Vocabulary::QueryComparator"`, that the value must belong to
66
+ # @param relationship [Symbol, nil] the DSL word that minted this attribute
67
+ # (`:reference_to`, `:has_many`, `:has_one` or `:belongs_to`), or `nil` for an
68
+ # ordinary attribute
54
69
  def initialize(name:, type:, list: false, default: nil, optional: false, pattern: nil,
55
70
  admits: nil, relationship: nil)
56
71
  @name = name.to_sym
@@ -63,13 +78,13 @@ module Hecks
63
78
  @relationship = relationship&.to_s
64
79
  end
65
80
 
66
- # A BARE CONSTANT IN A BLUEBOOK IS A NAME, EVEN WHEN RUBY HAS HEARD OF IT.
81
+ # A bare constant in a bluebook is a name, even when Ruby has heard of it.
67
82
  #
68
83
  # `BluebookBuilder.build` says exactly this and installs a `const_missing`
69
84
  # resolver that hands back the symbol — `attribute :target, Target` becomes
70
85
  # the name "Target" and nothing looks Target up. That works only while the
71
- # lookup FAILS, and `Facade::Surface` installs every aggregate name as a
72
- # TOP-LEVEL constant (its own comment, and `ConstShim`'s, both say so).
86
+ # lookup fails, and `Facade::Surface` installs every aggregate name as a
87
+ # top-level constant (its own comment, and `ConstShim`'s, both say so).
73
88
  #
74
89
  # So in one process: boot a domain with an aggregate named `Target`, then
75
90
  # load a chapter whose own value object is called `Target`, and Ruby
@@ -77,11 +92,11 @@ module Hecks
77
92
  # built against somebody else's aggregate — silently, with no refusal —
78
93
  # and the attribute stops meaning what the file plainly says.
79
94
  #
80
- # DEMODULISED, so both paths spell it the same: `:Target` and
95
+ # Demodulised, so both paths spell it the same: `:Target` and
81
96
  # `QualityControl::Target` are both "Target". A plain class stays itself —
82
97
  # `String` demodulises to "String" — so the ordinary case is untouched.
83
98
  # This does not undo the constant leak; it makes the leak unable to change
84
- # what a chapter MEANS, which is the part that has to hold.
99
+ # what a chapter means, which is the part that has to hold.
85
100
  def spell(type)
86
101
  return type if type.is_a?(Reference)
87
102
  return Naming.demodulise(type) if type.is_a?(Module)
@@ -91,8 +106,7 @@ module Hecks
91
106
  private :spell
92
107
 
93
108
  # Held because a declared vocabulary pins it — spec/vocabulary_conformance
94
- # holds `Primitive`'s members to this list. The `primitive?` predicate that
95
- # used to read it had no caller anywhere and is gone.
109
+ # holds `Primitive`'s members to this list.
96
110
  PRIMITIVES = Hecks::Vocabulary.fetch("Primitive")
97
111
 
98
112
  # `type` is spelled, never handed over. A Reference renders as
@@ -3,14 +3,14 @@ require_relative "traits"
3
3
  module Hecks
4
4
  module Bluebook
5
5
  module Behaviour
6
- # WHAT AN AGGREGATE DOES, as opposed to what it holds.
6
+ # What an aggregate does, as opposed to what it holds.
7
7
  #
8
8
  # The holding half — the field list, the readers, the emission — is
9
9
  # the same list the language already declares in
10
10
  # `language/bluebook/aggregate.bluebook`, said a second and third
11
11
  # time in Ruby. This half is not: derived identity, the name
12
12
  # indexes, the owner stamping and the finders are decisions about
13
- # HOW the declared shape is used, and no grammar states them.
13
+ # how the declared shape is used, and no grammar states them.
14
14
  #
15
15
  # Split so the holding half can be generated from the language
16
16
  # without any of this being in the blast radius of a regeneration.
@@ -24,13 +24,19 @@ module Hecks
24
24
  include Indexed
25
25
  include Owns
26
26
 
27
- # An aggregate is a MEMBER of its chapter's namespace — "Pizzas::Pizza" —
28
- # where everything else is declared ON its owner and joins with ".".
27
+ # An aggregate is a member of its chapter's namespace — "Pizzas::Pizza" —
28
+ # where everything else is declared on its owner and joins with ".".
29
+ #
30
+ # @return [String] the literal string `"::"`
29
31
  def hecks_separator = "::"
30
32
 
31
- # THE HOOK THE GENERATED CONSTRUCTOR CALLS once every declared
33
+ # The hook the generated constructor calls once every declared
32
34
  # field is assigned. Nothing here is derivable from the
33
35
  # declaration, which is exactly why it is not generated.
36
+ #
37
+ # @return [Bluebook::Aggregate] self, once identity is derived, every
38
+ # declaration is indexed and its commands, value objects, entities and
39
+ # queries are stamped as owned by it
34
40
  def settle
35
41
  derive_identity
36
42
  index_declarations
@@ -40,42 +46,68 @@ module Hecks
40
46
  self
41
47
  end
42
48
 
49
+ # Builds every by-name finder index `settle` needs — attributes, value
50
+ # objects, commands, queries, ports and projected fields.
51
+ #
52
+ # @return [void]
43
53
  def index_declarations
44
54
  index_attributes(@attributes)
45
55
  @value_objects_by_name = index_by_hecks_name(@value_objects)
46
56
  @commands_by_name = index_by_hecks_name(@commands)
47
57
  @queries_by_name = index_by_hecks_name(@queries)
48
58
  @ports_by_name = @ports.to_h { |port| [port.name, port] }
49
- # S12, ADR 0025 — keyed by SYMBOL, the same convention
59
+ # S12, ADR 0025 — keyed by symbol, the same convention
50
60
  # `Indexed#attribute` already uses; `GuardState` asks for one
51
61
  # by name at every dispatch, the rebuild sweep walks all of
52
62
  # them once per pass.
53
63
  @projected_fields_by_name = @projected_fields.to_h { |field| [field.name, field] }
54
64
  end
55
65
 
66
+ # Finds a declared `projects` field by its declared name.
67
+ #
68
+ # @param named [String, Symbol] the projected field's declared name
69
+ # @return [Bluebook::ProjectedField, nil] the field named `named`, or
70
+ # `nil` if none is declared under that name
56
71
  def projected_field(named) = @projected_fields_by_name[named.to_sym]
57
72
 
58
- # A value object is a CLASS now, so `name` is Ruby's answer (the constant
73
+ # A value object is a class now, so `name` is Ruby's answer (the constant
59
74
  # path) and the declared name is `hecks_name`. This finder is on its way
60
- # out — once an attribute's type IS the class there is nothing to find —
75
+ # out — once an attribute's type is the class there is nothing to find —
61
76
  # but every consumer still asks by type string, so it stays until they
62
77
  # stop.
78
+ #
79
+ # @param named [String, Symbol] the value object's declared name
80
+ # @return [Class, nil] the value object class (a `Bluebook::ValueObject`
81
+ # subclass) named `named`, or `nil` if none is declared under that name
63
82
  def value_object(named) = @value_objects_by_name[named.to_s]
83
+
84
+ # Finds a port attached to this aggregate by its declared name.
85
+ #
86
+ # @param named [String, Symbol] the port's declared name
87
+ # @return [Bluebook::DomainPort, nil] the port named `named`, or `nil` if
88
+ # none is attached under that name
64
89
  def port(named) = @ports_by_name[named.to_s]
65
90
 
66
- # A PORT IS DECLARED IN THE HECKSAGON, NOT THE BLUEBOOK — the
91
+ # A port is declared in the hecksagon, not the bluebook — the
67
92
  # boundary between the domain and its adapters, in hexagonal terms,
68
- # is exactly what a `.hecksagon` file already IS for every other
69
- # port (persistence, projection, ...). So this attaches AFTER the
93
+ # is exactly what a `.hecksagon` file already is for every other
94
+ # port (persistence, projection, ...). So this attaches after the
70
95
  # aggregate already exists and is registered — `HecksagonBuilder`
71
96
  # calls it once per `port` declaration, having already stamped each
72
97
  # operation's reference attributes with `declared_in = self`, since
73
98
  # nothing upstream of a hecksagon load does that for it.
99
+ #
100
+ # @param port [Bluebook::DomainPort] the aggregate-scoped port to attach
101
+ # @return [void]
74
102
  def add_port(port)
75
103
  @ports << port
76
104
  @ports_by_name[port.name] = port
77
105
  end
78
106
 
107
+ # Names the table, file or key persistence adapters store this aggregate
108
+ # under.
109
+ #
110
+ # @return [String] the aggregate's name, snake-cased
79
111
  def storage_name = Naming.snake(@name)
80
112
  end
81
113
  end
@@ -1,25 +1,38 @@
1
1
  module Hecks
2
2
  module Bluebook
3
3
  module Behaviour
4
- # WHAT AN ATTRIBUTE DOES. The declared half — name, type, list,
4
+ # **What an attribute does**. The declared half — name, type, list,
5
5
  # default, optional, pattern, admits — is what the language states
6
6
  # in `aggregate.bluebook`'s own `Field`. These are the questions
7
- # readers ask ABOUT that shape, which no declaration states.
7
+ # readers ask about that shape, which no declaration states.
8
8
  module Attribute
9
+ # Says whether this attribute was declared `list`.
10
+ #
11
+ # @return [Boolean] whether this attribute holds a list of values rather than one
9
12
  def list? = @list
13
+
14
+ # Says whether this attribute holds a single value rather than a list.
15
+ #
16
+ # @return [Boolean] whether this attribute holds a single value rather than a list
10
17
  def scalar? = !@list
18
+
19
+ # Says whether this attribute's type is another aggregate reached via `reference_to`.
20
+ #
21
+ # @return [Boolean] whether this attribute's type is a `reference_to` another aggregate
11
22
  def reference? = @type.is_a?(Reference)
12
23
 
13
- # MAY THIS FACT BE LEFT OUT?
24
+ # May this fact be left out?
14
25
  #
15
26
  # Required is the default and by far the common case — a command takes
16
- # the arguments it declares, and all of them — so the EXCEPTION is what
27
+ # the arguments it declares, and all of them — so the exception is what
17
28
  # gets marked. Marking the other way would annotate almost every
18
29
  # attribute in the corpus to say nothing.
19
30
  #
20
- # Only a COMMAND enforces this. An aggregate's own attributes are filled
31
+ # Only a command enforces this. An aggregate's own attributes are filled
21
32
  # by the commands that set them, and a value object's by its
22
33
  # constructor ; neither is a payload anyone hands in.
34
+ #
35
+ # @return [Boolean] whether a command may omit this attribute from its payload
23
36
  def optional? = @optional
24
37
  end
25
38
  end
@@ -3,19 +3,22 @@ require_relative "traits"
3
3
  module Hecks
4
4
  module Bluebook
5
5
  module Behaviour
6
- # WHAT A CHAPTER DOES. The declared half is the roll-call of what a
6
+ # **What a chapter does**. The declared half is the roll-call of what a
7
7
  # bluebook holds; these are the finders over it, plus `verbs` — the
8
8
  # chapter's own list of every dispatchable name, which is derived
9
9
  # from the aggregates rather than declared anywhere.
10
10
  module Chapter
11
11
  include Owns
12
12
 
13
- # THE HOOK THE GENERATED CONSTRUCTOR CALLS. Three things a
14
- # declaration does not state: that a chapter is the ROOT of the
13
+ # The hook the generated constructor calls. Three things a
14
+ # declaration does not state: that a chapter is the root of the
15
15
  # owner chain (nothing declares it, it is what having no owner
16
- # MEANS), the ports table — which a `.hecksagon` fills later, so
16
+ # means), the ports table — which a `.hecksagon` fills later, so
17
17
  # the bluebook cannot declare it — and stamping its own children,
18
18
  # the same act an Aggregate performs one level down.
19
+ #
20
+ # @return [Bluebook::Chapter] self, once root marking, the ports table and ownership
21
+ # stamping are all set up
19
22
  def settle
20
23
  @hecks_root = true
21
24
  @ports = []
@@ -24,18 +27,83 @@ module Hecks
24
27
  self
25
28
  end
26
29
 
30
+ # Finds a declared aggregate by name.
31
+ #
32
+ # @param named [String, Symbol] the aggregate's declared name
33
+ # @return [Bluebook::Aggregate, nil] the aggregate, or `nil` if none is declared
34
+ # by that name
27
35
  def aggregate(named) = @aggregates.find { |a| a.name == named.to_s }
36
+
37
+ # Finds a declared read model by its own name or by the query name it answers.
38
+ #
39
+ # @param named [String, Symbol] the read model's declared name, or its query name
40
+ # @return [Bluebook::ReadModel, nil] the read model, or `nil` if none matches
28
41
  def read_model(named) = @read_models.find { |model| model.name == named.to_s || model.query_name == named.to_s }
42
+
43
+ # Finds a port declared at this chapter's root by name.
44
+ #
45
+ # @param named [String, Symbol] the port's declared name
46
+ # @return [Bluebook::DomainPort, nil] the port, or `nil` if none is declared
47
+ # by that name
29
48
  def port(named) = @ports_by_name[named.to_s]
30
49
 
31
- # A PORT IS DECLARED IN THE HECKSAGON, not the bluebook — so it
50
+ # What this chapter declared it provides — `{ key => local verb }`
51
+ # for one capability, or nil when it declares none. Read by
52
+ # everything that resolves a capability's provider from what a
53
+ # chapter declares, rather than from the chapter's own name
54
+ # (`Registry#authorization_provider_for`).
55
+ #
56
+ # @param capability [String, Symbol] the capability's name, such as
57
+ # `Bluebook::Capabilities::AUTHORIZATION`
58
+ # @return [Hash{Symbol => String}, nil] each declared key mapped to its local verb,
59
+ # or `nil` if this chapter declares no `provides` row for that capability
60
+ def provision(capability)
61
+ rows = @provides.select { |row| row.capability == capability.to_s }
62
+ rows.empty? ? nil : rows.to_h { |row| [row.key.to_sym, row.verb] }
63
+ end
64
+
65
+ # Says whether this chapter declares that it provides a capability.
66
+ #
67
+ # @param capability [String, Symbol] the capability's name
68
+ # @return [Boolean] whether this chapter declares a `provides` row for that capability
69
+ def provides?(capability) = !provision(capability).nil?
70
+
71
+ # The declared verb for `key`, qualified with this chapter's own
72
+ # name — the spelling `Dispatcher#dispatch`/`#query` take.
73
+ #
74
+ # @param capability [String, Symbol] the capability's name
75
+ # @param key [String, Symbol] the provided key to resolve
76
+ # @return [String, nil] the verb qualified as `"ChapterName::verb"`, or `nil` if this
77
+ # chapter provides no such capability or key
78
+ def provided_verb(capability, key)
79
+ local = provision(capability)&.fetch(key.to_sym, nil)
80
+ local && "#{name}::#{local}"
81
+ end
82
+
83
+ # A port is declared in the hecksagon, not the bluebook — so it
32
84
  # attaches after the chapter already exists, the same way an
33
85
  # aggregate's own ports do.
86
+ #
87
+ # @param port [Bluebook::DomainPort] the operations-shaped port to attach
88
+ # @return [void]
34
89
  def add_port(port)
35
90
  @ports << port
36
91
  @ports_by_name[port.name] = port
37
92
  end
38
93
 
94
+ # A TRANSLATED REACTION IS ALSO DECLARED IN THE HECKSAGON —
95
+ # `HecksagonBuilder#translates` builds an ordinary `Policy` (same
96
+ # shape, same `PolicyInterpreter` runtime, as one written inside a
97
+ # `policy` block in this chapter's own `.bluebook`) and attaches it
98
+ # here, after the chapter already exists, the same reasoning
99
+ # `add_port` gives one method up. Kept out of the bluebook itself
100
+ # on purpose: which FOREIGN domain's event this chapter reacts to
101
+ # is a wiring/context-mapping decision, not a fact the domain
102
+ # states about its own model.
103
+ def add_policy(policy)
104
+ @policies << policy
105
+ end
106
+
39
107
  # Every dispatchable name this chapter answers to, spelled exactly
40
108
  # as Dispatcher#dispatch takes it. Derived from the aggregates,
41
109
  # never declared — which is why Projections::OIDC can hold its own
@@ -50,6 +118,9 @@ module Hecks
50
118
  # already proved out for the meta-domain's own grammar (S17, ADR
51
119
  # 0026) — entities nest arbitrarily deep (`Dispatch`, inside
52
120
  # `Handler`), so one flat level isn't enough.
121
+ #
122
+ # @return [Array<String>] every command verb reachable on this chapter, spelled
123
+ # `"Domain::Aggregate.command"` or, nested, `"Domain::Aggregate.Entity.command"`
53
124
  def verbs
54
125
  @aggregates.flat_map { |agg| aggregate_verbs(agg) }
55
126
  end