hecks 2.7.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 (463) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +11 -0
  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 +18 -99
  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 +15 -37
  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 +32 -142
  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 +17 -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 +38 -26
  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 +17 -65
  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 -189
  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 -106
  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 +54 -268
  109. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -308
  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 +10 -91
  114. data/lib/hecks/bluebook/model_check.rb +102 -600
  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 +84 -0
  128. data/lib/hecks/cli/document.rb +86 -0
  129. data/lib/hecks/cli/ir.rb +45 -0
  130. data/lib/hecks/cli/mcp.rb +31 -0
  131. data/lib/hecks/cli/mcp_door.rb +366 -0
  132. data/lib/hecks/cli/model_check.rb +203 -0
  133. data/lib/hecks/cli/project_cli.rb +90 -0
  134. data/lib/hecks/cli/project_diagrams.rb +36 -0
  135. data/lib/hecks/cli/run.rb +173 -0
  136. data/lib/hecks/cli/smoke_test.rb +45 -0
  137. data/lib/hecks/cli/stores.rb +53 -0
  138. data/lib/hecks/cli.rb +160 -0
  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 +98 -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 +95 -0
  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 +62 -65
  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 +7 -26
  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 +22 -122
  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 +151 -679
  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 +9 -16
  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 +46 -138
  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 +93 -0
  336. data/lib/hecks/release/runner/commands.rb +66 -0
  337. data/lib/hecks/release/runner/console.rb +52 -0
  338. data/lib/hecks/release/runner/gem_publisher.rb +43 -0
  339. data/lib/hecks/release/runner/git.rb +48 -0
  340. data/lib/hecks/release/runner/npm_publisher.rb +75 -0
  341. data/lib/hecks/release/runner/preflight.rb +104 -0
  342. data/lib/hecks/release/runner/published.rb +55 -0
  343. data/lib/hecks/release/runner/tagger.rb +80 -0
  344. data/lib/hecks/release/runner.rb +215 -0
  345. data/lib/hecks/rendering.rb +4 -26
  346. data/lib/hecks/router/namespace_installer.rb +12 -36
  347. data/lib/hecks/router.rb +4 -8
  348. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  349. data/lib/hecks/runtime/boot_gates.rb +3 -18
  350. data/lib/hecks/runtime/caller.rb +19 -59
  351. data/lib/hecks/runtime/capability_graph.rb +5 -21
  352. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  353. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
  354. data/lib/hecks/runtime/command_interpreter.rb +125 -307
  355. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  356. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  357. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  358. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  359. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  360. data/lib/hecks/runtime/dependency_planning.rb +30 -102
  361. data/lib/hecks/runtime/dispatcher.rb +82 -241
  362. data/lib/hecks/runtime/entity_element.rb +40 -462
  363. data/lib/hecks/runtime/entity_interpreter.rb +65 -220
  364. data/lib/hecks/runtime/errors.rb +32 -115
  365. data/lib/hecks/runtime/event.rb +3 -25
  366. data/lib/hecks/runtime/identity.rb +18 -154
  367. data/lib/hecks/runtime/instance.rb +18 -150
  368. data/lib/hecks/runtime/interpreting.rb +11 -70
  369. data/lib/hecks/runtime/invocation.rb +28 -164
  370. data/lib/hecks/runtime/loader.rb +48 -186
  371. data/lib/hecks/runtime/outbox.rb +38 -261
  372. data/lib/hecks/runtime/policy_interpreter.rb +38 -200
  373. data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
  374. data/lib/hecks/runtime/query_interpreter.rb +45 -187
  375. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  376. data/lib/hecks/runtime/read_model_interpreter.rb +139 -208
  377. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  378. data/lib/hecks/runtime/reference_hop.rb +8 -81
  379. data/lib/hecks/runtime/refusal_wording.rb +8 -89
  380. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  381. data/lib/hecks/runtime/registry/verification.rb +52 -225
  382. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  383. data/lib/hecks/runtime/registry.rb +103 -244
  384. data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
  385. data/lib/hecks/runtime/routing.rb +4 -15
  386. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  387. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  388. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  389. data/lib/hecks/runtime/tenant_check.rb +6 -68
  390. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  391. data/lib/hecks/runtime/value/admission.rb +16 -67
  392. data/lib/hecks/runtime/value/coercion.rb +155 -641
  393. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  394. data/lib/hecks/runtime/value.rb +12 -84
  395. data/lib/hecks/runtime.rb +17 -65
  396. data/lib/hecks/storehouse.rb +146 -567
  397. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
  398. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  399. data/lib/hecks/vendoring/git_environment.rb +2 -8
  400. data/lib/hecks/vendoring/git_source.rb +4 -10
  401. data/lib/hecks/vendoring.rb +7 -46
  402. data/lib/hecks/version.rb +3 -14
  403. data/lib/hecks/vocabulary.rb +5 -0
  404. data/lib/hecks.rb +32 -82
  405. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  406. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  407. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  408. metadata +30 -59
  409. data/lib/hecks/bench/cli.rb +0 -74
  410. data/lib/hecks/bench/environment.rb +0 -100
  411. data/lib/hecks/bench/postgres_probe.rb +0 -41
  412. data/lib/hecks/bench/report.rb +0 -116
  413. data/lib/hecks/bench/ruby_runner.rb +0 -151
  414. data/lib/hecks/bench/run.rb +0 -55
  415. data/lib/hecks/bench/rust_runner.rb +0 -165
  416. data/lib/hecks/bench/stats.rb +0 -75
  417. data/lib/hecks/bench/suite.rb +0 -172
  418. data/lib/hecks/bench/workload.rb +0 -137
  419. data/lib/hecks/bench.rb +0 -28
  420. data/lib/hecks/codemod.rb +0 -443
  421. data/lib/hecks/corpus.rb +0 -479
  422. data/lib/hecks/doc/reference.rb +0 -585
  423. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
  424. data/lib/hecks/fuzzing/combination_miner.rb +0 -178
  425. data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
  426. data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
  427. data/lib/hecks/fuzzing/differential.rb +0 -192
  428. data/lib/hecks/fuzzing/domain_generator.rb +0 -871
  429. data/lib/hecks/fuzzing/era_boundary.rb +0 -154
  430. data/lib/hecks/fuzzing/form_census.rb +0 -285
  431. data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
  432. data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
  433. data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
  434. data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
  435. data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
  436. data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
  437. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
  438. data/lib/hecks/fuzzing/properties/guards.rb +0 -398
  439. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
  440. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
  441. data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
  442. data/lib/hecks/fuzzing/properties/querying.rb +0 -260
  443. data/lib/hecks/fuzzing/properties.rb +0 -267
  444. data/lib/hecks/fuzzing/qa_settings.rb +0 -164
  445. data/lib/hecks/fuzzing/replay.rb +0 -930
  446. data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
  447. data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
  448. data/lib/hecks/fuzzing/self_consistency.rb +0 -825
  449. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
  450. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
  451. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
  452. data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
  453. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
  454. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
  455. data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
  456. data/lib/hecks/fuzzing/shrinker.rb +0 -309
  457. data/lib/hecks/fuzzing/structural_skips.rb +0 -53
  458. data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
  459. data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
  460. data/lib/hecks/fuzzing/value_generator.rb +0 -340
  461. data/lib/hecks/fuzzing.rb +0 -23
  462. data/lib/hecks/grammar/evolve.rb +0 -524
  463. data/lib/hecks/query_ir.rb +0 -482
@@ -13,10 +13,8 @@ require_relative "rebuild_sweep"
13
13
 
14
14
  module Hecks
15
15
  module Runtime
16
- # The dispatch pipeline for a command on an aggregate head. The payload
17
- # gate lives in command_interpreter/argument_gate.rb, the mutation walk
18
- # in command_interpreter/mutation_applier.rb; what stays here is the
19
- # order of the steps and how a record is addressed.
16
+ # The dispatch pipeline for a command on an aggregate head; the argument
17
+ # gate and mutation walk are mixed in from their own files.
20
18
  class CommandInterpreter
21
19
  include Interpreting
22
20
  include ArgumentGate
@@ -24,27 +22,17 @@ module Hecks
24
22
 
25
23
  attr_reader :registry
26
24
 
27
- # **The declared order** — Vocabulary::AggregateDispatchOrder
28
- # (language/bluebook/vocabulary.bluebook), read off the generated table
29
- # (lib/hecks/vocabulary.rb) rather than typed here. spec/vocabulary_
30
- # conformance_spec.rb holds every step to a real `step_<name>` handler,
31
- # both directions.
25
+ # Read off the generated table (lib/hecks/vocabulary.rb), not typed here.
26
+ # spec/vocabulary_conformance_spec.rb holds every step to a real
27
+ # `step_<name>` handler, both directions.
32
28
  DISPATCH_ORDER = Hecks::Vocabulary.symbols("AggregateDispatchOrder")
33
29
 
34
- # A last-resort safety valve, not the normal outcome path — see
35
- # `Runtime::StaleWrite`'s own comment. Two concurrent writers
36
- # against one aggregate resolve through exactly one retry in the
37
- # ordinary case (the loser's retried hydrate reads the winner's now-
38
- # committed state and its own `given` refuses for real, raising
39
- # `GivenNotMet`, not `StaleWrite`) — this cap exists for pathological
40
- # contention (many concurrent writers on one hot aggregate), not the
41
- # two-writer case.
30
+ # A cap for pathological contention (many concurrent writers on one hot
31
+ # aggregate) — the ordinary two-writer race resolves in a single retry.
42
32
  MAX_STALE_WRITE_RETRIES = 5
43
33
 
44
- # Every cross-step local held in one place, now that the sequence is
45
- # data-driven rather than `call`'s own literal method-call sequence —
46
- # `result` and `transition`/`old_state` default to nil until the step
47
- # that sets them runs, the same as unset locals would be before that point.
34
+ # Every cross-step local held in one place; fields default to nil until
35
+ # the step that sets them runs.
48
36
  Context = Struct.new(:domain, :aggregate, :command, :args, :repository, :instance, :transition, :old_state,
49
37
  :result, :correlation, :route, :plan, :strategy, :persistence_outcome, :pending_delegation,
50
38
  :dry_run, :correction_bindings, :outbox_rows, :invocation)
@@ -57,42 +45,18 @@ module Hecks
57
45
  @rules = rules
58
46
  end
59
47
 
60
- # `dry_run:` — Dispatcher#dry_run?'s own entry point. Every step up
61
- # through enforce_ensures/enforce_invariants runs exactly as a real
62
- # dispatch would (givens checked, mutations applied to `ctx.instance`
63
- # in memory); `step_save`/`step_emit` are the only two that read this
64
- # flag, each skipping its own real work — see their own comments.
65
- # Retries the whole method body on `StaleWrite` — a fresh `ctx`, a
66
- # fresh `step_hydrate` re-reading current state, so `enforce_givens`
67
- # re-evaluates against reality rather than the snapshot that just
68
- # went stale. See `MAX_STALE_WRITE_RETRIES`/`Runtime::StaleWrite`
69
- # for why exhaustion is a pathological-contention signal, not the
70
- # expected shape of a two-writer race.
71
- #
72
- # `invocation` — the `Runtime::Invocation` `Dispatcher` built for this
73
- # call. `ctx.args` is `invocation.to_args` (the same Hash routing
74
- # always handed this method), `ctx.route` its `target`.
48
+ # Dispatches `command` against `aggregate` through the declared step
49
+ # order, retrying once per concurrent-write conflict.
75
50
  #
76
51
  # @param domain [String, Symbol] the domain `aggregate` belongs to
77
52
  # @param aggregate [Bluebook::Aggregate] the aggregate the command acts on
78
53
  # @param command [Class] the command class (`Bluebook::Command` subclass) to dispatch
79
- # @param invocation [Runtime::Invocation] the invocation `Dispatcher` built for
80
- # this call
81
- # @param correlation [Hash{Symbol => Object}, nil] correlation head => value,
82
- # stamped on every emitted event when a saga leg causes this dispatch; nil
83
- # otherwise
84
- # @param dry_run [Boolean] whether to run every step through validation without
85
- # saving, emitting or enqueueing
86
- # @return [Array(Runtime::Instance, Array<Runtime::Event>,
87
- # Runtime::DependencyPlanning::Plan, Ports::Persistence::Execution,
88
- # Array<Runtime::Outbox::Row>)] the settled instance, emitted events,
89
- # execution plan, persistence outcome and outbox rows — the last
90
- # three nil on a dry run, which skips save/emit/outbox
91
- # @raise [StandardError] any class in `Runtime::DOMAIN_REFUSALS` when a
92
- # given/ensures/invariant/authorization/admissibility rule refuses
93
- # @raise [Runtime::StaleWrite] if concurrent writers beat this one through every
94
- # retry (`MAX_STALE_WRITE_RETRIES`)
95
- # @raise [Runtime::WiringError] if the aggregate's repository cannot be resolved
54
+ # @param invocation [Runtime::Invocation] the invocation built for this call
55
+ # @param correlation [Hash, nil] saga correlation stamped on emitted events, if any
56
+ # @param dry_run [Boolean] validate every step without saving, emitting or enqueueing
57
+ # @return [Array] the settled instance, events, plan, persistence outcome, and outbox rows
58
+ # @raise [StandardError] a domain refusal (Runtime::DOMAIN_REFUSALS) when a rule refuses
59
+ # @raise [Runtime::StaleWrite, Runtime::WiringError] every retry loses, or resolve fails
96
60
  def call(domain, aggregate, command, invocation, correlation = nil, dry_run: false)
97
61
  args = invocation.to_args
98
62
  route = invocation.target
@@ -104,11 +68,8 @@ module Hecks
104
68
  ctx.route = route
105
69
  ctx.dry_run = dry_run
106
70
  ctx.plan = DependencyPlanning::Analyzer.call(aggregate: aggregate, command: command)
107
- # **Resolved here, once, before hydration** — `Registry#repository`
108
- # memoizes, so this and `step_hydrate`'s own read of `ctx.repository`
109
- # (no second fetch there any more) always name the same instance;
110
- # the isolation decision below (lock vs. CAS+retry) needs the
111
- # repository's capabilities before a single step runs.
71
+ # Resolved once here, before hydration: `Registry#repository` memoizes,
72
+ # and the isolation decision below needs its capabilities up front.
112
73
  ctx.repository = @registry.repository(domain, aggregate)
113
74
  lock_id = Identity.best_effort(aggregate, args, route, reference_key: reference_key(command))
114
75
  run_dispatch_order_with_isolation(DISPATCH_ORDER, ctx, lock_key_id: lock_id)
@@ -122,14 +83,9 @@ module Hecks
122
83
 
123
84
  private
124
85
 
125
- # **A no-op, and untraced** — Vocabulary::AggregateDispatchOrder's
126
- # decode_arguments. Routing (`Runtime::Routing`) has already handed
127
- # `call` a decoded argument hash by the time any step runs, so there is
128
- # nothing left to decode here yet; like a conditional step that does
129
- # not fire, it records nothing in `trace`. Declared so the Rust
130
- # kernel's generated step enum carries the step its generated decoder
131
- # will move into (roadmap D2), and so a typed Invocation (I2) has a
132
- # step to be consumed at.
86
+ # No-op: `Routing` has already handed `call` a decoded argument hash,
87
+ # so there is nothing left to decode here yet. Kept as a step so the
88
+ # generated step enum has a slot to move the real decoder into later.
133
89
  def step_decode_arguments(_ctx); end
134
90
 
135
91
  def step_refuse_unknown_arguments(ctx)
@@ -154,7 +110,7 @@ module Hecks
154
110
 
155
111
  def step_hydrate(ctx)
156
112
  # `ctx.repository` is resolved once, in `#call`, before the
157
- # isolation decision (lock vs. CAS+retry) — not here any more.
113
+ # isolation decision (lock vs. CAS+retry) — not here.
158
114
  ctx.strategy = ctx.plan.strategy_for(capabilities: ctx.repository.capabilities)
159
115
  ctx.instance = step(:hydrate) do
160
116
  if ctx.plan.complete_state? && ctx.plan.state_independent?
@@ -171,14 +127,9 @@ module Hecks
171
127
 
172
128
  def step_enforce_givens(ctx)
173
129
  step(:enforce_givens) do
174
- # Structural, before the declared givens — the same ordering
175
- # NotFound/AlreadyExists already get at hydration: "does the
176
- # fact this command's corrects names even exist" is not a
177
- # domain rule an author wrote, it is a precondition for the
178
- # domain rules to mean anything at all. Also locates the
179
- # correction target itself, if `as:` named one — carried on
180
- # `ctx` so `step_enforce_ensures` (the settled-record half)
181
- # can bind the same name too, not just this pre-mutation half.
130
+ # Structural check before the declared givens (same ordering as
131
+ # NotFound/AlreadyExists at hydration). Also locates the correction
132
+ # target, if `as:` named one, so `step_enforce_ensures` can reuse it.
182
133
  ctx.correction_bindings = @rules.enforce_correction_target(ctx.instance, ctx.aggregate, ctx.command, domain: ctx.domain)
183
134
  @rules.enforce_givens(ctx.instance, ctx.command, ctx.args, domain: ctx.domain,
184
135
  declaring: ctx.aggregate, parent: ctx.instance, correction: ctx.correction_bindings)
@@ -220,61 +171,20 @@ module Hecks
220
171
  step(:advance_lifecycle) { ctx.instance[ctx.aggregate.lifecycle.field] = ctx.transition.target }
221
172
  end
222
173
 
223
- # The synchronous cousin of a policy's own `trigger` — see
224
- # `CommandBuilder#delegates_to`'s own comment for the full reasoning.
225
- # Runs after this command's own mutations/lifecycle (so a delegating
226
- # command could in principle still guard with its own `given`s first,
227
- # though the real use in `domain/chess` declares none) and before
228
- # `enforce_ensures`/`enforce_invariants`/`save`, so a refusal here
229
- # raises a real, unrescued exception and nothing from either side —
230
- # this command's own state, the target element's — has been saved
231
- # yet. `ctx.instance` is the same in-memory record `step_hydrate`
232
- # loaded and `step_save` will persist; `EntityElement.locate_chain`
233
- # mutates it in place exactly the way `EntityInterpreter`'s own
234
- # `step_locate_element`/`step_apply_mutations` mutate their own
235
- # freshly-loaded copy — the only difference is which already-in-
236
- # memory record gets handed in.
237
- #
238
- # Reimplements the entity command pipeline's own order (givens,
239
- # transition, mutations, ensures, emit) inline, deliberately — see
240
- # the comment above on why this must run strictly between this
241
- # command's own mutations and its ensures/invariants/save. Splitting
242
- # it would scatter that exact ordering across method boundaries and
243
- # force threading element/view/transition/old_element/settled
244
- # through as parameters — the `with:` bug documented just below is
245
- # exactly the kind of ordering/plumbing mistake that shape invites.
246
- # rubocop:disable-next Metrics/AbcSize
174
+ # The synchronous cousin of a policy's own `trigger` (see
175
+ # `CommandBuilder#delegates_to`). Runs between this command's own
176
+ # mutations and its ensures/invariants/save, so a refusal here leaves
177
+ # nothing committed on either side. Reimplements the entity pipeline
178
+ # (givens, transition, mutations, ensures, emit) inline, to keep that
179
+ # exact ordering in one place rather than threading state through
180
+ # method boundaries as parameters.
247
181
  def step_delegate_to_entity(ctx)
248
182
  delegation = ctx.command.mutations.find { |mutation| mutation.op == :delegate }
249
183
  return unless delegation
250
184
 
251
185
  step(:delegate_to_entity) do
252
- entity_name, _dot, command_name = delegation.target.to_s.rpartition(".")
253
- entity = ctx.aggregate.entities.find { |e| e.hecks_name == entity_name } ||
254
- raise(WiringError, "#{ctx.command.hecks_name} delegates_to #{entity_name}." \
255
- "#{command_name}, but #{ctx.aggregate.hecks_name} has no " \
256
- "entity named #{entity_name.inspect}")
257
- target_command = entity.command(command_name) ||
258
- raise(WiringError, "#{ctx.command.hecks_name} delegates_to " \
259
- "#{entity_name}.#{command_name}, which " \
260
- "#{entity_name} declares no such command")
261
-
262
- # `with:` remaps, it does not enumerate — starting from a copy
263
- # of this command's own already-resolved args (`ctx.args`) and
264
- # overlaying the explicit mapping on top means ambient context
265
- # the caller never had to think about (the aggregate's own
266
- # identity, addressed the ordinary way to reach `MoveKnight` at
267
- # all) flows through to the target untouched, the same as it
268
- # always would for a caller dispatching the entity command
269
- # directly. Confirmed necessary, not a defensive guess: a real
270
- # downstream domain's own AdvancePly-on-Moved policy silently
271
- # failed to re-locate its own aggregate (`reaction_log`: "no
272
- # Game with label.value ..."), because the emitted event's
273
- # payload — built from `target_args` alone — never carried
274
- # `label` at all when `with:` named only `id`/`to`.
275
- target_args = ctx.args.merge(
276
- delegation.source.to_h { |target_key, source_key| [target_key.to_sym, ctx.args[source_key]] }
277
- )
186
+ entity, target_command, command_name = resolve_delegation_target(ctx, delegation)
187
+ target_args = mapped_and_gated_delegation_args(ctx, delegation, entity, target_command)
278
188
 
279
189
  element = EntityElement.locate_chain(ctx.aggregate, [entity], ctx.instance, target_args, command_name)
280
190
  view = Instance.new(aggregate: entity, id: EntityElement.element_identity(entity, element).to_s, state: element)
@@ -292,18 +202,52 @@ module Hecks
292
202
  settled = Instance.new(aggregate: entity, id: view.id, state: element)
293
203
  @rules.enforce_ensures(settled, target_command, target_args, old: old_element, domain: ctx.domain, parent: ctx.instance)
294
204
 
295
- # Not emitted here — C7.2 (docs/semantics/bluebook-semantics.md):
296
- # a refused command records nothing, and the parent's own
297
- # `ensures`/`enforce_invariants`/`save` steps still run after
298
- # this one. The target's emission is parked and performed by
299
- # `step_emit`, after the parent committed — where every other
300
- # command's events are emitted too. (Without parking it here, the
301
- # entity leg's events would be on the event log and in the adapter
302
- # before the parent could refuse.)
205
+ # Not emitted here — C7.2: a refused command records nothing, and
206
+ # the parent's own ensures/invariants/save steps still run after
207
+ # this one. Parked and emitted by `step_emit`, after the parent
208
+ # commits, alongside every other command's events.
303
209
  ctx.pending_delegation = [target_command, target_args]
304
210
  end
305
211
  end
306
212
 
213
+ # The entity and command a `delegates_to` mutation names, resolved once so
214
+ # `step_delegate_to_entity` can read them as plain locals.
215
+ def resolve_delegation_target(ctx, delegation)
216
+ entity_name, _dot, command_name = delegation.target.to_s.rpartition(".")
217
+ entity = ctx.aggregate.entities.find { |e| e.hecks_name == entity_name } ||
218
+ raise(WiringError, "#{ctx.command.hecks_name} delegates_to #{entity_name}." \
219
+ "#{command_name}, but #{ctx.aggregate.hecks_name} has no " \
220
+ "entity named #{entity_name.inspect}")
221
+ target_command = entity.command(command_name) ||
222
+ raise(WiringError, "#{ctx.command.hecks_name} delegates_to " \
223
+ "#{entity_name}.#{command_name}, which " \
224
+ "#{entity_name} declares no such command")
225
+ [entity, target_command, command_name]
226
+ end
227
+
228
+ # Builds the target command's own args, then runs the same argument gate
229
+ # `EntityInterpreter` runs before a direct dispatch of that command — evaluated
230
+ # against the target command's own declared attributes and the already
231
+ # `with:`-mapped args, not the delegating command's. Without this, a `with:`
232
+ # mapping that omits one of the target's required attributes would silently
233
+ # mutate and persist state that was never validated at all.
234
+ # `extra_identity_heads:` exempts the entity's own identity field (e.g. `id`),
235
+ # which locates the element but is never a declared attribute.
236
+ def mapped_and_gated_delegation_args(ctx, delegation, entity, target_command)
237
+ # `with:` remaps, it does not enumerate — starting from a copy of this command's
238
+ # own resolved args and overlaying the explicit mapping means ambient context the
239
+ # caller never named (the aggregate's own identity) still flows through to the
240
+ # target, the same as a direct dispatch of the entity command would get.
241
+ target_args = ctx.args.merge(
242
+ delegation.source.to_h { |target_key, source_key| [target_key.to_sym, ctx.args[source_key]] }
243
+ )
244
+
245
+ refuse_unknown_arguments(ctx.domain, ctx.aggregate, target_command, target_args,
246
+ extra_identity_heads: entity.identity_heads)
247
+ refuse_absent_arguments(target_command, target_args)
248
+ normalize_args(ctx.aggregate, target_command, target_args)
249
+ end
250
+
307
251
  def step_enforce_ensures(ctx)
308
252
  step(:enforce_ensures) do
309
253
  @rules.enforce_ensures(ctx.instance, ctx.command, ctx.args, old: ctx.old_state,
@@ -315,33 +259,10 @@ module Hecks
315
259
  step(:enforce_invariants) { @rules.enforce_invariants(ctx.instance, ctx.aggregate, domain: ctx.domain) }
316
260
  end
317
261
 
318
- # `dry_run:` skips the write half of this step — see Dispatcher#
319
- # dry_run?'s own comment — but not `resolve_state_references`.
320
- # BUG#127: that check is validation, not persistence — it asks
321
- # whether the settled in-memory state names a reference target that
322
- # actually exists, the same question a real dispatch answers before
323
- # it ever writes anything. Skipping it under `dry_run:` made
324
- # `dry_run?` disagree with what a real dispatch immediately after it
325
- # would do: it answered `true` ("would succeed") for a command whose
326
- # real dispatch refuses with NotFound. Only the persist/raise-on-
327
- # conflict tail is genuinely persistence-only and stays behind the
328
- # early return.
329
- #
330
- # BUG#132 — a second validation-not-persistence check hid in that
331
- # same persist/raise-on-conflict tail: `persist_instance`'s own
332
- # ATOMIC_PUT branch (below) is where `hydrate_complete_state`'s
333
- # comment says a `creates?` command's duplicate check is
334
- # deliberately deferred to, for a strategy this fast — a read
335
- # (`repository.find`) any earlier, before the real write, would be
336
- # exactly the extra read `insert_only:` exists to avoid paying on
337
- # every real dispatch. But that means the check never ran at all under
338
- # `dry_run:` — not eagerly (hydration skips it for this exact
339
- # strategy, on purpose) and not deferred (this whole branch returns
340
- # first). `Roster::Roster.Open` against an already-open name is
341
- # this shape: `dry_run?` answered `true` for a real dispatch
342
- # immediately after it that refuses `AlreadyExists`. Under
343
- # `dry_run:` there is no write to race, so paying for that one read
344
- # is safe here — and only here.
262
+ # `dry_run:` skips persistence, but not `resolve_state_references` or
263
+ # the ATOMIC_PUT duplicate check (`check_dry_run_creates_duplicate`) —
264
+ # both are validation, not persistence, and a real dispatch would
265
+ # refuse on either before ever writing.
345
266
  def step_save(ctx)
346
267
  step(:save) { @rules.resolve_state_references(ctx.domain, ctx.aggregate, ctx.instance.state) }
347
268
 
@@ -357,11 +278,9 @@ module Hecks
357
278
  end
358
279
  end
359
280
 
360
- # See `step_save`'s own BUG#132 comment: the only path, real or dry,
361
- # able to catch a `creates?` command reusing an already-occupied
362
- # identity when `strategy` is `ATOMIC_PUT` — `hydrate_complete_
363
- # state` deliberately skips this exact case (its own comment), and
364
- # a dry run never reaches `persist_instance`'s matching branch.
281
+ # The only path, real or dry, that catches a `creates?` command reusing
282
+ # an occupied identity under ATOMIC_PUT — `hydrate_complete_state`
283
+ # deliberately defers this exact check to here.
365
284
  def check_dry_run_creates_duplicate(ctx)
366
285
  return unless ctx.strategy == DependencyPlanning::ATOMIC_PUT && ctx.command.creates?
367
286
  return unless ctx.repository.find(ctx.instance.id)
@@ -374,23 +293,15 @@ module Hecks
374
293
 
375
294
  def persist_instance(ctx)
376
295
  if ctx.strategy == DependencyPlanning::ATOMIC_PUT
377
- # A second creation is not a fresh one — see
378
- # hydrate_complete_state's own comment; the
379
- # same refusal, on the same terms, for the
380
- # complete-state path. `insert_only:` asks the
381
- # adapter to decide and refuse atomically
382
- # (never writing a `creates?` command over an
383
- # identity that already names a record) rather
384
- # than this interpreter reading the record
385
- # first to check — a `repository.find` before
386
- # every atomic_put would be exactly the read
387
- # this strategy exists to skip.
296
+ # `insert_only:` asks the adapter to refuse atomically rather than
297
+ # this interpreter reading the record first to check — a
298
+ # `repository.find` before every atomic_put would be exactly the
299
+ # read this strategy exists to skip.
388
300
  ctx.repository.atomic_put(ctx.instance, insert_only: ctx.command.creates?)
389
301
  else
390
- # `expected_version:` is `ctx.instance.version` — nil for a
391
- # brand-new record (never read from storage) or when the
392
- # repository isn't CAS-capable, either of which falls straight
393
- # through to a plain, unconditional save inside `AppendOnly#save`.
302
+ # `expected_version:` is nil for a brand-new record or a
303
+ # non-CAS repository, either of which falls through to a
304
+ # plain, unconditional save inside `AppendOnly#save`.
394
305
  ctx.repository.save(ctx.instance, expected_version: ctx.instance.version)
395
306
  end
396
307
  end
@@ -402,10 +313,9 @@ module Hecks
402
313
  identity: identity_reading(ctx.aggregate),
403
314
  offered: Rendering.describe(ctx.instance.id)))
404
315
  elsif ctx.persistence_outcome.status == :stale
405
- # Not a `RefusalWording.render` call — this is not a declared
406
- # vocabulary refusal, just a plain, informative message. See
407
- # `Runtime::StaleWrite`'s own comment: caught by `#call`'s
408
- # retry loop, re-raised only once retries are exhausted.
316
+ # Not a declared vocabulary refusal, just a plain, informative
317
+ # message — caught by `#call`'s retry loop, re-raised only once
318
+ # retries are exhausted.
409
319
  raise(StaleWrite,
410
320
  "#{ctx.command.hecks_name} on #{ctx.aggregate.hecks_name} " \
411
321
  "(#{identity_reading(ctx.aggregate)}: #{Rendering.describe(ctx.instance.id)}) lost a race — " \
@@ -413,28 +323,12 @@ module Hecks
413
323
  end
414
324
  end
415
325
 
416
- # The one-time, synchronous half of `projects` (S12, ADR 0025) —
417
- # `RebuildSweep` (`runtime/rebuild_sweep.rb`) is deliberately the
418
- # only thing that keeps a projected field current against a
419
- # target that changes after this record was written — no reactive
420
- # `Policy#for_each` keeping it live in real time, that stays
421
- # deferred, same as that file's own header explains. But without
422
- # some synchronous population, a projected field never gets an
423
- # initial value at all until an operator remembers to run a
424
- # sweep by hand — every acting command reading it (`given
425
- # ("customer is active") { customer_status == "active" }`, say)
426
- # would refuse a freshly created, genuinely active record for no
427
- # real reason, which is not the eventual-consistency tradeoff the
428
- # ADR accepts, just a bug. So: every time a record with `projects`
429
- # fields is about to save — creating or acting, either can be the
430
- # first time a referenced record resolves — read each one once,
431
- # here, using the exact same `RebuildSweep.remote_value` a sweep
432
- # itself would compute. This is still eventually consistent in
433
- # the sense the ADR means: a change on the target side after this
434
- # save still needs a sweep to reach here. It is only ever
435
- # synchronous with this record's own write, never a live read
436
- # triggered by a `given`/`ensures`/`invariant` mid-dispatch — the
437
- # boundary rule those enforce holds exactly as before.
326
+ # The one-time, synchronous half of `projects` (ADR 0025); `RebuildSweep`
327
+ # is what keeps a projected field current afterward. Without seeding it
328
+ # here, a freshly created record would read a nil projected field until
329
+ # an operator ran a sweep, refusing commands that depend on it for no
330
+ # real reason. Uses the same `RebuildSweep.remote_value` a sweep
331
+ # computes, and runs only at save time, never as a live read mid-dispatch.
438
332
  def seed_projected_fields(ctx)
439
333
  return if ctx.aggregate.projected_fields.empty?
440
334
 
@@ -446,16 +340,11 @@ module Hecks
446
340
  end
447
341
  end
448
342
 
449
- # A delegating command emits nothing of its own (`CommandBuilder#build`'s
450
- # own guard refuses declaring `emits` alongside `delegates_to`) — its
451
- # result is the target entity command's own `emits`, parked by
452
- # `step_delegate_to_entity` and emitted here, after save (C7.2), not
453
- # a second, empty call into `@rules.emit` for a command with no
454
- # announced events at all.
455
- #
456
- # `dry_run:` skips this too, same reasoning as `step_save` — nothing
457
- # was committed, so `ctx.result` stays nil and `Dispatcher#dry_run?`
458
- # never reads it (its own return value is just whether this raised).
343
+ # A delegating command emits nothing of its own; its result is the
344
+ # target entity command's own `emits`, parked by
345
+ # `step_delegate_to_entity` and emitted here, after save (C7.2).
346
+ # `dry_run:` skips this too — nothing was committed, so `ctx.result`
347
+ # stays nil.
459
348
  def step_emit(ctx)
460
349
  return if ctx.dry_run
461
350
 
@@ -500,61 +389,12 @@ module Hecks
500
389
  found.dup
501
390
  end
502
391
 
503
- # Wave 8 (equivalence-gap plan, item 1.6) — not done, and this
504
- # comment now says precisely how far it got, corrected from an
505
- # earlier version of itself that briefly (same PR, never released)
506
- # claimed the inventory was empty and deleted this method outright.
507
- # A full corpus audit (`DependencyPlanning::Analyzer.call` against
508
- # every `creates?`-true command in all 5 example domains — banking,
509
- # pizzas, chess, compliance, roster) found and fixed 4 real
510
- # authoring bugs (`Statement.Generate`, `CreatePizza`, `Chess::
511
- # Game.Start`, `Roster.Open` — each declared an attribute and
512
- # never `sets` it, so the field silently stayed nil on every
513
- # created record regardless of what a caller sent), plus a real
514
- # `DependencyPlanning::Analyzer` bug for entity-owned commands
515
- # (`root_aggregate:`, that class's own header) that surfaced one
516
- # more live corpus bug of its own (`payment_cards.bluebook`'s own
517
- # `Withdrawal.Dispute`, which never actually checked whether the
518
- # card was retired) — all real, all kept.
519
- #
520
- # Deleting this method on that basis turned out to be wrong,
521
- # caught by running the full suite rather than trusting the
522
- # audit's own scope: the 5 example domains are nowhere near the
523
- # whole inventory of `creates?`-true commands this fallback
524
- # actually carries. Dozens of separate, purpose-built spec
525
- # fixtures across the suite (`spec/fixtures/*.bluebook`, and
526
- # inline `Hecks.bluebook` blocks declared directly inside
527
- # individual spec files — governance, mutation ops, ports,
528
- # routing, tenant isolation, sagas, and more) declare their own
529
- # small `creates?`-true commands the same incomplete way, and
530
- # rely on this exact fallback to create anything at all. Deleting
531
- # it produced 218 failures across specs with nothing to do with
532
- # Wave 8's own subject — confirmed, not guessed, by actually
533
- # running `bundle exec rspec` after the deletion, not merely by
534
- # extrapolating from the one audited inventory. `spec/fixtures/
535
- # till.bluebook`'s own `Till.OpenTill` (missing `sets :number`,
536
- # the identical bug shape as the 4 fixed above) was fixed
537
- # alongside the four real corpus ones as one instance of this — but
538
- # it was one of many, not the last one, and finding the rest is
539
- # real, separate, much larger follow-up work this pass does not
540
- # attempt: a suite-wide audit of every declared bluebook fixture,
541
- # not just the 5 example domains the plan's own text named.
542
- #
543
- # Transitional compatibility for live source that has not yet
544
- # acquired explicit effects, same as it always was — deliberately
545
- # isolated from the normal routing and planning path so
546
- # `reference_to` no longer chooses how a migrated command hydrates
547
- # or persists. A real, future Wave 8 removes this once that wider
548
- # inventory is empty, not before.
549
- #
550
- # `ctx.plan.complete_state?` already claimed every command whose
551
- # plan is fully resolved in the two branches above `step_hydrate`
552
- # tries first, so reaching this check already means the plan is
553
- # incomplete — an un-migrated command still routes here on
554
- # `creates?` alone, the same as the old `hydrate` did, regardless
555
- # of whether it happens to have any mutations (`write_set`).
556
- # Requiring an empty write_set here refused every un-migrated
557
- # creating command that sets even one field.
392
+ # Transitional compatibility for a command that has not yet acquired
393
+ # explicit effects — isolated from the normal routing/planning path so
394
+ # `reference_to` does not choose how a migrated command hydrates. An
395
+ # un-migrated command routes here on `creates?` alone, regardless of
396
+ # whether it has any mutations; requiring an empty write_set here would
397
+ # wrongly exclude one that sets fields.
558
398
  def legacy_implicit_creation?(ctx)
559
399
  ctx.route.nil? && ctx.command.creates?
560
400
  end
@@ -587,19 +427,10 @@ module Hecks
587
427
  aggregate: aggregate.hecks_name,
588
428
  identity: identity_reading(aggregate)))
589
429
 
590
- # A second creation is not a fresh one — `creates?` on an identity
591
- # a record already exists under refuses (`AlreadyExists`) rather
592
- # than silently overwriting it, the same refusal `hydrate_prior_
593
- # or_initial`'s own body gives for its own complete-but-state-
594
- # dependent case, below. Only when `strategy` will not be ATOMIC_PUT:
595
- # an atomic-put-capable adapter enforces this itself, atomically,
596
- # via `insert_only:` in step_save (no read here, no race with the
597
- # write) — but `strategy_for` already fell back to a plain `save`
598
- # for an adapter with no atomic_put capability at all (Heki, today),
599
- # and a plain `save` never refuses an overwrite on its own. Skipping
600
- # this check for that case silently dropped the refusal instead of
601
- # deferring it — the very "no such call — no such check" gap Wave
602
- # 8 exists to close everywhere else.
430
+ # `creates?` on an occupied identity refuses (`AlreadyExists`), except
431
+ # under ATOMIC_PUT: that adapter enforces the same refusal itself,
432
+ # atomically, via `insert_only:` in `step_save` — reading here too
433
+ # would race the write for nothing.
603
434
  if command.creates? && strategy != DependencyPlanning::ATOMIC_PUT && repository.find(id)
604
435
  raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "creating_duplicate",
605
436
  command: command.hecks_name, aggregate: aggregate.hecks_name,
@@ -610,11 +441,9 @@ module Hecks
610
441
  Instance.new(aggregate: aggregate, id: id, args: args)
611
442
  end
612
443
 
613
- # A complete command may still depend on prior state: lifecycle guards
614
- # are the common case. Read once so an existing record is checked against
615
- # its real state; when no record exists, the aggregate's declared defaults
616
- # are the prior state. Completeness, not a reference marker, proves that
617
- # initializing that missing record is safe.
444
+ # A complete command may still depend on prior state (lifecycle guards
445
+ # are the common case) — read once, so an existing record is checked
446
+ # against its real state rather than assumed defaults.
618
447
  def hydrate_prior_or_initial(repository, aggregate, command, args, route)
619
448
  derived = identity_of(aggregate, args)
620
449
  if route && derived && route.aggregate.to_s != derived.to_s
@@ -629,20 +458,10 @@ module Hecks
629
458
  identity: identity_reading(aggregate)))
630
459
  found = repository.find(id)
631
460
 
632
- # A second creation is not a fresh one — see hydrate_complete_
633
- # state's own comment; the same refusal, on the same terms, for
634
- # a complete-but-state-dependent command (one with a `given`
635
- # reading its own prior state, which is what routes here instead
636
- # of hydrate_complete_state). Gated on `command.creates?`: a
637
- # non-creating command's own complete payload legitimately means
638
- # "act on whatever this identity already holds" (`found` is the
639
- # prior state this branch exists to supply), so only a genuine
640
- # creation reusing an already-occupied identity is a duplicate.
641
- # `SafeDepositBox.Rent` is exactly this shape — `given("box is
642
- # vacant")` makes it state-dependent, so without this check a
643
- # second Rent would silently hydrate the existing box as "prior
644
- # state" and refuse for the wrong reason (not vacant) instead of
645
- # the right one (already exists).
461
+ # Same refusal as `hydrate_complete_state`, for a complete-but-state-
462
+ # dependent command (one with a `given` on its own prior state).
463
+ # Gated on `creates?`: a non-creating command's complete payload just
464
+ # means "act on whatever this identity already holds".
646
465
  if found && command.creates?
647
466
  raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "creating_duplicate",
648
467
  command: command.hecks_name, aggregate: aggregate.hecks_name,
@@ -653,10 +472,9 @@ module Hecks
653
472
  found ? found.dup : Instance.new(aggregate: aggregate, id: id, args: args)
654
473
  end
655
474
 
656
- # The join, the dig, and the reading — all shared with `EntityInterpreter`
657
- # now, in `Runtime::Identity`, rather than kept as two copies that could
658
- # only ever drift. See that module for the reasoning ; these three stay
659
- # here, at the old names, purely so nothing below has to change.
475
+ # Shared with `EntityInterpreter`, in `Runtime::Identity`, rather than
476
+ # kept as two copies that could only ever drift; these three stay here,
477
+ # under their old names, so nothing below has to change.
660
478
  def identity_of(aggregate, args) = Identity.of(aggregate, args)
661
479
  def identity_from(aggregate, args, key) = Identity.from(aggregate, args, key)
662
480
  def identity_reading(construct) = Identity.reading(construct)