hecks 2.8.0 → 2.9.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 (404) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +4 -6
  3. data/lib/hecks/adapters/driven/claude_code.rb +4 -104
  4. data/lib/hecks/adapters/driven/d1.rb +44 -237
  5. data/lib/hecks/adapters/driven/folder.rb +17 -76
  6. data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
  7. data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
  8. data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
  9. data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
  10. data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
  11. data/lib/hecks/adapters/driven/heki.rb +13 -42
  12. data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
  15. data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
  16. data/lib/hecks/adapters/driven/lambda.rb +10 -84
  17. data/lib/hecks/adapters/driven/local_storage.rb +5 -40
  18. data/lib/hecks/adapters/driven/memory.rb +17 -98
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
  24. data/lib/hecks/adapters/driven/postgres.rb +145 -134
  25. data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
  26. data/lib/hecks/adapters/driven/prism.rb +7 -30
  27. data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
  28. data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
  29. data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
  30. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
  31. data/lib/hecks/adapters/driven/sqlite.rb +128 -65
  32. data/lib/hecks/adapters/driven/tenant_provisioner.rb +5 -48
  33. data/lib/hecks/adapters/driven.rb +5 -36
  34. data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
  35. data/lib/hecks/behaviors/dsl.rb +7 -25
  36. data/lib/hecks/behaviors/expectations.rb +31 -141
  37. data/lib/hecks/behaviors/ir.rb +7 -20
  38. data/lib/hecks/behaviors/rspec.rb +6 -17
  39. data/lib/hecks/behaviors/runner.rb +10 -34
  40. data/lib/hecks/behaviors.rb +4 -8
  41. data/lib/hecks/bluebook/aggregate.rb +9 -75
  42. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
  43. data/lib/hecks/bluebook/assembly/build.rb +4 -15
  44. data/lib/hecks/bluebook/assembly/contract.rb +15 -136
  45. data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
  46. data/lib/hecks/bluebook/assembly/marks.rb +31 -223
  47. data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
  48. data/lib/hecks/bluebook/assembly.rb +6 -45
  49. data/lib/hecks/bluebook/attribute.rb +12 -61
  50. data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
  51. data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
  52. data/lib/hecks/bluebook/behaviour/chapter.rb +14 -42
  53. data/lib/hecks/bluebook/behaviour/command.rb +14 -66
  54. data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
  55. data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
  56. data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
  57. data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
  58. data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
  59. data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
  60. data/lib/hecks/bluebook/behaviour/read_model.rb +25 -31
  61. data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
  62. data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
  63. data/lib/hecks/bluebook/capabilities.rb +17 -49
  64. data/lib/hecks/bluebook/chapter.rb +10 -48
  65. data/lib/hecks/bluebook/command.rb +23 -81
  66. data/lib/hecks/bluebook/domain_port.rb +14 -63
  67. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
  68. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
  69. data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
  70. data/lib/hecks/bluebook/dsl/binding_proxy.rb +16 -69
  71. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
  72. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
  73. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
  74. data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
  75. data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
  76. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
  77. data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
  78. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
  79. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -194
  80. data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
  81. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
  82. data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
  83. data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
  84. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
  85. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
  86. data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
  87. data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
  88. data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
  89. data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
  90. data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
  91. data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
  92. data/lib/hecks/bluebook/dsl/world_builder.rb +19 -111
  93. data/lib/hecks/bluebook/entity.rb +13 -71
  94. data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
  95. data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
  96. data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
  97. data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
  98. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
  99. data/lib/hecks/bluebook/expression/resolver.rb +37 -660
  100. data/lib/hecks/bluebook/hexagon.rb +5 -28
  101. data/lib/hecks/bluebook/lifecycle.rb +5 -16
  102. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
  103. data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
  104. data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
  105. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
  106. data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
  107. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
  108. data/lib/hecks/bluebook/meta_validator/shapes.rb +53 -267
  109. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +43 -305
  110. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
  111. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
  112. data/lib/hecks/bluebook/meta_validator.rb +79 -349
  113. data/lib/hecks/bluebook/model_check/client_profile.rb +6 -21
  114. data/lib/hecks/bluebook/model_check.rb +100 -597
  115. data/lib/hecks/bluebook/pattern_subset.rb +11 -69
  116. data/lib/hecks/bluebook/process_manager.rb +13 -83
  117. data/lib/hecks/bluebook/project_loader.rb +3 -39
  118. data/lib/hecks/bluebook/project_register.rb +12 -60
  119. data/lib/hecks/bluebook/query.rb +9 -48
  120. data/lib/hecks/bluebook/read_model.rb +57 -81
  121. data/lib/hecks/bluebook/reference.rb +7 -41
  122. data/lib/hecks/bluebook/smoke_test.rb +8 -98
  123. data/lib/hecks/bluebook/synthesizer.rb +13 -62
  124. data/lib/hecks/bluebook/translation.rb +26 -62
  125. data/lib/hecks/bluebook/value_object.rb +11 -36
  126. data/lib/hecks/bluebook.rb +5 -22
  127. data/lib/hecks/cache_dir.rb +4 -23
  128. data/lib/hecks/cli/document.rb +3 -21
  129. data/lib/hecks/cli/ir.rb +3 -5
  130. data/lib/hecks/cli/mcp.rb +2 -3
  131. data/lib/hecks/cli/mcp_door.rb +19 -114
  132. data/lib/hecks/cli/model_check.rb +18 -85
  133. data/lib/hecks/cli/project_cli.rb +7 -40
  134. data/lib/hecks/cli/project_diagrams.rb +0 -7
  135. data/lib/hecks/cli/run.rb +12 -58
  136. data/lib/hecks/cli/smoke_test.rb +2 -16
  137. data/lib/hecks/cli/stores.rb +1 -2
  138. data/lib/hecks/cli.rb +18 -24
  139. data/lib/hecks/construct.rb +8 -34
  140. data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
  141. data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
  142. data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
  143. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
  144. data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
  145. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
  146. data/lib/hecks/embryonaut_bluebook.rb +4 -71
  147. data/lib/hecks/facade/cli_door.rb +24 -106
  148. data/lib/hecks/facade/cli_runner.rb +28 -172
  149. data/lib/hecks/facade/command_request.rb +13 -33
  150. data/lib/hecks/facade/handle.rb +34 -121
  151. data/lib/hecks/facade/json_door.rb +31 -146
  152. data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
  153. data/lib/hecks/facade/surface/chapter.rb +10 -54
  154. data/lib/hecks/facade/surface.rb +8 -23
  155. data/lib/hecks/facade.rb +10 -25
  156. data/lib/hecks/forms/app.rb +15 -70
  157. data/lib/hecks/forms/command_form_renderer.rb +10 -29
  158. data/lib/hecks/forms/field_renderer.rb +17 -156
  159. data/lib/hecks/forms/field_shape.rb +30 -222
  160. data/lib/hecks/forms/html.rb +12 -47
  161. data/lib/hecks/forms/index_renderer.rb +1 -4
  162. data/lib/hecks/forms/page.rb +3 -9
  163. data/lib/hecks/forms/params.rb +14 -60
  164. data/lib/hecks/forms/port_argument.rb +2 -19
  165. data/lib/hecks/forms/query_form_renderer.rb +4 -79
  166. data/lib/hecks/forms/record_renderer.rb +8 -75
  167. data/lib/hecks/forms/record_table.rb +7 -50
  168. data/lib/hecks/forms/reference_options.rb +3 -30
  169. data/lib/hecks/forms/value_object_shape.rb +8 -35
  170. data/lib/hecks/forms.rb +5 -33
  171. data/lib/hecks/fqn.rb +5 -12
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
  175. data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
  176. data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
  177. data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
  178. data/lib/hecks/framework.rb +11 -80
  179. data/lib/hecks/freezer.rb +5 -39
  180. data/lib/hecks/grammar/expression.bluebook +15 -40
  181. data/lib/hecks/grammar/translation.bluebook +3 -6
  182. data/lib/hecks/grammar.rb +16 -41
  183. data/lib/hecks/ir.rb +14 -83
  184. data/lib/hecks/language/adapter.bluebook +2 -7
  185. data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
  186. data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
  187. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
  188. data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
  189. data/lib/hecks/language/bluebook/command.bluebook +27 -144
  190. data/lib/hecks/language/bluebook/entity.bluebook +24 -97
  191. data/lib/hecks/language/bluebook/policy.bluebook +16 -67
  192. data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
  193. data/lib/hecks/language/bluebook/projection.bluebook +126 -60
  194. data/lib/hecks/language/bluebook/query.bluebook +12 -43
  195. data/lib/hecks/language/bluebook/shape.bluebook +23 -73
  196. data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
  197. data/lib/hecks/language/bluebook/vocabulary.bluebook +82 -197
  198. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
  199. data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
  200. data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
  201. data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
  202. data/lib/hecks/language/oidc.json +35 -0
  203. data/lib/hecks/language/port.bluebook +4 -17
  204. data/lib/hecks/language/translation/translation.bluebook +3 -9
  205. data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
  206. data/lib/hecks/language/world/wiring.bluebook +1 -3
  207. data/lib/hecks/language/world/world.bluebook +5 -7
  208. data/lib/hecks/literal.rb +8 -50
  209. data/lib/hecks/mcp_door_scope.rb +9 -90
  210. data/lib/hecks/mcp_stdio_guard.rb +4 -41
  211. data/lib/hecks/naming.rb +29 -137
  212. data/lib/hecks/ports/access_control.rb +7 -23
  213. data/lib/hecks/ports/agent/answers.rb +5 -23
  214. data/lib/hecks/ports/agent.rb +47 -172
  215. data/lib/hecks/ports/authentication.rb +12 -39
  216. data/lib/hecks/ports/authorization.rb +16 -43
  217. data/lib/hecks/ports/clock.rb +5 -45
  218. data/lib/hecks/ports/extraction.rb +7 -12
  219. data/lib/hecks/ports/identity_assignment.rb +8 -29
  220. data/lib/hecks/ports/identity_generation.rb +6 -29
  221. data/lib/hecks/ports/identity_resolution.rb +2 -10
  222. data/lib/hecks/ports/key_vault.rb +9 -45
  223. data/lib/hecks/ports/persistence/append_only.rb +61 -64
  224. data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
  225. data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
  226. data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
  227. data/lib/hecks/ports/persistence/plugin.rb +13 -42
  228. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
  229. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
  231. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +6 -25
  232. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
  233. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
  242. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
  243. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
  244. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
  245. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
  246. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
  247. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
  248. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
  249. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
  250. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
  251. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
  252. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
  253. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
  254. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
  255. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
  256. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
  257. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -124
  258. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
  259. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
  260. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
  261. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
  262. data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
  263. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
  264. data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
  266. data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
  267. data/lib/hecks/ports/persistence/state_codec.rb +23 -103
  268. data/lib/hecks/ports/persistence.rb +7 -14
  269. data/lib/hecks/ports/projection.rb +42 -84
  270. data/lib/hecks/ports/query/in_memory.rb +10 -58
  271. data/lib/hecks/ports/query/ordering.rb +11 -32
  272. data/lib/hecks/ports/query.rb +14 -37
  273. data/lib/hecks/ports.rb +2 -8
  274. data/lib/hecks/projections/bootstrap_table.rb +11 -43
  275. data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
  276. data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
  277. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
  278. data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
  279. data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
  280. data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
  281. data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
  282. data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
  283. data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
  284. data/lib/hecks/projections/deploy/fargate.rb +35 -202
  285. data/lib/hecks/projections/deploy/lambda.rb +150 -678
  286. data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
  287. data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
  288. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
  289. data/lib/hecks/projections/deploy/preview/script.rb +2 -14
  290. data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
  291. data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
  292. data/lib/hecks/projections/deploy/preview/template.rb +2 -58
  293. data/lib/hecks/projections/deploy/preview.rb +2 -67
  294. data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
  295. data/lib/hecks/projections/deploy/scripts.rb +4 -57
  296. data/lib/hecks/projections/deploy/shared.rb +47 -146
  297. data/lib/hecks/projections/deploy/smoke.rb +4 -61
  298. data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
  299. data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
  300. data/lib/hecks/projections/deploy/template_diff.rb +5 -57
  301. data/lib/hecks/projections/diagrams.rb +20 -582
  302. data/lib/hecks/projections/glossary/html.rb +14 -130
  303. data/lib/hecks/projections/glossary/markdown.rb +8 -69
  304. data/lib/hecks/projections/glossary/mermaid.rb +9 -66
  305. data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
  306. data/lib/hecks/projections/glossary/sentences.rb +14 -159
  307. data/lib/hecks/projections/glossary.rb +30 -222
  308. data/lib/hecks/projections/ir.rb +3 -10
  309. data/lib/hecks/projections/model/deviations.rb +15 -42
  310. data/lib/hecks/projections/model.rb +12 -51
  311. data/lib/hecks/projections/oidc.rb +12 -56
  312. data/lib/hecks/projections/parser_table.rb +9 -28
  313. data/lib/hecks/projections/reference.rb +2 -15
  314. data/lib/hecks/projections/rust_vocabulary.rb +12 -250
  315. data/lib/hecks/projections/shape.rb +3 -29
  316. data/lib/hecks/projections/statements.rb +7 -86
  317. data/lib/hecks/projections/vocabulary.rb +6 -22
  318. data/lib/hecks/projections.rb +2 -16
  319. data/lib/hecks/projector/cli_projector.rb +60 -375
  320. data/lib/hecks/projector/docs_projector.rb +10 -201
  321. data/lib/hecks/projector/exporter.rb +42 -134
  322. data/lib/hecks/projector/ir_projector.rb +2 -9
  323. data/lib/hecks/projector/narrate_projector.rb +15 -189
  324. data/lib/hecks/projector/target.rb +13 -76
  325. data/lib/hecks/projector.rb +30 -121
  326. data/lib/hecks/query_specification/common/comparators.rb +5 -26
  327. data/lib/hecks/query_specification/common/comparison.rb +35 -143
  328. data/lib/hecks/query_specification/common/dsl.rb +8 -24
  329. data/lib/hecks/query_specification/common/null_policy.rb +20 -70
  330. data/lib/hecks/query_specification/common/options.rb +2 -6
  331. data/lib/hecks/query_specification/common/where_clause.rb +2 -10
  332. data/lib/hecks/query_specification/field_path.rb +37 -77
  333. data/lib/hecks/query_specification/hop_path.rb +12 -76
  334. data/lib/hecks/release/gem_pin.rb +2 -26
  335. data/lib/hecks/release/runner/ci_publisher.rb +3 -6
  336. data/lib/hecks/release/runner/commands.rb +2 -4
  337. data/lib/hecks/release/runner/gem_publisher.rb +2 -3
  338. data/lib/hecks/release/runner/npm_publisher.rb +6 -20
  339. data/lib/hecks/release/runner/preflight.rb +4 -19
  340. data/lib/hecks/release/runner/tagger.rb +2 -9
  341. data/lib/hecks/release/runner.rb +3 -32
  342. data/lib/hecks/rendering.rb +4 -26
  343. data/lib/hecks/router/namespace_installer.rb +12 -36
  344. data/lib/hecks/router.rb +4 -8
  345. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  346. data/lib/hecks/runtime/boot_gates.rb +3 -18
  347. data/lib/hecks/runtime/caller.rb +19 -59
  348. data/lib/hecks/runtime/capability_graph.rb +5 -21
  349. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  350. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
  351. data/lib/hecks/runtime/command_interpreter.rb +124 -306
  352. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  353. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  354. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  355. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  356. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  357. data/lib/hecks/runtime/dependency_planning.rb +30 -102
  358. data/lib/hecks/runtime/dispatcher.rb +82 -241
  359. data/lib/hecks/runtime/entity_element.rb +40 -462
  360. data/lib/hecks/runtime/entity_interpreter.rb +64 -219
  361. data/lib/hecks/runtime/errors.rb +32 -115
  362. data/lib/hecks/runtime/event.rb +3 -25
  363. data/lib/hecks/runtime/identity.rb +18 -154
  364. data/lib/hecks/runtime/instance.rb +18 -150
  365. data/lib/hecks/runtime/interpreting.rb +11 -70
  366. data/lib/hecks/runtime/invocation.rb +28 -164
  367. data/lib/hecks/runtime/loader.rb +48 -186
  368. data/lib/hecks/runtime/outbox.rb +38 -261
  369. data/lib/hecks/runtime/policy_interpreter.rb +38 -200
  370. data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
  371. data/lib/hecks/runtime/query_interpreter.rb +45 -187
  372. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  373. data/lib/hecks/runtime/read_model_interpreter.rb +117 -208
  374. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  375. data/lib/hecks/runtime/reference_hop.rb +8 -81
  376. data/lib/hecks/runtime/refusal_wording.rb +8 -89
  377. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  378. data/lib/hecks/runtime/registry/verification.rb +52 -225
  379. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  380. data/lib/hecks/runtime/registry.rb +99 -244
  381. data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
  382. data/lib/hecks/runtime/routing.rb +4 -15
  383. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  384. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  385. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  386. data/lib/hecks/runtime/tenant_check.rb +6 -68
  387. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  388. data/lib/hecks/runtime/value/admission.rb +16 -67
  389. data/lib/hecks/runtime/value/coercion.rb +155 -641
  390. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  391. data/lib/hecks/runtime/value.rb +12 -84
  392. data/lib/hecks/runtime.rb +17 -65
  393. data/lib/hecks/storehouse.rb +141 -568
  394. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
  395. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  396. data/lib/hecks/vendoring/git_environment.rb +2 -8
  397. data/lib/hecks/vendoring/git_source.rb +4 -10
  398. data/lib/hecks/vendoring.rb +7 -46
  399. data/lib/hecks/version.rb +3 -14
  400. data/lib/hecks.rb +26 -81
  401. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  402. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  403. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  404. metadata +3 -2
@@ -9,123 +9,61 @@ require_relative "../ports/persistence/remote_runtime"
9
9
 
10
10
  module Hecks
11
11
  module Runtime
12
- # **The write-side half of lambda routing** — `Runtime::Loader.boot`
13
- # constructs this instead of `Dispatcher` when the booted domain's
14
- # own `.world` declares Lambda routing (see loader.rb's own
15
- # `dispatcher_class_for`). Matches `Dispatcher`'s public shape
16
- # closely enough that everything built on top of a dispatcher —
17
- # `Handle`, `AggregateDoor`, `Facade::Surface` — needs no changes
18
- # at all: `Handle#run`'s entire contract is
19
- # `@dispatcher.dispatch("#{fqn}.#{command}", to: @id, with: args).instance.state`,
20
- # and both classes answer that identically.
12
+ # Dispatcher for a domain whose `.world` declares Lambda routing.
13
+ # Same public shape as `Dispatcher`; writes go remote, reads stay local.
21
14
  #
22
- # **Reads delegate, writes don't**. `query`/`reference_query` hand off
23
- # to a real `Dispatcher` built over the same registry — since that
24
- # registry's own repositories are already Lambda-backed
25
- # (`Adapters::Lambda`, `persisted_by("Lambda")`, Phase 2's other
26
- # half), the inherited query machinery (`QueryInterpreter`,
27
- # `Ports::Query::InMemory.execute`) works completely unchanged, no
28
- # query logic duplicated here. `dispatch` can't delegate the same
29
- # way: command validation (givens, constraints) has to actually
30
- # run in Rust for a Lambda-routed domain, not get pre-checked here
31
- # against incomplete local state and then merely persisted.
15
+ # Reads delegate to a local `Dispatcher` because the registry's repositories are
16
+ # already Lambda-backed. Writes cannot: givens and constraints must run in Rust,
17
+ # not be pre-checked here against incomplete local state.
32
18
  class RemoteDispatcher
33
19
  Result = Struct.new(:verb, :instance, :events, keyword_init: true) do
34
- # Reads the identity of the record the dispatch settled on.
20
+ # The settled record's identity.
35
21
  #
36
- # @return [String] the settled record's identity
22
+ # @return [String]
37
23
  def id = instance.id
38
24
 
39
- # Reads the settled record's attributes as one Hash.
25
+ # The settled record's attributes.
40
26
  #
41
- # @return [Hash{Symbol => Object}] the settled record's attributes, `:id` merged
42
- # in last
27
+ # @return [Hash{Symbol => Object}] `:id` merged in last
43
28
  def state = instance.to_h
44
29
  end
45
30
 
46
31
  attr_reader :registry
47
32
 
48
- # @param registry [Runtime::Registry] the booted registry this dispatcher fronts;
49
- # read-side calls (`query`/`reference_query`) delegate to a local `Dispatcher`
50
- # built over the same registry
33
+ # @param registry [Runtime::Registry] the booted registry this dispatcher fronts
51
34
  # @param region [String] the AWS region the routed Lambda function lives in
52
- # @param function [String, nil] the `.world`'s own `dispatched_by("Lambda")`
53
- # function name, when the deployment stack is not named `hecks-<domain>`; nil
54
- # resolves the function name from `ENV["DOMAIN_NAME"]` or `registry.root`
35
+ # @param function [String, nil] the `dispatched_by("Lambda")` function name; nil
36
+ # resolves it from `ENV["DOMAIN_NAME"]` or `registry.root`
55
37
  def initialize(registry, region: "us-east-1", function: nil)
56
38
  @registry = registry
57
- # `File.basename(registry.root)`, not `bluebooks.keys.first` —
58
- # matches `Adapters::Lambda`'s own function-name resolution
59
- # exactly (see its own comment on why: one merged Lambda per
60
- # deploy, not one per attached chapter, and `root` is the one
61
- # signal every bluebook in this registry shares regardless of
62
- # which one attached it) — including that same adapter's own
63
- # `ENV["DOMAIN_NAME"]`-first fix: `root` is always `/var/task`
64
- # inside a deployed Lambda, giving "task" instead of the real
65
- # domain name (a real, live AccessDeniedException on
66
- # "hecks-task" caught this).
67
- # `function:` — the `.world`'s own `dispatched_by("Lambda")`
68
- # naming of which function this is, for a deployment whose stack
69
- # name isn't `hecks-<domain>` (Client's own comment has the real
70
- # case). Absent, the resolution above is unchanged.
39
+ # ENV["DOMAIN_NAME"] first: inside a deployed Lambda `root` is always
40
+ # `/var/task`, which would resolve the function name to "task".
71
41
  @client = Adapters::Lambda::Client.new(domain: ENV["DOMAIN_NAME"] || File.basename(registry.root),
72
42
  region: region, function: function)
73
- # Read-side delegate only (see class comment) — never dispatched
74
- # through; a real Dispatcher's own `query`/`reference_query`
75
- # already resolve generically via `registry.repository(...)`,
76
- # so building one here reuses that instead of duplicating it.
77
43
  @local = Dispatcher.new(registry)
78
44
  end
79
45
 
80
- # Dispatches a command by verb, routing to the local `Dispatcher` or the
81
- # remote Lambda depending on the aggregate's bound adapter.
82
- #
83
- # Same shape as `Dispatcher#dispatch_flat` — everything but
84
- # `saga_correlation:` is forwarded through unread, `to:`/`with:`
85
- # included, and lifted out downstream by whichever path actually
86
- # dispatches (`@local.dispatch_flat` locally, the flat wire form
87
- # remotely). Not the strict `to:`/`with:`-only door `Dispatcher#
88
- # dispatch` is — see that class's own comment for why this file
89
- # never had one.
46
+ # Dispatches a command by verb, locally or through the routed Lambda depending
47
+ # on the aggregate's bound adapter.
90
48
  #
91
49
  # @param verb [String] the fully qualified verb, `"Domain::Aggregate.Command"`
92
- # or `"Domain::Aggregate.Entity.Command"`
93
- # @param saga_correlation [Hash, nil] correlation head => value, stamped on every
94
- # emitted event when a saga leg causes this dispatch; nil otherwise
95
- # @param args [Hash] the facts, plus optional `:to`/`:with` keys, read the same
96
- # way `dispatch_flat` reads them
50
+ # @param saga_correlation [Hash, nil] correlation head => value stamped on emitted events
51
+ # @param args [Hash] the facts, plus optional `:to`/`:with` keys
97
52
  # @return [RemoteDispatcher::Result] the verb, settled instance and emitted events
98
- # @raise [Runtime::UnknownVerb] if the verb is not fully qualified, or names a
99
- # domain or aggregate that is not declared
100
- # @raise [StandardError] any class in `Runtime::DOMAIN_REFUSALS` when a
101
- # locally-routed aggregate refuses the call
102
- # @raise [Runtime::StaleWrite] if concurrent local writers beat this one through
103
- # every retry, for a locally-routed aggregate
104
- # @raise [Runtime::WiringError] if the aggregate's adapter or repository cannot be
105
- # resolved, or the remote call is accepted but reports no mutation for it
53
+ # @raise [Runtime::UnknownVerb] if the verb is not fully qualified or names an
54
+ # undeclared domain or aggregate
106
55
  # @raise [Runtime::RemoteRefusal] if the routed Lambda refuses the call
56
+ # @raise [Runtime::WiringError] if the adapter cannot be resolved, or the remote call
57
+ # reports no mutation for the aggregate
107
58
  def dispatch(verb, saga_correlation: nil, **args)
108
59
  dispatch_flat(verb, args.merge(saga_correlation: saga_correlation))
109
60
  end
110
61
 
111
- # Routes a dispatch to the local `Dispatcher` when the aggregate's bound
112
- # adapter is not remote-backed (`Ports::Persistence::RemoteRuntime`),
113
- # otherwise dispatches through the routed Lambda. Same flat-facts wire
114
- # form as `Dispatcher#dispatch_flat`.
62
+ # Same routing as `dispatch`, with the flat-facts wire form of `Dispatcher#dispatch_flat`.
115
63
  #
116
- # @param verb [String] the fully qualified verb, in any shape `dispatch` accepts
117
- # @param args [Hash] the facts, plus optional Symbol keys `:to`, `:with` and
118
- # `:saga_correlation`, read as `dispatch`'s keywords of the same names; not mutated
64
+ # @param verb [String] the fully qualified verb
65
+ # @param args [Hash] the facts, plus optional Symbol keys `:to`, `:with`, `:saga_correlation`
119
66
  # @return [RemoteDispatcher::Result] the verb, settled instance and emitted events
120
- # @raise [Runtime::UnknownVerb] if the verb is not fully qualified, or names a
121
- # domain or aggregate that is not declared
122
- # @raise [StandardError] any class in `Runtime::DOMAIN_REFUSALS` when a
123
- # locally-routed aggregate refuses the call
124
- # @raise [Runtime::StaleWrite] if concurrent local writers beat this one through
125
- # every retry, for a locally-routed aggregate
126
- # @raise [Runtime::WiringError] if the aggregate's adapter or repository cannot be
127
- # resolved, or the remote call is accepted but reports no mutation for it
128
- # @raise [Runtime::RemoteRefusal] if the routed Lambda refuses the call
129
67
  def dispatch_flat(verb, args = {})
130
68
  args = args.dup
131
69
  saga_correlation = args.delete(:saga_correlation)
@@ -136,21 +74,8 @@ module Hecks
136
74
  raise(UnknownVerb,
137
75
  RefusalWording.render_site("UnknownVerb", "no_aggregate", domain: domain, aggregate: aggregate_name))
138
76
 
139
- # Not every aggregate in a lambda-routed domain is itself
140
- # lambda-bound — Member's real name->email rekey carries a
141
- # `compute` rule (era_check.rb's own `check_compute_rules!`),
142
- # which can only ever run against Postgres, permanently. Its
143
- # own `.hecksagon` bind stays "Postgres" even when
144
- # `dispatched_by("Lambda")` is on for everything else — checked
145
- # here by real capability (`Ports::Persistence::RemoteRuntime`,
146
- # §1), not by comparing the adapter's own name to the string
147
- # "Lambda" — a bind resolves to whatever adapter class actually
148
- # backs it, and only a class shaped like "the real interpreter
149
- # lives behind a call boundary" forwards here; anything else
150
- # (Postgres, Memory, any future local adapter) falls through to
151
- # the real local Dispatcher instead of being forwarded to a
152
- # Lambda that has no way to represent its lineage history at
153
- # all.
77
+ # Not every aggregate in a routed domain is Lambda-bound (a `compute` rule can
78
+ # only run against Postgres). Test the adapter's capability, not its name.
154
79
  adapter_name = Ports::Persistence::BindingPolicy.resolve(@registry, domain, aggregate).adapter
155
80
  unless @registry.adapter_class(adapter_name) <= Ports::Persistence::RemoteRuntime
156
81
  return @local.dispatch_flat(verb, args.merge(saga_correlation: saga_correlation))
@@ -161,15 +86,8 @@ module Hecks
161
86
  refusal = response.fetch("refusals", []).find { |r| r["verb"] == verb }
162
87
  raise RemoteRefusal, "#{verb} refused: #{refusal['error']}" if refusal
163
88
 
164
- # This step's own mutations — `mutations` is one entry per
165
- # replayed step (rust/host's rehydrate-and-replay design,
166
- # Phase 1), so `.last` is exactly the step just dispatched.
167
- # Matched by fully-qualified aggregate name, not just "the
168
- # first mutation" — a command whose reaction also mutates a
169
- # different aggregate (a policy, a saga leg) puts more than
170
- # one mutation in the same step, and the direct effect of
171
- # this verb is the one this dispatch's own caller expects
172
- # `.instance` to be.
89
+ # `mutations` has one entry per replayed step, so `.last` is this step. Match by
90
+ # aggregate name: a reaction can mutate other aggregates in the same step.
173
91
  fqn = "#{domain}::#{aggregate.hecks_name}"
174
92
  mutation = response.fetch("mutations", []).last&.find { |m| m["aggregate"] == fqn } ||
175
93
  raise(WiringError,
@@ -181,46 +99,17 @@ module Hecks
181
99
  Result.new(verb: verb, instance: instance, events: step_events(response))
182
100
  end
183
101
 
184
- # Delegates to the local `Dispatcher` built over the same registry — see the
185
- # class comment on why reads, unlike writes, need no remote-specific logic.
186
- #
187
- # @param verb [String, Symbol] the query's verb, in one of `Dispatcher#query`'s
188
- # three shapes
189
- # @param args [Hash{Symbol => Object}] the query's declared arguments
190
- # @return [Array<Hash>] see `Dispatcher#query`'s own return
191
- # @raise [Runtime::UnknownVerb] if the verb is not fully qualified, or names a
192
- # domain, aggregate, entity, query or read model that is not declared
193
- # @raise [Runtime::NotFound] if a read model's root reference names no record
194
- # @raise [Runtime::TypeMismatch] if an argument cannot be coerced to its declared type
195
- # @raise [KeyError] if a rooted read model is asked without its reference argument
196
- # @raise [Runtime::WiringError] if the aggregate's repository cannot be resolved
102
+ # Delegates to the local `Dispatcher`; see `Dispatcher#query`.
197
103
  def query(verb, **args) = @local.query(verb, **args)
198
104
 
199
- # Delegates to the local `Dispatcher` built over the same registry — see the
200
- # class comment on why reads, unlike writes, need no remote-specific logic.
201
- #
202
- # @param verb [String] the fully qualified query verb, `"Domain::Aggregate.Query"`
203
- # or `"Domain::Aggregate.Entity.Query"`
204
- # @param args [Hash{Symbol => Object}] the query's declared arguments
205
- # @return [Array<Hash>] one Hash per matching record, its state with `:id` merged
206
- # in last; for an entity query, one Hash per matching element
207
- # @raise [Runtime::UnknownVerb] if the verb is not fully qualified, or names a
208
- # domain, aggregate, entity or query that is not declared
209
- # @raise [Runtime::TypeMismatch] if an argument cannot be coerced to its declared type
210
- # @raise [Runtime::WiringError] if the aggregate's repository cannot be resolved
105
+ # Delegates to the local `Dispatcher`; see `Dispatcher#reference_query`.
211
106
  def reference_query(verb, **args) = @local.reference_query(verb, **args)
212
107
 
213
- # Fetches the full domain's event history, on every call — `{"read":
214
- # true}` replays the whole journal (Phase 1's `dispatch::read`),
215
- # so its own `events` array already is the complete log, the
216
- # same thing `@registry.event_log` would answer for a local
217
- # dispatch. Not cached: `AggregateDoor.events`/`Handle#events`
218
- # are not called in this codebase's own hot paths today: if that
219
- # changes, caching belongs here, not in every caller.
108
+ # Fetches the domain's whole event history from the routed Lambda on every call.
109
+ # Uncached: nothing calls it on a hot path.
220
110
  #
221
- # @return [Array<Runtime::Event>] every event in the routed Lambda's domain
222
- # journal, oldest first, with `occurred_at` always nil (the kernel is
223
- # timestamp-free by design)
111
+ # @return [Array<Runtime::Event>] every journaled event, oldest first, with
112
+ # `occurred_at` always nil
224
113
  def events
225
114
  @client.read.fetch("events", []).map { |e| build_event(e) }
226
115
  end
@@ -231,12 +120,8 @@ module Hecks
231
120
  response.fetch("events", []).map { |e| build_event(e) }
232
121
  end
233
122
 
234
- # Rust's own event JSON (kernel/cli.rs's `event_to_json`) carries
235
- # `name`/`aggregate`/`id`/`payload` only — no `occurred_at`
236
- # (nothing in the kernel tracks wall-clock time; every
237
- # replay is deterministic and timestamp-free by design). `nil`
238
- # here, not a synthesized `Time.now` that would silently lie
239
- # about when something actually happened.
123
+ # The kernel's event JSON carries no timestamp, so `occurred_at` stays nil
124
+ # rather than a synthesized `Time.now`.
240
125
  def build_event(json)
241
126
  Event.new(name: json["name"], aggregate: json["aggregate"], id: json["id"],
242
127
  payload: JSON.parse(JSON.generate(json["payload"]), symbolize_names: true), occurred_at: nil)
@@ -1,13 +1,7 @@
1
1
  module Hecks
2
2
  module Runtime
3
- # The invocation address is not part of a command's domain payload.
4
- # Aggregate commands carry one receiver identity; entity commands carry
5
- # the aggregate receiver followed by one identity for every entity hop.
6
- #
7
- # Reading a call's shape — what `to:` and `with:` mean, the BUG#7/#17/#18
8
- # rules — lives in `Runtime::Invocation.from_call` now (invocation.rb).
9
- # `envelope`/`payload` are kept as thin delegators for any caller still
10
- # naming them; the dispatcher itself builds an Invocation instead.
3
+ # The invocation address (`to:`), kept apart from a command's domain payload.
4
+ # Thin delegators to `Runtime::Invocation`, which reads a call's shape.
11
5
  module Routing
12
6
  Envelope = Struct.new(:aggregate, :entities, keyword_init: true) do
13
7
  # @param aggregate [String, #to_s] the receiving aggregate's identity
@@ -21,8 +15,7 @@ module Hecks
21
15
 
22
16
  module_function
23
17
 
24
- # Resolves a `to:` argument into a routing envelope. A thin delegator to
25
- # `Invocation.route`; the dispatcher itself builds an `Invocation` instead.
18
+ # Resolves a `to:` argument into a routing envelope (see `Invocation.route`).
26
19
  #
27
20
  # @param to [String, Hash, nil] a bare aggregate identity, or a Hash with
28
21
  # `aggregate:` and `entity:`/`entities:`; nil for no receiver
@@ -32,11 +25,7 @@ module Hecks
32
25
  # match `entity_depth`
33
26
  def envelope(to, entity_depth: 0) = Invocation.route(to, entity_depth: entity_depth)
34
27
 
35
- # Resolves a command's offered facts into its flat args hash. A thin
36
- # delegator to `Invocation.facts_for` for any caller still naming it.
37
- #
38
- # Loaded by invocation.rb (which requires this file), so `Invocation`
39
- # is always defined by the time either method runs.
28
+ # Resolves a command's offered facts into its flat args hash (see `Invocation.facts_for`).
40
29
  #
41
30
  # @param command [Class] a `Bluebook::Command` subclass (or port-operation class)
42
31
  # responding to `hecks_name` and `attributes`
@@ -3,80 +3,33 @@ require_relative "../value"
3
3
  module Hecks
4
4
  module Runtime
5
5
  class SagaInterpreter
6
- # How a saga decides which conversation an event belongs to — three
7
- # tiers, each one a lesson.
6
+ # How a saga decides which conversation an event belongs to.
8
7
  module Correlation
9
8
  private
10
9
 
11
- # A dotted path names the scalar field, rather than asking a value object
12
- # to stand in for one. `correlates_by :end_to_end` would key a saga on
13
- # the whole ExternalTransfer::EndToEndReference — and what a non-scalar
14
- # correlation key even is is representation-dependent (the object
15
- # itself? its serialised text?). `:"end_to_end.value"` reads the one
16
- # field with a single unambiguous rendering.
10
+ # Resolves the correlation key in three tiers: the payload path, the stamp from
11
+ # the dispatching saga, then the emitting record's own identity.
12
+ # A dotted path (`:"end_to_end.value"`) names one scalar; a whole value object
13
+ # has no single unambiguous rendering as a key.
17
14
  def saga_correlation(process_manager, event)
18
15
  path = process_manager.correlates_by.to_s.split(".")
19
- # A later event may already hold the scalar. `reference.value` digs a
20
- # value object's field out of a fresh declaration (TransferRequested's
21
- # `reference` is a TransferReference) — but a downstream event this
22
- # same value was smuggled through as a passthrough argument
23
- # (AccountDebited's `reference:`, resolved by `dispatch_args` to the
24
- # bare correlation string) carries it as a scalar already, with
25
- # nothing left to dig. `"xfer-1".respond_to?(:[])` is true — String
26
- # has its own `[]` (substring indexing) — so checking for keyed
27
- # lookup explicitly, rather than "responds to `[]` at all", is what
28
- # stops the second segment from being read as a symbol index into a
29
- # string that has already arrived.
16
+ # A downstream event may carry the bare scalar already. Strings respond to `[]`,
17
+ # so test for Hash/Value explicitly rather than `respond_to?(:[])`.
30
18
  value = path.reduce(event.payload) do |held, segment|
31
19
  held.is_a?(Hash) || held.is_a?(Value) ? held[segment.to_sym] : held
32
20
  end
33
21
  return value unless value.to_s.empty?
34
22
 
35
- # **The stamp** — `deliver_saga_dispatch` marks its own event before this
36
- # saga's next step ever asks, for a leg whose command declares
37
- # neither the correlation field itself nor the emitting aggregate's
38
- # own reference key (the two tiers above). command_interpreter/
39
- # argument_gate.rb names the old payload-only lookup "the weakest
40
- # part of the gate" : a correlation key arriving on a command only
41
- # because `correlation_keys` widens the undeclared-argument
42
- # allow-list domain-wide. This is the additive fix — a leg that
43
- # carries nothing correlation-shaped at all still correlates,
44
- # because the saga that dispatched it already knows the answer.
45
- # Keyed by `correlation_head`
46
- # rather than a bare scalar so an event stamped by one saga cannot be
47
- # misread by an unrelated one correlating on a different field.
23
+ # The stamp `deliver_saga_dispatch` puts on its own event, for legs whose command
24
+ # carries no correlation field. Keyed by `correlation_head` so another saga
25
+ # correlating on a different field cannot misread it.
48
26
  stamped = event.correlation && event.correlation[process_manager.correlation_head.to_s]
49
27
  return stamped unless stamped.nil? || stamped.to_s.empty?
50
28
 
51
- # A self-referencing leg carries the correlation forward under its
52
- # own emitting record's identity — `event.id`, not a field dug back
53
- # out of the payload, because an addressing key is not a fact the
54
- # payload should carry: routing is kept separate from payload
55
- # (`to:`/`with:`, the facade's own `Handle#run` always uses it), so
56
- # `own_key` (the aggregate's own reference-key convention, "wire",
57
- # "transfer") never rides along in the payload unfiltered. Digging
58
- # for it there would read an empty Hash for any self-referencing
59
- # leg with no other declared attributes (`OnboardingCase.Clear`,
60
- # `.Decline` — no `attribute` lines at all), silently stopping the
61
- # saga from advancing, forever, for exactly the leg this tier
62
- # exists to correlate.
63
- #
64
- # `event.id` says the identical thing this tier always meant —
65
- # "the record that just emitted this event, by its own identity" —
66
- # and unlike a payload dig it is populated by the record itself,
67
- # not by which dispatch convention the caller happened to use.
68
- #
69
- # Gated, still — a manually-dispatched command on a wholly
70
- # unrelated aggregate can share an event name this process_manager happens to
71
- # handle (`Drawer.Take` also emits "Taken", the same name a
72
- # saga-dispatched leg uses) with nothing this saga should read as
73
- # its own conversation. What makes a leg genuinely
74
- # self-referencing — the one fact worth trusting `event.id`
75
- # for — is that `correlates_by`'s own head field is this event's
76
- # own aggregate's declared identity, not merely a same-shaped
77
- # name: `OnboardingCase.identity_heads` really does include
78
- # `:reference`, `correlates_by :"reference.value"`'s own head ;
79
- # `Drawer.identity_heads` is `[:number]`, nowhere close.
29
+ # A self-referencing leg correlates by `event.id`: routing keys never ride in the
30
+ # payload, so a payload dig finds nothing for legs with no declared attributes.
31
+ # Gated on the head being the aggregate's own identity, so an unrelated aggregate
32
+ # emitting a same-named event is not read as this saga's conversation.
80
33
  self_identified?(process_manager, event) ? event.id : nil
81
34
  end
82
35