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
@@ -7,45 +7,12 @@ require_relative "registry"
7
7
 
8
8
  module Hecks
9
9
  module Runtime
10
- # `TenantScope`'s sibling — a hop folds to a synthetic `in`
11
- # where-clause the exact same way a tenant boundary folds to a
12
- # synthetic `eq` one, so every engine that already reads `.wheres`
13
- # answers a hop for free, with no per-engine code and no way for
14
- # one engine to forget it.
15
- #
16
- # A hop's own filtering never happens here — resolving one hop
17
- # means running one ordinary, adapter-agnostic query against the
18
- # hop's target aggregate (through the same Ports::Query boundary
19
- # any other query goes through), and folding the ids it answers
20
- # back in as a local membership check. A multi-hop chain resolves
21
- # from the far end inward: `fold` only ever peels off the head hop
22
- # (QuerySpecification::HopPath.next_hop, the one-step primitive),
23
- # and hands everything still left in the tail to a recursive
24
- # `apply` call — so hop 2, hop 3, and so on each get resolved by
25
- # their own ordinary query against their own target, one recursion
26
- # level at a time, never by this module trying to see the whole
27
- # chain at once. It terminates because each recursion is over a
28
- # strictly shorter dotted string, and
29
- # BluebookBuilder#validate_query_hops! already refused any chain
30
- # deep enough to matter before this ever runs.
10
+ # Folds each hop where-clause into a synthetic local `in` clause, the same trick
11
+ # `TenantScope` uses, so any engine that already reads `.wheres` handles a hop for free.
31
12
  module ReferenceHop
32
13
  module_function
33
14
 
34
15
  # Folds every hop clause in `declared.wheres` into a synthetic local `in` clause.
35
- #
36
- # @param declared [Bluebook::Query, Runtime::TenantScope::Scoped,
37
- # QuerySpecification::Common::Options] the declared query specification to fold hop
38
- # clauses of
39
- # @param args [Hash] the query's arguments, read when resolving each hop's own query
40
- # @param registry [Runtime::Registry] the booted registry to resolve each hop's target
41
- # repository from
42
- # @param domain [String] the domain `aggregate` belongs to
43
- # @param aggregate [Bluebook::Aggregate] the aggregate `declared` queries
44
- # @return [Bluebook::Query, Runtime::TenantScope::Scoped, QuerySpecification::Common::
45
- # Options, Hecks::Runtime::ReferenceHop::Folded] `declared` unchanged when it has no
46
- # hop clauses; otherwise a `Folded` wrapper whose `#wheres` replaces each hop clause
47
- # with its folded `in` clause
48
- # @raise [Runtime::WiringError] if a hop's target does not resolve (see `fold`)
49
16
  def apply(declared, args, registry:, domain:, aggregate:)
50
17
  hopped, local = declared.wheres.partition { |clause| QuerySpecification::HopPath.hop_head?(clause.field, aggregate.attributes) }
51
18
  return declared if hopped.empty?
@@ -55,28 +22,12 @@ module Hecks
55
22
  end
56
23
 
57
24
  # Folds one hop clause into a synthetic `in` clause over the hop attribute's own ids.
58
- #
59
- # @param clause [QuerySpecification::Common::WhereClause] the hop clause to fold; its
60
- # `field` names the hop path, dotted past the first segment
61
- # @param args [Hash] the query's arguments, read when resolving the inner query
62
- # @param registry [Runtime::Registry] the booted registry to resolve the hop's target
63
- # repository from
64
- # @param domain [String] the domain `aggregate` belongs to
65
- # @param aggregate [Bluebook::Aggregate] the aggregate `clause` is declared against
66
- # @return [QuerySpecification::Common::WhereClause] a synthetic `in` clause on the hop
67
- # attribute's name, whose value is every id the inner clause admits on the target
68
- # @raise [Runtime::WiringError] if the hop's target aggregate does not resolve
69
25
  def fold(clause, args, registry:, domain:, aggregate:)
70
26
  step = QuerySpecification::HopPath.next_hop(clause.field, aggregate.attributes)
71
27
  hop, rest = step
72
28
 
73
- # A guarantee BluebookBuilder#validate_query_hops! already gave
74
- # this exact clause before the domain ever booted. Held anyway:
75
- # an unresolvable target folding silently to "matches
76
- # everything" is precisely the failure shape this whole feature
77
- # exists to close, and a hop that cannot resolve (a
78
- # target aggregate unloaded after boot, say) is a real fact
79
- # worth a real error, not a query that quietly stops filtering.
29
+ # BluebookBuilder#validate_query_hops! already checked this hop at boot; if it
30
+ # still fails to resolve here, raise rather than silently matching everything.
80
31
  unless hop&.target
81
32
  raise WiringError,
82
33
  "#{clause.field} hops through a reference this domain cannot resolve " \
@@ -90,23 +41,8 @@ module Hecks
90
41
  QuerySpecification::Common::WhereClause.new(field: hop.attribute.name, op: "in", value: ids)
91
42
  end
92
43
 
93
- # Every id the inner clause admits on the hop's target — one
94
- # whole, ordinary query against the target's own repository,
95
- # through the very same Ports::Query boundary the outer ask
96
- # uses, so a hop is answered by whatever engine the target
97
- # aggregate is actually bound to (which may not be the engine
98
- # the outer aggregate is bound to at all) rather than by a
99
- # second reading of the comparators.
100
- #
101
- # @param domain [String] the domain `target` belongs to
102
- # @param target [Bluebook::Aggregate] the hop's target aggregate to query
103
- # @param wheres [Array<QuerySpecification::Common::WhereClause>] the inner clause(s) to
104
- # run against `target`
105
- # @param args [Hash] the outer query's arguments, read when resolving the inner query
106
- # @param registry [Runtime::Registry] the booted registry to resolve `target`'s
107
- # repository from
108
- # @return [Array<String>] every distinct id the inner clause(s) admit on `target`
109
- # @raise [Runtime::WiringError] if a hop nested inside `wheres` does not resolve
44
+ # Every id the inner clause(s) admit on `target`, queried through `target`'s own
45
+ # repository — which may be bound to a different engine than the outer aggregate.
110
46
  def matching_ids(domain, target, wheres, args, registry:)
111
47
  spec = apply(QuerySpecification::Common::Options.new(wheres: wheres), args,
112
48
  registry: registry, domain: domain, aggregate: target)
@@ -117,18 +53,9 @@ module Hecks
117
53
  rows.map { |row| row.id.to_s }.uniq
118
54
  end
119
55
 
120
- # Never returned to a caller that might call an IR-level method
121
- # (`to_h`, …) whose own internal `wheres` read would resolve
122
- # against the original object, not this override — the exact
123
- # caution TenantScope::Scoped's own comment gives, for the exact
124
- # same reason: SimpleDelegator only intercepts calls made
125
- # directly on the wrapper.
56
+ # SimpleDelegator only intercepts calls made directly on this wrapper, so never
57
+ # hand it to a caller that reads `wheres` via an IR-level method like `to_h`.
126
58
  class Folded < SimpleDelegator
127
- # @param declared [Bluebook::Query, Runtime::TenantScope::Scoped,
128
- # QuerySpecification::Common::Options] the wrapped query specification, delegated to
129
- # for everything but `#wheres`
130
- # @param wheres [Array<QuerySpecification::Common::WhereClause>] the replacement
131
- # where-clauses, hop clauses folded to synthetic `in` clauses
132
59
  def initialize(declared, wheres)
133
60
  super(declared)
134
61
  @wheres = wheres
@@ -2,25 +2,8 @@ require_relative "../vocabulary"
2
2
 
3
3
  module Hecks
4
4
  module Runtime
5
- # Every DomainRefusal wording that is not already data — `given`/
6
- # `ensures`/a declared `invariant` already carry their own description,
7
- # read at dispatch time off the command or value object that declared
8
- # them. These are different in kind: language-level refusals, the same
9
- # wording for every domain, not authored per-bluebook.
10
- #
11
- # Read off the generated table, not typed a second time. The rows are
12
- # Vocabulary::RefusalTemplate (language/bluebook/vocabulary.bluebook),
13
- # projected into lib/hecks/vocabulary.rb by bin/project_vocabulary and
14
- # into rust/src/kernel/vocab/refusal_template.rs by
15
- # bin/project_rust_vocabulary; both regenerations are diffed in CI, so
16
- # there is no hand copy left here to drift. Declared order is kept.
17
- #
18
- # **The arguments are data too**. Vocabulary::RefusalSiteArgument names the
19
- # values each site takes and how each is written (a list's separator,
20
- # its sort, its empty reading, its quoting). Call sites use
21
- # `render_site` and hand over raw values; the Rust kernel's typed
22
- # `render_args` applies the same rows, and the projection that writes it
23
- # pins every site's output against `format_argument`/`substitute` here.
5
+ # Language-level DomainRefusal wording, the same for every domain.
6
+ # Templates and argument formats are read from the generated Vocabulary tables.
24
7
  module RefusalWording
25
8
  TEMPLATES = Hecks::Vocabulary.rows("RefusalTemplate")
26
9
  .to_h { |row| [[row["refusal"], row["site"]].freeze, row["template"]] }
@@ -31,40 +14,13 @@ module Hecks
31
14
 
32
15
  module_function
33
16
 
34
- # Renders `refusal`/`site`'s template, substituting `values` verbatim.
35
- #
36
- # Plain text substitution, never expression syntax — a template is
37
- # read, not evaluated. Values arrive already formatted; prefer
38
- # `render_site`, which formats them off the declared rows.
39
- #
40
- # @param refusal [String] the `DomainRefusal` class name, such as `"UnknownArgument"`
41
- # @param site [String] the template site within `refusal`, such as `"unknown_args"`
42
- # @param values [Hash{Symbol => #to_s}] each `{name}` placeholder's already-formatted
43
- # replacement text
44
- # @return [String] the rendered refusal message
45
- # @raise [KeyError] if no `RefusalTemplate` row declares `refusal`/`site`
17
+ # Renders `refusal`/`site`'s template, substituting already-formatted `values`.
46
18
  def render(refusal, site, **values)
47
19
  substitute(template(refusal, site), values)
48
20
  end
49
21
 
50
- # The one door call sites use: exactly the arguments the site
51
- # declares (a missing or undeclared one raises ArgumentError before
52
- # any wording exists), each formatted by its RefusalSiteArgument row,
53
- # substituted in declared order.
54
- #
55
- # RefusalWording.render_site("UnknownArgument", "unknown_args",
56
- # command: "Close", unknown: [:parcel], declared: [])
57
- # # => "Close does not declare parcel — it takes none"
58
- #
59
- # @param refusal [String] the `DomainRefusal` class name, such as `"UnknownArgument"`
60
- # @param site [String] the template site within `refusal`, such as `"unknown_args"`
61
- # @param arguments [Hash{Symbol => Object}] raw values, one per `RefusalSiteArgument`
62
- # row declared for `refusal`/`site`; formatted per row before substitution
63
- # @return [String] the rendered refusal message
64
- # @raise [ArgumentError] if `arguments` is missing a declared argument or offers one
65
- # `refusal`/`site` does not declare
66
- # @raise [KeyError] if no `RefusalTemplate`/`RefusalSiteArgument` rows declare
67
- # `refusal`/`site`
22
+ # Renders a site's template from raw `arguments`, formatting each per its row.
23
+ # Raises ArgumentError on a missing or undeclared argument, KeyError on an unknown site.
68
24
  def render_site(refusal, site, **arguments)
69
25
  specs = argument_rows(refusal, site)
70
26
  declared = specs.map { |spec| spec["argument"].to_sym }
@@ -78,15 +34,7 @@ module Hecks
78
34
  render_with(template(refusal, site), specs, arguments)
79
35
  end
80
36
 
81
- # `render_site` without the registry lookups: a template, its
82
- # argument rows, and raw values. The Rust projection calls this with
83
- # the chapter's own rows to compute the expected wording it pins.
84
- #
85
- # @param template [String] the raw template, `{name}` placeholders unsubstituted
86
- # @param specs [Array<Hash>] the `RefusalSiteArgument` rows to format `arguments`
87
- # against, one per declared argument name
88
- # @param arguments [Hash{Symbol => Object}] raw values, one per entry in `specs`
89
- # @return [String] the rendered refusal message
37
+ # `render_site` without the registry lookups; the Rust projection calls it with its own rows.
90
38
  def render_with(template, specs, arguments)
91
39
  values = specs.to_h do |spec|
92
40
  name = spec["argument"].to_sym
@@ -95,15 +43,7 @@ module Hecks
95
43
  substitute(template, values)
96
44
  end
97
45
 
98
- # One argument, written the way its row says. A list is sorted first
99
- # (before quoting), then each item quoted, then joined; an empty list
100
- # reads `when_empty`. A scalar is quoted or taken as its own text.
101
- #
102
- # @param spec [Hash] the argument's `RefusalSiteArgument` row (`"shape"`, `"quoting"`,
103
- # and, for a list, `"sorted"`, `"separator"`, `"when_empty"`)
104
- # @param value [Object, Array] the raw value to format; an Array (or anything
105
- # `Array()`-coercible) for a `"list"`-shaped spec, a scalar otherwise
106
- # @return [String] the formatted text
46
+ # A list is sorted, then quoted, then joined; an empty list reads `when_empty`.
107
47
  def format_argument(spec, value)
108
48
  inspect = spec.fetch("quoting") == "inspect"
109
49
  return inspect ? value.inspect : value.to_s unless spec.fetch("shape") == "list"
@@ -114,22 +54,10 @@ module Hecks
114
54
  items.empty? ? spec.fetch("when_empty") : items.join(spec.fetch("separator"))
115
55
  end
116
56
 
117
- # Replaces each `{name}` placeholder in `template` with its value's text.
118
- #
119
- # @param template [String] the raw template, `{name}` placeholders unsubstituted
120
- # @param values [Hash{Symbol => #to_s}] each placeholder name mapped to its
121
- # replacement text
122
- # @return [String] `template` with every `{name}` placeholder substituted
123
57
  def substitute(template, values)
124
58
  values.reduce(template) { |text, (key, value)| text.gsub("{#{key}}", value.to_s) }
125
59
  end
126
60
 
127
- # Looks up the raw template text declared for one refusal site.
128
- #
129
- # @param refusal [String] the `DomainRefusal` class name, such as `"UnknownArgument"`
130
- # @param site [String] the template site within `refusal`, such as `"unknown_args"`
131
- # @return [String] the raw `RefusalTemplate` text, `{name}` placeholders unsubstituted
132
- # @raise [KeyError] if no `RefusalTemplate` row declares `refusal`/`site`
133
61
  def template(refusal, site)
134
62
  TEMPLATES.fetch([refusal, site]) do
135
63
  raise KeyError, "no refusal template for #{refusal}/#{site} — declare it in " \
@@ -137,16 +65,7 @@ module Hecks
137
65
  end
138
66
  end
139
67
 
140
- # The declared arguments for one refusal site.
141
- #
142
- # Read lazily, not into a constant: bin/project_vocabulary boots
143
- # `hecks` (and so this file) before it writes the table a newly
144
- # declared site's rows live in.
145
- #
146
- # @param refusal [String] the `DomainRefusal` class name, such as `"UnknownArgument"`
147
- # @param site [String] the template site within `refusal`, such as `"unknown_args"`
148
- # @return [Array<Hash>] the `RefusalSiteArgument` rows declared for `refusal`/`site`
149
- # @raise [KeyError] if no `RefusalSiteArgument` rows declare `refusal`/`site`
68
+ # Read lazily: bin/project_vocabulary boots this file before it writes these rows.
150
69
  def argument_rows(refusal, site)
151
70
  @argument_rows ||= Hecks::Vocabulary.rows("RefusalSiteArgument")
152
71
  .group_by { |row| [row["refusal"], row["site"]] }
@@ -3,52 +3,10 @@ require_relative "../saga_pending_dispatch"
3
3
  module Hecks
4
4
  module Runtime
5
5
  class Registry
6
- # **Saga persistence** — no new DSL verb, no new world setting.
7
- # Whatever adapter a domain's own aggregates already use for
8
- # durability, its sagas use the same one, automatically: three
9
- # optional adapter methods (`save_saga`/`delete_saga`/`each_saga`),
10
- # `respond_to?`-checked the same way `Ports::Persistence::AppendOnly`
11
- # already treats an adapter's own optional methods
12
- # (`find`/`all`/`count`/`reset!`/`events`/`record_event`).
6
+ # A domain's sagas persist through whatever adapter its aggregates already use;
7
+ # `save_saga`/`delete_saga`/`each_saga` are optional, `respond_to?`-checked capabilities.
13
8
  module SagaPersistence
14
- # Resolved and memoized per domain — the first real aggregate
15
- # declared in the domain's own bluebook is the resolution
16
- # anchor. `Ports::Persistence::BindingPolicy.resolve` for that
17
- # aggregate already falls back to a domain-level default bind
18
- # (§0's `Hecksagon#bind_for`) before falling back to Memory, so
19
- # this needs no separate "was a default declared" branch of its
20
- # own — whatever adapter that anchor resolves to already is the
21
- # domain's own default in the normal case (every aggregate
22
- # shares one adapter), and is the honest, documented fallback
23
- # for the rarer domain genuinely split across more than one
24
- # local adapter with no default declared.
25
- #
26
- # Genuinely lazy, genuinely POST-boot — unlike `rehydrate_sagas!`
27
- # below (boot-only), this is called from live dispatch
28
- # (`SagaInterpreter#checkpoint`/`#end_saga`, on every saga
29
- # transition), so the first call for a given domain can come from
30
- # any dispatching thread, not just the boot thread. `@saga_persistence`
31
- # itself is a plain Hash stood up once in `Registry#initialize` (no
32
- # race on the container), but `resolve_saga_persistence` is real work
33
- # (a `BindingPolicy.resolve` plus a lazy `repository` build) whose
34
- # result — the actual adapter instance a domain's sagas persist
35
- # through — must be the same object for every caller: two threads
36
- # racing the first lookup and each building their own adapter would
37
- # silently split one domain's saga writes across two adapter
38
- # instances (worse than `Dispatcher#reaction_depth`'s M20 — that bug
39
- # corrupted a counter; this one can corrupt which store a saga's
40
- # state lands in). Double-checked locking against a dedicated mutex
41
- # — never `@saga_mutex` — see `Registry#initialize`'s own comment for
42
- # why reusing that one would deadlock.
43
- # Resolves and memoizes the adapter a domain's sagas persist through.
44
- #
45
- # @param domain [String, Symbol] the domain to resolve saga persistence for
46
- # @return [Adapters::Heki, Adapters::Postgres, Adapters::Sqlite, Adapters::D1,
47
- # Ports::Persistence::Plugins::Era::PostgresEra, Ports::Persistence::NullSagaStore]
48
- # the same adapter instance the domain's anchor aggregate persists through, when it
49
- # implements `save_saga`; `NULL_SAGA_STORE` for a domain with no anchor aggregate, an
50
- # adapter that does not implement the capability, a `RemoteRuntime`-shaped adapter,
51
- # or a `Runtime::WiringError` resolving the anchor's own bind
9
+ # Memoized per domain via double-checked locking; never reuse @saga_mutex, see #initialize.
52
10
  def saga_persistence(domain)
53
11
  key = domain.to_s
54
12
  @saga_persistence[key] || @saga_persistence_mutex.synchronize do
@@ -56,34 +14,11 @@ module Hecks
56
14
  end
57
15
  end
58
16
 
59
- # Every domain whose sagas can persist somewhere: one with a hecksagon, or one a
60
- # world's `default_adapter` binds though it has none.
61
- #
62
- # @return [Array<String>] the domain names, hecksagon domains first
63
17
  def saga_domains = @hecksagons.keys | @bluebooks.keys.select { |domain| default_adapter_for(domain) }
64
18
 
65
- # Walks every loaded domain in `saga_domains`, repopulating `saga_instances` from
66
- # whatever `saga_persistence(domain)` resolves to — a real store
67
- # for a domain whose adapter answers the capability, `each_saga`
68
- # yielding real rows; `NULL_SAGA_STORE`'s own `each_saga` for
69
- # everything else, which never yields at all, so this needs no
70
- # `respond_to?` branch of its own — the same reason every other
71
- # call site in this capability never needs one. Called once at
72
- # boot (`Loader.boot`, between `verify!` and dispatcher
73
- # construction) — a process that's been running has no reason to
74
- # re-walk its own already-current `saga_instances`.
75
- #
76
- # Boot-time-only (or its test-runner equivalent) — this method's
77
- # only callers are `Loader.run_boot_gates!` (single-threaded, before
78
- # `dispatcher_for` ever exists) and `Registry#reset_runtime_state!`
79
- # (single-threaded test runner — see that method's own comment).
80
- # Never called from live dispatch, so `@saga_instances` mutation
81
- # here has no concurrent caller to race, unlike its other two write
82
- # points inside `@saga_mutex.synchronize` blocks (`saga_interpreter.
83
- # rb`), which genuinely do and are guarded accordingly.
19
+ # Boot-time-only (Loader.run_boot_gates!, before dispatch exists); no concurrent
20
+ # writer to race here, unlike saga_interpreter.rb's writes under @saga_mutex.
84
21
  # rubocop:disable-next Hecks/ThreadSharedIvarMutation
85
- #
86
- # @return [Hecks::Runtime::Registry] self
87
22
  def rehydrate_sagas!
88
23
  saga_domains.each do |domain|
89
24
  saga_persistence(domain).each_saga do |process_manager, correlation, state, memory, completed_compensations = []|
@@ -98,33 +33,12 @@ module Hecks
98
33
 
99
34
  private
100
35
 
101
- # The other half of the outbox-shaped fix (see saga_pending_
102
- # dispatch.rb) — `SAGA_PENDING_DISPATCH_KEY`, if the crashed
103
- # process left it standing, means the row's own `state` was
104
- # checkpointed but the dispatch cascade that justifies it may
105
- # never have run. Stripped out of `memory` before it becomes
106
- # this instance's live `:memory` (so nothing downstream — a
107
- # `given`, a `with:` mapping, the fuzzer — ever sees it), and
108
- # surfaced loudly instead: a warning plus a `saga_log` entry,
109
- # never an automatic redrive (see saga_pending_dispatch.rb for
110
- # why redriving without idempotent delivery would be worse than
111
- # the stall). This is real, durable crash-recovery visibility —
112
- # still not the reconciliation itself, which stays a human's
113
- # call until hecks has idempotent redelivery to make it safe.
36
+ # A pending dispatch marker left by a crash: state was checkpointed but the dispatch
37
+ # cascade it justifies may never have run. Surfaced as a warning, never auto-redriven.
114
38
  # rubocop:disable-next Hecks/ThreadSharedIvarMutation -- same
115
- # justification as `rehydrate_sagas!`'s own disable comment
116
- # above: this method's only caller is that boot-time-only walk,
117
- # never live dispatch, so `@saga_log` has no concurrent writer
118
- # to race here.
119
39
  def warn_stalled_saga(domain, process_manager, correlation, state, pending)
120
- # `.transform_keys(&:to_sym)` — Heki's own `each_saga` only
121
- # symbolizes `memory`'s top-level keys (`SagaStore#each_saga`'s
122
- # own `transform_keys`, one level deep); a value nested under
123
- # one of those keys, like this marker, comes back with plain
124
- # string keys from Heki specifically, symbol keys already from
125
- # Postgres/SQLite/D1's own `symbolize_names: true` parse. Normalizing
126
- # here, once, is simpler than teaching every adapter's own
127
- # shallow/deep parsing convention about this one reserved key.
40
+ # Heki's each_saga only symbolizes memory's top-level keys, not nested values;
41
+ # Postgres/SQLite/D1 already return symbol keys. Normalize here once for all adapters.
128
42
  pending = pending.transform_keys(&:to_sym)
129
43
  dispatches = Array(pending[:dispatches]).join(", ")
130
44
  warn "[hecks] #{domain} rehydrated #{process_manager} instance #{correlation.inspect} in state " \
@@ -146,13 +60,7 @@ module Hecks
146
60
  adapter = repository(domain, anchor).adapter
147
61
  adapter.respond_to?(:save_saga) ? adapter : Ports::Persistence::NULL_SAGA_STORE
148
62
  rescue WiringError
149
- # A domain not cleanly wired for its own anchor aggregate's
150
- # persistence (a test fixture binding only the aggregates it
151
- # exercises, say) degrades saga persistence to the same no-op
152
- # default an unbound domain gets — never raises out of a
153
- # dispatch that would otherwise have succeeded. Matches this
154
- # capability's own framing throughout: automatic wherever it's
155
- # cleanly supported, silently absent wherever it isn't.
63
+ # An unwired domain degrades to no saga persistence rather than raising mid-dispatch.
156
64
  Ports::Persistence::NULL_SAGA_STORE
157
65
  end
158
66
  end