hecks 1.3.0 → 1.4.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 (355) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +7 -7
  3. data/lib/hecks/adapters/driven/d1.rb +187 -23
  4. data/lib/hecks/adapters/driven/folder.rb +10 -10
  5. data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
  6. data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
  7. data/lib/hecks/adapters/driven/heki/journal.rb +5 -4
  8. data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
  9. data/lib/hecks/adapters/driven/heki.rb +13 -7
  10. data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
  11. data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
  12. data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
  13. data/lib/hecks/adapters/driven/lambda.rb +39 -33
  14. data/lib/hecks/adapters/driven/local_storage.rb +17 -10
  15. data/lib/hecks/adapters/driven/memory.rb +205 -9
  16. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -1
  17. data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
  18. data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
  19. data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
  20. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
  21. data/lib/hecks/adapters/driven/postgres.rb +175 -28
  22. data/lib/hecks/adapters/driven/prism.rb +4 -4
  23. data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
  24. data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
  25. data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
  26. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
  27. data/lib/hecks/adapters/driven/sqlite.rb +181 -21
  28. data/lib/hecks/adapters/driven.rb +4 -4
  29. data/lib/hecks/adapters/driving/github_webhook.rb +18 -18
  30. data/lib/hecks/behaviors/dsl.rb +2 -2
  31. data/lib/hecks/behaviors/expectations.rb +23 -23
  32. data/lib/hecks/behaviors/ir.rb +1 -1
  33. data/lib/hecks/behaviors/rspec.rb +1 -1
  34. data/lib/hecks/behaviors/runner.rb +2 -2
  35. data/lib/hecks/behaviors.rb +1 -1
  36. data/lib/hecks/bluebook/aggregate.rb +13 -13
  37. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
  38. data/lib/hecks/bluebook/assembly/build.rb +1 -1
  39. data/lib/hecks/bluebook/assembly/contract.rb +39 -16
  40. data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
  41. data/lib/hecks/bluebook/assembly/marks.rb +18 -18
  42. data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
  43. data/lib/hecks/bluebook/assembly.rb +9 -9
  44. data/lib/hecks/bluebook/attribute.rb +9 -9
  45. data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
  46. data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
  47. data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
  48. data/lib/hecks/bluebook/behaviour/command.rb +23 -23
  49. data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -7
  50. data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
  51. data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
  52. data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
  53. data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
  54. data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
  55. data/lib/hecks/bluebook/behaviour/query.rb +1 -1
  56. data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
  57. data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
  58. data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
  59. data/lib/hecks/bluebook/capabilities.rb +27 -0
  60. data/lib/hecks/bluebook/chapter.rb +28 -9
  61. data/lib/hecks/bluebook/command.rb +12 -12
  62. data/lib/hecks/bluebook/domain_port.rb +9 -9
  63. data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
  64. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
  65. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +96 -96
  66. data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
  67. data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
  68. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
  69. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
  70. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
  71. data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
  72. data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
  73. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
  74. data/lib/hecks/bluebook/dsl/entity_builder.rb +56 -56
  75. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
  76. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
  77. data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
  78. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
  79. data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
  80. data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
  81. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
  82. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +35 -35
  83. data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
  84. data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
  85. data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
  86. data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
  87. data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
  88. data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
  89. data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
  90. data/lib/hecks/bluebook/entity.rb +11 -11
  91. data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
  92. data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
  93. data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
  94. data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
  95. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
  96. data/lib/hecks/bluebook/expression/resolver.rb +60 -62
  97. data/lib/hecks/bluebook/hexagon.rb +1 -1
  98. data/lib/hecks/bluebook/lifecycle.rb +1 -1
  99. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
  100. data/lib/hecks/bluebook/meta_validator/judge.rb +101 -87
  101. data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
  102. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
  103. data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
  104. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
  105. data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
  106. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
  107. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
  108. data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
  109. data/lib/hecks/bluebook/meta_validator.rb +70 -70
  110. data/lib/hecks/bluebook/model_check.rb +170 -84
  111. data/lib/hecks/bluebook/pattern_subset.rb +9 -9
  112. data/lib/hecks/bluebook/policy.rb +15 -13
  113. data/lib/hecks/bluebook/process_manager.rb +14 -14
  114. data/lib/hecks/bluebook/project_register.rb +6 -6
  115. data/lib/hecks/bluebook/query.rb +4 -4
  116. data/lib/hecks/bluebook/read_model.rb +14 -14
  117. data/lib/hecks/bluebook/reference.rb +8 -8
  118. data/lib/hecks/bluebook/smoke_test.rb +19 -19
  119. data/lib/hecks/bluebook/synthesizer.rb +12 -12
  120. data/lib/hecks/bluebook/translation.rb +4 -4
  121. data/lib/hecks/bluebook/value_object.rb +6 -6
  122. data/lib/hecks/bluebook.rb +2 -2
  123. data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
  124. data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
  125. data/lib/hecks/codemod.rb +36 -35
  126. data/lib/hecks/construct.rb +6 -6
  127. data/lib/hecks/corpus.rb +317 -0
  128. data/lib/hecks/deprecation.rb +95 -0
  129. data/lib/hecks/doc/reference.rb +19 -19
  130. data/lib/hecks/embryonaut_bluebook.rb +11 -11
  131. data/lib/hecks/facade/cli_door.rb +69 -10
  132. data/lib/hecks/facade/cli_runner.rb +105 -24
  133. data/lib/hecks/facade/command_request.rb +23 -0
  134. data/lib/hecks/facade/handle.rb +79 -32
  135. data/lib/hecks/facade/json_door.rb +106 -25
  136. data/lib/hecks/facade/surface/aggregate_door.rb +42 -27
  137. data/lib/hecks/facade/surface/chapter.rb +26 -17
  138. data/lib/hecks/facade/surface.rb +16 -3
  139. data/lib/hecks/facade.rb +15 -4
  140. data/lib/hecks/forms/app.rb +46 -30
  141. data/lib/hecks/forms/command_form_renderer.rb +70 -9
  142. data/lib/hecks/forms/field_renderer.rb +142 -6
  143. data/lib/hecks/forms/field_shape.rb +180 -19
  144. data/lib/hecks/forms/html.rb +51 -7
  145. data/lib/hecks/forms/index_renderer.rb +14 -2
  146. data/lib/hecks/forms/params.rb +120 -23
  147. data/lib/hecks/forms/port_argument.rb +2 -2
  148. data/lib/hecks/forms/query_form_renderer.rb +2 -2
  149. data/lib/hecks/forms/record_renderer.rb +2 -2
  150. data/lib/hecks/forms/record_table.rb +1 -1
  151. data/lib/hecks/forms/value_object_shape.rb +3 -3
  152. data/lib/hecks/forms.rb +24 -4
  153. data/lib/hecks/fqn.rb +1 -1
  154. data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
  155. data/lib/hecks/framework.rb +48 -17
  156. data/lib/hecks/freezer.rb +11 -11
  157. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
  158. data/lib/hecks/fuzzing/combination_miner.rb +119 -0
  159. data/lib/hecks/fuzzing/concurrent_dispatch.rb +140 -45
  160. data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
  161. data/lib/hecks/fuzzing/differential.rb +158 -0
  162. data/lib/hecks/fuzzing/domain_generator.rb +694 -0
  163. data/lib/hecks/fuzzing/era_boundary.rb +37 -18
  164. data/lib/hecks/fuzzing/form_census.rb +35 -20
  165. data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
  166. data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
  167. data/lib/hecks/fuzzing/isolated_boot.rb +62 -62
  168. data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
  169. data/lib/hecks/fuzzing/persistence_parity.rb +19 -21
  170. data/lib/hecks/fuzzing/properties/corrections.rb +9 -9
  171. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +154 -49
  172. data/lib/hecks/fuzzing/properties/guards.rb +42 -42
  173. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
  174. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +22 -35
  175. data/lib/hecks/fuzzing/properties/outbox.rb +24 -24
  176. data/lib/hecks/fuzzing/properties/querying.rb +17 -17
  177. data/lib/hecks/fuzzing/properties.rb +64 -40
  178. data/lib/hecks/fuzzing/qa_settings.rb +152 -0
  179. data/lib/hecks/fuzzing/replay.rb +114 -114
  180. data/lib/hecks/fuzzing/rotation_priority.rb +10 -10
  181. data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
  182. data/lib/hecks/fuzzing/self_consistency.rb +141 -112
  183. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +35 -35
  184. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
  185. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
  186. data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
  187. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +58 -31
  188. data/lib/hecks/fuzzing/sequence_generator.rb +111 -23
  189. data/lib/hecks/fuzzing/shrinker.rb +197 -0
  190. data/lib/hecks/fuzzing/structural_skips.rb +23 -130
  191. data/lib/hecks/fuzzing/sweep_depth.rb +4 -4
  192. data/lib/hecks/fuzzing/target_capabilities.rb +49 -18
  193. data/lib/hecks/fuzzing/value_generator.rb +14 -14
  194. data/lib/hecks/fuzzing.rb +5 -0
  195. data/lib/hecks/grammar/evolve.rb +10 -10
  196. data/lib/hecks/grammar.rb +7 -7
  197. data/lib/hecks/ir.rb +13 -13
  198. data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
  199. data/lib/hecks/language/bluebook/policy.bluebook +11 -1
  200. data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
  201. data/lib/hecks/language/oidc.json +5 -0
  202. data/lib/hecks/literal.rb +9 -9
  203. data/lib/hecks/naming.rb +25 -25
  204. data/lib/hecks/ports/access_control.rb +58 -2
  205. data/lib/hecks/ports/agent/answers.rb +83 -6
  206. data/lib/hecks/ports/agent.rb +119 -35
  207. data/lib/hecks/ports/authentication.rb +44 -4
  208. data/lib/hecks/ports/authorization.rb +53 -11
  209. data/lib/hecks/ports/clock.rb +42 -23
  210. data/lib/hecks/ports/extraction.rb +16 -0
  211. data/lib/hecks/ports/identity_assignment.rb +24 -2
  212. data/lib/hecks/ports/identity_generation.rb +17 -3
  213. data/lib/hecks/ports/identity_resolution.rb +18 -1
  214. data/lib/hecks/ports/loading.rb +4 -0
  215. data/lib/hecks/ports/persistence/append_only.rb +172 -8
  216. data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
  217. data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
  218. data/lib/hecks/ports/persistence/execution.rb +4 -0
  219. data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
  220. data/lib/hecks/ports/persistence/plugin.rb +42 -4
  221. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
  222. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
  223. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
  224. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
  225. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
  226. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
  227. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
  228. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
  229. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
  230. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
  231. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +28 -5
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +57 -4
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
  241. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
  242. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
  243. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
  244. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +42 -8
  245. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
  246. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
  247. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
  248. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -19
  249. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
  250. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -1
  251. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
  252. data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
  253. data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
  254. data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
  255. data/lib/hecks/ports/persistence/state_codec.rb +319 -0
  256. data/lib/hecks/ports/persistence.rb +36 -1
  257. data/lib/hecks/ports/projection.rb +61 -7
  258. data/lib/hecks/ports/query/in_memory.rb +3 -3
  259. data/lib/hecks/ports/query/ordering.rb +6 -6
  260. data/lib/hecks/ports/query.rb +35 -0
  261. data/lib/hecks/projections/bootstrap_table.rb +112 -0
  262. data/lib/hecks/projections/diagrams.rb +75 -75
  263. data/lib/hecks/projections/glossary/html.rb +3 -3
  264. data/lib/hecks/projections/glossary/markdown.rb +3 -3
  265. data/lib/hecks/projections/glossary/mermaid.rb +1 -1
  266. data/lib/hecks/projections/glossary/sections.rb +1 -1
  267. data/lib/hecks/projections/glossary/sentences.rb +5 -5
  268. data/lib/hecks/projections/glossary.rb +15 -15
  269. data/lib/hecks/projections/ir.rb +1 -1
  270. data/lib/hecks/projections/model/deviations.rb +18 -17
  271. data/lib/hecks/projections/model.rb +25 -21
  272. data/lib/hecks/projections/oidc.rb +7 -7
  273. data/lib/hecks/projections/parser_table.rb +5 -5
  274. data/lib/hecks/projections/reference.rb +3 -3
  275. data/lib/hecks/projections/rust_vocabulary.rb +443 -0
  276. data/lib/hecks/projections/shape.rb +2 -2
  277. data/lib/hecks/projections/statements.rb +11 -11
  278. data/lib/hecks/projections/vocabulary.rb +9 -9
  279. data/lib/hecks/projections.rb +5 -3
  280. data/lib/hecks/projector/cli_projector.rb +29 -29
  281. data/lib/hecks/projector/docs_projector.rb +13 -13
  282. data/lib/hecks/projector/exporter.rb +42 -21
  283. data/lib/hecks/projector/ir_projector.rb +1 -1
  284. data/lib/hecks/projector/narrate_projector.rb +11 -11
  285. data/lib/hecks/projector/target.rb +13 -13
  286. data/lib/hecks/projector.rb +15 -15
  287. data/lib/hecks/query_ir.rb +47 -47
  288. data/lib/hecks/query_specification/common/comparators.rb +19 -3
  289. data/lib/hecks/query_specification/common/comparison.rb +109 -27
  290. data/lib/hecks/query_specification/common/dsl.rb +65 -9
  291. data/lib/hecks/query_specification/common/null_policy.rb +57 -13
  292. data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
  293. data/lib/hecks/query_specification/common/options.rb +25 -0
  294. data/lib/hecks/query_specification/field_path.rb +69 -15
  295. data/lib/hecks/query_specification/hop_path.rb +57 -20
  296. data/lib/hecks/query_specification/read_model/specification.rb +4 -0
  297. data/lib/hecks/rendering.rb +3 -3
  298. data/lib/hecks/router/namespace_installer.rb +3 -3
  299. data/lib/hecks/router.rb +1 -1
  300. data/lib/hecks/runtime/aggregate_lock.rb +11 -11
  301. data/lib/hecks/runtime/boot_gates.rb +3 -3
  302. data/lib/hecks/runtime/caller.rb +8 -8
  303. data/lib/hecks/runtime/capability_graph.rb +2 -2
  304. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
  305. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +42 -63
  306. data/lib/hecks/runtime/command_interpreter.rb +159 -102
  307. data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
  308. data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
  309. data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
  310. data/lib/hecks/runtime/command_rules/emission.rb +18 -1
  311. data/lib/hecks/runtime/command_rules/references.rb +35 -35
  312. data/lib/hecks/runtime/command_rules.rb +3 -0
  313. data/lib/hecks/runtime/dependency_planning.rb +11 -11
  314. data/lib/hecks/runtime/dispatcher.rb +283 -90
  315. data/lib/hecks/runtime/entity_element.rb +151 -67
  316. data/lib/hecks/runtime/entity_interpreter.rb +108 -82
  317. data/lib/hecks/runtime/errors.rb +19 -19
  318. data/lib/hecks/runtime/event.rb +6 -6
  319. data/lib/hecks/runtime/identity.rb +22 -22
  320. data/lib/hecks/runtime/instance.rb +39 -14
  321. data/lib/hecks/runtime/interpreting.rb +12 -12
  322. data/lib/hecks/runtime/invocation.rb +276 -0
  323. data/lib/hecks/runtime/loader.rb +14 -14
  324. data/lib/hecks/runtime/outbox.rb +23 -23
  325. data/lib/hecks/runtime/policy_interpreter.rb +54 -54
  326. data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
  327. data/lib/hecks/runtime/query_interpreter.rb +55 -64
  328. data/lib/hecks/runtime/reaction_invocation.rb +20 -20
  329. data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
  330. data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
  331. data/lib/hecks/runtime/reference_hop.rb +6 -6
  332. data/lib/hecks/runtime/refusal_wording.rb +92 -115
  333. data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
  334. data/lib/hecks/runtime/registry/verification.rb +36 -26
  335. data/lib/hecks/runtime/registry.rb +45 -28
  336. data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
  337. data/lib/hecks/runtime/routing.rb +10 -153
  338. data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
  339. data/lib/hecks/runtime/saga_interpreter.rb +64 -64
  340. data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
  341. data/lib/hecks/runtime/tenant_check.rb +9 -9
  342. data/lib/hecks/runtime/tenant_scope.rb +5 -5
  343. data/lib/hecks/runtime/value/admission.rb +75 -30
  344. data/lib/hecks/runtime/value/coercion.rb +318 -127
  345. data/lib/hecks/runtime/value/entity_list_coercion.rb +34 -34
  346. data/lib/hecks/runtime/value.rb +21 -21
  347. data/lib/hecks/runtime.rb +7 -7
  348. data/lib/hecks/storehouse.rb +64 -64
  349. data/lib/hecks/version.rb +3 -3
  350. data/lib/hecks/vocabulary.rb +205 -4
  351. data/lib/hecks.rb +13 -11
  352. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
  353. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
  354. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
  355. metadata +22 -2
@@ -24,15 +24,14 @@ module Hecks
24
24
 
25
25
  attr_reader :registry
26
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.
27
+ # **The declared order** — Vocabulary::AggregateDispatchOrder
28
+ # (language/bluebook/vocabulary.bluebook), read off the generated table
29
+ # (lib/hecks/vocabulary.rb) rather than typed here. spec/vocabulary_
30
+ # conformance_spec.rb holds every step to a real `step_<name>` handler,
31
+ # both directions.
33
32
  DISPATCH_ORDER = Hecks::Vocabulary.symbols("AggregateDispatchOrder")
34
33
 
35
- # A LAST-RESORT SAFETY VALVE, NOT THE NORMAL OUTCOME PATH — see
34
+ # **A last-resort safety valve, not the normal outcome path** — see
36
35
  # `Runtime::StaleWrite`'s own comment. Two concurrent writers
37
36
  # against one aggregate resolve through exactly one retry in the
38
37
  # ordinary case (the loser's retried hydrate reads the winner's now-
@@ -42,13 +41,13 @@ module Hecks
42
41
  # two-writer case.
43
42
  MAX_STALE_WRITE_RETRIES = 5
44
43
 
45
- # EVERY CROSS-STEP LOCAL `call` used to thread through its own literal
44
+ # Every cross-step local `call` used to thread through its own literal
46
45
  # sequence, held in one place now that the sequence is data-driven —
47
46
  # `result` and `transition`/`old_state` default to nil until the step
48
47
  # that sets them runs, same as they were unset locals before that point.
49
48
  Context = Struct.new(:domain, :aggregate, :command, :args, :repository, :instance, :transition, :old_state,
50
49
  :result, :correlation, :route, :plan, :strategy, :persistence_outcome, :pending_delegation,
51
- :dry_run, :correction_bindings, :outbox_rows)
50
+ :dry_run, :correction_bindings, :outbox_rows, :invocation)
52
51
 
53
52
  def initialize(registry, rules:)
54
53
  @registry = registry
@@ -60,21 +59,28 @@ module Hecks
60
59
  # dispatch would (givens checked, mutations applied to `ctx.instance`
61
60
  # in memory); `step_save`/`step_emit` are the only two that read this
62
61
  # flag, each skipping its own real work — see their own comments.
63
- # RETRIES THE WHOLE METHOD BODY on `StaleWrite` — a fresh `ctx`, a
62
+ # Retries the whole method body on `StaleWrite` — a fresh `ctx`, a
64
63
  # fresh `step_hydrate` re-reading current state, so `enforce_givens`
65
64
  # re-evaluates against reality rather than the snapshot that just
66
65
  # went stale. See `MAX_STALE_WRITE_RETRIES`/`Runtime::StaleWrite`
67
66
  # for why exhaustion is a pathological-contention signal, not the
68
67
  # expected shape of a two-writer race.
69
- def call(domain, aggregate, command, args, correlation = nil, route: nil, dry_run: false)
68
+ #
69
+ # `invocation` — the `Runtime::Invocation` `Dispatcher` built for this
70
+ # call. `ctx.args` is `invocation.to_args` (the same Hash routing
71
+ # always handed this method), `ctx.route` its `target`.
72
+ def call(domain, aggregate, command, invocation, correlation = nil, dry_run: false)
73
+ args = invocation.to_args
74
+ route = invocation.target
70
75
  attempt = 0
71
76
  begin
72
77
  ctx = Context.new(domain, aggregate, command, args)
78
+ ctx.invocation = invocation
73
79
  ctx.correlation = correlation
74
80
  ctx.route = route
75
81
  ctx.dry_run = dry_run
76
82
  ctx.plan = DependencyPlanning::Analyzer.call(aggregate: aggregate, command: command)
77
- # RESOLVED HERE, ONCE, BEFORE HYDRATION — `Registry#repository`
83
+ # **Resolved here, once, before hydration** — `Registry#repository`
78
84
  # memoizes, so this and `step_hydrate`'s own read of `ctx.repository`
79
85
  # (no second fetch there any more) always name the same instance;
80
86
  # the isolation decision below (lock vs. CAS+retry) needs the
@@ -92,6 +98,16 @@ module Hecks
92
98
 
93
99
  private
94
100
 
101
+ # **A no-op, and untraced** — Vocabulary::AggregateDispatchOrder's
102
+ # decode_arguments. Routing (`Runtime::Routing`) has already handed
103
+ # `call` a decoded argument hash by the time any step runs, so there is
104
+ # nothing left to decode here yet; like a conditional step that does
105
+ # not fire, it records nothing in `trace`. Declared so the Rust
106
+ # kernel's generated step enum carries the step its generated decoder
107
+ # will move into (roadmap D2), and so a typed Invocation (I2) has a
108
+ # step to be consumed at.
109
+ def step_decode_arguments(_ctx); end
110
+
95
111
  def step_refuse_unknown_arguments(ctx)
96
112
  step(:refuse_unknown_arguments) { refuse_unknown_arguments(ctx.domain, ctx.aggregate, ctx.command, ctx.args) }
97
113
  end
@@ -131,14 +147,14 @@ module Hecks
131
147
 
132
148
  def step_enforce_givens(ctx)
133
149
  step(:enforce_givens) do
134
- # STRUCTURAL, before the declared givens — the same ordering
150
+ # Structural, before the declared givens — the same ordering
135
151
  # NotFound/AlreadyExists already get at hydration: "does the
136
152
  # fact this command's corrects names even exist" is not a
137
153
  # domain rule an author wrote, it is a precondition for the
138
154
  # domain rules to mean anything at all. Also locates the
139
155
  # correction target itself, if `as:` named one — carried on
140
156
  # `ctx` so `step_enforce_ensures` (the settled-record half)
141
- # can bind the SAME name too, not just this pre-mutation half.
157
+ # can bind the same name too, not just this pre-mutation half.
142
158
  ctx.correction_bindings = @rules.enforce_correction_target(ctx.instance, ctx.aggregate, ctx.command, domain: ctx.domain)
143
159
  @rules.enforce_givens(ctx.instance, ctx.command, ctx.args, domain: ctx.domain,
144
160
  declaring: ctx.aggregate, parent: ctx.instance, correction: ctx.correction_bindings)
@@ -157,12 +173,12 @@ module Hecks
157
173
 
158
174
  def step_apply_mutations(ctx)
159
175
  # The state as the givens saw it — what `old` names inside an
160
- # ensures. A shallow dup suffices: mutations REPLACE fields (set,
176
+ # ensures. A shallow dup suffices: mutations replace fields (set,
161
177
  # arithmetic via Value#with, append builds a new array), never
162
178
  # edit a held value in place.
163
179
  ctx.old_state = ctx.instance.state.dup unless ctx.command.ensures.empty?
164
180
  step(:apply_mutations) do
165
- # ONE UPDATE SET OVER THE PRE-DISPATCH STATE (C4.2, docs/
181
+ # One update set over the pre-dispatch state (C4.2, docs/
166
182
  # semantics/bluebook-semantics.md): every effect's sources read
167
183
  # `pre` — the state as it was before this command — and its
168
184
  # target is written to the candidate; declaration order carries
@@ -180,19 +196,19 @@ module Hecks
180
196
  step(:advance_lifecycle) { ctx.instance[ctx.aggregate.lifecycle.field] = ctx.transition.target }
181
197
  end
182
198
 
183
- # THE SYNCHRONOUS COUSIN OF A POLICY'S OWN `trigger` — see
199
+ # The synchronous cousin of a policy's own `trigger` — see
184
200
  # `CommandBuilder#delegates_to`'s own comment for the full reasoning.
185
- # Runs AFTER this command's own mutations/lifecycle (so a delegating
201
+ # Runs after this command's own mutations/lifecycle (so a delegating
186
202
  # command could in principle still guard with its own `given`s first,
187
- # though the real use in `domain/chess` declares none) and BEFORE
203
+ # though the real use in `domain/chess` declares none) and before
188
204
  # `enforce_ensures`/`enforce_invariants`/`save`, so a refusal here
189
- # raises a real, unrescued exception and NOTHING from either side —
205
+ # raises a real, unrescued exception and nothing from either side —
190
206
  # this command's own state, the target element's — has been saved
191
- # yet. `ctx.instance` is the SAME in-memory record `step_hydrate`
207
+ # yet. `ctx.instance` is the same in-memory record `step_hydrate`
192
208
  # loaded and `step_save` will persist; `EntityElement.locate_chain`
193
209
  # mutates it in place exactly the way `EntityInterpreter`'s own
194
- # `step_locate_element`/`step_apply_mutations` mutate their OWN
195
- # freshly-loaded copy — the only difference is WHICH already-in-
210
+ # `step_locate_element`/`step_apply_mutations` mutate their own
211
+ # freshly-loaded copy — the only difference is which already-in-
196
212
  # memory record gets handed in.
197
213
  #
198
214
  # Reimplements the entity command pipeline's own order (givens,
@@ -219,8 +235,8 @@ module Hecks
219
235
  "#{entity_name}.#{command_name}, which " \
220
236
  "#{entity_name} declares no such command")
221
237
 
222
- # `with:` REMAPS, it does not ENUMERATE — starting from a copy
223
- # of THIS command's own already-resolved args (`ctx.args`) and
238
+ # `with:` remaps, it does not enumerate — starting from a copy
239
+ # of this command's own already-resolved args (`ctx.args`) and
224
240
  # overlaying the explicit mapping on top means ambient context
225
241
  # the caller never had to think about (the aggregate's own
226
242
  # identity, addressed the ordinary way to reach `MoveKnight` at
@@ -228,7 +244,7 @@ module Hecks
228
244
  # always would for a caller dispatching the entity command
229
245
  # directly. Confirmed necessary, not a defensive guess: a real
230
246
  # downstream domain's own AdvancePly-on-Moved policy silently
231
- # failed to re-locate its OWN aggregate (`reaction_log`: "no
247
+ # failed to re-locate its own aggregate (`reaction_log`: "no
232
248
  # Game with label.value ..."), because the emitted event's
233
249
  # payload — built from `target_args` alone — never carried
234
250
  # `label` at all when `with:` named only `id`/`to`.
@@ -252,7 +268,7 @@ module Hecks
252
268
  settled = Instance.new(aggregate: entity, id: view.id, state: element)
253
269
  @rules.enforce_ensures(settled, target_command, target_args, old: old_element, domain: ctx.domain, parent: ctx.instance)
254
270
 
255
- # NOT emitted here — C7.2 (docs/semantics/bluebook-semantics.md):
271
+ # Not emitted here — C7.2 (docs/semantics/bluebook-semantics.md):
256
272
  # a refused command records nothing, and the parent's own
257
273
  # `ensures`/`enforce_invariants`/`save` steps still run after
258
274
  # this one. The target's emission is parked and performed by
@@ -275,29 +291,70 @@ module Hecks
275
291
  step(:enforce_invariants) { @rules.enforce_invariants(ctx.instance, ctx.aggregate, domain: ctx.domain) }
276
292
  end
277
293
 
278
- # `dry_run:` skips this — see Dispatcher#dry_run?'s own comment. The
279
- # same conditional-skip shape `step_assign_creation_attributes`
280
- # already has (`return unless ctx.command.creates?`), not a new
281
- # pattern: a step that does not apply this time traces nothing,
282
- # rather than a caller having to branch around it.
294
+ # `dry_run:` skips the write half of this step — see Dispatcher#
295
+ # dry_run?'s own comment — but not `resolve_state_references`.
296
+ # BUG#127: that check is validation, not persistence — it asks
297
+ # whether the settled in-memory state names a reference target that
298
+ # actually exists, the same question a real dispatch answers before
299
+ # it ever writes anything. Skipping it under `dry_run:` made
300
+ # `dry_run?` disagree with what a real dispatch immediately after it
301
+ # would do: it answered `true` ("would succeed") for a command whose
302
+ # real dispatch refuses with NotFound. Only the persist/raise-on-
303
+ # conflict tail is genuinely persistence-only and stays behind the
304
+ # early return.
305
+ #
306
+ # BUG#132 — a second validation-not-persistence check hid in that
307
+ # same persist/raise-on-conflict tail: `persist_instance`'s own
308
+ # ATOMIC_PUT branch (below) is where `hydrate_complete_state`'s
309
+ # comment says a `creates?` command's duplicate check is
310
+ # deliberately deferred to, for a strategy this fast — a read
311
+ # (`repository.find`) any earlier, before the real write, would be
312
+ # exactly the extra read `insert_only:` exists to avoid paying on
313
+ # every real dispatch. But that means the check never ran at all under
314
+ # `dry_run:` — not eagerly (hydration skips it for this exact
315
+ # strategy, on purpose) and not deferred (this whole branch returns
316
+ # first). `Roster::Roster.Open` against an already-open name is
317
+ # this shape: `dry_run?` answered `true` for a real dispatch
318
+ # immediately after it that refuses `AlreadyExists`. Under
319
+ # `dry_run:` there is no write to race, so paying for that one read
320
+ # is safe here — and only here.
283
321
  def step_save(ctx)
284
- return if ctx.dry_run
322
+ step(:save) { @rules.resolve_state_references(ctx.domain, ctx.aggregate, ctx.instance.state) }
323
+
324
+ if ctx.dry_run
325
+ step(:save) { check_dry_run_creates_duplicate(ctx) }
326
+ return
327
+ end
285
328
 
286
329
  step(:save) do
287
- @rules.resolve_state_references(ctx.domain, ctx.aggregate, ctx.instance.state)
288
330
  seed_projected_fields(ctx)
289
331
  ctx.persistence_outcome = persist_instance(ctx)
290
332
  raise_for_persistence_outcome!(ctx)
291
333
  end
292
334
  end
293
335
 
336
+ # See `step_save`'s own BUG#132 comment: the only path, real or dry,
337
+ # able to catch a `creates?` command reusing an already-occupied
338
+ # identity when `strategy` is `ATOMIC_PUT` — `hydrate_complete_
339
+ # state` deliberately skips this exact case (its own comment), and
340
+ # a dry run never reaches `persist_instance`'s matching branch.
341
+ def check_dry_run_creates_duplicate(ctx)
342
+ return unless ctx.strategy == DependencyPlanning::ATOMIC_PUT && ctx.command.creates?
343
+ return unless ctx.repository.find(ctx.instance.id)
344
+
345
+ raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "creating_duplicate",
346
+ command: ctx.command.hecks_name, aggregate: ctx.aggregate.hecks_name,
347
+ identity: identity_reading(ctx.aggregate),
348
+ offered: Rendering.describe(ctx.instance.id)))
349
+ end
350
+
294
351
  def persist_instance(ctx)
295
352
  if ctx.strategy == DependencyPlanning::ATOMIC_PUT
296
- # A SECOND CREATION IS NOT A FRESH ONE — see
353
+ # **A second creation is not a fresh one** — see
297
354
  # hydrate_complete_state's own comment; the
298
355
  # same refusal, on the same terms, for the
299
356
  # complete-state path. `insert_only:` asks the
300
- # ADAPTER to decide and refuse ATOMICALLY
357
+ # adapter to decide and refuse atomically
301
358
  # (never writing a `creates?` command over an
302
359
  # identity that already names a record) rather
303
360
  # than this interpreter reading the record
@@ -316,12 +373,12 @@ module Hecks
316
373
 
317
374
  def raise_for_persistence_outcome!(ctx)
318
375
  if ctx.persistence_outcome.status == :conflicted
319
- raise(AlreadyExists, RefusalWording.render("AlreadyExists", "creating_duplicate",
320
- command: ctx.command.hecks_name, aggregate: ctx.aggregate.hecks_name,
321
- identity: identity_reading(ctx.aggregate),
322
- offered: Rendering.describe(ctx.instance.id)))
376
+ raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "creating_duplicate",
377
+ command: ctx.command.hecks_name, aggregate: ctx.aggregate.hecks_name,
378
+ identity: identity_reading(ctx.aggregate),
379
+ offered: Rendering.describe(ctx.instance.id)))
323
380
  elsif ctx.persistence_outcome.status == :stale
324
- # NOT a `RefusalWording.render` call — this is not a declared
381
+ # Not a `RefusalWording.render` call — this is not a declared
325
382
  # vocabulary refusal, just a plain, informative message. See
326
383
  # `Runtime::StaleWrite`'s own comment: caught by `#call`'s
327
384
  # retry loop, re-raised only once retries are exhausted.
@@ -332,26 +389,26 @@ module Hecks
332
389
  end
333
390
  end
334
391
 
335
- # THE ONE-TIME, SYNCHRONOUS HALF OF `projects` (S12, ADR 0025) —
392
+ # The one-time, synchronous half of `projects` (S12, ADR 0025) —
336
393
  # `RebuildSweep` (`runtime/rebuild_sweep.rb`) is deliberately the
337
- # ONLY thing that keeps a projected field current against a
338
- # target that changes AFTER this record was written — no reactive
394
+ # only thing that keeps a projected field current against a
395
+ # target that changes after this record was written — no reactive
339
396
  # `Policy#for_each` keeping it live in real time, that stays
340
397
  # deferred, same as that file's own header explains. But without
341
- # SOME synchronous population, a projected field never gets an
342
- # INITIAL value at all until an operator remembers to run a
398
+ # some synchronous population, a projected field never gets an
399
+ # initial value at all until an operator remembers to run a
343
400
  # sweep by hand — every acting command reading it (`given
344
401
  # ("customer is active") { customer_status == "active" }`, say)
345
402
  # would refuse a freshly created, genuinely active record for no
346
403
  # real reason, which is not the eventual-consistency tradeoff the
347
404
  # ADR accepts, just a bug. So: every time a record with `projects`
348
405
  # fields is about to save — creating or acting, either can be the
349
- # first time a referenced record resolves — read each one ONCE,
406
+ # first time a referenced record resolves — read each one once,
350
407
  # here, using the exact same `RebuildSweep.remote_value` a sweep
351
408
  # itself would compute. This is still eventually consistent in
352
- # the sense the ADR means: a change on the TARGET side after this
409
+ # the sense the ADR means: a change on the target side after this
353
410
  # save still needs a sweep to reach here. It is only ever
354
- # SYNCHRONOUS with THIS record's own write, never a live read
411
+ # synchronous with this record's own write, never a live read
355
412
  # triggered by a `given`/`ensures`/`invariant` mid-dispatch — the
356
413
  # boundary rule those enforce holds exactly as before.
357
414
  def seed_projected_fields(ctx)
@@ -365,10 +422,10 @@ module Hecks
365
422
  end
366
423
  end
367
424
 
368
- # A DELEGATING COMMAND EMITS NOTHING OF ITS OWN (`CommandBuilder#build`'s
425
+ # A delegating command emits nothing of its own (`CommandBuilder#build`'s
369
426
  # own guard refuses declaring `emits` alongside `delegates_to`) — its
370
- # result IS the target entity command's own `emits`, parked by
371
- # `step_delegate_to_entity` and emitted HERE, after save (C7.2), not
427
+ # result is the target entity command's own `emits`, parked by
428
+ # `step_delegate_to_entity` and emitted here, after save (C7.2), not
372
429
  # a second, empty call into `@rules.emit` for a command with no
373
430
  # announced events at all.
374
431
  #
@@ -397,53 +454,53 @@ module Hecks
397
454
  def hydrate_existing(repository, aggregate, command, args, route = nil)
398
455
  if route
399
456
  found = repository.find(route.aggregate) ||
400
- raise(NotFound, RefusalWording.render("NotFound", "record_missing",
401
- aggregate: aggregate.hecks_name,
402
- identity: identity_reading(aggregate),
403
- offered: Rendering.describe(route.aggregate)))
457
+ raise(NotFound, RefusalWording.render_site("NotFound", "record_missing",
458
+ aggregate: aggregate.hecks_name,
459
+ identity: identity_reading(aggregate),
460
+ offered: Rendering.describe(route.aggregate)))
404
461
  return found.dup
405
462
  end
406
463
 
407
464
  id = identity_of(aggregate, args) ||
408
465
  identity_from(aggregate, args, :id) ||
409
466
  identity_from(aggregate, args, reference_key(command)) ||
410
- raise(NotFound, RefusalWording.render("NotFound", "acting_no_identity",
411
- command: command.hecks_name, aggregate: aggregate.hecks_name,
412
- identity: identity_reading(aggregate)))
467
+ raise(NotFound, RefusalWording.render_site("NotFound", "acting_no_identity",
468
+ command: command.hecks_name, aggregate: aggregate.hecks_name,
469
+ identity: identity_reading(aggregate)))
413
470
  found = repository.find(id) ||
414
- raise(NotFound, RefusalWording.render("NotFound", "record_missing",
415
- aggregate: aggregate.hecks_name,
416
- identity: identity_reading(aggregate),
417
- offered: Rendering.describe(id)))
471
+ raise(NotFound, RefusalWording.render_site("NotFound", "record_missing",
472
+ aggregate: aggregate.hecks_name,
473
+ identity: identity_reading(aggregate),
474
+ offered: Rendering.describe(id)))
418
475
  found.dup
419
476
  end
420
477
 
421
- # WAVE 8 (equivalence-gap plan, item 1.6) — NOT done, and this
478
+ # Wave 8 (equivalence-gap plan, item 1.6) — not done, and this
422
479
  # comment now says precisely how far it got, corrected from an
423
- # EARLIER version of itself that briefly (same PR, never released)
480
+ # earlier version of itself that briefly (same PR, never released)
424
481
  # claimed the inventory was empty and deleted this method outright.
425
482
  # A full corpus audit (`DependencyPlanning::Analyzer.call` against
426
- # every `creates?`-true command in all 5 EXAMPLE domains — banking,
483
+ # every `creates?`-true command in all 5 example domains — banking,
427
484
  # pizzas, chess, compliance, roster) found and fixed 4 real
428
485
  # authoring bugs (`Statement.Generate`, `CreatePizza`, `Chess::
429
486
  # Game.Start`, `Roster.Open` — each declared an attribute and
430
487
  # never `sets` it, so the field silently stayed nil on every
431
488
  # created record regardless of what a caller sent), plus a real
432
- # `DependencyPlanning::Analyzer` bug for ENTITY-owned commands
489
+ # `DependencyPlanning::Analyzer` bug for entity-owned commands
433
490
  # (`root_aggregate:`, that class's own header) that surfaced one
434
491
  # more live corpus bug of its own (`payment_cards.bluebook`'s own
435
492
  # `Withdrawal.Dispute`, which never actually checked whether the
436
493
  # card was retired) — all real, all kept.
437
494
  #
438
- # DELETING THIS METHOD ON THAT BASIS TURNED OUT TO BE WRONG,
495
+ # Deleting this method on that basis turned out to be wrong,
439
496
  # caught by running the full suite rather than trusting the
440
497
  # audit's own scope: the 5 example domains are nowhere near the
441
- # WHOLE inventory of `creates?`-true commands this fallback
498
+ # whole inventory of `creates?`-true commands this fallback
442
499
  # actually carries. Dozens of separate, purpose-built spec
443
500
  # fixtures across the suite (`spec/fixtures/*.bluebook`, and
444
501
  # inline `Hecks.bluebook` blocks declared directly inside
445
502
  # individual spec files — governance, mutation ops, ports,
446
- # routing, tenant isolation, sagas, and more) declare their OWN
503
+ # routing, tenant isolation, sagas, and more) declare their own
447
504
  # small `creates?`-true commands the same incomplete way, and
448
505
  # rely on this exact fallback to create anything at all. Deleting
449
506
  # it produced 218 failures across specs with nothing to do with
@@ -462,7 +519,7 @@ module Hecks
462
519
  # acquired explicit effects, same as it always was — deliberately
463
520
  # isolated from the normal routing and planning path so
464
521
  # `reference_to` no longer chooses how a migrated command hydrates
465
- # or persists. A real, future Wave 8 removes this once THAT wider
522
+ # or persists. A real, future Wave 8 removes this once that wider
466
523
  # inventory is empty, not before.
467
524
  #
468
525
  # `ctx.plan.complete_state?` already claimed every command whose
@@ -471,7 +528,7 @@ module Hecks
471
528
  # incomplete — an un-migrated command still routes here on
472
529
  # `creates?` alone, the same as the old `hydrate` did, regardless
473
530
  # of whether it happens to have any mutations (`write_set`).
474
- # Requiring an EMPTY write_set here refused every un-migrated
531
+ # Requiring an empty write_set here refused every un-migrated
475
532
  # creating command that sets even one field.
476
533
  def legacy_implicit_creation?(ctx)
477
534
  ctx.route.nil? && ctx.command.creates?
@@ -479,14 +536,14 @@ module Hecks
479
536
 
480
537
  def hydrate_legacy_creation(repository, aggregate, command, args)
481
538
  id = identity_of(aggregate, args) ||
482
- raise(NotFound, RefusalWording.render("NotFound", "creating_no_identity",
483
- command: command.hecks_name, aggregate: aggregate.hecks_name,
484
- identity: identity_reading(aggregate)))
539
+ raise(NotFound, RefusalWording.render_site("NotFound", "creating_no_identity",
540
+ command: command.hecks_name, aggregate: aggregate.hecks_name,
541
+ identity: identity_reading(aggregate)))
485
542
  if repository.find(id)
486
- raise(AlreadyExists, RefusalWording.render("AlreadyExists", "creating_duplicate",
487
- command: command.hecks_name, aggregate: aggregate.hecks_name,
488
- identity: identity_reading(aggregate),
489
- offered: Rendering.describe(id)))
543
+ raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "creating_duplicate",
544
+ command: command.hecks_name, aggregate: aggregate.hecks_name,
545
+ identity: identity_reading(aggregate),
546
+ offered: Rendering.describe(id)))
490
547
  end
491
548
 
492
549
  Instance.new(aggregate: aggregate, id: id, args: args)
@@ -500,29 +557,29 @@ module Hecks
500
557
  end
501
558
 
502
559
  id = route&.aggregate || derived ||
503
- raise(NotFound, RefusalWording.render("NotFound", "creating_no_identity",
504
- command: command.hecks_name,
505
- aggregate: aggregate.hecks_name,
506
- identity: identity_reading(aggregate)))
560
+ raise(NotFound, RefusalWording.render_site("NotFound", "creating_no_identity",
561
+ command: command.hecks_name,
562
+ aggregate: aggregate.hecks_name,
563
+ identity: identity_reading(aggregate)))
507
564
 
508
- # A SECOND CREATION IS NOT A FRESH ONE — `creates?` on an identity
565
+ # **A second creation is not a fresh one** — `creates?` on an identity
509
566
  # a record already exists under refuses (`AlreadyExists`) rather
510
567
  # than silently overwriting it, the same refusal `hydrate_prior_
511
568
  # or_initial`'s own body gives for its own complete-but-state-
512
- # dependent case, below. ONLY when `strategy` will NOT be ATOMIC_PUT:
569
+ # dependent case, below. Only when `strategy` will not be ATOMIC_PUT:
513
570
  # an atomic-put-capable adapter enforces this itself, atomically,
514
571
  # via `insert_only:` in step_save (no read here, no race with the
515
572
  # write) — but `strategy_for` already fell back to a plain `save`
516
573
  # for an adapter with no atomic_put capability at all (Heki, today),
517
574
  # and a plain `save` never refuses an overwrite on its own. Skipping
518
- # this check for THAT case silently dropped the refusal instead of
575
+ # this check for that case silently dropped the refusal instead of
519
576
  # deferring it — the very "no such call — no such check" gap Wave
520
577
  # 8 exists to close everywhere else.
521
578
  if command.creates? && strategy != DependencyPlanning::ATOMIC_PUT && repository.find(id)
522
- raise(AlreadyExists, RefusalWording.render("AlreadyExists", "creating_duplicate",
523
- command: command.hecks_name, aggregate: aggregate.hecks_name,
524
- identity: identity_reading(aggregate),
525
- offered: Rendering.describe(id)))
579
+ raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "creating_duplicate",
580
+ command: command.hecks_name, aggregate: aggregate.hecks_name,
581
+ identity: identity_reading(aggregate),
582
+ offered: Rendering.describe(id)))
526
583
  end
527
584
 
528
585
  Instance.new(aggregate: aggregate, id: id, args: args)
@@ -541,19 +598,19 @@ module Hecks
541
598
  end
542
599
 
543
600
  id = route&.aggregate || derived ||
544
- raise(NotFound, RefusalWording.render("NotFound", "creating_no_identity",
545
- command: command.hecks_name,
546
- aggregate: aggregate.hecks_name,
547
- identity: identity_reading(aggregate)))
601
+ raise(NotFound, RefusalWording.render_site("NotFound", "creating_no_identity",
602
+ command: command.hecks_name,
603
+ aggregate: aggregate.hecks_name,
604
+ identity: identity_reading(aggregate)))
548
605
  found = repository.find(id)
549
606
 
550
- # A SECOND CREATION IS NOT A FRESH ONE — see hydrate_complete_
607
+ # **A second creation is not a fresh one** — see hydrate_complete_
551
608
  # state's own comment; the same refusal, on the same terms, for
552
609
  # a complete-but-state-dependent command (one with a `given`
553
610
  # reading its own prior state, which is what routes here instead
554
611
  # of hydrate_complete_state). Gated on `command.creates?`: a
555
- # NON-creating command's own complete payload legitimately means
556
- # "act on whatever this identity already holds" (`found` IS the
612
+ # non-creating command's own complete payload legitimately means
613
+ # "act on whatever this identity already holds" (`found` is the
557
614
  # prior state this branch exists to supply), so only a genuine
558
615
  # creation reusing an already-occupied identity is a duplicate.
559
616
  # `SafeDepositBox.Rent` is exactly this shape — `given("box is
@@ -562,16 +619,16 @@ module Hecks
562
619
  # for the wrong reason (not vacant) instead of the right one
563
620
  # (already exists).
564
621
  if found && command.creates?
565
- raise(AlreadyExists, RefusalWording.render("AlreadyExists", "creating_duplicate",
566
- command: command.hecks_name, aggregate: aggregate.hecks_name,
567
- identity: identity_reading(aggregate),
568
- offered: Rendering.describe(id)))
622
+ raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "creating_duplicate",
623
+ command: command.hecks_name, aggregate: aggregate.hecks_name,
624
+ identity: identity_reading(aggregate),
625
+ offered: Rendering.describe(id)))
569
626
  end
570
627
 
571
628
  found ? found.dup : Instance.new(aggregate: aggregate, id: id, args: args)
572
629
  end
573
630
 
574
- # THE JOIN, THE DIG, AND THE READING — all shared with `EntityInterpreter`
631
+ # **The join, the dig, and the reading** — all shared with `EntityInterpreter`
575
632
  # now, in `Runtime::Identity`, rather than kept as two copies that could
576
633
  # only ever drift. See that module for the reasoning ; these three stay
577
634
  # here, at the old names, purely so nothing below has to change.