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