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