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
@@ -1,12 +1,13 @@
1
- require_relative "errors"
2
- require_relative "refusal_wording"
3
- require_relative "../rendering"
4
-
5
1
  module Hecks
6
2
  module Runtime
7
3
  # The invocation address is not part of a command's domain payload.
8
4
  # Aggregate commands carry one receiver identity; entity commands carry
9
5
  # the aggregate receiver followed by one identity for every entity hop.
6
+ #
7
+ # Reading a call's shape — what `to:` and `with:` mean, the BUG#7/#17/#18
8
+ # rules — lives in `Runtime::Invocation.from_call` now (invocation.rb).
9
+ # `envelope`/`payload` are kept as thin delegators for any caller still
10
+ # naming them; the dispatcher itself builds an Invocation instead.
10
11
  module Routing
11
12
  Envelope = Struct.new(:aggregate, :entities, keyword_init: true) do
12
13
  def initialize(aggregate:, entities: [])
@@ -17,158 +18,14 @@ module Hecks
17
18
 
18
19
  module_function
19
20
 
20
- def envelope(to, entity_depth: 0)
21
- return nil if to.nil?
22
-
23
- aggregate, entities = to.is_a?(Hash) ? parse_envelope_hash(to) : scalar_envelope(to)
24
-
25
- raise TypeMismatch, "to: must name the receiving aggregate identity" if aggregate.nil? || aggregate.to_s.empty?
26
- if entities.size != entity_depth
27
- raise TypeMismatch,
28
- "to: for an entity command needs #{entity_depth} entity " \
29
- "#{entity_depth == 1 ? 'identity' : 'identities'} after the aggregate — got #{entities.size}"
30
- end
31
- raise TypeMismatch, "to: contains a blank entity identity" if entities.any? do |identity|
32
- identity.nil? || identity.to_s.empty?
33
- end
34
-
35
- Envelope.new(aggregate: aggregate, entities: entities)
36
- end
37
-
38
- # BUG#7 — the non-Hash half of `to:` used to accept ANY Ruby object
39
- # (`[to, []]`, unconditionally) as though it were a ready-made
40
- # aggregate identity scalar. That is looser than the JSON step
41
- # boundary (`bin/run`/`Fuzzing::Replay`/`StepBuilder`) ever needs it
42
- # to be — every legitimate caller already hands this a `String`
43
- # (`Naming.identity` canonicalizes every identity to one before it
44
- # ever reaches a `to:`/legacy-args door) — and looser than Rust's own
45
- # hand-written mirror of this exact boundary
46
- # (`rust/src/kernel/routing.rs#RoutingEnvelope::from_json`, its own
47
- # header: "Generated routers accept this shape while retaining the
48
- # legacy mixed-args object as a compatibility input during
49
- # migration"), which refuses anything that is neither a JSON string
50
- # nor object outright, TypeMismatch, before ever reaching a domain's
51
- # own command payload.
52
- #
53
- # The gap surfaced live on `examples/roster` — the first domain in
54
- # this corpus to declare a command attribute literally named `to`
55
- # (`Roster::Roster.Mark`, deliberately, per that bluebook's own
56
- # header comment). `bin/qa_sweep`'s legacy-args dispatch convention
57
- # (`runtime.dispatch(verb, **symbolize(args))`, `step_builder.rb`)
58
- # flattens a command's own declared fact and its routing target into
59
- # ONE Ruby kwargs hash — completely ordinary for every other domain,
60
- # since Ruby's keyword-argument binding only steals a key that
61
- # collides with `dispatch`'s own `to:`/`with:`/`saga_correlation:`
62
- # parameter names. `Mark`'s `to` does collide, so a fuzzer-corrupted
63
- # scalar offered for it (an out-of-range Integer, from
64
- # `InvalidValueGenerator.corrupt`) was routed here as the AGGREGATE
65
- # IDENTITY instead of the domain's own required argument — accepted
66
- # unconditionally, leaving `Mark`'s own `to` fact absent from the
67
- # payload entirely. Ruby refused `AbsentArgument` ("Mark was not
68
- # given to — it takes to"); Rust's stricter `RoutingEnvelope::
69
- # from_json` refuses the malformed scalar itself, TypeMismatch,
70
- # before the domain payload is ever examined — the observed
71
- # divergence. Tightening this branch to Rust's own contract (a
72
- # scalar `to:` must be a `String`) makes both refuse the same way,
73
- # for the same reason, at the same step.
74
- def scalar_envelope(to)
75
- return [to, []] if to.is_a?(String)
76
-
77
- raise TypeMismatch, "to: must be a string aggregate identity or an entity route, got #{Rendering.describe(to)}"
78
- end
79
- private_class_method :scalar_envelope
80
-
81
- # The Hash-shaped half of `to:` — pulled out of `envelope` because
82
- # it is a self-contained parse (raises on an unrecognized key, then
83
- # returns the pair) with no dependency on anything `envelope` does
84
- # afterward; the validations that follow apply the same way
85
- # whichever branch produced `aggregate`/`entities`.
86
- def parse_envelope_hash(to)
87
- hash = to.transform_keys(&:to_sym)
88
- unknown = hash.keys - %i[aggregate entity entities]
89
- raise TypeMismatch, "to: does not recognize #{unknown.sort.join(', ')}" unless unknown.empty?
90
-
91
- [hash[:aggregate], entity_identities(hash)]
92
- end
93
- private_class_method :parse_envelope_hash
21
+ def envelope(to, entity_depth: 0) = Invocation.route(to, entity_depth: entity_depth)
94
22
 
95
- # `with:` is deliberately strict. Compatibility-only calls still pass
96
- # loose keyword arguments through the old addressing gate, but a caller
97
- # choosing the explicit envelope cannot smuggle receiver identity back
98
- # into the payload.
23
+ # Loaded by invocation.rb (which requires this file), so `Invocation`
24
+ # is always defined by the time either method runs.
99
25
  def payload(command, with:, legacy:)
100
- if with && !legacy.empty?
101
- raise TypeMismatch,
102
- "dispatch takes command facts in with:, not both with: and loose keyword arguments"
103
- end
104
-
105
- return legacy unless with
106
- raise TypeMismatch, "with: must be a hash of command facts" unless with.is_a?(Hash)
107
-
108
- offered = with.transform_keys(&:to_sym)
109
- declared = command.attributes.map { |attribute| attribute.name.to_sym }
110
- refuse_unknown_facts!(command, offered, declared)
111
- refuse_absent_facts!(command, offered, declared)
112
- offered
113
- end
114
-
115
- # The two `with:` shape checks `payload` runs in sequence — pulled
116
- # out because each is a self-contained "compute a difference, raise
117
- # if non-empty" rule with no dependency on the other. Order stays
118
- # unknown-before-absent, exactly as inline: `payload` calls them in
119
- # that order, so a fact that is both unknown AND leaves something
120
- # else absent still raises UnknownArgument first, same as before.
121
- def refuse_unknown_facts!(command, offered, declared)
122
- unknown = (offered.keys - declared).sort
123
- return if unknown.empty?
124
-
125
- reading = declared.empty? ? "none" : declared.join(", ")
126
- raise UnknownArgument,
127
- RefusalWording.render("UnknownArgument", "unknown_args",
128
- command: command.hecks_name, unknown: unknown.join(", "),
129
- declared: reading)
130
- end
131
- private_class_method :refuse_unknown_facts!
132
-
133
- def refuse_absent_facts!(command, offered, declared)
134
- absent = command.attributes.reject(&:optional?).map { |attribute| attribute.name.to_sym } - offered.keys
135
- return if absent.empty?
136
-
137
- reading = declared.empty? ? "none" : declared.join(", ")
138
- raise AbsentArgument,
139
- RefusalWording.render("AbsentArgument", "absent_args",
140
- command: command.hecks_name, absent: absent.sort.join(", "),
141
- declared: reading)
142
- end
143
- private_class_method :refuse_absent_facts!
144
-
145
- # BUG#18 — an entity route naming NO entity at all (`entities: []`,
146
- # or neither `entity:` nor `entities:` present) used to come back
147
- # `[]` here unremarked, which only ever refused downstream for an
148
- # ENTITY command (`envelope`'s own `entities.size != entity_depth`
149
- # check, entity_depth >= 1) — for an AGGREGATE-level command
150
- # (entity_depth 0), `[].size == 0` trivially satisfied that check,
151
- # so the degenerate Hash `{aggregate:, entities: []}` reached the
152
- # command's own validation instead of being refused as malformed
153
- # routing. Rust's `RoutingEnvelope::from_json` never let it get
154
- # that far: the Hash branch refuses "entity route requires at
155
- # least one entity identity" unconditionally, before any
156
- # entity_depth is known. Refusing it here too, at the same point,
157
- # closes the gap without touching the scalar branch (a bare
158
- # aggregate identity, no Hash at all) that every ordinary aggregate
159
- # dispatch already uses instead of this shape (`Facade::Handle#
160
- # dispatch`'s own `to: @id`, `CommandRequest`'s own header —
161
- # "aggregate command: { to: "record-id", ... }" — and, now,
162
- # `Judge#address`).
163
- def entity_identities(hash)
164
- raise TypeMismatch, "to: takes entity: or entities:, not both" if hash.key?(:entities) && hash.key?(:entity)
165
-
166
- identities = hash.key?(:entities) ? Array(hash[:entities]) : Array(hash[:entity])
167
- raise TypeMismatch, "to: entity route requires at least one entity identity" if identities.empty?
168
-
169
- identities
26
+ facts = Invocation.facts_for(command, with: with, legacy: legacy)
27
+ Invocation.new(verb: nil, target: nil, facts: facts).to_args
170
28
  end
171
- private_class_method :entity_identities
172
29
  end
173
30
  end
174
31
  end
@@ -3,27 +3,27 @@ require_relative "../value"
3
3
  module Hecks
4
4
  module Runtime
5
5
  class SagaInterpreter
6
- # How a saga decides WHICH CONVERSATION an event belongs to — three
6
+ # How a saga decides which conversation an event belongs to — three
7
7
  # tiers, each one a lesson.
8
8
  module Correlation
9
9
  private
10
10
 
11
- # A DOTTED PATH NAMES THE SCALAR FIELD, rather than asking a value object
11
+ # A dotted path names the scalar field, rather than asking a value object
12
12
  # to stand in for one. `correlates_by :end_to_end` would key a saga on
13
13
  # the whole ExternalTransfer::EndToEndReference — and what a non-scalar
14
- # correlation key even IS is representation-dependent (the object
14
+ # correlation key even is is representation-dependent (the object
15
15
  # itself? its serialised text?). `:"end_to_end.value"` reads the one
16
16
  # field with a single unambiguous rendering.
17
17
  def saga_correlation(process_manager, event)
18
18
  path = process_manager.correlates_by.to_s.split(".")
19
- # A LATER EVENT MAY ALREADY HOLD THE SCALAR. `reference.value` digs a
20
- # value object's field out of a FRESH declaration (TransferRequested's
21
- # `reference` IS a TransferReference) — but a downstream event this
19
+ # A later event may already hold the scalar. `reference.value` digs a
20
+ # value object's field out of a fresh declaration (TransferRequested's
21
+ # `reference` is a TransferReference) — but a downstream event this
22
22
  # same value was smuggled through as a passthrough argument
23
23
  # (AccountDebited's `reference:`, resolved by `dispatch_args` to the
24
24
  # bare correlation string) carries it as a scalar already, with
25
25
  # nothing left to dig. `"xfer-1".respond_to?(:[])` is true — String
26
- # has its OWN `[]` (substring indexing) — so checking for keyed
26
+ # has its own `[]` (substring indexing) — so checking for keyed
27
27
  # lookup explicitly, rather than "responds to `[]` at all", is what
28
28
  # stops the second segment from being read as a symbol index into a
29
29
  # string that has already arrived.
@@ -32,9 +32,9 @@ module Hecks
32
32
  end
33
33
  return value unless value.to_s.empty?
34
34
 
35
- # THE STAMP — `deliver_saga_dispatch` marks its own event before this
35
+ # **The stamp** — `deliver_saga_dispatch` marks its own event before this
36
36
  # saga's next step ever asks, for a leg whose command declares
37
- # NEITHER the correlation field itself nor the emitting aggregate's
37
+ # neither the correlation field itself nor the emitting aggregate's
38
38
  # own reference key (the two tiers above). command_interpreter/
39
39
  # argument_gate.rb names the old payload-only lookup "the weakest
40
40
  # part of the gate" : a correlation key arriving on a command only
@@ -48,17 +48,17 @@ module Hecks
48
48
  stamped = event.correlation && event.correlation[process_manager.correlation_head.to_s]
49
49
  return stamped unless stamped.nil? || stamped.to_s.empty?
50
50
 
51
- # A SELF-REFERENCING LEG carries the correlation forward under its
51
+ # A self-referencing leg carries the correlation forward under its
52
52
  # own emitting record's identity — `event.id`, not a field dug back
53
53
  # out of the payload. This used to read `event.payload[own_key]`
54
54
  # (`own_key` the aggregate's own reference-key convention, "wire",
55
- # "transfer"), which only ever held a value because LEGACY dispatch
55
+ # "transfer"), which only ever held a value because legacy dispatch
56
56
  # left the self-addressing key riding along in the payload
57
57
  # unfiltered. Routing separated from payload (`to:`/`with:`, the
58
58
  # facade's own `Handle#run` always uses it) closed exactly that
59
59
  # leak — correctly, since an addressing key is not a fact the
60
60
  # payload should carry — which left this tier reading an empty
61
- # Hash for any self-referencing leg with no OTHER declared
61
+ # Hash for any self-referencing leg with no other declared
62
62
  # attributes (`OnboardingCase.Clear`, `.Decline` — no `attribute`
63
63
  # lines at all): the saga silently stopped advancing, forever, for
64
64
  # exactly the leg this tier exists to correlate.
@@ -68,14 +68,14 @@ module Hecks
68
68
  # and unlike a payload dig it is populated by the record itself,
69
69
  # not by which dispatch convention the caller happened to use.
70
70
  #
71
- # GATED, still — a manually-dispatched command on a wholly
72
- # UNRELATED aggregate can share an event NAME this process_manager happens to
71
+ # Gated, still — a manually-dispatched command on a wholly
72
+ # unrelated aggregate can share an event name this process_manager happens to
73
73
  # handle (`Drawer.Take` also emits "Taken", the same name a
74
- # SAGA-DISPATCHED leg uses) with nothing this saga should read as
74
+ # saga-dispatched leg uses) with nothing this saga should read as
75
75
  # its own conversation. What makes a leg genuinely
76
76
  # self-referencing — the one fact worth trusting `event.id`
77
- # for — is that `correlates_by`'s own head field IS this event's
78
- # OWN aggregate's declared identity, not merely a same-shaped
77
+ # for — is that `correlates_by`'s own head field is this event's
78
+ # own aggregate's declared identity, not merely a same-shaped
79
79
  # name: `OnboardingCase.identity_heads` really does include
80
80
  # `:reference`, `correlates_by :"reference.value"`'s own head ;
81
81
  # `Drawer.identity_heads` is `[:number]`, nowhere close.
@@ -49,26 +49,26 @@ module Hecks
49
49
 
50
50
  private
51
51
 
52
- # THE CHECKPOINT WRITE, shared by every mutation site below —
53
- # holds `saga_mutex` across BOTH the in-memory Hash mutation and
52
+ # The checkpoint write, shared by every mutation site below —
53
+ # holds `saga_mutex` across both the in-memory Hash mutation and
54
54
  # the persistence write (§7), not just the Hash mutation alone:
55
- # two threads racing the SAME (process_manager, correlation) key
55
+ # two threads racing the same (process_manager, correlation) key
56
56
  # could otherwise interleave their writes out of order, silently
57
57
  # reordering a saga's own transition history — worse for the
58
58
  # adapters with no locking of their own (Heki) than for Postgres.
59
59
  # `deep_copy` guards against the exact shape of bug PR #175 itself
60
60
  # already found once (over-freezing a live, still-mutated Hash) —
61
- # never hand a persistence adapter the SAME object `advance_saga`/
61
+ # never hand a persistence adapter the same object `advance_saga`/
62
62
  # `unwind` go on to mutate in place; round-tripping through JSON
63
63
  # is also what guarantees the value is safe for every adapter that
64
64
  # itself calls `JSON.generate` on it.
65
65
  # `pending:` — see saga_pending_dispatch.rb. Injected into the
66
- # WRITTEN copy of memory only, never into `instance[:memory]`
66
+ # written copy of memory only, never into `instance[:memory]`
67
67
  # itself: every other reader of a live instance's memory
68
68
  # (`dispatch_args`'s "opening event memory" scope, the fuzzer's
69
69
  # own round-trip/shape checks, `saga_spec.rb`'s exact-equality
70
70
  # assertion against a fresh instance's seeded memory) sees exactly
71
- # what it always did. The marker exists ONLY in the persisted
71
+ # what it always did. The marker exists only in the persisted
72
72
  # blob, and only for as long as a dispatch cascade is genuinely
73
73
  # in flight for this instance.
74
74
  def checkpoint(process_manager, correlation, instance, domain, pending: nil)
@@ -82,8 +82,8 @@ module Hecks
82
82
  end
83
83
 
84
84
  # `deep_copy` is `JSON.parse(JSON.generate(hash), ...)`, which
85
- # only accepts an OBJECT at the top level — `completed_compensations`
86
- # is an ARRAY, so it gets its own wrap-and-unwrap rather than a
85
+ # only accepts an object at the top level — `completed_compensations`
86
+ # is an array, so it gets its own wrap-and-unwrap rather than a
87
87
  # second, parallel `deep_copy_array` reimplementing the same
88
88
  # round-trip. `|| []` — an instance from before this field existed
89
89
  # (or one that has never completed a compensable leg) rehydrates
@@ -105,15 +105,15 @@ module Hecks
105
105
  created = @registry.saga_mutex.synchronize do
106
106
  next false if @registry.saga_instances[process_manager.name].key?(correlation)
107
107
 
108
- # `.dup`, NOT THE SAME OBJECT — a fresh saga's own memory starts
109
- # as a COPY of the starting event's own payload, never the
108
+ # `.dup`, not the same object — a fresh saga's own memory starts
109
+ # as a copy of the starting event's own payload, never the
110
110
  # payload itself. A saga's own memory is meant to be written
111
111
  # into over its lifetime (remember-style, growing beyond what
112
112
  # the starting event carried) ; the payload it was seeded from
113
- # is a fact about something that ALREADY happened, logged and
113
+ # is a fact about something that already happened, logged and
114
114
  # emitted before the saga ever saw it. Sharing the one Hash
115
115
  # object between them means a write into the saga's own memory
116
- # is silently ALSO a write into an already-emitted event's own
116
+ # is silently also a write into an already-emitted event's own
117
117
  # payload — retroactively adding a field nothing announced.
118
118
  # `event.payload` is deep-frozen by `Event#emit!` by the time
119
119
  # this runs, so a naive in-place write here would raise
@@ -132,11 +132,11 @@ module Hecks
132
132
  instance: correlation, born: true, state: process_manager.states.first }
133
133
  end
134
134
 
135
- # THE MUTEX COVERS ONLY THE STATE-CHECK-AND-MUTATE-AND-CHECKPOINT
136
- # STEP, never the dispatch cascade that follows — `deliver_saga_
135
+ # The mutex covers only the state-check-and-mutate-and-checkpoint
136
+ # step, never the dispatch cascade that follows — `deliver_saga_
137
137
  # dispatch` calls `@door.reenter`, which can recursively re-enter
138
- # THIS SAME interpreter (a saga's own leg triggering another saga,
139
- # or itself again) on the SAME thread, and `Mutex` is not
138
+ # this same interpreter (a saga's own leg triggering another saga,
139
+ # or itself again) on the same thread, and `Mutex` is not
140
140
  # reentrant: holding it across that call would deadlock the
141
141
  # thread against itself the moment any real chain did that.
142
142
  def advance_saga(process_manager, event, domain)
@@ -156,7 +156,7 @@ module Hecks
156
156
  @registry.saga_log << record.merge(advanced: false, reason: "no conversation remembers #{correlation.inspect}")
157
157
  next false
158
158
  end
159
- # THE LEG IS CHOSEN BY (EVENT, CURRENT STATE) — C10.3. Read
159
+ # The leg is chosen by (event, current state) — C10.3. Read
160
160
  # under the mutex, against the state this instance holds right
161
161
  # now, so two legs on the same event from different states each
162
162
  # answer exactly when their own state is current.
@@ -189,7 +189,7 @@ module Hecks
189
189
  { on: event.name, from: from_state, to: to_state, dispatches: handler.dispatches.map(&:command_name) }
190
190
  end
191
191
 
192
- # THE SHARED TAIL of `advance_saga` and `unwind` — both are "guard,
192
+ # The shared tail of `advance_saga` and `unwind` — both are "guard,
193
193
  # mutate, checkpoint-with-pending" under the mutex (kept separate
194
194
  # per caller: `advance_saga`'s own guard also has to handle "no
195
195
  # instance at all", `unwind`'s doesn't), then this: log the real
@@ -197,11 +197,11 @@ module Hecks
197
197
  # pending marker once that cascade — however it ended — is done.
198
198
  def settle_transition(process_manager, event, handler, instance, correlation, domain, record, pre_state,
199
199
  drain_compensations: false)
200
- # `from:`/`to:` are the INSTANCE'S OWN real pre/post state — read
200
+ # `from:`/`to:` are the instance's own real pre/post state — read
201
201
  # back from `instance` itself, never re-derived from `handler.
202
202
  # from_state`/`handler.to_state` a second time. `Properties.saga_
203
203
  # advances_follow_declared_handlers` (fuzzing/properties.rb) builds
204
- # its OWN "declared edges" list from this SAME handler object (via
204
+ # its own "declared edges" list from this same handler object (via
205
205
  # `process_manager.handlers`), so a log entry that just echoed `handler.
206
206
  # from_state`/`handler.to_state` back could never disagree with
207
207
  # that list no matter what the runtime actually did — the entry
@@ -213,10 +213,10 @@ module Hecks
213
213
  # real mismatch instead of vanishing into a tautology.
214
214
  @registry.saga_log << record.merge(advanced: true, from: pre_state, to: instance[:state])
215
215
 
216
- # DERIVED COMPENSATION FIRST, NEWEST-FIRST — only for `unwind`'s
217
- # own call (`drain_compensations: true`): every leg THIS INSTANCE
216
+ # **Derived compensation first, newest-first** — only for `unwind`'s
217
+ # own call (`drain_compensations: true`): every leg this instance
218
218
  # actually completed that declared its own `compensates`, popped
219
- # and dispatched in reverse completion order, BEFORE any
219
+ # and dispatched in reverse completion order, before any
220
220
  # hand-written `on :refused` dispatches below — coexistence, not
221
221
  # replacement. Drained (not just read) as it fires: a saga's own
222
222
  # `on :refused` handler is guarded against re-entry by `unwind`'s
@@ -234,19 +234,19 @@ module Hecks
234
234
  deliver_saga_dispatch(process_manager, spec, event, instance, correlation, domain)
235
235
  end
236
236
 
237
- # THE CLEAR — guarded by the SAME identity check `end_saga`'s own
237
+ # **The clear** — guarded by the same identity check `end_saga`'s own
238
238
  # `.delete` return value implies: `deliver_saga_dispatch`'s
239
- # `@door.reenter` can synchronously trigger this SAME correlation's
239
+ # `@door.reenter` can synchronously trigger this same correlation's
240
240
  # `ends_on` event as a nested reaction (a leg's own dispatch is
241
241
  # what makes the saga's terminal event fire), which deletes this
242
242
  # row from the store before this line ever runs. Writing the
243
- # clear unconditionally would RESURRECT a legitimately-ended saga
243
+ # clear unconditionally would resurrect a legitimately-ended saga
244
244
  # — this diff's own first attempt did exactly that, caught by
245
245
  # `saga_durability_spec.rb`'s "deletes the checkpoint once a saga
246
246
  # genuinely ends" — so this only re-checkpoints when `instance`
247
- # is still THE SAME object `@saga_instances` holds for this
247
+ # is still the same object `@saga_instances` holds for this
248
248
  # correlation (`.equal?`, not `==`: a fresh saga reborn under the
249
- # same correlation between then and now is a DIFFERENT instance,
249
+ # same correlation between then and now is a different instance,
250
250
  # and writing this stale one's state onto that one's row would be
251
251
  # its own corruption). Under the mutex — dispatching is over by
252
252
  # now, so this is not the reentrancy hazard `advance_saga`'s own
@@ -264,9 +264,9 @@ module Hecks
264
264
  args = dispatch_args(process_manager, spec, event, instance, correlation)
265
265
  record = { process_manager: process_manager.name, instance: correlation, dispatch: spec.command_name }
266
266
 
267
- # THE RAW INPUTS `args` WAS RESOLVED FROM, captured alongside the
267
+ # The raw inputs `args` was resolved from, captured alongside the
268
268
  # result — never re-derived from history[:saga_instances] later
269
- # (that only ever holds the FINAL memory, after every step has
269
+ # (that only ever holds the final memory, after every step has
270
270
  # run; this dispatch's own memory, at the moment it actually
271
271
  # fired, is a different fact for a saga whose memory keeps
272
272
  # changing). `spec.with_spec.empty?` skipped: nothing declared
@@ -282,7 +282,7 @@ module Hecks
282
282
  end
283
283
 
284
284
  if @door.reaction_depth_reached?
285
- # THE CEILING IS NOT A DOMAIN DECISION EITHER — same reasoning as a
285
+ # The ceiling is not a domain decision either — same reasoning as a
286
286
  # crash, below — but unlike a crash there is nothing ambiguous
287
287
  # about it: the leg unambiguously did not run, so it unwinds
288
288
  # exactly like a refusal instead of stranding the instance for a
@@ -301,18 +301,18 @@ module Hecks
301
301
  attempt = 0
302
302
  compensation_recorded = false
303
303
  begin
304
- # RECORDED BEFORE DISPATCHING, not after `@door.reenter`
305
- # returns — `@door.reenter` can recursively RE-ENTER THIS SAME
306
- # saga interpreter (the event THIS dispatch emits triggers a
307
- # LATER handler, which can itself refuse and unwind) entirely
308
- # WITHIN this one call, before it ever returns here. Recording
309
- # "after reenter succeeds" would be too late for a NESTED
304
+ # Recorded before dispatching, not after `@door.reenter`
305
+ # returns — `@door.reenter` can recursively re-enter this same
306
+ # saga interpreter (the event this dispatch emits triggers a
307
+ # later handler, which can itself refuse and unwind) entirely
308
+ # within this one call, before it ever returns here. Recording
309
+ # "after reenter succeeds" would be too late for a nested
310
310
  # refusal to ever see this leg's own compensation — found
311
311
  # live: Settlement's own AccountDebited handler refuses
312
- # Account.Credit and unwinds from INSIDE Account.Debit's own
312
+ # Account.Credit and unwinds from inside Account.Debit's own
313
313
  # `reenter` call, so "delivered: true, then record" left the
314
314
  # ledger empty at the exact moment it was needed. Popped back
315
- # off in the rescues below if THIS leg's own attempt is the
315
+ # off in the rescues below if this leg's own attempt is the
316
316
  # one that failed — never left recorded for a refusal that
317
317
  # was never this leg's own to compensate.
318
318
  if spec.compensates && !compensation_recorded
@@ -336,7 +336,7 @@ module Hecks
336
336
  rescue *DOMAIN_REFUSALS => e
337
337
  unrecord_compensation(instance, correlation, domain, process_manager) if compensation_recorded
338
338
  # Same rule as the policy interpreter : a refusal by the target is
339
- # a recorded outcome, and the leg that raised it UNWINDS — see
339
+ # a recorded outcome, and the leg that raised it unwinds — see
340
340
  # `unwind`'s own comment for why the procedure runs its
341
341
  # compensation here rather than leaving the money (or whatever
342
342
  # else a leg moved) sitting out.
@@ -345,13 +345,13 @@ module Hecks
345
345
  rescue StandardError => e
346
346
  unrecord_compensation(instance, correlation, domain, process_manager) if compensation_recorded
347
347
  compensation_recorded = false
348
- # A DEFECT, not a refusal — see PolicyInterpreter#deliver's own
348
+ # A defect, not a refusal — see PolicyInterpreter#deliver's own
349
349
  # comment for the full reasoning: the same DOMAIN_REFUSALS split,
350
350
  # and the same "the triggering command already succeeded and
351
351
  # persisted by the time this runs" fact that makes catching it
352
352
  # here safe rather than reckless.
353
353
  #
354
- # UNLIKE a refusal, a crash is not a decision the domain made, so
354
+ # Unlike a refusal, a crash is not a decision the domain made, so
355
355
  # it does not unwind on the first failure — MAX_DEFECT_RETRIES
356
356
  # gives a transient failure (a DB timeout, a race, a cold start)
357
357
  # a chance to clear on its own, retrying the identical dispatch,
@@ -381,36 +381,36 @@ module Hecks
381
381
  end
382
382
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
383
383
 
384
- # THE ROLLBACK HALF of `deliver_saga_dispatch`'s own speculative
384
+ # The rollback half of `deliver_saga_dispatch`'s own speculative
385
385
  # pre-record (that method's own comment for why it has to be
386
- # speculative) — THIS leg's own attempt is the one that failed,
386
+ # speculative) — this leg's own attempt is the one that failed,
387
387
  # so whatever was just pushed for it was never actually earned.
388
388
  # `.pop`, not a search-and-delete: nothing else can have pushed
389
- # AFTER this leg's own entry without this leg's own `@door.
389
+ # after this leg's own entry without this leg's own `@door.
390
390
  # reenter` call having already returned (the recursive re-entry
391
- # this whole mechanism exists for only ever runs BETWEEN this
391
+ # this whole mechanism exists for only ever runs between this
392
392
  # push and this leg's own return, and a nested refusal that
393
393
  # consumed it already popped it itself — this rollback only ever
394
- # runs for THIS leg's own, still-present entry).
394
+ # runs for this leg's own, still-present entry).
395
395
  def unrecord_compensation(instance, correlation, domain, process_manager)
396
396
  instance[:completed_compensations].pop
397
397
  checkpoint(process_manager, correlation, instance, domain)
398
398
  end
399
399
 
400
- # A refused leg UNWINDS — the procedure runs the leg declared `on :refused`,
400
+ # A refused leg unwinds — the procedure runs the leg declared `on :refused`,
401
401
  # which is where the compensation lives. So does a leg that hit the
402
402
  # reaction-depth ceiling, and so does a leg that crashed and stayed
403
403
  # crashing through MAX_DEFECT_RETRIES — see `deliver_saga_dispatch`'s own
404
404
  # comments for why each of those is safe to route here.
405
405
  #
406
- # Until this existed a refusal was RECORDED and nothing else happened. The
406
+ # Until this existed a refusal was recorded and nothing else happened. The
407
407
  # wire's thousand was taken from the source, refused by the destination, and
408
408
  # sat nowhere until a human dispatched the reversal by hand ; banking's
409
409
  # settlement left a debit standing with no credit and no compensation at all.
410
410
  # Both bluebooks had written the compensating leg. Nothing armed it.
411
411
  #
412
- # A compensation that is itself refused does NOT unwind again, and needs no
413
- # flag to stop it: the state moves to the compensating leg's to_state BEFORE
412
+ # A compensation that is itself refused does not unwind again, and needs no
413
+ # flag to stop it: the state moves to the compensating leg's to_state before
414
414
  # its dispatches run, so a second refusal finds the instance no longer in
415
415
  # from_state and records that instead. The check is the guard.
416
416
  def unwind(process_manager, event, instance, correlation, domain)
@@ -442,7 +442,7 @@ module Hecks
442
442
 
443
443
  # See `settle_transition`'s own comment on `pre_state`/
444
444
  # `instance[:state]` — the real observed transition, not a
445
- # second read of the SAME handler object `Properties.saga_
445
+ # second read of the same handler object `Properties.saga_
446
446
  # advances_follow_declared_handlers` checks this log against.
447
447
  # `drain_compensations: true` — only `unwind`'s own call site
448
448
  # fires derived compensation; `advance_saga`'s own call never
@@ -451,16 +451,16 @@ module Hecks
451
451
  drain_compensations: true)
452
452
  end
453
453
 
454
- # A DERIVED COMPENSATION — `entry[:args]` is already resolved
454
+ # A derived compensation — `entry[:args]` is already resolved
455
455
  # (`record_completed_compensation`'s own comment for why), so this
456
456
  # skips `dispatch_args` entirely and goes straight to delivery,
457
- # through the SAME retry-on-defect path an ordinary forward leg
457
+ # through the same retry-on-defect path an ordinary forward leg
458
458
  # uses. Never re-enters `unwind` on its own failure — a
459
459
  # compensation that itself refuses is a real, pre-existing gap
460
460
  # this feature makes visible rather than closes (see this file's
461
461
  # own class-level notes); `compensation_failed: true` tags it
462
462
  # distinctly in the log instead of recording it identically to an
463
- # ordinary failed delivery, and every OTHER completed compensation
463
+ # ordinary failed delivery, and every other completed compensation
464
464
  # still queued still gets its own attempt.
465
465
  def deliver_derived_compensation(process_manager, entry, correlation, domain)
466
466
  record = { process_manager: process_manager.name, instance: correlation, dispatch: entry[:command_name] }
@@ -506,40 +506,40 @@ module Hecks
506
506
  )
507
507
  end
508
508
 
509
- # BUG#6 — UNCONDITIONALLY THE SAGA'S OWN HOME DOMAIN, never inferred
509
+ # BUG#6 — unconditionally the saga's own home domain, never inferred
510
510
  # from `command_name`'s own shape. This used to guess: a leftover
511
511
  # `::` after `Naming.command_ref`'s own rewrite was read as "already
512
512
  # domain-qualified" and left alone. That heuristic cannot actually
513
513
  # tell a genuinely cross-domain reference (`Banking::Account::
514
- # Debit` -> one `::` survives) apart from a SAME-DOMAIN entity
514
+ # Debit` -> one `::` survives) apart from a same-domain entity
515
515
  # command reference (`Manifest::Slot::Fill` -> one `::` survives
516
516
  # too, for an unrelated reason — entity nesting, not a domain
517
517
  # qualifier) — both collapse to the identical "one `::` left" shape,
518
518
  # and the string alone carries no further signal to split them
519
519
  # (confirmed against `Naming.command_ref`'s own rewrite: it only
520
- # ever strips the LAST `::`, so the count of what remains is blind
520
+ # ever strips the last `::`, so the count of what remains is blind
521
521
  # to why it's there). Picking the cross-domain reading unconditionally
522
522
  # left `qa/stress_domains/waybill`'s own `Packing` saga dispatching
523
- # `Manifest::Slot::Fill` — an entity command in its OWN domain —
523
+ # `Manifest::Slot::Fill` — an entity command in its own domain —
524
524
  # unprefixed, so `Naming.split_verb` read "Manifest" as a domain
525
525
  # name instead of this chapter's own aggregate, and the dispatch
526
526
  # failed with `UnknownVerb`, silently recorded as an ordinary
527
527
  # domain refusal rather than surfacing as the real bug it is.
528
528
  #
529
- # THE FIX MIRRORS `PolicyInterpreter#deliver`'s OWN MECHANISM,
529
+ # The fix mirrors `PolicyInterpreter#deliver`'s own mechanism,
530
530
  # which never had this bug: a policy's cross-domain target is a
531
- # SEPARATE, EXPLICIT field (`Policy#target_domain`, set only by the
531
+ # separate, explicit field (`Policy#target_domain`, set only by the
532
532
  # `across` keyword) — `deliver` unconditionally builds
533
533
  # `"#{policy.target_domain || domain}::#{policy.trigger_command}"`,
534
- # never asking whether `trigger_command` LOOKS already-qualified.
534
+ # never asking whether `trigger_command` looks already-qualified.
535
535
  # A saga's own `dispatch`/`compensates` has no such explicit field
536
- # and no keyword to set one — and, confirmed against the ENTIRE
536
+ # and no keyword to set one — and, confirmed against the entire
537
537
  # corpus (banking's Onboarding/Settlement/ExternalSettlement,
538
538
  # quality_control's BugCiWatch, and this domain's own Packing),
539
539
  # no saga anywhere ever dispatches genuinely cross-domain: "every
540
540
  # command a saga fires lands inside its own bluebook chapter"
541
541
  # (`Projections::Diagrams#saga_diagram`'s own comment, written
542
- # independently of this fix and still true). So the home domain IS
542
+ # independently of this fix and still true). So the home domain is
543
543
  # the only explicit context a saga dispatch ever has — this applies
544
544
  # it the same way `deliver` applies its own default (no `across`)
545
545
  # case, without inventing a keyword nothing in the corpus needs.