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
@@ -2,37 +2,68 @@ require_relative "word_gate"
2
2
  module Hecks
3
3
  module Bluebook
4
4
  module DSL
5
- # Parses a top-level `.port` file's `Hecks.port "Name" do verb "x";
6
- # signal :effect end` body into a `Port` — the adapter-facing shape (one
7
- # verb, one signal, an optional method contract in `answers`) a domain
8
- # calls OUT through, as opposed to `DomainPortBuilder`'s own inbound/
9
- # outbound operations.
5
+ # Parses a `verb "x"; signal :effect` port body into a `Port` — the
6
+ # adapter-facing shape (one verb, one signal, an optional method
7
+ # contract in `answers`) a domain calls out through, as opposed to
8
+ # `DomainPortBuilder`'s own inbound/outbound operations.
9
+ #
10
+ # `Hecks.port` (lib/hecks.rb) parses a top-level `.port` file through
11
+ # `DomainPortBuilder`, whose bare-verb branch builds the identical
12
+ # `Port`; this builder is the reference shape `spec/dsl_spec.rb` holds
13
+ # that branch to.
10
14
  class PortBuilder
11
15
  GRAMMAR_CONTEXT = "Port".freeze
12
16
 
13
17
  include WordGate
14
18
 
19
+ # @param name [String] the port's name, as written after `Hecks.port`
15
20
  def initialize(name)
16
21
  @name = name
17
22
  @signal = :reply
18
23
  @answers = []
19
24
  end
20
25
 
26
+ # Names the verb a domain's aggregates call this port by when a hecksagon binds it.
27
+ #
28
+ # @param value [String, Symbol] the verb, such as `"asked_by"` or `"persisted_by"`
29
+ # @return [String] the verb as stored
21
30
  def verb(value) = @verb = value.to_s
31
+
32
+ # Sets whether the domain waits for a value back; a port left unset signals `:reply`.
33
+ #
34
+ # @param value [Symbol, String] `:reply` when the adapter answers with a value,
35
+ # `:effect` when it is called only for its effect
36
+ # @return [Symbol] the signal as stored
22
37
  def signal(value) = @signal = value.to_sym
23
38
 
24
- # THE METHOD CONTRACT — the fact a `.port` file's `verb`/`signal`
25
- # never carried: what an adapter must actually RESPOND TO for a
39
+ # Declares one method an adapter bound to this port must respond to.
40
+ #
41
+ # **The method contract** — the fact a `.port` file's `verb`/`signal`
42
+ # never carried: what an adapter must actually respond to for a
26
43
  # dispatch to reach it without a bare `NoMethodError`. Declared the
27
44
  # same repeatable way `AdapterBuilder#field`/`#secret` already are,
28
45
  # so `verify!` can check it with `respond_to?` at boot instead of
29
46
  # the runtime discovering it live.
47
+ #
48
+ # @param name [Symbol, String] the method name, such as `:ask`
49
+ # @return [Array<Symbol>] every method declared so far, this one last
30
50
  def answers(name) = @answers << name.to_sym
31
51
 
52
+ # Assembles the collected verb, signal and method contract, judged by the port language.
53
+ #
54
+ # @return [Bluebook::Port] the port, returned once the language accepts it
55
+ # @raise [Bluebook::DSL::Malformed] if the port language refuses the declaration
32
56
  def build
33
57
  MetaValidator.call_port(Port.new(name: @name, verb: @verb, signal: @signal, answers: @answers))
34
58
  end
35
59
 
60
+ # Evaluates a port block against a fresh builder and returns what it built.
61
+ #
62
+ # @param name [String] the port's name
63
+ # @yield the port body, evaluated with the builder as `self`; may be omitted
64
+ # @return [Bluebook::Port] the judged port
65
+ # @raise [Bluebook::DSL::Malformed] if the port language refuses the declaration, or
66
+ # the block uses a word the `Port` grammar does not admit
36
67
  def self.build(name, &block)
37
68
  builder = new(name)
38
69
  builder.instance_eval(&block) if block
@@ -14,31 +14,34 @@ module Hecks
14
14
  include AttributeCollector
15
15
  include WordGate
16
16
 
17
- # `to:` — THE SANCTIONED REPLACEMENT for `reference_to` inside an
18
- # operation body, added here rather than left as a documented-but-
19
- # unbuilt promise: reference_to_impl's own refusal message has told
20
- # authors to "pass the receiving aggregate in to:" since #335, but
21
- # no `to:` argument existed anywhere in DomainPort's own grammar
22
- # (domain_port.bluebook) for operation/tells/asks to receive it —
23
- # confirmed by grep across every lib/hecks/language file, not
24
- # assumed.
17
+ # `to:` — the sanctioned replacement for `reference_to` inside an
18
+ # operation body: `reference_to_impl`'s own refusal message tells
19
+ # authors to "pass the receiving aggregate in to:", and this is
20
+ # the argument DomainPort's own grammar (domain_port.bluebook)
21
+ # gives operation/tells/asks to receive it.
25
22
  #
26
- # GENUINE ROUTING METADATA, NOT AN ATTRIBUTE — matching
23
+ # **Genuine routing metadata, not an attribute** — matching
27
24
  # rust/parser/src/parse/domain_port.rs's own header comment ("The
28
25
  # receiving aggregate is routing metadata supplied by to:, not an
29
26
  # operation attribute"), which anticipated this shape before either
30
27
  # side actually built it. Stored separately (below, threaded to
31
28
  # PortOperation as `to:`) rather than reusing reference_to_impl's
32
29
  # own attribute-adding path — Dispatcher#port_invocation
33
- # (lib/hecks/runtime/dispatcher.rb) is the ONE place that resolves
30
+ # (lib/hecks/runtime/dispatcher.rb) is the one place that resolves
34
31
  # routing at dispatch time; it gained a second, purely additive
35
- # branch for this (falls back to a plain attribute NAMED for the
32
+ # branch for this (falls back to a plain attribute named for the
36
33
  # owning aggregate's own identified_by field, the same "declare
37
34
  # only external facts with attribute" the refusal message
38
35
  # describes) rather than folding `to:` into identity_attribute's
39
36
  # existing Reference-attribute scan, which every operation already
40
37
  # in the corpus (Banking, pizzas, lifeadelics' own vendored
41
38
  # PaymentGateway) still relies on unchanged.
39
+ #
40
+ # @param name [String] the operation's name
41
+ # @param to [Symbol, String, Module, nil] the aggregate the operation routes to; stored
42
+ # demodulised, and nil when the operation names none
43
+ # @param owner [String, nil] name of the aggregate the enclosing port is declared on
44
+ # @param direction [Symbol] `:inbound` for `operation`/`tells`, `:outbound` for `asks`
42
45
  def initialize(name, to: nil, owner: nil, direction: :inbound)
43
46
  @name = name
44
47
  @to = to && Naming.demodulise(to)
@@ -47,23 +50,33 @@ module Hecks
47
50
  @emits = []
48
51
  end
49
52
 
50
- # ALWAYS an attribute, never the self-reference `CommandBuilder`
53
+ # Refuses `reference_to` inside an operation body, except while shadow-parsing, where it
54
+ # adds the reference attribute frozen era text declared.
55
+ #
56
+ # Always an attribute, never the self-reference `CommandBuilder`
51
57
  # spells — a port operation has no `creates?`/`acts_on` distinction
52
58
  # to protect, so there is nothing for the self-reference branch to be
53
- # FOR here. `reference_to Payment, as: :payment_id` reads the same
59
+ # for here. `reference_to Payment, as: :payment_id` reads the same
54
60
  # even though the target happens to equal the owning aggregate.
55
- # RENAMED FROM `reference_to` — item #13's full metaprogrammed
56
- # dispatch (slice 4b). Bootstrap-reachable, in
57
- # GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.
61
+ # Answers the `reference_to` word through the table's `calls:`
62
+ # column — item #13's full metaprogrammed dispatch (slice 4b).
63
+ # Bootstrap-reachable, in `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
58
64
  #
59
- # DISABLED, #335 — kept only for MetaValidator's own shadow-parsing
65
+ # Disabled, #335 — kept only for MetaValidator's own shadow-parsing
60
66
  # pass (the self-hosted grammar's own KeywordSeed/ArgumentSeed rows
61
67
  # for "reference_to" in this context are themselves declared using
62
68
  # this construct, one level up — deleting the Ruby method would
63
69
  # break the language describing itself, not just old domain
64
- # authors). Every REAL domain author reaches `to:` instead, above —
70
+ # authors). Every real domain author reaches `to:` instead, above —
65
71
  # a genuinely different mechanism now, not a relocated spelling of
66
72
  # this one (see `to:`'s own comment).
73
+ #
74
+ # @param type [Symbol, String, Module] the referenced aggregate, written as a bare constant
75
+ # @param as [Symbol, nil] the attribute's name; nil derives it from the target, as in
76
+ # `:payment_id` under shadow-parsing
77
+ # @return [nil] nothing useful; the reference lands in `attributes`
78
+ # @raise [Bluebook::DSL::Malformed] always outside shadow-parsing, since routing belongs
79
+ # in `to:`; under shadow-parsing, if the attribute name is already declared
67
80
  def reference_to_impl(type, as: nil)
68
81
  unless MetaValidator.shadow_parsing?
69
82
  raise Malformed,
@@ -74,9 +87,13 @@ module Hecks
74
87
  add_reference!(type, as: as)
75
88
  end
76
89
 
90
+ # Names one event an inbound operation records once the external fact has arrived.
91
+ #
92
+ # @param event_name [String, Symbol] the event's name, such as `"PaymentSettled"`
93
+ # @return [Array<String>] every event named so far, this one last
77
94
  def emits(event_name) = @emits << event_name.to_s
78
95
 
79
- # THE TWO HALVES OF AN `asks`. An outbound call has exactly two
96
+ # The two halves of an `asks`. An outbound call has exactly two
80
97
  # endings and the chapter names both — `answers` for what came back,
81
98
  # `refuses` for what the outside said instead. They are separate words
82
99
  # rather than two `emits` because a reader has to be able to tell them
@@ -87,6 +104,12 @@ module Hecks
87
104
  # kind-driven coerce-and-assign with nothing else, now executed
88
105
  # by `GenericDispatch`.
89
106
 
107
+ # Assembles the operation, refusing words that belong to the other direction.
108
+ #
109
+ # @return [Bluebook::PortOperation] the operation with its attributes, events and routing
110
+ # @raise [Bluebook::DSL::Malformed] if an inbound operation declares no `emits` or
111
+ # declares `answers`/`refuses`, or an outbound one declares `emits` or lacks either
112
+ # `answers` or `refuses`
90
113
  def build
91
114
  outbound = @direction == :outbound
92
115
  refuse_wrong_words!(outbound)
@@ -96,7 +119,7 @@ module Hecks
96
119
  direction: @direction, answers: @answers, refuses: @refuses, to: @to
97
120
  )
98
121
 
99
- # AN INBOUND OPERATION STILL HAS TO SAY SOMETHING. Only inbound: an
122
+ # An inbound operation still has to say something. Only inbound: an
100
123
  # `asks` says it with `answers`/`refuses` instead, and
101
124
  # `refuse_wrong_words!` above has already insisted on both.
102
125
  if !outbound && @emits.empty?
@@ -110,7 +133,7 @@ module Hecks
110
133
 
111
134
  private
112
135
 
113
- # THE ONE PLACE a reference attribute actually gets added — both
136
+ # The one place a reference attribute actually gets added — both
114
137
  # `to:` (initialize, above) and reference_to_impl's own shadow-
115
138
  # parsing branch call this, so there is exactly one real
116
139
  # implementation of "carry a Reference-typed external fact,"
@@ -120,7 +143,7 @@ module Hecks
120
143
  attribute_impl(as || default_reference_name(target), Reference.new(target))
121
144
  end
122
145
 
123
- # EACH DIRECTION REFUSES THE OTHER'S WORDS. `emits` on an `asks` looks
146
+ # **Each direction refuses the other's words**. `emits` on an `asks` looks
124
147
  # right and is not: it would name one ending and leave the other
125
148
  # nowhere. `answers` on a `tells` is worse — there is no channel back
126
149
  # to an inbound caller at all, so it would read as a promise the
@@ -147,10 +170,21 @@ module Hecks
147
170
  end
148
171
  end
149
172
 
173
+ # Evaluates one operation block against a fresh builder and returns what it built.
174
+ #
150
175
  # `private` above (scoping the instance methods between it and here)
151
176
  # doesn't reach a singleton method — correctly so: `.build` is this
152
177
  # builder's real public entry point (DomainPortBuilder calls it),
153
178
  # never meant to be private.
179
+ #
180
+ # @param name [String] the operation's name
181
+ # @param to [Symbol, String, Module, nil] the aggregate the operation routes to, or nil
182
+ # @param owner [String, nil] name of the aggregate the enclosing port is declared on
183
+ # @param direction [Symbol] `:inbound` for `operation`/`tells`, `:outbound` for `asks`
184
+ # @yield the operation body, evaluated with the builder as `self`; may be omitted
185
+ # @return [Bluebook::PortOperation] the built operation
186
+ # @raise [Bluebook::DSL::Malformed] if the body uses the other direction's words, omits
187
+ # the ones its own direction requires, or uses a word the grammar does not admit
154
188
  # rubocop:disable-next Lint/IneffectiveAccessModifier
155
189
  def self.build(name, to: nil, owner: nil, direction: :inbound, &block)
156
190
  builder = new(name, to: to, owner: owner, direction: direction)
@@ -7,7 +7,7 @@ module Hecks
7
7
  # correlates its instances (`correlates_by`), and the `transition`-
8
8
  # declared state machine whose `dispatch`es (each optionally paired
9
9
  # with its own per-dispatch `compensates`) become its handlers. States
10
- # are DERIVED from the transitions rather than declared separately
10
+ # are derived from the transitions rather than declared separately
11
11
  # (S7, ADR 0025).
12
12
  class ProcessManagerBuilder
13
13
  GRAMMAR_CONTEXT = "ProcessManager".freeze
@@ -16,26 +16,39 @@ module Hecks
16
16
 
17
17
  include WordGate
18
18
 
19
+ # @param name [String] the process manager's own name, as written after `process_manager`
19
20
  def initialize(name)
20
21
  @name = name
21
22
  @handlers = []
22
23
  end
23
24
 
24
- # `starts_on Transfer::TransferRequested` — BARE CONSTANT
25
- # ACCEPTED (ADR 0025, S6 — "events first-class"), resolved
25
+ # Records the event that begins a fresh instance of this process manager.
26
+ #
27
+ # `starts_on Transfer::TransferRequested` — bare constant
28
+ # accepted (ADR 0025, S6 — "events first-class"), resolved
26
29
  # through `ConstShim` the same way `on_impl`/`transition_impl`
27
30
  # resolve an event reference — but through `Naming.
28
- # event_name_ref`, NOT `Naming.event_ref` (that method's own
31
+ # event_name_ref`, not `Naming.event_ref` (that method's own
29
32
  # header has the full account: `SagaInterpreter` matches
30
- # `pm.starts_on`/`pm.ends_on` against a BARE `event.name`, never
33
+ # `pm.starts_on`/`pm.ends_on` against a bare `event.name`, never
31
34
  # a "." qualified one). A plain String still passes through
32
35
  # unchanged, both for `shadow_parse` and for any corpus site a
33
36
  # future pass hasn't migrated yet.
37
+ #
38
+ # @param event_ref [Symbol, String, Module] the event, as a bare constant (a
39
+ # `ScopedConstant` module `ConstShim` resolves) or quoted text
40
+ # @return [void]
34
41
  def starts_on_impl(event_ref)
35
42
  @starts_on = Naming.event_name_ref(event_ref)
36
43
  end
37
44
 
45
+ # Records the event this process manager expects to close out its instance on.
46
+ #
38
47
  # `ends_on` — same reasoning as `starts_on_impl`, above.
48
+ #
49
+ # @param event_ref [Symbol, String, Module] the event, as a bare constant (a
50
+ # `ScopedConstant` module `ConstShim` resolves) or quoted text
51
+ # @return [void]
39
52
  def ends_on_impl(event_ref)
40
53
  @ends_on = Naming.event_name_ref(event_ref)
41
54
  end
@@ -45,34 +58,37 @@ module Hecks
45
58
  # kind-driven coerce-and-assign with nothing else, executed by
46
59
  # `GenericDispatch`.
47
60
  #
48
- # `starts_on`/`ends_on` USED TO be table-driven the same way,
49
- # until ADR 0025 S6 gave each a second, `kind: "constant"`
50
- # ArgumentSeed row (`starts_on Transfer::TransferRequested`) —
51
- # `GenericDispatch::COERCE_BY_KIND` only knows `"text"`/
52
- # `"symbol"`, and `shape_for` refuses to pick a `single_fill`
53
- # shape at all once two Argument rows share a `fills:` target
54
- # (`arguments.size == 1` below), so both words are hand-written
55
- # again, `calls:`-routed like `transition` already is.
56
-
57
- # ONE STATE-MACHINE VOCABULARY (S7, ADR 0025 — "events and
58
- # reactions"): the SAME word `Lifecycle#transition` already
61
+ # `starts_on`/`ends_on` are hand-written rather than table-driven
62
+ # the same way, because ADR 0025 S6 gives each a second,
63
+ # `kind: "constant"` ArgumentSeed row (`starts_on
64
+ # Transfer::TransferRequested`) — `GenericDispatch::COERCE_BY_KIND`
65
+ # only knows `"text"`/`"symbol"`, and `shape_for` refuses to pick a
66
+ # `single_fill` shape at all once two Argument rows share a
67
+ # `fills:` target (`arguments.size == 1` below), so both words are
68
+ # `calls:`-routed like `transition` already is.
69
+
70
+ # Records one leg of this process manager's state machine: the event that takes it, the
71
+ # state(s) it applies from, and the dispatches (and optional compensation) it fires.
72
+ #
73
+ # One state-machine vocabulary (S7, ADR 0025 — "events and
74
+ # reactions"): the same word `Lifecycle#transition` already
59
75
  # carries, one level over — `transition "AccountDebited" =>
60
76
  # "awaiting_credit", from: "requested" do ... end` replaces `on
61
77
  # "AccountDebited", transition: { "requested" => "awaiting_
62
78
  # credit" } do ... end`. Same bare rocket-pair argument shape
63
- # (not a NAMED `transition:` kwarg wrapping a second Hash), same
79
+ # (not a named `transition:` kwarg wrapping a second Hash), same
64
80
  # `from:` — including the array form Lifecycle's own commands
65
81
  # could already take and a process manager's own events could
66
- # not — and the states a procedure runs on are DERIVED from the
82
+ # not — and the states a procedure runs on are derived from the
67
83
  # transitions that name them, the same way `Behaviour::Lifecycle
68
84
  # #states` already derives an aggregate's ; `state "x"` lines
69
85
  # duplicated exactly what the transition list already said,
70
86
  # and could drift from it (`validate!`'s own "undeclared state"
71
87
  # check existed only because they could).
72
88
  #
73
- # `starts_on`/`ends_on` are NOT unified into this — verified
89
+ # `starts_on`/`ends_on` are not unified into this — verified
74
90
  # against the real corpus rather than assumed: Settlement's own
75
- # `ends_on "TransferSettled"` names an event NONE of its own
91
+ # `ends_on "TransferSettled"` names an event none of its own
76
92
  # transitions ever handle (`Transfer.Settle`'s own emission, a
77
93
  # full step downstream of the transition that dispatches it),
78
94
  # so "the terminal state's own event" is not a fact the
@@ -81,25 +97,33 @@ module Hecks
81
97
  # cover the case, which is not less vocabulary than keeping the
82
98
  # one that already says it correctly.
83
99
  #
84
- # EXPANDS IMMEDIATELY, unlike `Lifecycle#transition` (which
100
+ # Expands immediately, unlike `Lifecycle#transition` (which
85
101
  # defers to `Behaviour::Lifecycle#expand`, called at emission
86
102
  # time) — `ProcessManager`'s own IR constructor takes `states:`/
87
103
  # `handlers:` exactly as it always has, so the runtime
88
104
  # (`Behaviour::ProcessManager`, `SagaInterpreter`, saga
89
105
  # persistence/rehydration) needs no change at all: what changed
90
- # is how the DECLARATION reaches that same shape, not the shape
106
+ # is how the declaration reaches that same shape, not the shape
91
107
  # a real run ever sees or persists.
92
- # RENAMED FROM `transition` — item #13's full metaprogrammed
93
- # dispatch (slice 4c). Not bootstrap-reachable (checked
94
- # directly — no core/attached chapter declares a ProcessManager
95
- # of its own).
108
+ # Reached through `calls: "transition_impl"`. Not exercised during boot itself — checked
109
+ # directly, no core/attached chapter declares a `ProcessManager` of its own — though the
110
+ # (context, word) pair is still carried in `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`
111
+ # unconditionally, along with every other `calls:`-routed row.
112
+ #
113
+ # @param mapping [Hash] one `event => target_state` pair plus a required `from:` key
114
+ # naming the source state(s) (`String`, `Symbol`, or `Array<String, Symbol>`) this
115
+ # transition applies from
116
+ # @yield the transition's dispatch body, `instance_eval`'d against a fresh `HandlerBuilder`
117
+ # @return [void]
118
+ # @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] if `mapping` names
119
+ # no `from:`
96
120
  def transition_impl(mapping, &block)
97
121
  mapping = mapping.dup
98
122
  from = mapping.delete(:from)
99
123
 
100
- # ALWAYS REQUIRED, unlike `Lifecycle#transition`'s own `from:`
124
+ # Always required, unlike `Lifecycle#transition`'s own `from:`
101
125
  # — an aggregate's unconstrained transition is admitted from
102
- # ANY current state (`Behaviour::Lifecycle#applies_from?`
126
+ # any current state (`Behaviour::Lifecycle#applies_from?`
103
127
  # returns true for a nil `from`), a reading `SagaInterpreter#
104
128
  # advance_saga`'s own admission check does not share: it tests
105
129
  # `instance[:state] == handler.from_state` by plain equality,
@@ -120,14 +144,14 @@ module Hecks
120
144
  handler.instance_eval(&block) if block
121
145
 
122
146
  mapping.each do |event_type, target|
123
- # BARE CONSTANT ACCEPTED (ADR 0025, S6 — "events first-
147
+ # Bare constant accepted (ADR 0025, S6 — "events first-
124
148
  # class"), `transition Account::AccountDebited => "state"` —
125
- # `Naming.event_name_ref`, NOT the DOTTED `Naming.event_ref`
149
+ # `Naming.event_name_ref`, not the dotted `Naming.event_ref`
126
150
  # transform `PolicyBuilder#on_impl` uses (that method's own
127
151
  # header has the full account, found live wiring a real
128
152
  # migrated corpus site into `bin/model_check` for the first
129
153
  # time: `SagaInterpreter#advance_saga` matches `handler.
130
- # event_type` against a BARE `event.name`, never a "."
154
+ # event_type` against a bare `event.name`, never a "."
131
155
  # qualified one — a policy's own cross-aggregate match
132
156
  # works differently, splitting the qualifier apart from the
133
157
  # name rather than comparing the whole string). Writing the
@@ -142,6 +166,14 @@ module Hecks
142
166
  end
143
167
  end
144
168
 
169
+ # Assembles the declared transitions into a `ProcessManager`, after validating them.
170
+ #
171
+ # @return [Bluebook::ProcessManager] the built process manager, with its own `states`
172
+ # derived from the declared transitions
173
+ # @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] if `correlates_by`
174
+ # is undeclared or names a whole field rather than one scalar, if `starts_on` is
175
+ # undeclared, if no transition was declared, or if two transitions answer the same event
176
+ # from the same source state
145
177
  def build
146
178
  validate!
147
179
 
@@ -155,6 +187,13 @@ module Hecks
155
187
  )
156
188
  end
157
189
 
190
+ # Evaluates a `process_manager` block against a fresh builder and returns what it built.
191
+ #
192
+ # @param name [String] the process manager's own name
193
+ # @yield the process manager's body, `instance_eval`'d against a new builder; may be
194
+ # omitted
195
+ # @return [Bluebook::ProcessManager] the built process manager
196
+ # @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] see `#build`
158
197
  def self.build(name, &block)
159
198
  builder = new(name)
160
199
  builder.instance_eval(&block) if block
@@ -163,12 +202,12 @@ module Hecks
163
202
 
164
203
  private
165
204
 
166
- # ONE DECLARED TRANSITION IS SEVERAL ROWS when `from` names more
205
+ # One declared transition is several rows when `from` names more
167
206
  # than one source state — `Behaviour::Lifecycle#expand`'s own
168
207
  # comment, the identical fan-out, one level over: a
169
208
  # `ProcessManagerHandler` only ever carries a single `from_state`,
170
209
  # so a `from: [...]` transition mints one row per source, each
171
- # carrying the SAME dispatches.
210
+ # carrying the same dispatches.
172
211
  def expand(event_type, transition, dispatches)
173
212
  sources = transition.from.nil? ? [nil] : Array(transition.from)
174
213
 
@@ -182,12 +221,12 @@ module Hecks
182
221
  end
183
222
  end
184
223
 
185
- # DERIVED, not declared (S7) — every state this procedure ever
224
+ # Derived, not declared (S7) — every state this procedure ever
186
225
  # runs on is already named by some transition's own `from_state`
187
226
  # or `to_state`; a state nothing transitions into or out of is
188
227
  # not a state this procedure has, the same reading
189
228
  # `Behaviour::Lifecycle#states` already gives an aggregate's own
190
- # field. FIRST-SEEN ORDER, walking declaration order — `begin_
229
+ # field. First-seen order, walking declaration order — `begin_
191
230
  # saga`'s own `pm.states.first` is what a fresh instance starts
192
231
  # in, so the order has to survive the derivation, not just the
193
232
  # membership.
@@ -201,15 +240,15 @@ module Hecks
201
240
  "nothing would tie its events to one instance"
202
241
  end
203
242
 
204
- # THE FIELD, NAMED — never the value object that carries it. A bare
243
+ # **The field, named** — never the value object that carries it. A bare
205
244
  # `correlates_by :end_to_end` reads whatever the payload holds under
206
- # that key AS the correlation key, and what a non-scalar key even
245
+ # that key as the correlation key, and what a non-scalar key even
207
246
  # is stays open (the object itself? its serialised text?).
208
247
  # Requiring the dotted spelling —
209
248
  # `:"end_to_end.value"` — makes every correlates_by name a scalar
210
249
  # by construction, the same discipline `identified_by` already
211
250
  # holds a head to. This is a syntactic check, not a type check: it
212
- # does not know or care whether the field IS a value object, only
251
+ # does not know or care whether the field is a value object, only
213
252
  # that the declaration cannot leave that question open.
214
253
  unless @correlates_by.to_s.include?(".")
215
254
  raise InvalidProcessManager, "#{@name} correlates_by #{@correlates_by.inspect}, which names a whole " \
@@ -231,7 +270,7 @@ module Hecks
231
270
  end
232
271
 
233
272
  # C10.3 — a leg is selected by (event, current state), so two
234
- # legs answering the SAME event from the SAME state would leave
273
+ # legs answering the same event from the same state would leave
235
274
  # the runtime to pick by declaration order, silently. Refused
236
275
  # here, where the declaration can still be read whole. (`from:
237
276
  # [...]` fan-out counts: `transition E => "a", from: ["x", "y"]`
@@ -253,7 +292,7 @@ module Hecks
253
292
  end
254
293
  end
255
294
 
256
- # THE BODY OF ONE `transition ... do ... end` block — collects the
295
+ # The body of one `transition ... do ... end` block — collects the
257
296
  # `dispatch` calls (each optionally opening its own `compensates`
258
297
  # via the nested `DispatchBuilder`) that fire when this transition
259
298
  # is taken.
@@ -264,19 +303,22 @@ module Hecks
264
303
 
265
304
  include WordGate
266
305
 
306
+ # Starts this handler's own dispatch list empty.
267
307
  def initialize = @dispatches = []
268
308
 
269
- # THE COMMAND ITSELF (ADR 0025, "events and reactions" — command
309
+ # Records one command this transition dispatches, and, if given a block, the
310
+ # compensation that reverses it.
311
+ #
312
+ # The command itself (ADR 0025, "events and reactions" — command
270
313
  # references become first-class), same shape and same reasons
271
314
  # as `PolicyBuilder#trigger`'s own header — bare constant live,
272
315
  # quoted text only under shadow-parsing (S0a's bridge; frozen
273
316
  # era text still writes `dispatch "Banking::Account.Debit"`).
274
317
  #
275
- # RENAMED FROM `dispatch` — item #13's full metaprogrammed
276
- # dispatch (slice 4), same reasoning as trigger_impl above.
318
+ # Reached through `calls: "dispatch_impl"`.
277
319
  #
278
- # AN OPTIONAL BLOCK OPENS `compensates` ON THIS DISPATCH
279
- # SPECIFICALLY — per-dispatch saga compensation, replacing a
320
+ # An optional block opens `compensates` on this dispatch
321
+ # specifically — per-dispatch saga compensation, replacing a
280
322
  # hand-written list at the saga's own `on :refused` leg. Real,
281
323
  # live bug this closes: `examples/banking/bluebook/transfers_
282
324
  # and_payments.bluebook`'s own `Settlement` saga wrote a
@@ -287,6 +329,17 @@ module Hecks
287
329
  # (`SagaInterpreter`'s own `completed_compensations`) and
288
330
  # compensates only those, newest first, instead of trusting an
289
331
  # author's static list to be complete and correctly ordered.
332
+ #
333
+ # @param command_ref [Symbol, String, Module] the command, as a bare constant (a
334
+ # `ScopedConstant` module `ConstShim` resolves) or, under shadow-parsing, quoted text
335
+ # @param with [Hash{Symbol => Object}, nil] a projection onto the command's own
336
+ # arguments, the same `key => value` shape a policy's own `trigger ..., with:` takes;
337
+ # `nil` forwards the triggering context verbatim
338
+ # @yield the dispatch's own `compensates` body, `instance_eval`'d against a
339
+ # `DispatchBuilder`
340
+ # @return [Bluebook::DispatchSpec] the dispatch just recorded
341
+ # @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] if `command_ref`
342
+ # is quoted text outside shadow-parsing
290
343
  def dispatch_impl(command_ref, with: nil, &block)
291
344
  if command_ref.is_a?(::String) && !MetaValidator.shadow_parsing?
292
345
  raise InvalidProcessManager,
@@ -315,9 +368,9 @@ module Hecks
315
368
  spec
316
369
  end
317
370
 
318
- # THE NESTED SCOPE `dispatch ... do ... end` OPENS — one word
371
+ # The nested scope `dispatch ... do ... end` opens — one word
319
372
  # only (`compensates`), the compensating half of the dispatch it
320
- # sits inside. Its own `compensates_impl` builds a SECOND
373
+ # sits inside. Its own `compensates_impl` builds a second
321
374
  # `DispatchSpec`, shape-identical to `HandlerBuilder#dispatch_
322
375
  # impl`'s own — a compensation takes the exact same two
323
376
  # arguments (a bare command constant, an optional `with:`)
@@ -330,6 +383,17 @@ module Hecks
330
383
 
331
384
  include WordGate
332
385
 
386
+ # Records the command that reverses the dispatch this `compensates` block sits inside.
387
+ #
388
+ # @param command_ref [Symbol, String, Module] the compensating command, as a bare
389
+ # constant (a `ScopedConstant` module `ConstShim` resolves) or, under shadow-parsing,
390
+ # quoted text
391
+ # @param with [Hash{Symbol => Object}, nil] a projection onto the command's own
392
+ # arguments, the same shape `HandlerBuilder#dispatch_impl`'s own `with:` takes; `nil`
393
+ # forwards the triggering context verbatim
394
+ # @return [Bluebook::DispatchSpec] the compensating dispatch just recorded
395
+ # @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] if `command_ref`
396
+ # is quoted text outside shadow-parsing
333
397
  def compensates_impl(command_ref, with: nil)
334
398
  if command_ref.is_a?(::String) && !MetaValidator.shadow_parsing?
335
399
  raise InvalidProcessManager,