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
@@ -16,22 +16,36 @@ module Hecks
16
16
  class PolicyInterpreter
17
17
  attr_reader :registry
18
18
 
19
+ # @param registry [Runtime::Registry] the booted registry whose loaded
20
+ # bluebooks are scanned for candidate policies
21
+ # @param door [Runtime::Dispatcher] the dispatcher reactions re-enter through,
22
+ # and whose reaction-depth guard is checked before each delivery
19
23
  def initialize(registry, door:)
20
24
  @registry = registry
21
25
  @door = door
22
26
  end
23
27
 
28
+ # Fires every declared policy `event` triggers, recording each outcome on
29
+ # the registry's reaction log.
30
+ #
24
31
  # `deliver` returns `nil` for a policy whose `where` did not hold —
25
- # SILENTLY, the same as a policy `policies_for` never selected at all
32
+ # silently, the same as a policy `policies_for` never selected at all
26
33
  # (an `event_qualifier` miss carries no reaction_log entry either) —
27
- # so nothing is appended for it. A `for_each` policy answers an ARRAY
34
+ # so nothing is appended for it. A `for_each` policy answers an array
28
35
  # (one record per matched row) rather than one record ; `Array(...)`
29
36
  # is wrong here (it would explode a plain record Hash into its own
30
37
  # key/value pairs), so the two shapes are told apart explicitly.
31
- # `only:` — ONE `[policy, home_domain]` pair, the outbox relay's
38
+ # `only:` — one `[policy, home_domain]` pair, the outbox relay's
32
39
  # way of running exactly the consumer a row names (`Runtime::
33
40
  # Outbox::Relay#run_consumer`) instead of every policy that
34
41
  # matches the event. Selection is otherwise identical.
42
+ #
43
+ # @param event [Runtime::Event] the just-emitted event to react to
44
+ # @param domain [String, Symbol] the domain `event`'s own aggregate belongs
45
+ # to, the emitting domain's policies fire first
46
+ # @param only [Array(Bluebook::Policy, String), nil] one `[policy, home_domain]`
47
+ # pair to run exactly, instead of scanning every loaded bluebook for candidates
48
+ # @return [void]
35
49
  def react(event, domain, only: nil)
36
50
  selected = only ? [only] : policies_for(event, domain)
37
51
  selected.each do |policy, home_domain|
@@ -44,13 +58,13 @@ module Hecks
44
58
 
45
59
  private
46
60
 
47
- # SCANS EVERY LOADED BLUEBOOK, not just the emitting command's own —
48
- # a policy commonly lives in the CONSUMER's bluebook, reacting
61
+ # Scans every loaded bluebook, not just the emitting command's own —
62
+ # a policy commonly lives in the consumer's bluebook, reacting
49
63
  # passively to an event a different domain's aggregate emits (the
50
64
  # corpus-standard shape : `world/conception/bluebook/domain_cell.bluebook`'s
51
65
  # ConceiveOnAbsorption reacts `on "DomainAbsorbed"`, an event
52
66
  # `body/organs/bluebook/gut.bluebook`'s Gut.Absorb emits — two
53
- # different bluebooks, joined only by the event's NAME). Restricting
67
+ # different bluebooks, joined only by the event's name). Restricting
54
68
  # the scan to the emitting domain's own bluebook (the previous
55
69
  # shape) silently drops every cross-domain reaction : the policy is
56
70
  # never even a candidate, no refusal, no log entry, nothing —
@@ -58,12 +72,12 @@ module Hecks
58
72
  # which encode this exact cross-bluebook cascade as their contract.
59
73
  #
60
74
  # Returns [policy, home_domain] pairs rather than bare policies —
61
- # `deliver`/`deliver_for_each` fall back to the REACTING policy's
62
- # OWN domain (not the emitting one) when a trigger or for_each route
75
+ # `deliver`/`deliver_for_each` fall back to the reacting policy's
76
+ # own domain (not the emitting one) when a trigger or for_each route
63
77
  # is bare, and that fallback has to travel with each match now that
64
78
  # a single event can surface policies from several different homes.
65
79
  #
66
- # THE EMITTING DOMAIN'S OWN POLICIES FIRST, in declaration order,
80
+ # The emitting domain's own policies first, in declaration order,
67
81
  # then other bluebooks' in load order (C10.2) — the same order the
68
82
  # outbox lays its rows in (`Outbox::Fanout.policies`) and the Rust
69
83
  # kernel runs (`react_policies`: its own table, then cross-domain).
@@ -79,7 +93,7 @@ module Hecks
79
93
  end
80
94
  end
81
95
 
82
- # THE GUARD, evaluated against the triggering event's own payload —
96
+ # The guard, evaluated against the triggering event's own payload —
83
97
  # a policy has no aggregate instance of its own to read state from,
84
98
  # so `state` is empty and every bare name a `where` resolves comes
85
99
  # from `attrs` (Expression::Resolver#fetch checks `attrs` before
@@ -88,7 +102,7 @@ module Hecks
88
102
  # policy simply has none of). Called from inside `deliver`'s own
89
103
  # rescue-guarded body (both callers, below) — never guarded here —
90
104
  # so an EvaluationError (an unresolvable field, a bad comparison) is
91
- # caught the SAME way any other reaction defect is, not swallowed as
105
+ # caught the same way any other reaction defect is, not swallowed as
92
106
  # though the policy had merely declined to fire.
93
107
  def where_holds?(policy, event)
94
108
  return true if policy.where.to_s.empty?
@@ -97,7 +111,7 @@ module Hecks
97
111
  end
98
112
 
99
113
  def deliver(policy, event, domain)
100
- # `record` ASSIGNED BEFORE ANY BRANCH THAT CAN RAISE, same reason
114
+ # `record` assigned before any branch that can raise, same reason
101
115
  # `deliver_for_each`'s own header gives : both rescue clauses below
102
116
  # call `.merge` on it, and a defect raised before it existed would
103
117
  # be caught here only to raise a second, different NoMethodError
@@ -121,7 +135,7 @@ module Hecks
121
135
  # fatal to the command that emitted the event.
122
136
  record.merge(delivered: false, reason: e.message)
123
137
  rescue StandardError => e
124
- # A DEFECT, not a refusal — a NoMethodError in an interpreter, a
138
+ # A defect, not a refusal — a NoMethodError in an interpreter, a
125
139
  # NameError from a missing constant, a TypeError from a bad
126
140
  # assumption : exactly the class of thing DOMAIN_REFUSALS
127
141
  # (errors.rb, see the comment above that constant) deliberately
@@ -129,19 +143,19 @@ module Hecks
129
143
  # folding a crash into the same `delivered: false` shape as an
130
144
  # ordinary refusal makes a broken runtime read as normal operation
131
145
  # in the log. This clause does not reopen that hole: it is a
132
- # SECOND, narrower rescue, tried only once the first one above has
146
+ # second, narrower rescue, tried only once the first one above has
133
147
  # already declined to match, so a legitimate refusal still takes
134
148
  # the branch above and a defect always takes this one.
135
149
  #
136
- # Catching it HERE is safe for a fact this method's caller cannot
150
+ # Catching it here is safe for a fact this method's caller cannot
137
151
  # see from where it sits: by the time `react` runs, the command
138
- # that EMITTED `event` has already succeeded and PERSISTED —
152
+ # that emitted `event` has already succeeded and persisted —
139
153
  # `Dispatcher#dispatch` calls `@policies.react` only after its own
140
154
  # `announced` events are already in hand. Letting this exception
141
155
  # keep propagating would not undo that command (nothing here is
142
156
  # transactional across aggregates) — it would only blow up the
143
- # ORIGINAL caller's `dispatch` call for a failure that happened in
144
- # a DIFFERENT command, one the caller never asked to run and has no
157
+ # original caller's `dispatch` call for a failure that happened in
158
+ # a different command, one the caller never asked to run and has no
145
159
  # way to compensate for. So the defect is recorded, distinguishably
146
160
  # (`defect: true`, plus the error's own class — nothing here is
147
161
  # allowed to read like an ordinary refusal), warned to STDERR so it
@@ -152,19 +166,19 @@ module Hecks
152
166
  record.merge(delivered: false, reason: e.message, defect: true, error_class: e.class.name)
153
167
  end
154
168
 
155
- # THE FAN-OUT — `policy.for_each` names a query ; this runs it
169
+ # **The fan-out** — `policy.for_each` names a query ; this runs it
156
170
  # against the triggering event's own payload (the same source
157
171
  # `deliver`'s own ordinary path forwards to `trigger` wholesale) and
158
172
  # fires `trigger` once per row, merging each row's own id into the
159
- # forwarded payload under whichever key THE TARGET COMMAND ITSELF
173
+ # forwarded payload under whichever key the target command itself
160
174
  # expects to be addressed by (`Behaviour::Command#addressing_key_for`
161
175
  # — never a guessed, one-size mint: `Account.Freeze`, addressed by
162
- # `account` because it is declared ON Account and self-references
176
+ # `account` because it is declared on Account and self-references
163
177
  # it, refused every dispatch for as long as this hardcoded
164
178
  # `<aggregate>_id` instead — a real bug, found wiring `for_each`
165
179
  # into a real domain for the first time, not a hypothetical). A
166
180
  # refusal is recorded per row and the fan-out continues ; a crash
167
- # resolving the QUERY ITSELF, or the TARGET COMMAND'S OWN inability
181
+ # resolving the query itself, or the target command's own inability
168
182
  # to address this aggregate at all (`addressing_key_for` answering
169
183
  # `nil` — a domain-authoring mistake, not a data problem), is a
170
184
  # single top-level defect for the policy, the same shape `deliver`'s
@@ -174,9 +188,9 @@ module Hecks
174
188
 
175
189
  query_domain, aggregate_name, query_name = policy.for_each_route(domain)
176
190
  aggregate = resolve_query_aggregate(query_domain, aggregate_name, policy.for_each)
177
- # THE QUERY READS THE EVENT, never the projection — `with:` says
178
- # what the TRIGGER is given, and the fan-out's query is asking a
179
- # different question (WHICH rows) in the event's own vocabulary.
191
+ # The query reads the event, never the projection — `with:` says
192
+ # what the trigger is given, and the fan-out's query is asking a
193
+ # different question (which rows) in the event's own vocabulary.
180
194
  query_args = for_each_query_args(aggregate.query(query_name), event)
181
195
  rows = QueryInterpreter.new(@registry).call(query_domain, aggregate, query_name, query_args)
182
196
  reference_key = addressing_key_for(target, aggregate_name)
@@ -192,20 +206,19 @@ module Hecks
192
206
  record.merge(delivered: false, reason: e.message, defect: true, error_class: e.class.name)
193
207
  end
194
208
 
195
- # THE EVENT'S OWN IDENTITY IS A FACT TOO, not only its payload. A
196
- # for_each query commonly filters by the EMITTING record's own
209
+ # The event's own identity is a fact too, not only its payload. A
210
+ # for_each query commonly filters by the emitting record's own
197
211
  # identity (`OpenForCustomer`'s own `reference:`, scoping by the
198
- # very customer who was just suspended) — which used to arrive for
199
- # free because LEGACY dispatch left the self-addressing key riding
200
- # along in `event.payload` unfiltered. Routing separated from
201
- # payload (`to:`/`with:`, what the facade's own bang-methods always
202
- # use) correctly stopped carrying it there, which left this query
203
- # silently seeing NEITHER the field it needs NOR any error saying
204
- # why — an empty result read as "nothing to freeze" instead of "the
205
- # customer" the whole reaction exists to catch.
212
+ # very customer who was just suspended) — a fact routing keeps
213
+ # separate from payload (`to:`/`with:`, what the facade's own
214
+ # bang-methods always use), so `event.payload` alone does not carry
215
+ # it. Without this, the query would silently see neither the field
216
+ # it needs nor any error saying why — an empty result read as
217
+ # "nothing to freeze" instead of "the customer" the whole reaction
218
+ # exists to catch.
206
219
  #
207
- # Merged in ONLY when the query declares an argument by that exact
208
- # name AND the emitting aggregate's own identity is genuinely what
220
+ # Merged in only when the query declares an argument by that exact
221
+ # name and the emitting aggregate's own identity is genuinely what
209
222
  # that name means (`construct.identity_heads`) — the same guard
210
223
  # `SagaInterpreter::Correlation#self_identified?` uses for the
211
224
  # identical shape one call away, so an unrelated aggregate sharing
@@ -229,22 +242,22 @@ module Hecks
229
242
  args
230
243
  end
231
244
 
232
- # WHAT THE TRIGGER IS GIVEN. Undeclared, the event's whole payload
245
+ # **What the trigger is given**. Undeclared, the event's whole payload
233
246
  # forwards verbatim — the behaviour every policy had before `with:`
234
247
  # existed, and still the right default for a trigger shaped like
235
248
  # its event.
236
249
  #
237
250
  # Declared, it is the same reading a saga's own `dispatch ...,
238
251
  # with:` gets (`SagaInterpreter#dispatch_args`): a Symbol names a
239
- # field on the SOURCE below, anything else is a literal the policy
252
+ # field on the source below, anything else is a literal the policy
240
253
  # supplies itself. A saga additionally resolves against its own
241
254
  # memory and correlation key; a policy has neither — it holds
242
255
  # nothing between events — so the source is the event, plus:
243
256
  #
244
- # `extra` is a FAN-OUT'S ROW KEY, merged into the source BEFORE the
257
+ # `extra` is a fan-out's row key, merged into the source before the
245
258
  # projection rather than after it. That is what lets a `for_each`
246
259
  # policy name the row it is acting on — `with: { account: :account }`
247
- # — and therefore what lets one send the row and NOTHING ELSE. A
260
+ # — and therefore what lets one send the row and nothing else. A
248
261
  # trigger needing only which record to act on is the ordinary case
249
262
  # for a fan-out, and before this it could not be written: the whole
250
263
  # event rode along, and the target had to declare every field of it
@@ -261,26 +274,26 @@ module Hecks
261
274
  label: "#{policy.name}'s trigger"
262
275
  )
263
276
 
264
- # THE RAW INPUTS `args` WAS RESOLVED FROM — same additive,
277
+ # The raw inputs `args` was resolved from — same additive,
265
278
  # Ruby-only shape SagaInterpreter#deliver_saga_dispatch's own
266
279
  # saga_dispatch_log gets, for Properties.dispatch_binding_
267
280
  # fidelity's own independent re-derivation of Policy#with_spec's
268
281
  # 2-branch resolution (Symbol → payload lookup, anything else →
269
282
  # literal — a policy holds no correlation and no memory, so
270
283
  # `payload` — the merged event-payload-plus-fan-out-row source —
271
- # is the WHOLE source, unlike a saga's own 4-branch one).
284
+ # is the whole source, unlike a saga's own 4-branch one).
272
285
  @registry.policy_dispatch_log << { policy: policy.name, on: event.name, payload: payload,
273
286
  with_spec: policy.with_spec, args: args }
274
287
  args
275
288
  end
276
289
 
277
- # THE EMITTING RECORD'S OWN IDENTITY IS A FACT A PROJECTION MAY READ
290
+ # The emitting record's own identity is a fact a projection may read
278
291
  # — the same reasoning `for_each_query_args` gives one method up,
279
- # extended from the fan-out's QUERY to the trigger's own `with:`.
292
+ # extended from the fan-out's query to the trigger's own `with:`.
280
293
  # Routing separated from payload (`to:`/`with:`) stopped carrying
281
294
  # the emitting aggregate's identity in the payload, which is right
282
295
  # for the event (a KnightCaptured is a fact about a knight, not a
283
- # re-statement of which game) but left a CROSS-aggregate reaction
296
+ # re-statement of which game) but left a cross-aggregate reaction
284
297
  # with no way to say which record to address: chess's Graveyard is
285
298
  # one-per-game, fed by policy from every piece's own Captured
286
299
  # event, and its burials had no way to name the game — the
@@ -288,10 +301,10 @@ module Hecks
288
301
  # graveyard's identity and refused every one ("no Graveyard with
289
302
  # label.value \"bb\""). Same-aggregate targets were already covered
290
303
  # (`ReactionInvocation.source_receiver_for` lifts Event.id), so
291
- # this is the OTHER aggregate's half of that.
304
+ # this is the other aggregate's half of that.
292
305
  #
293
306
  # Offered under the emitting aggregate's own identity heads, only
294
- # to an EXPLICIT projection (a legacy wholesale forward keeps its
307
+ # to an explicit projection (a legacy wholesale forward keeps its
295
308
  # exact old payload), and never over a value the payload itself
296
309
  # carries. `event.id` is the scalar the identity resolves to, so a
297
310
  # projection naming it as the target's own identity head routes it
@@ -309,9 +322,9 @@ module Hecks
309
322
  construct.identity_heads.to_h { |head| [head.to_sym, event.id] }
310
323
  end
311
324
 
312
- # RESOLVES `target` ("Domain::Aggregate.Command", the same shape
313
- # `deliver`'s own caller already built) back to its OWN declared
314
- # command, then asks IT how it expects to be addressed by a row of
325
+ # Resolves `target` ("Domain::Aggregate.Command", the same shape
326
+ # `deliver`'s own caller already built) back to its own declared
327
+ # command, then asks it how it expects to be addressed by a row of
315
328
  # `aggregate_name` — see `Behaviour::Command#addressing_key_for`'s
316
329
  # own comment for the two shapes that answers. Raises (caught by
317
330
  # `deliver_for_each`'s own outer `rescue StandardError`, the same
@@ -342,8 +355,8 @@ module Hecks
342
355
  reason: "reaction depth #{@door.max_reaction_depth} reached")
343
356
  end
344
357
 
345
- # ALREADY MERGED, by `trigger_args` — the row key belongs in the
346
- # source a `with:` projection reads FROM, not bolted onto its
358
+ # Already merged, by `trigger_args` — the row key belongs in the
359
+ # source a `with:` projection reads from, not bolted onto its
347
360
  # result, or a projection could never name the row it acts on.
348
361
  @door.reenter(target, **reaction_invocation(target, args, policy, event))
349
362
  row_record.merge(delivered: true)
@@ -361,20 +374,20 @@ module Hecks
361
374
  )
362
375
  end
363
376
 
364
- # `for_each`'s own QUERY route moved onto the Policy itself
377
+ # `for_each`'s own query route moved onto the Policy itself
365
378
  # (Behaviour::Policy#for_each_route) — one reading the interpreter
366
379
  # and the fuzzer's fan-out property both call, rather than the
367
- # same split spelled twice. The reference-KEY the dispatch itself
368
- # uses is `addressing_key_for`, above — a property of the TARGET
369
- # COMMAND, not of the policy, so it lives on `Behaviour::Command`
380
+ # same split spelled twice. The reference-key the dispatch itself
381
+ # uses is `addressing_key_for`, above — a property of the target
382
+ # command, not of the policy, so it lives on `Behaviour::Command`
370
383
  # instead.
371
384
 
372
385
  def resolve_query_aggregate(domain, aggregate_name, verb)
373
386
  bluebook = @registry.bluebook(domain) ||
374
- raise(UnknownVerb, RefusalWording.render("UnknownVerb", "no_domain", domain: domain.inspect, verb: verb))
387
+ raise(UnknownVerb, RefusalWording.render_site("UnknownVerb", "no_domain", domain: domain, verb: verb))
375
388
  bluebook.aggregate(aggregate_name) ||
376
- raise(UnknownVerb, RefusalWording.render("UnknownVerb", "no_aggregate",
377
- domain: domain, aggregate: aggregate_name.inspect))
389
+ raise(UnknownVerb, RefusalWording.render_site("UnknownVerb", "no_aggregate",
390
+ domain: domain, aggregate: aggregate_name))
378
391
  end
379
392
  end
380
393
  end
@@ -19,20 +19,43 @@ module Hecks
19
19
  include Interpreting
20
20
  include CommandInterpreter::ArgumentGate
21
21
 
22
- Context = Struct.new(:domain, :aggregate, :operation, :args, :route, :instance, :result)
22
+ Context = Struct.new(:domain, :aggregate, :operation, :args, :route, :instance, :result, :invocation)
23
23
 
24
24
  DISPATCH_ORDER = %i[
25
25
  refuse_unknown_arguments refuse_absent_arguments normalize_args resolve_references resolve_route emit
26
26
  ].freeze
27
27
 
28
+ # @param registry [Runtime::Registry] the booted registry this interpreter dispatches
29
+ # against
30
+ # @param rules [Runtime::CommandRules] the shared command-rule checks (references) this
31
+ # interpreter's steps call
28
32
  def initialize(registry, rules:)
29
33
  @registry = registry
30
34
  @rules = rules
31
35
  end
32
36
 
33
- def call(domain, aggregate, operation, args, route:)
34
- ctx = Context.new(domain, aggregate, operation, args)
35
- ctx.route = route
37
+ # `invocation` — the `Runtime::Invocation` `Dispatcher` built;
38
+ # `ctx.args` is its `to_args`, `ctx.route` its `target`.
39
+ #
40
+ # @param domain [String] the domain the aggregate belongs to
41
+ # @param aggregate [Bluebook::Aggregate] the aggregate the port operation belongs to
42
+ # @param operation [Bluebook::PortOperation] the port operation to dispatch
43
+ # @param invocation [Runtime::Invocation] the invocation `Dispatcher` built for this call
44
+ # @return [Array<Runtime::Event>] the events recorded: the operation's own `emits` for
45
+ # an inbound operation, or a single `answers`/`refuses` event for an outbound one
46
+ # (an adapter failure is recorded as a `refuses` event, not raised)
47
+ # @raise [Runtime::UnknownArgument] if `invocation` offers an argument the operation
48
+ # does not declare
49
+ # @raise [Runtime::AbsentArgument] if `invocation` omits a non-optional declared
50
+ # argument
51
+ # @raise [Runtime::TypeMismatch] if an offered argument does not coerce to its declared
52
+ # type
53
+ # @raise [Runtime::NotFound] if an offered reference or the operation's own receiving
54
+ # record cannot be found
55
+ def call(domain, aggregate, operation, invocation)
56
+ ctx = Context.new(domain, aggregate, operation, invocation.to_args)
57
+ ctx.invocation = invocation
58
+ ctx.route = invocation.target
36
59
  run_dispatch_order(DISPATCH_ORDER, ctx)
37
60
  ctx.result
38
61
  end
@@ -66,13 +89,13 @@ module Hecks
66
89
  ctx.result = step(:emit) { ctx.operation.outbound? ? ask(ctx) : emit(ctx) }
67
90
  end
68
91
 
69
- # THE DOMAIN CALLING OUT, AND BOTH ENDINGS RECORDED.
92
+ # The domain calling out, and both endings recorded.
70
93
  #
71
94
  # The adapter is found the same way every other port's is — by name,
72
95
  # across whatever adapters this boot loaded — so an `asks` is bound by
73
96
  # an adapter declaring `port "IssueTracker"` and nothing new to learn.
74
97
  #
75
- # EVERY FAILURE IS AN ANSWER. A raise from the far side of a boundary is
98
+ # Every failure is an answer. A raise from the far side of a boundary is
76
99
  # not an exception in this domain's terms, it is the outside saying no,
77
100
  # and the chapter already named the word for that. So the rescue is
78
101
  # deliberately wide: a timeout, a bad credential, an adapter that does
@@ -80,22 +103,22 @@ module Hecks
80
103
  # `refuses` event, carrying what was said. A policy reacts to it, a
81
104
  # retry counter reads it, and nothing has to catch anything.
82
105
  #
83
- # AN ASK IS HANDED THE RECORD IT IS ABOUT.
106
+ # An ask is handed the record it is about.
84
107
  #
85
- # An INBOUND operation deliberately cannot read state — it is the
108
+ # An inbound operation deliberately cannot read state — it is the
86
109
  # anti-corruption boundary, translating a fact from outside, and letting
87
110
  # it read the aggregate would make it a second place rules live. That
88
111
  # rule was written for that direction and does not survive the crossing.
89
112
  #
90
113
  # An outbound one almost always needs the record. `asks "File"` names
91
114
  # `reference_to Ticket` and the adapter needs the ticket's repository,
92
- # title and body — which are ON the ticket, and which the policy that
115
+ # title and body — which are on the ticket, and which the policy that
93
116
  # triggered this cannot supply because a command's event payload is its
94
- # ARGUMENTS, not its state. Without this, every ask would have to have
117
+ # arguments, not its state. Without this, every ask would have to have
95
118
  # its data re-passed through the command that fired it, so the same text
96
119
  # would live in two places and could differ.
97
120
  #
98
- # ARGUMENTS WIN over state, because an argument is what THIS call said
121
+ # Arguments win over state, because an argument is what this call said
99
122
  # and state is what the record happens to hold.
100
123
  def ask(ctx)
101
124
  payload = held_state(ctx).merge(materialise(ctx.args))
@@ -105,13 +128,13 @@ module Hecks
105
128
  announce(ctx, ctx.operation.refuses, ctx.args.merge(refusal: { value: "#{e.class}: #{e.message}" }))
106
129
  end
107
130
 
108
- # THE ANSWER IS SPREAD, NOT NESTED — and that is what makes the loop
109
- # close. A policy re-enters its target with the event payload VERBATIM;
131
+ # The answer is spread, not nested — and that is what makes the loop
132
+ # close. A policy re-enters its target with the event payload verbatim;
110
133
  # it cannot reach inside a key. So an answer tucked under `answered:`
111
134
  # can be read by a human and by nothing else, and the command that
112
135
  # should record the issue number never gets one.
113
136
  #
114
- # Spread, the adapter's own keys ARE the arguments of whatever command
137
+ # Spread, the adapter's own keys are the arguments of whatever command
115
138
  # reacts to the answering event. Which is a real contract on the adapter
116
139
  # — it must return what that command takes, in the shape the runtime
117
140
  # coerces (`{ number: { value: 43 } }`, not `43`) — and naming it here
@@ -134,7 +157,7 @@ module Hecks
134
157
  end
135
158
  end
136
159
 
137
- # THE RECORD, IF THERE IS ONE. A record that does not exist yet is not
160
+ # The record, if there is one. A record that does not exist yet is not
138
161
  # an error here — the ask still goes, carrying only its arguments, and
139
162
  # whatever the adapter makes of that is its own business. Refusing
140
163
  # would put a second existence check behind the one `resolve_references`
@@ -145,10 +168,10 @@ module Hecks
145
168
  {}
146
169
  end
147
170
 
148
- # THE PORT THIS OPERATION BELONGS TO, found by asking the aggregate
171
+ # The port this operation belongs to, found by asking the aggregate
149
172
  # rather than threading it through the call — the dispatcher already
150
173
  # resolved it once to get here, and a second parameter carried purely so
151
- # this method can read it would be a parameter every OTHER step ignores.
174
+ # this method can read it would be a parameter every other step ignores.
152
175
  def port_name_for(ctx)
153
176
  owning = ctx.aggregate.ports.find { |port| port.operations.any? { |op| op.equal?(ctx.operation) } }
154
177
  owning&.name or raise WiringError,
@@ -185,9 +208,9 @@ module Hecks
185
208
  [event]
186
209
  end
187
210
 
188
- # THE ONE PLACE THIS DIFFERS FROM CommandRules::Emission — there is no
211
+ # The one place this differs from CommandRules::Emission — there is no
189
212
  # mutated instance to read an id off, because nothing was hydrated or
190
- # saved. The record this event is ABOUT is named by whichever attribute
213
+ # saved. The record this event is about is named by whichever attribute
191
214
  # is a reference to the owning aggregate (PortOperationBuilder#build
192
215
  # already refused to build an operation with none), so its coerced
193
216
  # value — already a plain id, never an object, per