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
@@ -3,13 +3,16 @@ require_relative "traits"
3
3
  module Hecks
4
4
  module Bluebook
5
5
  module Behaviour
6
- # WHAT A COMMAND DOES. EXTENDED, not included — a command is a
7
- # CLASS, one per declared verb.
6
+ # **What a command does**. Extended, not included — a command is a
7
+ # class, one per declared verb.
8
8
  module Command
9
9
  include Indexed
10
10
 
11
11
  # Indexed once — attributes are final once absorbed, and every
12
12
  # dispatch asks this finder by name.
13
+ #
14
+ # @return [Class] self — the command class, once its attributes are
15
+ # indexed
13
16
  def settle
14
17
  index_attributes(@attributes)
15
18
  self
@@ -17,8 +20,8 @@ module Hecks
17
20
 
18
21
  # The construct this verb acts upon — the construct itself, not its name.
19
22
  #
20
- # A verb declared on an ENTITY always acts on that piece. It never
21
- # self-references, because an element is addressed THROUGH its parent —
23
+ # A verb declared on an entity always acts on that piece. It never
24
+ # self-references, because an element is addressed through its parent —
22
25
  # which means `creates?` answers true for every one of them, and reading
23
26
  # `acts_on` off `creates?` alone would report that `LedgerEntry.Amend`
24
27
  # brings a ledger entry into being. Three of banking's commands were
@@ -26,10 +29,15 @@ module Hecks
26
29
  #
27
30
  # On an aggregate, a creating command acts on no existing root, so nil is
28
31
  # the truth: there is nothing there yet.
32
+ #
33
+ # @return [Class, Bluebook::Aggregate, nil] the entity class (a
34
+ # `Bluebook::Entity` subclass) this verb acts on when declared on an
35
+ # entity, the aggregate instance when declared on an aggregate and
36
+ # non-creating, or `nil` for a creating command
29
37
  def acts_on
30
- # FULLY QUALIFIED, and it has to be: inside `module Behaviour`
38
+ # Fully qualified, and it has to be: inside `module Behaviour`
31
39
  # the bare name `Entity` resolves to Behaviour::Entity — this
32
- # module's SIBLING — not to the construct. Comparing a Class to
40
+ # module's sibling — not to the construct. Comparing a Class to
33
41
  # a Module answers nil, so the guard silently fell through and
34
42
  # every entity verb reported acting on nothing.
35
43
  return hecks_owner if hecks_owner.is_a?(Class) && hecks_owner < Bluebook::Entity
@@ -37,20 +45,27 @@ module Hecks
37
45
  creates? ? nil : hecks_owner
38
46
  end
39
47
 
48
+ # Whether this command creates a new root rather than acting on one.
49
+ #
50
+ # @return [Boolean] whether this command creates a new root — true when
51
+ # it declares no `reference_to` back to its own owner
40
52
  def creates? = @references.nil?
41
53
 
42
- # EVERY REASON THIS VERB CAN REFUSE ON A RULE — the descriptions of
43
- # its givens AND its ensures, the exact text the runtime quotes
54
+ # Every reason this verb can refuse on a rule — the descriptions of
55
+ # its givens and its ensures, the exact text the runtime quotes
44
56
  # after "refused — " when a guard is not met (see
45
57
  # command_rules/admissibility.rb's GivenNotMet/EnsuresNotMet). A
46
58
  # property that asks "did the runtime only ever refuse for a rule
47
59
  # the language wrote" reads this rather than re-deriving the two
48
60
  # collections; `compact` because a rule's description is optional
49
61
  # (behavior.bluebook's Rule) and an unnamed one quotes nothing.
62
+ #
63
+ # @return [Array<String>] every named given's and ensure's own
64
+ # description text, skipping unnamed rules
50
65
  def guard_descriptions = (@givens + @ensures).map(&:description).compact
51
66
 
52
- # THE ARGUMENT NAME THAT ADDRESSES an instance of `aggregate_name`
53
- # for THIS command — the one fact `PolicyInterpreter`'s own
67
+ # The argument name that addresses an instance of `aggregate_name`
68
+ # for this command — the one fact `PolicyInterpreter`'s own
54
69
  # `for_each` fan-out needs and, until this reading existed, had
55
70
  # to guess at (see git blame: `Behaviour::Policy
56
71
  # #fan_out_reference_key`, which hardcoded `<aggregate>_id`
@@ -58,32 +73,37 @@ module Hecks
58
73
  # addressing command as a result — `Account.Freeze`, addressed
59
74
  # by `number`/`account`, not `account_id`).
60
75
  #
61
- # TWO SHAPES, the same two `CommandBuilder#reference_to` already
76
+ # Two shapes, the same two `CommandBuilder#reference_to` already
62
77
  # tells apart at declare time (command_builder.rb's own comment
63
- # on `cross_reference` — "`as:` MEANS a named attribute... a
64
- # command can point at another instance of its OWN kind"):
78
+ # on `cross_reference` — "`as:` means a named attribute... a
79
+ # command can point at another instance of its own kind"):
65
80
  #
66
- # SELF-ADDRESSING — `references == aggregate_name` (this verb
67
- # is declared ON the very aggregate it acts on, `reference_to
81
+ # self-addressing — `references == aggregate_name` (this verb
82
+ # is declared on the very aggregate it acts on, `reference_to
68
83
  # Account` on a command Account itself owns). No attribute was
69
- # minted for it at all; the SAME bare key
84
+ # minted for it at all; the same bare key
70
85
  # `CommandInterpreter::ArgumentGate#reference_key` already
71
86
  # accepts as "addressing, not describing" is reused here
72
87
  # rather than re-derived — one door, not two.
73
88
  #
74
- # CROSS-REFERENCING — a real, declared reference-typed
75
- # attribute whose OWN target is `aggregate_name` (`customer_id`
76
- # on `Account.Open`, or whatever `as:` named it). Its NAME is
89
+ # cross-referencing — a real, declared reference-typed
90
+ # attribute whose own target is `aggregate_name` (`customer_id`
91
+ # on `Account.Open`, or whatever `as:` named it). Its name is
77
92
  # the key, exactly as declared — never re-derived from the
78
93
  # target's name, because an `as:` reference's name and its
79
94
  # target's snake case can legitimately differ (`Transfer`'s own
80
95
  # `source`/`destination`, both `Reference<Account>`).
81
96
  #
82
- # `nil` when neither shape matches — a CREATING command (nothing
97
+ # `nil` when neither shape matches — a creating command (nothing
83
98
  # to address yet) or one that simply never references this
84
99
  # aggregate at all. A caller minting a fan-out dispatch is
85
100
  # expected to treat `nil` as "this command cannot be addressed by
86
101
  # a row of this aggregate," not to fall back on a guess.
102
+ #
103
+ # @param aggregate_name [String, Symbol] the aggregate a fan-out dispatch is
104
+ # addressing an instance of
105
+ # @return [String, nil] the argument name that addresses it, or nil if this command
106
+ # cannot be addressed by a row of that aggregate
87
107
  def addressing_key_for(aggregate_name)
88
108
  return Naming.reference_key(aggregate_name) if references.to_s == aggregate_name.to_s
89
109
 
@@ -92,15 +112,25 @@ module Hecks
92
112
  end
93
113
  end
94
114
 
95
- # A MUTATION'S OWN READINGS. Included (not extended) — Mutation is
115
+ # **A mutation's own readings**. Included (not extended) — Mutation is
96
116
  # a Struct, so these are instance methods.
97
117
  module Mutation
98
- # An APPEND binds several fields at once, each from either a command
99
- # ARGUMENT (a Symbol) or a LITERAL. It used to spell the Symbol bare
100
- # and inspect the rest, which is the opposite of what a where-clause
101
- # did with the same two kinds — see Hecks::Literal.
118
+ # An append binds several fields at once, each from either a command
119
+ # argument (a Symbol) or a literal. Spelled through `Literal.render`,
120
+ # the same self-describing form a where-clause's own value already
121
+ # uses, rather than a bare Symbol paired with an inspected literal —
122
+ # see Hecks::Literal.
123
+ #
124
+ # @return [Hash{Symbol => String}] the append's field bindings, each
125
+ # value rendered through `Literal.render`
102
126
  def appended_fields = source.transform_values { |value| Literal.render(value) }
103
127
 
128
+ # Classifies this mutation's source for the wire, the counterpart
129
+ # `Assembly::Marks#classified` reads back.
130
+ #
131
+ # @return [Hash{Symbol => Object}] `{kind: "argument", name:}` for a
132
+ # command argument, `{kind: "state", name:}` for a state
133
+ # self-reference, or `{kind: "literal", value:}` for a literal value
104
134
  def classified_source
105
135
  if source.is_a?(Symbol)
106
136
  { kind: "argument", name: source.to_s }
@@ -3,17 +3,19 @@ require_relative "traits"
3
3
  module Hecks
4
4
  module Bluebook
5
5
  module Behaviour
6
- # WHAT ONE PORT OPERATION DOES.
6
+ # **What one port operation does**.
7
7
  module PortOperation
8
8
  include Indexed
9
9
 
10
- # An operation declares no reference of its own — the OWNER is
10
+ # An operation declares no reference of its own — the owner is
11
11
  # what it acts for, and `identity_attribute` is how that is found.
12
+ #
13
+ # @return [nil] always `nil`
12
14
  def references = nil
13
15
 
14
- # NEVER a creating command — a port operation always acts on an
16
+ # Never a creating command — a port operation always acts on an
15
17
  # aggregate that already exists (`operation.to`/`identity_attribute`
16
- # both name where its RECEIVER comes from, never a birth). Answered
18
+ # both name where its receiver comes from, never a birth). Answered
17
19
  # explicitly, not derived from `references` the way `Command
18
20
  # #creates?` is (`references.nil?` would read every operation as
19
21
  # creating, since `references` above is unconditionally nil) —
@@ -21,27 +23,45 @@ module Hecks
21
23
  # `target.command.creates?` on a port operation the same way it
22
24
  # already does on an ordinary command, and correctly lift a
23
25
  # same-aggregate policy's own Event.id as the operation's receiver.
26
+ #
27
+ # @return [Boolean] always `false`
24
28
  def creates? = false
25
29
 
30
+ # Finds the reference-typed attribute through which this operation addresses
31
+ # its owning aggregate.
32
+ #
33
+ # @param owner_name [String, Symbol] the owning aggregate's name
34
+ # @return [Bluebook::Attribute, nil] the reference-typed attribute targeting
35
+ # `owner_name`, or `nil` if this operation declares none
26
36
  def identity_attribute(owner_name)
27
37
  @attributes.find { |attribute| attribute.reference? && attribute.type.target_name == owner_name.to_s }
28
38
  end
29
39
 
30
- # THE SAME READING `Command#addressing_key_for` gives, minus its
40
+ # The same reading `Command#addressing_key_for` gives, minus its
31
41
  # self-addressing branch — a port operation's `references` is
32
42
  # unconditionally nil (above), so it never means "this verb is
33
- # declared ON the very aggregate it acts on" the way a command's
43
+ # declared on the very aggregate it acts on" the way a command's
34
44
  # does; a port operation's only path back to its owner is a real,
35
45
  # declared reference-typed attribute, which is exactly what
36
46
  # `identity_attribute` already finds. Needed for the identical
37
47
  # reason `Command#addressing_key_for` is: `ReactionInvocation
38
48
  # #aggregate_aliases` calls it on whatever `target.command` holds,
39
49
  # a `PortOperation` now included since a policy can trigger one.
50
+ #
51
+ # @param aggregate_name [String, Symbol] the name of the aggregate a row of which
52
+ # would address this operation
53
+ # @return [Symbol, nil] the reference-typed attribute's name a caller passes to
54
+ # address that row, or `nil` if this operation cannot be addressed by one
40
55
  def addressing_key_for(aggregate_name) = identity_attribute(aggregate_name)&.name
41
56
  end
42
57
 
43
- # WHAT A PORT DOES — one finder over its declared operations.
58
+ # **What a port does** — one finder over its declared operations.
44
59
  module DomainPort
60
+ # Finds a declared operation by name.
61
+ #
62
+ # @param named [String, Symbol] the operation's declared name
63
+ # @return [Bluebook::PortOperation, nil] the operation, or `nil` if none is
64
+ # declared by that name
45
65
  def operation(named) = @operations.find { |op| op.hecks_name == named.to_s }
46
66
  end
47
67
  end
@@ -3,8 +3,8 @@ require_relative "traits"
3
3
  module Hecks
4
4
  module Bluebook
5
5
  module Behaviour
6
- # WHAT AN ENTITY DOES. EXTENDED, not included — an entity is a
7
- # CLASS, so this is singleton behaviour.
6
+ # **What an entity does**. Extended, not included — an entity is a
7
+ # class, so this is singleton behaviour.
8
8
  #
9
9
  # `settle` is the same seam an Aggregate has, reached from `absorb`
10
10
  # rather than from a constructor because a declared entity is built
@@ -17,41 +17,53 @@ module Hecks
17
17
  include Indexed
18
18
  include Owns
19
19
 
20
+ # The hook `absorb` calls once every declared field is assigned.
21
+ #
22
+ # @return [Class] this entity's own class (a `Bluebook::Entity` subclass), self,
23
+ # once identity and indexes are derived
20
24
  def settle
21
25
  derive_identity
22
26
  index_declarations
23
27
  self
24
28
  end
25
29
 
30
+ # Builds every by-name index this entity answers finders through.
31
+ #
32
+ # @return [void]
26
33
  def index_declarations
27
34
  index_attributes(@attributes)
28
35
  @commands_by_name = index_by_hecks_name(@commands)
29
36
  @queries_by_name = index_by_hecks_name(@queries)
30
37
  end
31
38
 
32
- # S17, ADR 0026 — `@entities`, now genuinely NESTED entities
39
+ # S17, ADR 0026 — `@entities`, now genuinely nested entities
33
40
  # (Dispatch, inside Handler) rather than always `[]`. Kept as a
34
41
  # real reader rather than a hardcoded empty list for two
35
42
  # reasons at once: `Value::Coercion#for_attribute` calls
36
- # `.entities` on WHATEVER OWNER it is handed — an aggregate's or
37
- # an entity's own — the moment it meets ANY `list_of(...)`
43
+ # `.entities` on whatever owner it is handed — an aggregate's or
44
+ # an entity's own — the moment it meets any `list_of(...)`
38
45
  # attribute (entity-typed or not — `hydrate_entity_list`'s own
39
46
  # fallback, `return value unless entity`, only runs once
40
47
  # `.entities` has already answered) ; and `EntityInterpreter`
41
- # now walks a DOTTED chain of entities one level at a time
48
+ # now walks a dotted chain of entities one level at a time
42
49
  # (`walk_entity_chain`) exactly the way an aggregate's own
43
50
  # `.entities` is walked for its direct children. Entity's own
44
51
  # header comment already promises it stays "structurally
45
52
  # interchangeable with an aggregate" for exactly this reason.
53
+ #
54
+ # @return [Array<Class>] this entity's own nested entities (each a `Bluebook::Entity`
55
+ # subclass), or `[]` if it declares none
46
56
  def entities = @entities || []
47
57
 
48
- # A piece OWNS the verbs declared on it, so they can state an
58
+ # A piece owns the verbs declared on it, so they can state an
49
59
  # identity — `Banking::Account.Ledger.Deposit` rather than a
50
60
  # command that cannot say what it belongs to. Separate from
51
- # `settle` because `declare` stamps AFTER absorbing, once the
61
+ # `settle` because `declare` stamps after absorbing, once the
52
62
  # subclass that will own them exists. `@entities` too now
53
63
  # (S17, ADR 0026) — a nested entity states its own owner chain
54
64
  # exactly the way a nested command does.
65
+ #
66
+ # @return [void]
55
67
  def stamp_children = stamp(@commands, @queries, @entities)
56
68
  end
57
69
  end
@@ -1,18 +1,32 @@
1
1
  module Hecks
2
2
  module Bluebook
3
3
  module Behaviour
4
- # WHAT A HECKSAGON DOES — the lookups over its declared binds.
4
+ # **What a hecksagon does** — the lookups over its declared binds.
5
5
  module Hecksagon
6
6
  # Aggregate-specific bind wins when one was declared; otherwise
7
7
  # falls back to a domain-level default (`b.aggregate.nil?` — see
8
8
  # `HecksagonBuilder#method_missing`). Checked directly rather than
9
9
  # via `aggregate_name`/`Naming.demodulise`, which would need its own
10
10
  # nil-handling for the default row.
11
+ #
12
+ # @param aggregate_name [String, Symbol] the aggregate to find a bind for
13
+ # @param verb [String, Symbol] the port verb name to find a bind for
14
+ # @return [Bluebook::Bind, nil] the aggregate-specific bind when one is
15
+ # declared, the domain-level default bind for `verb` otherwise, or
16
+ # `nil` when neither exists
11
17
  def bind_for(aggregate_name, verb)
12
18
  @binds.find { |b| b.aggregate_name == aggregate_name.to_s && b.verb.to_s == verb.to_s } ||
13
19
  @binds.find { |b| b.aggregate.nil? && b.verb.to_s == verb.to_s }
14
20
  end
15
21
 
22
+ # Every bind for `verb`, aggregate-specific ones winning as a group over
23
+ # the domain-level default.
24
+ #
25
+ # @param aggregate_name [String, Symbol] the aggregate to find binds for
26
+ # @param verb [String, Symbol] the port verb name to find binds for
27
+ # @return [Array<Bluebook::Bind>] every aggregate-specific bind for
28
+ # `verb` when any are declared, otherwise every domain-level default
29
+ # bind for `verb`
16
30
  def binds_for(aggregate_name, verb)
17
31
  specific = @binds.select { |b| b.aggregate_name == aggregate_name.to_s && b.verb.to_s == verb.to_s }
18
32
  return specific if specific.any?
@@ -21,22 +35,34 @@ module Hecks
21
35
  end
22
36
  end
23
37
 
24
- # WHAT A WORLD DOES — settings lookup, with the adapter-specific
38
+ # **What a world does** — settings lookup, with the adapter-specific
25
39
  # entry falling back to the verb's own.
26
40
  module World
41
+ # Finds the generic settings declared for a port verb.
42
+ #
43
+ # @param verb [String, Symbol] the port verb name, such as `"persistence"`
44
+ # @return [Hash{Symbol => Object}] the settings declared for `verb`, or
45
+ # `{}` when none are declared
27
46
  def for_verb(verb) = @settings.fetch(verb.to_s, {})
28
47
 
29
48
  # The generic `verb` entry (`persisted_by("Heki") do dir :default
30
49
  # end`) only answers for the adapter it actually names — falling
31
50
  # back to it unconditionally applies one adapter's settings to an
32
51
  # unrelated one. Real, corpus-caught bug: a hecksagon binding two
33
- # aggregates to two different adapters under the SAME verb (one to
52
+ # aggregates to two different adapters under the same verb (one to
34
53
  # Heki, one to Memory) sent Memory's lookup down Heki's generic
35
54
  # entry, then failed `check_settings` with "Memory does not
36
55
  # declare :dir" — the generic entry's own `settings[:adapter]`
37
56
  # names Heki, not Memory, so the fallback was never actually for
38
57
  # this bind. `{}` is exactly right when nothing was configured for
39
- # THIS adapter — Memory, which takes no values at all.
58
+ # this adapter — Memory, which takes no values at all.
59
+ #
60
+ # @param verb [String, Symbol] the port verb name to find settings for
61
+ # @param adapter [String, Symbol] the adapter name, such as `"Heki"`, to
62
+ # find settings for
63
+ # @return [Hash{Symbol => Object}] the adapter-qualified settings when
64
+ # declared, the generic verb settings when they name this same
65
+ # adapter, or `{}` otherwise
40
66
  def for_binding(verb, adapter)
41
67
  qualified = @settings["#{verb}:#{adapter.to_s.downcase}"]
42
68
  return qualified if qualified
@@ -1,27 +1,48 @@
1
1
  module Hecks
2
2
  module Bluebook
3
3
  module Behaviour
4
- # WHAT A LIFECYCLE DOES. Its declared half is a field, a starting
5
- # state and a transition list. Everything here READS that — which
4
+ # **What a lifecycle does**. Its declared half is a field, a starting
5
+ # state and a transition list. Everything here reads that — which
6
6
  # states exist, which transition a command takes, and how one
7
7
  # declared transition expands into the several rows the emission
8
8
  # carries when `from` names more than one source state.
9
9
  module Lifecycle
10
+ # Lists every state this lifecycle can be in.
11
+ #
12
+ # @return [Array<String>] every state this lifecycle can be in — the
13
+ # default plus each declared transition's own target, deduplicated
10
14
  def states
11
15
  ([default] + transitions.map { |_command, t| t.target }).uniq
12
16
  end
13
17
 
18
+ # Finds every transition a command declares.
19
+ #
20
+ # @param command [String, Symbol] the command name to find declared
21
+ # transitions for
22
+ # @return [Array<Bluebook::StateTransition>] every transition declared
23
+ # for `command`
14
24
  def transitions_for(command)
15
25
  transitions.select { |name, _| name == command.to_s }.map { |_, t| t }
16
26
  end
17
27
 
28
+ # Finds the state a command moves the record to.
29
+ #
30
+ # @param command [String, Symbol] the command name to find the target
31
+ # state for
32
+ # @param current_state [String, Symbol, nil] the record's current
33
+ # state, to pick the one declared transition that admits it; `nil`
34
+ # to skip that check and take the first declared transition
35
+ # @return [String, nil] the state `command` moves the record to, or
36
+ # `nil` when `command` declares no transition at all
37
+ # @raise [Runtime::WiringError] if `command` declares transitions but
38
+ # none of them admits `current_state`
18
39
  def target_for(command, current_state = nil)
19
40
  match_transition(command, current_state)&.target
20
41
  end
21
42
 
22
43
  private
23
44
 
24
- # ONE DECLARED TRANSITION IS SEVERAL ROWS when `from` names more
45
+ # One declared transition is several rows when `from` names more
25
46
  # than one source state — the emission carries them flat, so the
26
47
  # fan-out happens here rather than in whatever reads it.
27
48
  def expand(command, transition)
@@ -37,10 +58,10 @@ module Hecks
37
58
  return nil if matches.empty?
38
59
  return matches.first unless current_state
39
60
 
40
- # NOT `|| matches.first` — that used to silently hand back an
41
- # ARBITRARY declared transition for `command` whenever none of
61
+ # Not `|| matches.first` — that would silently hand back an
62
+ # arbitrary declared transition for `command` whenever none of
42
63
  # them actually admitted `current_state`, picking a `target`
43
- # that command dispatch would in fact have refused (that
64
+ # that command dispatch would in fact refuse (that
44
65
  # refusal is `CommandRules::Admissibility#admissible_transition`'s
45
66
  # own job, which raises `LifecycleRefused` for exactly this
46
67
  # case rather than guessing — this module has no state subject
@@ -1,34 +1,53 @@
1
1
  module Hecks
2
2
  module Bluebook
3
3
  module Behaviour
4
- # WHAT A POLICY DOES. Its declared half is four plain fields; these
4
+ # **What a policy does**. Its declared half is four plain fields; these
5
5
  # are the readings taken off them.
6
6
  module Policy
7
- # The BLUEBOOK's name for this construct, asked the same way of a class
7
+ # The bluebook's name for this construct, asked the same way of a class
8
8
  # that has crossed over and of an IR object that has not. Collapses into
9
9
  # Construct when this one crosses.
10
+ #
11
+ # @return [String] this policy's declared name
10
12
  def hecks_name = @name
11
13
 
14
+ # The domain-and-aggregate prefix of the triggering event's name.
15
+ #
16
+ # @return [String, nil] the part of `on_event` before its first `.`
17
+ # (the `"Domain::Aggregate"` prefix), or `nil` when `on_event`
18
+ # carries no dot
12
19
  def event_qualifier = Naming.qualifier(@on_event)
13
20
 
21
+ # The bare name of the triggering event, without its domain/aggregate
22
+ # prefix.
23
+ #
24
+ # @return [String] the part of `on_event` after its first `.` (the
25
+ # bare event name), or the whole string when it carries no dot
14
26
  def event_name = Naming.unqualified(@on_event)
15
27
 
16
- # WHETHER THIS POLICY FANS OUT — `for_each` names a query, and a
28
+ # Whether this policy fans out — `for_each` names a query, and a
17
29
  # non-empty one turns a single reaction into one dispatch per row
18
30
  # the query answers. Read by the interpreter that runs the fan-out
19
31
  # and by the property that checks it dispatched once per row.
32
+ #
33
+ # @return [Boolean] whether `for_each` names a query
20
34
  def fans_out? = !@for_each.to_s.empty?
21
35
 
22
- # WHETHER THIS POLICY IS GUARDED — a non-empty `where` decides
36
+ # Whether this policy is guarded — a non-empty `where` decides
23
37
  # whether the policy fires at all, read against the triggering
24
38
  # event's own payload.
39
+ #
40
+ # @return [Boolean] whether `where` is declared and non-empty
25
41
  def guarded? = !@where.to_s.empty?
26
42
 
27
- # THE STRUCTURED FORM OF `where`, derived once — the same tree
43
+ # The structured form of `where`, derived once — the same tree
28
44
  # `AstJson.emit_predicate` spells for every rule row, memoized
29
45
  # here because a policy is consulted once per event, not once per
30
46
  # boot. Nil when there is no `where`, exactly as the wire carries
31
47
  # it.
48
+ #
49
+ # @return [Hash, nil] the JSON-shaped boolean AST for `where`, or
50
+ # `nil` when this policy is not guarded
32
51
  def where_ast
33
52
  if defined?(@where_ast)
34
53
  @where_ast
@@ -40,30 +59,36 @@ module Hecks
40
59
  # The rule-shaped reading of the guard, for `Evaluator.call_rule`
41
60
  # — a policy's `where` has no description (nothing refuses with
42
61
  # it; an unmet where is a silent skip).
62
+ #
63
+ # @return [Bluebook::Given] the guard as a rule, with no description
64
+ # and its `ast` already resolved via `where_ast`
43
65
  def where_rule = @where_rule ||= Given.new(description: nil, canonical: @where, ast: where_ast)
44
66
 
45
- # THE FAN-OUT QUERY'S ROUTE, split the way the runtime runs it:
67
+ # The fan-out query's route, split the way the runtime runs it:
46
68
  # `[query_domain, aggregate_name, query_name]`. The query runs
47
- # against the triggering event's OWN domain unless `for_each`
69
+ # against the triggering event's own domain unless `for_each`
48
70
  # names one ("Domain::Aggregate.query"). Deliberately independent
49
71
  # of `across`/`target_domain`, which name where `trigger` fires,
50
72
  # not where the fan-out's own query runs.
73
+ #
74
+ # @param default_domain [String, Symbol] the triggering event's own
75
+ # domain, used when `for_each` names no domain of its own
76
+ # @return [Array(String, String, String)] `[domain, aggregate_name,
77
+ # query_name]` the fan-out query resolves against
51
78
  def for_each_route(default_domain)
52
79
  path, query_name = @for_each.to_s.split(".", 2)
53
80
  domain, aggregate = path.to_s.include?("::") ? path.split("::", 2) : [default_domain.to_s, path]
54
81
  [domain, aggregate, query_name]
55
82
  end
56
83
 
57
- # THE ARGUMENT NAME A FAN-OUT DISPATCH mints each matched row's id
58
- # under used to be minted HERE, unconditionally, as `<aggregate>
59
- # _id` — a real bug: a target command declared ON the very
60
- # aggregate it self-references (`Account.Freeze`) is addressed by
61
- # its OWN identity field's name, not a synthetic foreign key, and
62
- # every such dispatch refused. That question is not this policy's
63
- # to answer at all — it depends on the TARGET COMMAND'S OWN
64
- # declared shape, not on the aggregate name alone — so it now
65
- # lives on `Behaviour::Command#addressing_key_for`, asked of the
66
- # resolved target command by `PolicyInterpreter#addressing_key_for`.
84
+ # The argument name a fan-out dispatch mints each matched row's id
85
+ # under is not minted here — it depends on the target command's own
86
+ # declared shape (a target command declared on the very aggregate it
87
+ # self-references, such as `Account.Freeze`, is addressed by its own
88
+ # identity field's name, not a synthetic foreign key), not on the
89
+ # aggregate name alone, so it lives on
90
+ # `Behaviour::Command#addressing_key_for`, asked of the resolved
91
+ # target command by `PolicyInterpreter#addressing_key_for`.
67
92
  end
68
93
  end
69
94
  end
@@ -1,13 +1,17 @@
1
1
  module Hecks
2
2
  module Bluebook
3
3
  module Behaviour
4
- # WHAT A PROCESS MANAGER DOES. Its declared half is the trigger,
4
+ # **What a process manager does**. Its declared half is the trigger,
5
5
  # the states and the handlers; the compensation half — `saga` — is
6
- # DERIVED from the handler that answers a refusal, not declared.
6
+ # derived from the handler that answers a refusal, not declared.
7
7
  module ProcessManager
8
+ # The bluebook's name for this construct.
9
+ #
10
+ # @return [String, nil] the process manager's declared name, or `nil` before it
11
+ # is set
8
12
  def hecks_name = @name
9
13
 
10
- # THE LEG THAT ANSWERS — selected by (event, current state), C10.3
14
+ # The leg that answers — selected by (event, current state), C10.3
11
15
  # (docs/semantics/bluebook-semantics.md). Two legs may answer the
12
16
  # same event from different states; which one runs is decided by
13
17
  # the state the instance is in, never by declaration order. With
@@ -17,39 +21,63 @@ module Hecks
17
21
  # selection). Build refuses two legs on one (event, state) pair
18
22
  # (`ProcessManagerBuilder#validate!`), so a stated lookup is
19
23
  # unambiguous by construction.
24
+ #
25
+ # @param event [String, Symbol] the triggering event's name
26
+ # @param state [String, Symbol, nil] the instance's current state; `nil` asks
27
+ # declaratively, ignoring state
28
+ # @return [Bluebook::ProcessManagerHandler, nil] the matching handler row, or `nil`
29
+ # if no declared leg answers this (event, state) pair
20
30
  def handler_for(event, state = nil)
21
31
  @handlers.find { |h| h.event_type == event.to_s && (state.nil? || h.from_state == state.to_s) }
22
32
  end
23
33
 
34
+ # Finds every declared leg for an event, regardless of source state.
35
+ #
36
+ # @param event [String, Symbol] the triggering event's name
37
+ # @return [Array<Bluebook::ProcessManagerHandler>] every handler row declared for
38
+ # `event`; `[]` if none is declared
24
39
  def handlers_for(event) = @handlers.select { |h| h.event_type == event.to_s }
25
40
 
41
+ # Says whether any declared leg answers an event.
42
+ #
43
+ # @param event [String, Symbol] the triggering event's name
44
+ # @return [Boolean] whether any handler row is declared for `event`
26
45
  def handles?(event) = @handlers.any? { |h| h.event_type == event.to_s }
27
46
 
28
- # WHETHER A STATE IS ONE THIS PROCEDURE DECLARES — asked of a value
47
+ # Whether a state is one this procedure declares — asked of a value
29
48
  # a real run left a saga instance holding (its live or rehydrated
30
49
  # state), the way `Lifecycle#states` is asked of an aggregate's
31
50
  # resting field. A rehydrated instance in a state no handler could
32
51
  # have reached is corruption the durable round-trip introduced.
52
+ #
53
+ # @param state [String, Symbol] the state to check
54
+ # @return [Boolean] whether `state` is one of this procedure's declared states
33
55
  def declares_state?(state) = @states.map(&:to_s).include?(state.to_s)
34
56
 
57
+ # Names the payload field a triggering event's instances are correlated by.
58
+ #
59
+ # @return [Symbol] the head of `correlates_by` — the part before the first `.`
35
60
  def correlation_head = @correlates_by.to_s.split(".").first.to_sym
36
61
 
37
62
  # The compensation half of a procedure, read off the handler that
38
- # answers REFUSED.
63
+ # answers `REFUSED`.
39
64
  #
40
65
  # nil for a procedure with no answer to a refusal, which is a legitimate
41
66
  # thing to be — a hiring pipeline cannot un-interview anybody.
67
+ #
68
+ # @return [Bluebook::Saga, nil] the derived saga, or `nil` if no handler answers
69
+ # `REFUSED`
42
70
  def saga
43
71
  leg = handler_for(Bluebook::ProcessManager::REFUSED)
44
72
  return nil unless leg
45
73
 
46
- # `compensations` — a STATIC PREVIEW, declaration order, not
74
+ # `compensations` — a static preview, declaration order, not
47
75
  # one instance's own runtime history (which legs a given
48
76
  # instance actually completed is per-instance state,
49
77
  # `SagaInterpreter`'s own `completed_compensations`, not a
50
78
  # fact `Saga` — a pure declaration reading — could ever hold).
51
- # Every `compensates` ANY handler's own dispatch declares,
52
- # forward declaration order, THEN whatever this leg's own
79
+ # Every `compensates` any handler's own dispatch declares,
80
+ # forward declaration order, then whatever this leg's own
53
81
  # hand-written body still lists — coexistence, not replacement
54
82
  # (`ProcessManagerBuilder::HandlerBuilder#dispatch_impl`'s own
55
83
  # comment): a saga can derive some of its compensation and
@@ -60,6 +88,9 @@ module Hecks
60
88
  to_state: leg.to_state, compensations: derived + leg.dispatches)
61
89
  end
62
90
 
91
+ # Says whether this procedure declares a handler for `REFUSED`.
92
+ #
93
+ # @return [Boolean] whether this procedure has a derivable saga
63
94
  def saga? = !saga.nil?
64
95
  end
65
96
  end