hecks 0.2.0 → 0.3.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 (408) hide show
  1. checksums.yaml +5 -5
  2. data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +127 -0
  4. data/lib/hecks/adapters/driven/d1.adapter +12 -0
  5. data/lib/hecks/adapters/driven/d1.rb +403 -0
  6. data/lib/hecks/adapters/driven/folder.adapter +3 -0
  7. data/lib/hecks/adapters/driven/folder.rb +199 -0
  8. data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
  9. data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
  10. data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
  11. data/lib/hecks/adapters/driven/governance_authorization.rb +62 -0
  12. data/lib/hecks/adapters/driven/heki/journal.rb +51 -0
  13. data/lib/hecks/adapters/driven/heki/saga_store.rb +0 -0
  14. data/lib/hecks/adapters/driven/heki/snapshot.rb +38 -0
  15. data/lib/hecks/adapters/driven/heki.adapter +4 -0
  16. data/lib/hecks/adapters/driven/heki.rb +148 -0
  17. data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
  18. data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
  19. data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
  20. data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
  21. data/lib/hecks/adapters/driven/lambda.adapter +4 -0
  22. data/lib/hecks/adapters/driven/lambda.rb +131 -0
  23. data/lib/hecks/adapters/driven/memory.adapter +3 -0
  24. data/lib/hecks/adapters/driven/memory.rb +103 -0
  25. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
  26. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
  27. data/lib/hecks/adapters/driven/postgres/codec.rb +80 -0
  28. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +168 -0
  29. data/lib/hecks/adapters/driven/postgres.adapter +5 -0
  30. data/lib/hecks/adapters/driven/postgres.rb +391 -0
  31. data/lib/hecks/adapters/driven/postgres_era/lineage/era_store.rb +171 -0
  32. data/lib/hecks/adapters/driven/postgres_era/lineage/field_cache.rb +190 -0
  33. data/lib/hecks/adapters/driven/postgres_era/lineage/head_compiler.rb +429 -0
  34. data/lib/hecks/adapters/driven/postgres_era/lineage/mint_transaction.rb +166 -0
  35. data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +286 -0
  36. data/lib/hecks/adapters/driven/postgres_era/lineage/resumable_backfill.rb +168 -0
  37. data/lib/hecks/adapters/driven/postgres_era/lineage/tail_merge.rb +163 -0
  38. data/lib/hecks/adapters/driven/postgres_era/lineage/transform_installer.rb +114 -0
  39. data/lib/hecks/adapters/driven/postgres_era/lineage.rb +137 -0
  40. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb +89 -0
  41. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +82 -0
  42. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
  43. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/minter.rb +132 -0
  44. data/lib/hecks/adapters/driven/postgres_era/lineage_manager.rb +74 -0
  45. data/lib/hecks/adapters/driven/postgres_era.adapter +6 -0
  46. data/lib/hecks/adapters/driven/postgres_era.rb +671 -0
  47. data/lib/hecks/adapters/driven/prism.adapter +3 -0
  48. data/lib/hecks/adapters/driven/prism.rb +80 -0
  49. data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
  50. data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
  51. data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
  52. data/lib/hecks/adapters/driven/sqlite/codec.rb +63 -0
  53. data/lib/hecks/adapters/driven/sqlite/projection.rb +105 -0
  54. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +180 -0
  55. data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
  56. data/lib/hecks/adapters/driven/sqlite.rb +285 -0
  57. data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
  58. data/lib/hecks/adapters/driven/system_clock.rb +14 -0
  59. data/lib/hecks/adapters/driven.rb +36 -0
  60. data/lib/hecks/adapters.rb +6 -0
  61. data/lib/hecks/behaviors/dsl.rb +96 -0
  62. data/lib/hecks/behaviors/expectations.rb +280 -0
  63. data/lib/hecks/behaviors/ir.rb +31 -0
  64. data/lib/hecks/behaviors/rspec.rb +42 -0
  65. data/lib/hecks/behaviors/runner.rb +96 -0
  66. data/lib/hecks/behaviors.rb +25 -0
  67. data/lib/hecks/bluebook/aggregate.rb +108 -0
  68. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
  69. data/lib/hecks/bluebook/assembly/build.rb +48 -0
  70. data/lib/hecks/bluebook/assembly/contract.rb +112 -0
  71. data/lib/hecks/bluebook/assembly/contracts.rb +409 -0
  72. data/lib/hecks/bluebook/assembly/marks.rb +227 -0
  73. data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
  74. data/lib/hecks/bluebook/assembly.rb +78 -0
  75. data/lib/hecks/bluebook/attribute.rb +96 -0
  76. data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
  77. data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
  78. data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
  79. data/lib/hecks/bluebook/behaviour/command.rb +116 -0
  80. data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
  81. data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
  82. data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
  83. data/lib/hecks/bluebook/behaviour/lifecycle.rb +51 -0
  84. data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
  85. data/lib/hecks/bluebook/behaviour/process_manager.rb +38 -0
  86. data/lib/hecks/bluebook/behaviour/query.rb +10 -0
  87. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
  88. data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
  89. data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
  90. data/lib/hecks/bluebook/chapter.rb +68 -0
  91. data/lib/hecks/bluebook/command.rb +124 -0
  92. data/lib/hecks/bluebook/domain_port.rb +102 -0
  93. data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
  94. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +906 -0
  95. data/lib/hecks/bluebook/dsl/attribute_collector.rb +327 -0
  96. data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
  97. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1018 -0
  98. data/lib/hecks/bluebook/dsl/command_builder.rb +626 -0
  99. data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
  100. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
  101. data/lib/hecks/bluebook/dsl/entity_builder.rb +305 -0
  102. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
  103. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
  104. data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
  105. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
  106. data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
  107. data/lib/hecks/bluebook/dsl/policy_builder.rb +120 -0
  108. data/lib/hecks/bluebook/dsl/port_builder.rb +30 -0
  109. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
  110. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +207 -0
  111. data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
  112. data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
  113. data/lib/hecks/bluebook/dsl/rule_reference.rb +172 -0
  114. data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
  115. data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
  116. data/lib/hecks/bluebook/dsl/word_gate.rb +221 -0
  117. data/lib/hecks/bluebook/dsl/world_builder.rb +77 -0
  118. data/lib/hecks/bluebook/dsl.rb +45 -0
  119. data/lib/hecks/bluebook/entity.rb +103 -0
  120. data/lib/hecks/bluebook/expression/canonical_form.rb +55 -0
  121. data/lib/hecks/bluebook/expression/evaluator.rb +261 -0
  122. data/lib/hecks/bluebook/expression/projection.json +170 -0
  123. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
  124. data/lib/hecks/bluebook/expression/resolver.rb +630 -0
  125. data/lib/hecks/bluebook/expression.rb +13 -0
  126. data/lib/hecks/bluebook/hexagon.rb +60 -0
  127. data/lib/hecks/bluebook/lifecycle.rb +42 -0
  128. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
  129. data/lib/hecks/bluebook/meta_validator/judge.rb +559 -0
  130. data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
  131. data/lib/hecks/bluebook/meta_validator/port_judge.rb +47 -0
  132. data/lib/hecks/bluebook/meta_validator/readings.rb +350 -0
  133. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +312 -0
  134. data/lib/hecks/bluebook/meta_validator/shapes.rb +246 -0
  135. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
  136. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
  137. data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
  138. data/lib/hecks/bluebook/meta_validator.rb +452 -0
  139. data/lib/hecks/bluebook/model_check.rb +318 -0
  140. data/lib/hecks/bluebook/pattern_subset.rb +120 -0
  141. data/lib/hecks/bluebook/policy.rb +41 -0
  142. data/lib/hecks/bluebook/process_manager.rb +86 -0
  143. data/lib/hecks/bluebook/project_discovery.rb +30 -0
  144. data/lib/hecks/bluebook/project_loader.rb +40 -0
  145. data/lib/hecks/bluebook/project_register.rb +82 -0
  146. data/lib/hecks/bluebook/query.rb +61 -0
  147. data/lib/hecks/bluebook/read_model.rb +109 -0
  148. data/lib/hecks/bluebook/reference.rb +74 -0
  149. data/lib/hecks/bluebook/smoke_test.rb +166 -0
  150. data/lib/hecks/bluebook/synthesizer.rb +95 -0
  151. data/lib/hecks/bluebook/translation.rb +92 -0
  152. data/lib/hecks/bluebook/value_object.rb +58 -0
  153. data/lib/hecks/bluebook.rb +74 -0
  154. data/lib/hecks/codemod.rb +342 -0
  155. data/lib/hecks/construct.rb +71 -0
  156. data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
  157. data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
  158. data/lib/hecks/deploy/oidc.json +18 -0
  159. data/lib/hecks/doc/reference.rb +389 -0
  160. data/lib/hecks/embryonaut_bluebook.rb +75 -0
  161. data/lib/hecks/facade/cli_door.rb +116 -0
  162. data/lib/hecks/facade/cli_runner.rb +190 -0
  163. data/lib/hecks/facade/command_request.rb +105 -0
  164. data/lib/hecks/facade/handle.rb +173 -0
  165. data/lib/hecks/facade/json_door.rb +154 -0
  166. data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
  167. data/lib/hecks/facade/surface/chapter.rb +107 -0
  168. data/lib/hecks/facade/surface.rb +48 -0
  169. data/lib/hecks/facade.rb +44 -0
  170. data/lib/hecks/forms/app.rb +300 -0
  171. data/lib/hecks/forms/command_form_renderer.rb +113 -0
  172. data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
  173. data/lib/hecks/forms/field_renderer.rb +170 -0
  174. data/lib/hecks/forms/field_shape.rb +232 -0
  175. data/lib/hecks/forms/html.rb +53 -0
  176. data/lib/hecks/forms/index_renderer.rb +35 -0
  177. data/lib/hecks/forms/page.rb +157 -0
  178. data/lib/hecks/forms/params.rb +132 -0
  179. data/lib/hecks/forms/query_form_renderer.rb +114 -0
  180. data/lib/hecks/forms/record_renderer.rb +115 -0
  181. data/lib/hecks/forms/record_table.rb +63 -0
  182. data/lib/hecks/forms/reference_options.rb +30 -0
  183. data/lib/hecks/forms/value_object_shape.rb +46 -0
  184. data/lib/hecks/forms.rb +54 -0
  185. data/lib/hecks/fqn.rb +94 -0
  186. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
  187. data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
  188. data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
  189. data/lib/hecks/framework/bluebook/governance.bluebook +130 -0
  190. data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
  191. data/lib/hecks/framework/oidc.json +39 -0
  192. data/lib/hecks/framework.rb +90 -0
  193. data/lib/hecks/freezer.rb +67 -0
  194. data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
  195. data/lib/hecks/fuzzing/isolated_boot.rb +92 -0
  196. data/lib/hecks/fuzzing/properties.rb +1146 -0
  197. data/lib/hecks/fuzzing/replay.rb +635 -0
  198. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
  199. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +54 -0
  200. data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
  201. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +185 -0
  202. data/lib/hecks/fuzzing/sequence_generator.rb +130 -0
  203. data/lib/hecks/fuzzing/value_generator.rb +177 -0
  204. data/lib/hecks/fuzzing.rb +12 -0
  205. data/lib/hecks/grammar/evolve.rb +291 -0
  206. data/lib/hecks/grammar/expression.bluebook +417 -0
  207. data/lib/hecks/grammar/expression_operators.json +1152 -0
  208. data/lib/hecks/grammar/grammar.hecksagon +20 -0
  209. data/lib/hecks/grammar/oidc.json +69 -0
  210. data/lib/hecks/grammar/translation.bluebook +203 -0
  211. data/lib/hecks/grammar.rb +178 -0
  212. data/lib/hecks/ir.rb +126 -0
  213. data/lib/hecks/language/adapter.bluebook +116 -0
  214. data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
  215. data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
  216. data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
  217. data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
  218. data/lib/hecks/language/bluebook/command.bluebook +437 -0
  219. data/lib/hecks/language/bluebook/entity.bluebook +316 -0
  220. data/lib/hecks/language/bluebook/policy.bluebook +172 -0
  221. data/lib/hecks/language/bluebook/process_manager.bluebook +264 -0
  222. data/lib/hecks/language/bluebook/projection.bluebook +267 -0
  223. data/lib/hecks/language/bluebook/query.bluebook +245 -0
  224. data/lib/hecks/language/bluebook/shape.bluebook +292 -0
  225. data/lib/hecks/language/bluebook/syntax.bluebook +447 -0
  226. data/lib/hecks/language/bluebook/vocabulary.bluebook +364 -0
  227. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
  228. data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
  229. data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
  230. data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
  231. data/lib/hecks/language/oidc.json +313 -0
  232. data/lib/hecks/language/port.bluebook +92 -0
  233. data/lib/hecks/language/translation/translation.bluebook +110 -0
  234. data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
  235. data/lib/hecks/language/world/wiring.bluebook +62 -0
  236. data/lib/hecks/language/world/world.bluebook +84 -0
  237. data/lib/hecks/literal.rb +125 -0
  238. data/lib/hecks/naming.rb +121 -0
  239. data/lib/hecks/ports/access_control.port +4 -0
  240. data/lib/hecks/ports/access_control.rb +62 -0
  241. data/lib/hecks/ports/agent/answers.rb +104 -0
  242. data/lib/hecks/ports/agent.port +4 -0
  243. data/lib/hecks/ports/agent.rb +167 -0
  244. data/lib/hecks/ports/authentication.port +4 -0
  245. data/lib/hecks/ports/authentication.rb +50 -0
  246. data/lib/hecks/ports/authorization.port +4 -0
  247. data/lib/hecks/ports/authorization.rb +59 -0
  248. data/lib/hecks/ports/clock.port +4 -0
  249. data/lib/hecks/ports/clock.rb +62 -0
  250. data/lib/hecks/ports/extraction.port +4 -0
  251. data/lib/hecks/ports/extraction.rb +37 -0
  252. data/lib/hecks/ports/identity_assignment.port +4 -0
  253. data/lib/hecks/ports/identity_assignment.rb +45 -0
  254. data/lib/hecks/ports/identity_generation.port +4 -0
  255. data/lib/hecks/ports/identity_generation.rb +49 -0
  256. data/lib/hecks/ports/identity_resolution.port +4 -0
  257. data/lib/hecks/ports/identity_resolution.rb +40 -0
  258. data/lib/hecks/ports/loading.port +4 -0
  259. data/lib/hecks/ports/loading.rb +13 -0
  260. data/lib/hecks/ports/persistence/append_only.rb +102 -0
  261. data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
  262. data/lib/hecks/ports/persistence/execution.rb +18 -0
  263. data/lib/hecks/ports/persistence/lineage.rb +277 -0
  264. data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
  266. data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
  267. data/lib/hecks/ports/persistence.port +4 -0
  268. data/lib/hecks/ports/persistence.rb +39 -0
  269. data/lib/hecks/ports/projection.port +4 -0
  270. data/lib/hecks/ports/projection.rb +76 -0
  271. data/lib/hecks/ports/query/in_memory.rb +60 -0
  272. data/lib/hecks/ports/query/ordering.rb +41 -0
  273. data/lib/hecks/ports/query.rb +36 -0
  274. data/lib/hecks/ports.rb +27 -0
  275. data/lib/hecks/projections/diagrams.rb +413 -0
  276. data/lib/hecks/projections/ir.rb +18 -0
  277. data/lib/hecks/projections/model/deviations.rb +98 -0
  278. data/lib/hecks/projections/model.rb +145 -0
  279. data/lib/hecks/projections/oidc.rb +110 -0
  280. data/lib/hecks/projections/parser_table.rb +159 -0
  281. data/lib/hecks/projections/reference.rb +38 -0
  282. data/lib/hecks/projections/shape.rb +31 -0
  283. data/lib/hecks/projections/statements.rb +110 -0
  284. data/lib/hecks/projections/vocabulary.rb +100 -0
  285. data/lib/hecks/projections.rb +32 -0
  286. data/lib/hecks/projector/cli_projector.rb +437 -0
  287. data/lib/hecks/projector/docs_projector.rb +321 -0
  288. data/lib/hecks/projector/exporter.rb +141 -0
  289. data/lib/hecks/projector/ir_projector.rb +18 -0
  290. data/lib/hecks/projector/narrate_projector.rb +243 -0
  291. data/lib/hecks/projector/target.rb +97 -0
  292. data/lib/hecks/projector.rb +186 -0
  293. data/lib/hecks/query_ir.rb +392 -0
  294. data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
  295. data/lib/hecks/query_specification/common/comparators.rb +25 -0
  296. data/lib/hecks/query_specification/common/comparison.rb +174 -0
  297. data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
  298. data/lib/hecks/query_specification/common/dsl.rb +58 -0
  299. data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
  300. data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
  301. data/lib/hecks/query_specification/common/null_policy.rb +80 -0
  302. data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
  303. data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
  304. data/lib/hecks/query_specification/common/options.rb +32 -0
  305. data/lib/hecks/query_specification/common/order_by.rb +9 -0
  306. data/lib/hecks/query_specification/common/specification.rb +10 -0
  307. data/lib/hecks/query_specification/common/where_clause.rb +9 -0
  308. data/lib/hecks/query_specification/field_path.rb +89 -0
  309. data/lib/hecks/query_specification/hop_path.rb +130 -0
  310. data/lib/hecks/query_specification/read_model/specification.rb +18 -0
  311. data/lib/hecks/query_specification.rb +14 -0
  312. data/lib/hecks/rendering.rb +48 -0
  313. data/lib/hecks/router/namespace_installer.rb +157 -0
  314. data/lib/hecks/router.rb +70 -0
  315. data/lib/hecks/runtime/caller.rb +50 -0
  316. data/lib/hecks/runtime/capability_graph.rb +44 -0
  317. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +124 -0
  318. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +248 -0
  319. data/lib/hecks/runtime/command_interpreter.rb +413 -0
  320. data/lib/hecks/runtime/command_rules/admissibility.rb +307 -0
  321. data/lib/hecks/runtime/command_rules/arithmetic.rb +254 -0
  322. data/lib/hecks/runtime/command_rules/authorization.rb +64 -0
  323. data/lib/hecks/runtime/command_rules/emission.rb +34 -0
  324. data/lib/hecks/runtime/command_rules/references.rb +181 -0
  325. data/lib/hecks/runtime/command_rules.rb +28 -0
  326. data/lib/hecks/runtime/dependency_planning.rb +231 -0
  327. data/lib/hecks/runtime/dispatcher.rb +286 -0
  328. data/lib/hecks/runtime/entity_element.rb +253 -0
  329. data/lib/hecks/runtime/entity_interpreter.rb +228 -0
  330. data/lib/hecks/runtime/era_check.rb +136 -0
  331. data/lib/hecks/runtime/era_guard/shape_diff.rb +128 -0
  332. data/lib/hecks/runtime/era_guard.rb +193 -0
  333. data/lib/hecks/runtime/era_tamper.rb +61 -0
  334. data/lib/hecks/runtime/errors.rb +100 -0
  335. data/lib/hecks/runtime/event.rb +51 -0
  336. data/lib/hecks/runtime/identity.rb +128 -0
  337. data/lib/hecks/runtime/instance.rb +120 -0
  338. data/lib/hecks/runtime/interpreting.rb +69 -0
  339. data/lib/hecks/runtime/loader.rb +143 -0
  340. data/lib/hecks/runtime/policy_interpreter.rb +366 -0
  341. data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
  342. data/lib/hecks/runtime/query_interpreter.rb +254 -0
  343. data/lib/hecks/runtime/reaction_invocation.rb +245 -0
  344. data/lib/hecks/runtime/read_model_interpreter.rb +266 -0
  345. data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
  346. data/lib/hecks/runtime/reference_hop.rb +99 -0
  347. data/lib/hecks/runtime/refusal_wording.rb +124 -0
  348. data/lib/hecks/runtime/registry/saga_persistence.rb +70 -0
  349. data/lib/hecks/runtime/registry/verification.rb +158 -0
  350. data/lib/hecks/runtime/registry.rb +218 -0
  351. data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
  352. data/lib/hecks/runtime/routing.rb +96 -0
  353. data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
  354. data/lib/hecks/runtime/saga_interpreter.rb +301 -0
  355. data/lib/hecks/runtime/storage_shape.rb +120 -0
  356. data/lib/hecks/runtime/tenant_check.rb +84 -0
  357. data/lib/hecks/runtime/tenant_scope.rb +55 -0
  358. data/lib/hecks/runtime/value/admission.rb +112 -0
  359. data/lib/hecks/runtime/value/coercion.rb +500 -0
  360. data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
  361. data/lib/hecks/runtime/value.rb +125 -0
  362. data/lib/hecks/runtime.rb +95 -0
  363. data/lib/hecks/translation/audit/approval_digest.rb +31 -0
  364. data/lib/hecks/translation/audit/layer_one.rb +37 -0
  365. data/lib/hecks/translation/audit/layer_two.rb +77 -0
  366. data/lib/hecks/translation/audit/unfed_report.rb +42 -0
  367. data/lib/hecks/translation/audit.rb +70 -0
  368. data/lib/hecks/translation/reattest.rb +72 -0
  369. data/lib/hecks/translation/rule_compiler.rb +120 -0
  370. data/lib/hecks/translation/scaffold/differ.rb +183 -0
  371. data/lib/hecks/translation/scaffold/renderer.rb +41 -0
  372. data/lib/hecks/translation/scaffold/writer.rb +28 -0
  373. data/lib/hecks/translation/scaffold.rb +30 -0
  374. data/lib/hecks/translation.rb +11 -0
  375. data/lib/hecks/version.rb +16 -0
  376. data/lib/hecks/vocabulary.rb +213 -0
  377. data/lib/hecks.rb +118 -6
  378. metadata +401 -222
  379. data/bin/hecks +0 -7
  380. data/bin/hecks-package +0 -65
  381. data/bin/hecks_console +0 -12
  382. data/bin/hecks_serverless +0 -6
  383. data/lib/cli/build.rb +0 -14
  384. data/lib/cli/command_runner.rb +0 -28
  385. data/lib/cli/console.rb +0 -10
  386. data/lib/cli/generate.rb +0 -37
  387. data/lib/cli/hecks-cli.rb +0 -27
  388. data/lib/cli/test.rb +0 -57
  389. data/lib/console/commands.rb +0 -8
  390. data/lib/console/hecks-console.rb +0 -1
  391. data/lib/packager/README.md +0 -0
  392. data/lib/packager/app_runner.rb +0 -21
  393. data/lib/packager/args.rb +0 -26
  394. data/lib/packager/compatibility/fixnum.rb +0 -6
  395. data/lib/packager/hecks.rb +0 -39
  396. data/lib/packager/query_runner.rb +0 -21
  397. data/lib/packager/resources/Dockerfile +0 -11
  398. data/lib/packager/resources/app_binary +0 -7
  399. data/lib/packager/resources/bundle_config +0 -3
  400. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
  401. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
  402. data/lib/serverless/Domain +0 -32
  403. data/lib/serverless/cli.rb +0 -75
  404. data/lib/serverless/resources/command_name.js +0 -5
  405. data/lib/serverless/resources/environment.js +0 -7
  406. data/lib/serverless/resources/handler.js.tt +0 -28
  407. data/lib/serverless/resources/run_binary.js +0 -22
  408. data/lib/serverless/resources/serverless.yml +0 -20
@@ -0,0 +1,413 @@
1
+ require_relative "interpreting"
2
+ require_relative "command_interpreter/argument_gate"
3
+ require_relative "command_interpreter/mutation_applier"
4
+ require_relative "../rendering"
5
+ require_relative "errors"
6
+ require_relative "identity"
7
+ require_relative "dependency_planning"
8
+ require_relative "../ports/persistence/execution"
9
+ require_relative "instance"
10
+ require_relative "refusal_wording"
11
+ require_relative "entity_element"
12
+
13
+ module Hecks
14
+ module Runtime
15
+ # The dispatch pipeline for a command on an aggregate head. The payload
16
+ # gate lives in command_interpreter/argument_gate.rb, the mutation walk
17
+ # in command_interpreter/mutation_applier.rb; what stays here is the
18
+ # order of the steps and how a record is addressed.
19
+ class CommandInterpreter
20
+ include Interpreting
21
+ include ArgumentGate
22
+ include MutationApplier
23
+
24
+ attr_reader :registry
25
+
26
+ # THE DECLARED ORDER, HAND-TYPED — mirrors Vocabulary::AggregateDispatchOrder
27
+ # (language/bluebook/vocabulary.bluebook:188-205), held equal to it by
28
+ # spec/vocabulary_conformance_spec.rb the same way every other vocabulary
29
+ # in that file is (RefusalWording::TEMPLATES, CommandRules::MUTATION_OPS,
30
+ # ...) rather than read live off the meta-domain at every dispatch —
31
+ # Runtime::RefusalWording's own doc comment gives the same reason.
32
+ DISPATCH_ORDER = Hecks::Vocabulary.symbols("AggregateDispatchOrder")
33
+
34
+ # EVERY CROSS-STEP LOCAL `call` used to thread through its own literal
35
+ # sequence, held in one place now that the sequence is data-driven —
36
+ # `result` and `transition`/`old_state` default to nil until the step
37
+ # that sets them runs, same as they were unset locals before that point.
38
+ Context = Struct.new(:domain, :aggregate, :command, :args, :repository, :instance, :transition, :old_state,
39
+ :result, :correlation, :route, :plan, :strategy, :persistence_outcome, :delegated_events,
40
+ :dry_run)
41
+
42
+ def initialize(registry, rules:)
43
+ @registry = registry
44
+ @rules = rules
45
+ end
46
+
47
+ # `dry_run:` — Dispatcher#dry_run?'s own entry point. Every step up
48
+ # through enforce_ensures/enforce_invariants runs exactly as a real
49
+ # dispatch would (givens checked, mutations applied to `ctx.instance`
50
+ # in memory); `step_save`/`step_emit` are the only two that read this
51
+ # flag, each skipping its own real work — see their own comments.
52
+ def call(domain, aggregate, command, args, correlation = nil, route: nil, dry_run: false)
53
+ ctx = Context.new(domain, aggregate, command, args)
54
+ ctx.correlation = correlation
55
+ ctx.route = route
56
+ ctx.dry_run = dry_run
57
+ ctx.plan = DependencyPlanning::Analyzer.call(aggregate: aggregate, command: command)
58
+ run_dispatch_order(DISPATCH_ORDER, ctx)
59
+ [ctx.instance, ctx.result, ctx.plan, ctx.persistence_outcome]
60
+ end
61
+
62
+ private
63
+
64
+ def step_refuse_unknown_arguments(ctx)
65
+ step(:refuse_unknown_arguments) { refuse_unknown_arguments(ctx.domain, ctx.aggregate, ctx.command, ctx.args) }
66
+ end
67
+
68
+ def step_refuse_absent_arguments(ctx)
69
+ step(:refuse_absent_arguments) { refuse_absent_arguments(ctx.command, ctx.args) }
70
+ end
71
+
72
+ def step_normalize_args(ctx)
73
+ ctx.args = step(:normalize_args) { normalize_args(ctx.aggregate, ctx.command, ctx.args) }
74
+ end
75
+
76
+ def step_refuse_role_mismatch(ctx)
77
+ step(:refuse_role_mismatch) { @rules.refuse_role_mismatch(ctx.command, ctx.domain) }
78
+ end
79
+
80
+ def step_resolve_references(ctx)
81
+ step(:resolve_references) { @rules.resolve_references(ctx.domain, ctx.command, ctx.args) }
82
+ end
83
+
84
+ def step_hydrate(ctx)
85
+ ctx.repository = @registry.repository(ctx.domain, ctx.aggregate)
86
+ ctx.strategy = ctx.plan.strategy_for(capabilities: ctx.repository.capabilities)
87
+ ctx.instance = step(:hydrate) {
88
+ if ctx.plan.complete_state? && ctx.plan.state_independent?
89
+ hydrate_complete_state(ctx.repository, ctx.aggregate, ctx.command, ctx.args, ctx.route, ctx.strategy)
90
+ elsif ctx.plan.complete_state?
91
+ hydrate_prior_or_initial(ctx.repository, ctx.aggregate, ctx.command, ctx.args, ctx.route)
92
+ elsif legacy_implicit_creation?(ctx)
93
+ hydrate_legacy_creation(ctx.repository, ctx.aggregate, ctx.command, ctx.args)
94
+ else
95
+ hydrate_existing(ctx.repository, ctx.aggregate, ctx.command, ctx.args, ctx.route)
96
+ end
97
+ }
98
+ end
99
+
100
+ def step_enforce_givens(ctx)
101
+ step(:enforce_givens) {
102
+ @rules.enforce_givens(ctx.instance, ctx.command, ctx.args, domain: ctx.domain,
103
+ declaring: ctx.aggregate, parent: ctx.instance)
104
+ }
105
+ end
106
+
107
+ def step_admissible_transition(ctx)
108
+ ctx.transition = step(:admissible_transition) { @rules.admissible_transition(ctx.aggregate, ctx.command, ctx.instance) }
109
+ end
110
+
111
+ def step_assign_creation_attributes(ctx)
112
+ return unless legacy_implicit_creation?(ctx)
113
+
114
+ step(:assign_creation_attributes) { assign_creation_attributes(ctx.instance, ctx.aggregate, ctx.command, ctx.args) }
115
+ end
116
+
117
+ def step_apply_mutations(ctx)
118
+ # The state as the givens saw it — what `old` names inside an
119
+ # ensures. A shallow dup suffices: mutations REPLACE fields (set,
120
+ # arithmetic via Value#with, append builds a new array), never
121
+ # edit a held value in place.
122
+ ctx.old_state = ctx.instance.state.dup unless ctx.command.ensures.empty?
123
+ step(:apply_mutations) {
124
+ ctx.command.mutations.each { |mutation|
125
+ apply(ctx.instance, ctx.aggregate, mutation, ctx.args)
126
+ }
127
+ }
128
+ end
129
+
130
+ def step_advance_lifecycle(ctx)
131
+ return unless ctx.transition
132
+
133
+ step(:advance_lifecycle) { ctx.instance[ctx.aggregate.lifecycle.field] = ctx.transition.target }
134
+ end
135
+
136
+ # THE SYNCHRONOUS COUSIN OF A POLICY'S OWN `trigger` — see
137
+ # `CommandBuilder#delegates_to`'s own comment for the full reasoning.
138
+ # Runs AFTER this command's own mutations/lifecycle (so a delegating
139
+ # command could in principle still guard with its own `given`s first,
140
+ # though the real use in `domain/chess` declares none) and BEFORE
141
+ # `enforce_ensures`/`enforce_invariants`/`save`, so a refusal here
142
+ # raises a real, unrescued exception and NOTHING from either side —
143
+ # this command's own state, the target element's — has been saved
144
+ # yet. `ctx.instance` is the SAME in-memory record `step_hydrate`
145
+ # loaded and `step_save` will persist; `EntityElement.locate_chain`
146
+ # mutates it in place exactly the way `EntityInterpreter`'s own
147
+ # `step_locate_element`/`step_apply_mutations` mutate their OWN
148
+ # freshly-loaded copy — the only difference is WHICH already-in-
149
+ # memory record gets handed in.
150
+ def step_delegate_to_entity(ctx)
151
+ delegation = ctx.command.mutations.find { |mutation| mutation.op == :delegate }
152
+ return unless delegation
153
+
154
+ step(:delegate_to_entity) {
155
+ entity_name, _dot, command_name = delegation.target.to_s.rpartition(".")
156
+ entity = ctx.aggregate.entities.find { |e| e.hecks_name == entity_name } ||
157
+ raise(WiringError, "#{ctx.command.hecks_name} delegates_to #{entity_name}." \
158
+ "#{command_name}, but #{ctx.aggregate.hecks_name} has no " \
159
+ "entity named #{entity_name.inspect}")
160
+ target_command = entity.command(command_name) ||
161
+ raise(WiringError, "#{ctx.command.hecks_name} delegates_to " \
162
+ "#{entity_name}.#{command_name}, which " \
163
+ "#{entity_name} declares no such command")
164
+
165
+ # `with:` REMAPS, it does not ENUMERATE — starting from a copy
166
+ # of THIS command's own already-resolved args (`ctx.args`) and
167
+ # overlaying the explicit mapping on top means ambient context
168
+ # the caller never had to think about (the aggregate's own
169
+ # identity, addressed the ordinary way to reach `MoveKnight` at
170
+ # all) flows through to the target untouched, the same as it
171
+ # always would for a caller dispatching the entity command
172
+ # directly. Confirmed necessary, not a defensive guess: a real
173
+ # downstream domain's own AdvancePly-on-Moved policy silently
174
+ # failed to re-locate its OWN aggregate (`reaction_log`: "no
175
+ # Game with label.value ..."), because the emitted event's
176
+ # payload — built from `target_args` alone — never carried
177
+ # `label` at all when `with:` named only `id`/`to`.
178
+ target_args = ctx.args.merge(
179
+ delegation.source.to_h { |target_key, source_key| [target_key.to_sym, ctx.args[source_key]] }
180
+ )
181
+
182
+ element = EntityElement.locate_chain(ctx.aggregate, [entity], ctx.instance, target_args, command_name)
183
+ view = Instance.new(aggregate: entity, id: EntityElement.element_identity(entity, element).to_s, state: element)
184
+
185
+ @rules.enforce_givens(view, target_command, target_args, domain: ctx.domain, declaring: entity, parent: ctx.instance)
186
+ transition = @rules.admissible_transition(entity, target_command, view)
187
+
188
+ old_element = target_command.ensures.empty? ? nil : element.dup
189
+ target_command.mutations.each { |mutation|
190
+ EntityElement.apply_to_element(@rules, ctx.aggregate, entity, element, mutation, target_args)
191
+ }
192
+ element[entity.lifecycle.field] = transition.target if transition
193
+
194
+ settled = Instance.new(aggregate: entity, id: view.id, state: element)
195
+ @rules.enforce_ensures(settled, target_command, target_args, old: old_element, domain: ctx.domain, parent: ctx.instance)
196
+
197
+ ctx.delegated_events = @rules.emit(target_command, ctx.domain, ctx.aggregate, ctx.instance, target_args, ctx.repository)
198
+ }
199
+ end
200
+
201
+ def step_enforce_ensures(ctx)
202
+ step(:enforce_ensures) {
203
+ @rules.enforce_ensures(ctx.instance, ctx.command, ctx.args, old: ctx.old_state,
204
+ domain: ctx.domain, parent: ctx.instance)
205
+ }
206
+ end
207
+
208
+ def step_enforce_invariants(ctx)
209
+ step(:enforce_invariants) { @rules.enforce_invariants(ctx.instance, ctx.aggregate, domain: ctx.domain) }
210
+ end
211
+
212
+ # `dry_run:` skips this — see Dispatcher#dry_run?'s own comment. The
213
+ # same conditional-skip shape `step_assign_creation_attributes`
214
+ # already has (`return unless ctx.command.creates?`), not a new
215
+ # pattern: a step that does not apply this time traces nothing,
216
+ # rather than a caller having to branch around it.
217
+ def step_save(ctx)
218
+ return if ctx.dry_run
219
+
220
+ step(:save) do
221
+ @rules.resolve_state_references(ctx.domain, ctx.aggregate, ctx.instance.state)
222
+ ctx.persistence_outcome = if ctx.strategy == DependencyPlanning::ATOMIC_PUT
223
+ # A SECOND CREATION IS NOT A FRESH ONE — see
224
+ # hydrate_legacy_creation's own comment; the
225
+ # same refusal, on the same terms, for the
226
+ # complete-state path. `insert_only:` asks the
227
+ # ADAPTER to decide and refuse ATOMICALLY
228
+ # (never writing a `creates?` command over an
229
+ # identity that already names a record) rather
230
+ # than this interpreter reading the record
231
+ # first to check — a `repository.find` before
232
+ # every atomic_put would be exactly the read
233
+ # this strategy exists to skip.
234
+ ctx.repository.atomic_put(ctx.instance, insert_only: ctx.command.creates?)
235
+ else
236
+ ctx.repository.save(ctx.instance)
237
+ Ports::Persistence::Outcome.new(status: :saved, instance: ctx.instance)
238
+ end
239
+ if ctx.persistence_outcome.status == :conflicted
240
+ raise(AlreadyExists, RefusalWording.render("AlreadyExists", "creating_duplicate",
241
+ command: ctx.command.hecks_name, aggregate: ctx.aggregate.hecks_name,
242
+ identity: identity_reading(ctx.aggregate),
243
+ offered: Rendering.describe(ctx.instance.id)))
244
+ end
245
+ end
246
+ end
247
+
248
+ # A DELEGATING COMMAND EMITS NOTHING OF ITS OWN (`CommandBuilder#build`'s
249
+ # own guard refuses declaring `emits` alongside `delegates_to`) — its
250
+ # result IS whatever `step_delegate_to_entity` already collected from
251
+ # the target entity command's own `emits`, not a second, empty call
252
+ # into `@rules.emit` for a command with no announced events at all.
253
+ #
254
+ # `dry_run:` skips this too, same reasoning as `step_save` — nothing
255
+ # was committed, so `ctx.result` stays nil and `Dispatcher#dry_run?`
256
+ # never reads it (its own return value is just whether this raised).
257
+ def step_emit(ctx)
258
+ return if ctx.dry_run
259
+
260
+ ctx.result = step(:emit) {
261
+ # `ctx.delegated_events` is only ever set by `step_delegate_to_entity`,
262
+ # and only when this command carries a `:delegate` mutation — an
263
+ # empty Array (the target genuinely emitted nothing) is still
264
+ # truthy in Ruby, so this reads correctly either way.
265
+ next ctx.delegated_events if ctx.delegated_events
266
+
267
+ @rules.emit(ctx.command, ctx.domain, ctx.aggregate, ctx.instance, ctx.args, ctx.repository, ctx.correlation)
268
+ }
269
+ end
270
+
271
+ def hydrate_existing(repository, aggregate, command, args, route = nil)
272
+ if route
273
+ found = repository.find(route.aggregate) ||
274
+ raise(NotFound, RefusalWording.render("NotFound", "record_missing",
275
+ aggregate: aggregate.hecks_name,
276
+ identity: identity_reading(aggregate),
277
+ offered: Rendering.describe(route.aggregate)))
278
+ return found.dup
279
+ end
280
+
281
+ id = identity_of(aggregate, args) ||
282
+ identity_from(aggregate, args, :id) ||
283
+ identity_from(aggregate, args, reference_key(command)) ||
284
+ raise(NotFound, RefusalWording.render("NotFound", "acting_no_identity",
285
+ command: command.hecks_name, aggregate: aggregate.hecks_name,
286
+ identity: identity_reading(aggregate)))
287
+ found = repository.find(id) ||
288
+ raise(NotFound, RefusalWording.render("NotFound", "record_missing",
289
+ aggregate: aggregate.hecks_name,
290
+ identity: identity_reading(aggregate),
291
+ offered: Rendering.describe(id)))
292
+ found.dup
293
+ end
294
+
295
+ # Transitional compatibility for live source that has not yet acquired
296
+ # explicit effects. It is deliberately isolated from the normal routing
297
+ # and planning path so `reference_to` no longer chooses how a migrated
298
+ # command hydrates or persists. Wave 8 removes this after the inventory is
299
+ # empty; frozen eras retain their own shadow parser.
300
+ #
301
+ # `ctx.plan.complete_state?` already claimed every command whose plan is
302
+ # fully resolved in the two branches above `step_hydrate` tries first, so
303
+ # reaching this check already means the plan is incomplete — an
304
+ # un-migrated command still routes here on `creates?` alone, the same as
305
+ # the old `hydrate` did, regardless of whether it happens to have any
306
+ # mutations (`write_set`). Requiring an EMPTY write_set here refused
307
+ # every un-migrated creating command that sets even one field.
308
+ def legacy_implicit_creation?(ctx)
309
+ ctx.route.nil? && ctx.command.creates?
310
+ end
311
+
312
+ def hydrate_legacy_creation(repository, aggregate, command, args)
313
+ id = identity_of(aggregate, args) ||
314
+ raise(NotFound, RefusalWording.render("NotFound", "creating_no_identity",
315
+ command: command.hecks_name, aggregate: aggregate.hecks_name,
316
+ identity: identity_reading(aggregate)))
317
+ if repository.find(id)
318
+ raise(AlreadyExists, RefusalWording.render("AlreadyExists", "creating_duplicate",
319
+ command: command.hecks_name, aggregate: aggregate.hecks_name,
320
+ identity: identity_reading(aggregate),
321
+ offered: Rendering.describe(id)))
322
+ end
323
+
324
+ Instance.new(aggregate: aggregate, id: id)
325
+ end
326
+
327
+ def hydrate_complete_state(repository, aggregate, command, args, route, strategy)
328
+ derived = identity_of(aggregate, args)
329
+ if route && derived && route.aggregate.to_s != derived.to_s
330
+ raise TypeMismatch,
331
+ "#{command.hecks_name} routes to #{route.aggregate.inspect}, but its identity facts name #{derived.inspect}"
332
+ end
333
+
334
+ id = route&.aggregate || derived ||
335
+ raise(NotFound, RefusalWording.render("NotFound", "creating_no_identity",
336
+ command: command.hecks_name,
337
+ aggregate: aggregate.hecks_name,
338
+ identity: identity_reading(aggregate)))
339
+
340
+ # A SECOND CREATION IS NOT A FRESH ONE — see hydrate_legacy_creation's
341
+ # own comment; the same refusal, on the same terms, for the
342
+ # complete-state path. ONLY when `strategy` will NOT be ATOMIC_PUT:
343
+ # an atomic-put-capable adapter enforces this itself, atomically,
344
+ # via `insert_only:` in step_save (no read here, no race with the
345
+ # write) — but `strategy_for` already fell back to a plain `save`
346
+ # for an adapter with no atomic_put capability at all (Heki, today),
347
+ # and a plain `save` never refuses an overwrite on its own. Skipping
348
+ # this check for THAT case silently dropped the refusal instead of
349
+ # deferring it — the very "no such call — no such check" gap Wave
350
+ # 8 exists to close everywhere else.
351
+ if command.creates? && strategy != DependencyPlanning::ATOMIC_PUT && repository.find(id)
352
+ raise(AlreadyExists, RefusalWording.render("AlreadyExists", "creating_duplicate",
353
+ command: command.hecks_name, aggregate: aggregate.hecks_name,
354
+ identity: identity_reading(aggregate),
355
+ offered: Rendering.describe(id)))
356
+ end
357
+
358
+ Instance.new(aggregate: aggregate, id: id)
359
+ end
360
+
361
+ # A complete command may still depend on prior state: lifecycle guards
362
+ # are the common case. Read once so an existing record is checked against
363
+ # its real state; when no record exists, the aggregate's declared defaults
364
+ # are the prior state. Completeness, not a reference marker, proves that
365
+ # initializing that missing record is safe.
366
+ def hydrate_prior_or_initial(repository, aggregate, command, args, route)
367
+ derived = identity_of(aggregate, args)
368
+ if route && derived && route.aggregate.to_s != derived.to_s
369
+ raise TypeMismatch,
370
+ "#{command.hecks_name} routes to #{route.aggregate.inspect}, but its identity facts name #{derived.inspect}"
371
+ end
372
+
373
+ id = route&.aggregate || derived ||
374
+ raise(NotFound, RefusalWording.render("NotFound", "creating_no_identity",
375
+ command: command.hecks_name,
376
+ aggregate: aggregate.hecks_name,
377
+ identity: identity_reading(aggregate)))
378
+ found = repository.find(id)
379
+
380
+ # A SECOND CREATION IS NOT A FRESH ONE — see hydrate_legacy_
381
+ # creation's own comment; the same refusal, on the same terms, for
382
+ # a complete-but-state-dependent command (one with a `given`
383
+ # reading its own prior state, which is what routes here instead
384
+ # of hydrate_complete_state). Gated on `command.creates?`: a
385
+ # NON-creating command's own complete payload legitimately means
386
+ # "act on whatever this identity already holds" (`found` IS the
387
+ # prior state this branch exists to supply), so only a genuine
388
+ # creation reusing an already-occupied identity is a duplicate.
389
+ # `SafeDepositBox.Rent` is exactly this shape — `given("box is
390
+ # vacant")` makes it state-dependent, so a second Rent used to
391
+ # silently hydrate the existing box as "prior state" and refuse
392
+ # for the wrong reason (not vacant) instead of the right one
393
+ # (already exists).
394
+ if found && command.creates?
395
+ raise(AlreadyExists, RefusalWording.render("AlreadyExists", "creating_duplicate",
396
+ command: command.hecks_name, aggregate: aggregate.hecks_name,
397
+ identity: identity_reading(aggregate),
398
+ offered: Rendering.describe(id)))
399
+ end
400
+
401
+ found ? found.dup : Instance.new(aggregate: aggregate, id: id)
402
+ end
403
+
404
+ # THE JOIN, THE DIG, AND THE READING — all shared with `EntityInterpreter`
405
+ # now, in `Runtime::Identity`, rather than kept as two copies that could
406
+ # only ever drift. See that module for the reasoning ; these three stay
407
+ # here, at the old names, purely so nothing below has to change.
408
+ def identity_of(aggregate, args) = Identity.of(aggregate, args)
409
+ def identity_from(aggregate, args, key) = Identity.from(aggregate, args, key)
410
+ def identity_reading(construct) = Identity.reading(construct)
411
+ end
412
+ end
413
+ end