hecks 1.2.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 (358) 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 +60 -2
  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/postgres_era.adapter +5 -0
  23. data/lib/hecks/adapters/driven/prism.rb +4 -4
  24. data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
  25. data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
  26. data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
  27. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
  28. data/lib/hecks/adapters/driven/sqlite.rb +181 -21
  29. data/lib/hecks/adapters/driven.rb +4 -4
  30. data/lib/hecks/adapters/driving/github_webhook.rb +145 -0
  31. data/lib/hecks/behaviors/dsl.rb +2 -2
  32. data/lib/hecks/behaviors/expectations.rb +51 -23
  33. data/lib/hecks/behaviors/ir.rb +1 -1
  34. data/lib/hecks/behaviors/rspec.rb +1 -1
  35. data/lib/hecks/behaviors/runner.rb +2 -2
  36. data/lib/hecks/behaviors.rb +1 -1
  37. data/lib/hecks/bluebook/aggregate.rb +13 -13
  38. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
  39. data/lib/hecks/bluebook/assembly/build.rb +1 -1
  40. data/lib/hecks/bluebook/assembly/contract.rb +39 -16
  41. data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
  42. data/lib/hecks/bluebook/assembly/marks.rb +18 -18
  43. data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
  44. data/lib/hecks/bluebook/assembly.rb +9 -9
  45. data/lib/hecks/bluebook/attribute.rb +9 -9
  46. data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
  47. data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
  48. data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
  49. data/lib/hecks/bluebook/behaviour/command.rb +23 -23
  50. data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -3
  51. data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
  52. data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
  53. data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
  54. data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
  55. data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
  56. data/lib/hecks/bluebook/behaviour/query.rb +1 -1
  57. data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
  58. data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
  59. data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
  60. data/lib/hecks/bluebook/capabilities.rb +27 -0
  61. data/lib/hecks/bluebook/chapter.rb +28 -9
  62. data/lib/hecks/bluebook/command.rb +12 -12
  63. data/lib/hecks/bluebook/domain_port.rb +9 -9
  64. data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
  65. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
  66. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +96 -96
  67. data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
  68. data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
  69. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
  70. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
  71. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
  72. data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
  73. data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
  74. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
  75. data/lib/hecks/bluebook/dsl/entity_builder.rb +56 -56
  76. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
  77. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
  78. data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
  79. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
  80. data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
  81. data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
  82. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
  83. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +35 -35
  84. data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
  85. data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
  86. data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
  87. data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
  88. data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
  89. data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
  90. data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
  91. data/lib/hecks/bluebook/entity.rb +11 -11
  92. data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
  93. data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
  94. data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
  95. data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
  96. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
  97. data/lib/hecks/bluebook/expression/resolver.rb +60 -62
  98. data/lib/hecks/bluebook/hexagon.rb +1 -1
  99. data/lib/hecks/bluebook/lifecycle.rb +1 -1
  100. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
  101. data/lib/hecks/bluebook/meta_validator/judge.rb +122 -86
  102. data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
  103. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
  104. data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
  105. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
  106. data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
  107. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
  108. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
  109. data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
  110. data/lib/hecks/bluebook/meta_validator.rb +70 -70
  111. data/lib/hecks/bluebook/model_check.rb +301 -84
  112. data/lib/hecks/bluebook/pattern_subset.rb +9 -9
  113. data/lib/hecks/bluebook/policy.rb +15 -13
  114. data/lib/hecks/bluebook/process_manager.rb +14 -14
  115. data/lib/hecks/bluebook/project_register.rb +6 -6
  116. data/lib/hecks/bluebook/query.rb +4 -4
  117. data/lib/hecks/bluebook/read_model.rb +14 -14
  118. data/lib/hecks/bluebook/reference.rb +8 -8
  119. data/lib/hecks/bluebook/smoke_test.rb +19 -19
  120. data/lib/hecks/bluebook/synthesizer.rb +12 -12
  121. data/lib/hecks/bluebook/translation.rb +4 -4
  122. data/lib/hecks/bluebook/value_object.rb +6 -6
  123. data/lib/hecks/bluebook.rb +2 -2
  124. data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
  125. data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
  126. data/lib/hecks/codemod.rb +36 -35
  127. data/lib/hecks/construct.rb +6 -6
  128. data/lib/hecks/corpus.rb +317 -0
  129. data/lib/hecks/deprecation.rb +95 -0
  130. data/lib/hecks/doc/reference.rb +19 -19
  131. data/lib/hecks/embryonaut_bluebook.rb +11 -11
  132. data/lib/hecks/facade/cli_door.rb +69 -10
  133. data/lib/hecks/facade/cli_runner.rb +105 -24
  134. data/lib/hecks/facade/command_request.rb +23 -0
  135. data/lib/hecks/facade/handle.rb +79 -32
  136. data/lib/hecks/facade/json_door.rb +106 -25
  137. data/lib/hecks/facade/surface/aggregate_door.rb +42 -27
  138. data/lib/hecks/facade/surface/chapter.rb +26 -17
  139. data/lib/hecks/facade/surface.rb +16 -3
  140. data/lib/hecks/facade.rb +15 -4
  141. data/lib/hecks/forms/app.rb +46 -30
  142. data/lib/hecks/forms/command_form_renderer.rb +70 -9
  143. data/lib/hecks/forms/field_renderer.rb +142 -6
  144. data/lib/hecks/forms/field_shape.rb +183 -20
  145. data/lib/hecks/forms/html.rb +51 -7
  146. data/lib/hecks/forms/index_renderer.rb +14 -2
  147. data/lib/hecks/forms/params.rb +120 -23
  148. data/lib/hecks/forms/port_argument.rb +2 -2
  149. data/lib/hecks/forms/query_form_renderer.rb +2 -2
  150. data/lib/hecks/forms/record_renderer.rb +2 -2
  151. data/lib/hecks/forms/record_table.rb +1 -1
  152. data/lib/hecks/forms/value_object_shape.rb +3 -3
  153. data/lib/hecks/forms.rb +24 -4
  154. data/lib/hecks/fqn.rb +1 -1
  155. data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
  156. data/lib/hecks/framework.rb +48 -17
  157. data/lib/hecks/freezer.rb +11 -11
  158. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
  159. data/lib/hecks/fuzzing/combination_miner.rb +119 -0
  160. data/lib/hecks/fuzzing/concurrent_dispatch.rb +361 -0
  161. data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
  162. data/lib/hecks/fuzzing/differential.rb +158 -0
  163. data/lib/hecks/fuzzing/domain_generator.rb +694 -0
  164. data/lib/hecks/fuzzing/era_boundary.rb +124 -0
  165. data/lib/hecks/fuzzing/form_census.rb +199 -0
  166. data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
  167. data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
  168. data/lib/hecks/fuzzing/isolated_boot.rb +226 -38
  169. data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
  170. data/lib/hecks/fuzzing/persistence_parity.rb +161 -0
  171. data/lib/hecks/fuzzing/properties/corrections.rb +100 -0
  172. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +321 -41
  173. data/lib/hecks/fuzzing/properties/guards.rb +129 -26
  174. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
  175. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +31 -11
  176. data/lib/hecks/fuzzing/properties/outbox.rb +142 -0
  177. data/lib/hecks/fuzzing/properties/querying.rb +17 -17
  178. data/lib/hecks/fuzzing/properties.rb +84 -39
  179. data/lib/hecks/fuzzing/qa_settings.rb +152 -0
  180. data/lib/hecks/fuzzing/replay.rb +224 -114
  181. data/lib/hecks/fuzzing/rotation_priority.rb +94 -0
  182. data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
  183. data/lib/hecks/fuzzing/self_consistency.rb +676 -0
  184. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +526 -0
  185. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +101 -30
  186. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +55 -8
  187. data/lib/hecks/fuzzing/sequence_generator/picker.rb +31 -11
  188. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +123 -32
  189. data/lib/hecks/fuzzing/sequence_generator.rb +152 -31
  190. data/lib/hecks/fuzzing/shrinker.rb +197 -0
  191. data/lib/hecks/fuzzing/structural_skips.rb +39 -0
  192. data/lib/hecks/fuzzing/sweep_depth.rb +53 -0
  193. data/lib/hecks/fuzzing/target_capabilities.rb +180 -0
  194. data/lib/hecks/fuzzing/value_generator.rb +66 -14
  195. data/lib/hecks/fuzzing.rb +11 -0
  196. data/lib/hecks/grammar/evolve.rb +10 -10
  197. data/lib/hecks/grammar.rb +7 -7
  198. data/lib/hecks/ir.rb +13 -13
  199. data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
  200. data/lib/hecks/language/bluebook/policy.bluebook +11 -1
  201. data/lib/hecks/language/bluebook/vocabulary.bluebook +365 -15
  202. data/lib/hecks/language/oidc.json +5 -0
  203. data/lib/hecks/literal.rb +9 -9
  204. data/lib/hecks/naming.rb +89 -21
  205. data/lib/hecks/ports/access_control.rb +58 -2
  206. data/lib/hecks/ports/agent/answers.rb +83 -6
  207. data/lib/hecks/ports/agent.rb +119 -35
  208. data/lib/hecks/ports/authentication.rb +44 -4
  209. data/lib/hecks/ports/authorization.rb +53 -11
  210. data/lib/hecks/ports/clock.rb +42 -23
  211. data/lib/hecks/ports/extraction.rb +16 -0
  212. data/lib/hecks/ports/identity_assignment.rb +24 -2
  213. data/lib/hecks/ports/identity_generation.rb +17 -3
  214. data/lib/hecks/ports/identity_resolution.rb +18 -1
  215. data/lib/hecks/ports/loading.rb +4 -0
  216. data/lib/hecks/ports/persistence/append_only.rb +172 -8
  217. data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
  218. data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
  219. data/lib/hecks/ports/persistence/execution.rb +4 -0
  220. data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
  221. data/lib/hecks/ports/persistence/plugin.rb +42 -4
  222. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
  223. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
  224. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
  225. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
  226. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
  227. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
  228. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
  229. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
  230. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
  231. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +160 -58
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +28 -5
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +44 -6
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +57 -4
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +338 -83
  242. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
  243. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
  244. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
  245. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +42 -8
  246. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
  247. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
  248. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
  249. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -19
  250. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
  251. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -1
  252. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
  253. data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
  254. data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
  255. data/lib/hecks/ports/persistence/repository_factory.rb +29 -6
  256. data/lib/hecks/ports/persistence/state_codec.rb +319 -0
  257. data/lib/hecks/ports/persistence.rb +36 -1
  258. data/lib/hecks/ports/projection.rb +61 -7
  259. data/lib/hecks/ports/query/in_memory.rb +3 -3
  260. data/lib/hecks/ports/query/ordering.rb +6 -6
  261. data/lib/hecks/ports/query.rb +35 -0
  262. data/lib/hecks/projections/bootstrap_table.rb +112 -0
  263. data/lib/hecks/projections/diagrams.rb +75 -75
  264. data/lib/hecks/projections/glossary/html.rb +250 -0
  265. data/lib/hecks/projections/glossary/markdown.rb +105 -0
  266. data/lib/hecks/projections/glossary/mermaid.rb +110 -0
  267. data/lib/hecks/projections/glossary/page.css +271 -0
  268. data/lib/hecks/projections/glossary/page.js +72 -0
  269. data/lib/hecks/projections/glossary/sections.rb +17 -0
  270. data/lib/hecks/projections/glossary/sentences.rb +205 -0
  271. data/lib/hecks/projections/glossary.rb +214 -286
  272. data/lib/hecks/projections/ir.rb +1 -1
  273. data/lib/hecks/projections/model/deviations.rb +18 -17
  274. data/lib/hecks/projections/model.rb +25 -21
  275. data/lib/hecks/projections/oidc.rb +7 -7
  276. data/lib/hecks/projections/parser_table.rb +5 -5
  277. data/lib/hecks/projections/reference.rb +3 -3
  278. data/lib/hecks/projections/rust_vocabulary.rb +443 -0
  279. data/lib/hecks/projections/shape.rb +2 -2
  280. data/lib/hecks/projections/statements.rb +11 -11
  281. data/lib/hecks/projections/vocabulary.rb +9 -9
  282. data/lib/hecks/projections.rb +5 -3
  283. data/lib/hecks/projector/cli_projector.rb +29 -29
  284. data/lib/hecks/projector/docs_projector.rb +13 -13
  285. data/lib/hecks/projector/exporter.rb +42 -21
  286. data/lib/hecks/projector/ir_projector.rb +1 -1
  287. data/lib/hecks/projector/narrate_projector.rb +15 -22
  288. data/lib/hecks/projector/target.rb +13 -13
  289. data/lib/hecks/projector.rb +15 -15
  290. data/lib/hecks/query_ir.rb +47 -47
  291. data/lib/hecks/query_specification/common/comparators.rb +19 -3
  292. data/lib/hecks/query_specification/common/comparison.rb +132 -24
  293. data/lib/hecks/query_specification/common/dsl.rb +65 -9
  294. data/lib/hecks/query_specification/common/null_policy.rb +57 -13
  295. data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
  296. data/lib/hecks/query_specification/common/options.rb +25 -0
  297. data/lib/hecks/query_specification/field_path.rb +69 -15
  298. data/lib/hecks/query_specification/hop_path.rb +57 -20
  299. data/lib/hecks/query_specification/read_model/specification.rb +4 -0
  300. data/lib/hecks/rendering.rb +3 -3
  301. data/lib/hecks/router/namespace_installer.rb +3 -3
  302. data/lib/hecks/router.rb +1 -1
  303. data/lib/hecks/runtime/aggregate_lock.rb +11 -11
  304. data/lib/hecks/runtime/boot_gates.rb +3 -3
  305. data/lib/hecks/runtime/caller.rb +8 -8
  306. data/lib/hecks/runtime/capability_graph.rb +2 -2
  307. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
  308. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +64 -67
  309. data/lib/hecks/runtime/command_interpreter.rb +159 -102
  310. data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
  311. data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
  312. data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
  313. data/lib/hecks/runtime/command_rules/emission.rb +18 -1
  314. data/lib/hecks/runtime/command_rules/references.rb +94 -19
  315. data/lib/hecks/runtime/command_rules.rb +3 -0
  316. data/lib/hecks/runtime/dependency_planning.rb +11 -11
  317. data/lib/hecks/runtime/dispatcher.rb +283 -90
  318. data/lib/hecks/runtime/entity_element.rb +282 -46
  319. data/lib/hecks/runtime/entity_interpreter.rb +160 -69
  320. data/lib/hecks/runtime/errors.rb +19 -19
  321. data/lib/hecks/runtime/event.rb +6 -6
  322. data/lib/hecks/runtime/identity.rb +22 -22
  323. data/lib/hecks/runtime/instance.rb +39 -14
  324. data/lib/hecks/runtime/interpreting.rb +12 -12
  325. data/lib/hecks/runtime/invocation.rb +276 -0
  326. data/lib/hecks/runtime/loader.rb +14 -14
  327. data/lib/hecks/runtime/outbox.rb +23 -23
  328. data/lib/hecks/runtime/policy_interpreter.rb +54 -54
  329. data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
  330. data/lib/hecks/runtime/query_interpreter.rb +111 -56
  331. data/lib/hecks/runtime/reaction_invocation.rb +76 -9
  332. data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
  333. data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
  334. data/lib/hecks/runtime/reference_hop.rb +6 -6
  335. data/lib/hecks/runtime/refusal_wording.rb +92 -112
  336. data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
  337. data/lib/hecks/runtime/registry/verification.rb +36 -26
  338. data/lib/hecks/runtime/registry.rb +56 -27
  339. data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
  340. data/lib/hecks/runtime/routing.rb +10 -88
  341. data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
  342. data/lib/hecks/runtime/saga_interpreter.rb +93 -56
  343. data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
  344. data/lib/hecks/runtime/tenant_check.rb +9 -9
  345. data/lib/hecks/runtime/tenant_scope.rb +5 -5
  346. data/lib/hecks/runtime/value/admission.rb +75 -30
  347. data/lib/hecks/runtime/value/coercion.rb +379 -226
  348. data/lib/hecks/runtime/value/entity_list_coercion.rb +248 -0
  349. data/lib/hecks/runtime/value.rb +28 -23
  350. data/lib/hecks/runtime.rb +7 -7
  351. data/lib/hecks/storehouse.rb +64 -64
  352. data/lib/hecks/version.rb +3 -3
  353. data/lib/hecks/vocabulary.rb +207 -5
  354. data/lib/hecks.rb +13 -11
  355. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
  356. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
  357. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
  358. metadata +43 -2
@@ -1,6 +1,7 @@
1
1
  require "fileutils"
2
2
  require "tmpdir"
3
3
  require_relative "isolated_boot"
4
+ require_relative "self_consistency"
4
5
  require_relative "../query_specification/common/comparators"
5
6
  require_relative "../query_specification/common/where_clause"
6
7
  require_relative "../query_specification/field_path"
@@ -8,17 +9,17 @@ require_relative "../ports/query/in_memory"
8
9
 
9
10
  module Hecks
10
11
  module Fuzzing
11
- # A step list, replayed IN-PROCESS against a fresh boot — the same
12
+ # A step list, replayed in-process against a fresh boot — the same
12
13
  # copy-to-tmp-and-reset preamble SequenceGenerator#call uses, and the
13
14
  # same observable surface bin/run prints (instances, events,
14
15
  # refusals, reactions, sagas, queries), but returned as data rather
15
16
  # than JSON on stdout.
16
17
  #
17
- # NOT what SequenceGenerator itself dispatches through while
18
+ # Not what SequenceGenerator itself dispatches through while
18
19
  # generating — that inline execution feeds the picker's own
19
20
  # known_ids tracking and stays exactly as it is. This exists for
20
- # everything ELSE that needs "given a step list, boot fresh and tell
21
- # me what happened": bin/fuzz recomputing a shrink candidate's TRUE
21
+ # everything else that needs "given a step list, boot fresh and tell
22
+ # me what happened": bin/fuzz recomputing a shrink candidate's true
22
23
  # event count (removing a step changes what the sequence actually
23
24
  # produces, so a shrunk candidate cannot reuse the original claim),
24
25
  # and the declared-property checks in properties.rb. Both want it
@@ -34,32 +35,32 @@ module Hecks
34
35
  module Replay
35
36
  module_function
36
37
 
37
- # THE AD HOC FILTER'S OWN COMPARATOR ROSTER — read directly from
38
+ # The ad hoc filter's own comparator roster — read directly from
38
39
  # QuerySpecification::Common::COMPARATORS (the same nine names
39
40
  # Vocabulary::QueryComparator declares), never re-typed. A
40
41
  # declared bluebook query never sees an `op:` outside this set —
41
- # `admits: "Vocabulary::QueryComparator"` refuses one at DECLARE
42
+ # `admits: "Vocabulary::QueryComparator"` refuses one at declare
42
43
  # time — but a `"filter"`-shaped query step (below) has no
43
44
  # declare-time gate at all, so this method gates it here instead.
44
45
  #
45
- # NOT rust/src/kernel/query_comparators.rs's own ground truth —
46
+ # Not rust/src/kernel/query_comparators.rs's own ground truth —
46
47
  # that hand-maintained Rust enum is missing `none_in_state` (the
47
48
  # 9th comparator, added after the enum was written) and has
48
49
  # already drifted; do not treat it as authoritative until item #9
49
50
  # of the whole-project table-unification survey closes that gap.
50
51
  FILTER_COMPARATORS = Hecks::QuerySpecification::Common::COMPARATORS.map(&:to_s).freeze
51
52
 
52
- # THE TWO CLASSES `#enforce_givens`/`#enforce_lifecycle_guard`
53
+ # The two classes `#enforce_givens`/`#enforce_lifecycle_guard`
53
54
  # themselves ever raise — see Admissibility's own doc comment,
54
- # `command_rules/admissibility.rb`. Any OTHER DOMAIN_REFUSAL a step
55
+ # `command_rules/admissibility.rb`. Any other DOMAIN_REFUSAL a step
55
56
  # raises (TypeMismatch, EnsuresNotMet, InvariantViolation, ...)
56
- # proves the guard itself did NOT fire, since it runs first in
57
+ # proves the guard itself did not fire, since it runs first in
57
58
  # DISPATCH_ORDER.
58
59
  GUARD_REFUSAL_CLASSES = [Runtime::GivenNotMet, Runtime::LifecycleRefused].freeze
59
60
 
60
61
  # One tightly ordered loop over steps, with several "oracle" snapshots
61
62
  # (reaction_mark, fan_out_snapshot, guard_check, mutation_trace) that
62
- # must be taken at very specific points RELATIVE TO dispatch — see
63
+ # must be taken at very specific points relative to dispatch — see
63
64
  # fan_out_snapshot's own comment above for the real, previously-
64
65
  # shipped bug this exact before/after ordering fixes. Splitting this
65
66
  # into smaller methods would mean threading five-plus oracle-state
@@ -71,22 +72,43 @@ module Hecks
71
72
  # rubocop:disable-next Metrics/CyclomaticComplexity
72
73
  # rubocop:disable-next Metrics/MethodLength
73
74
  # rubocop:disable-next Metrics/PerceivedComplexity
74
- def call(domain_path, steps, adapter: :memory)
75
- # See isolated_boot.rb's own header: resets data/ AND rebinds
75
+ # `self_consistency:` — off by default, same "existing callers see no
76
+ # change" contract `adapter:` already has. `bin/qa_sweep` is the one
77
+ # real caller that opts in (gated by `QualityControlDials::
78
+ # SELF_CONSISTENCY_CHECKS`/`--self-consistency`): `bin/fuzz`,
79
+ # `Properties.check`'s own callers, and every existing spec keep
80
+ # calling this with no second axis of comparison at all, exactly as
81
+ # before. Computed here, not by a caller reading `runtime` back out
82
+ # afterward — `runtime` and the whole `IsolatedBoot` tmp directory
83
+ # go out of scope the moment this method returns (see this file's
84
+ # own header), so `Hecks::Fuzzing::SelfConsistency.check` has to run
85
+ # while both are still alive, against the exact same repositories
86
+ # this replay's own dispatch loop just wrote to.
87
+ #
88
+ # `database:`/`schema:` — only meaningful, and required, for
89
+ # `adapter: :postgres_era` — see `IsolatedBoot#rebind_to_postgres_era!`'s
90
+ # own header for why that one mode takes caller-owned connection
91
+ # identity rather than a shared default the way `:postgres` does.
92
+ # Forwarded straight through, unchanged, exactly like `adapter:`
93
+ # itself already was.
94
+ def call(domain_path, steps, adapter: :memory, database: nil, schema: nil, self_consistency: false)
95
+ # See isolated_boot.rb's own header: resets data/ and rebinds
76
96
  # persistence to the chosen adapter (Memory by default), since a
77
97
  # Postgres-bound domain's real store lives outside the copied
78
98
  # directory and cannot be reached by resetting data/ alone.
79
- IsolatedBoot.call(domain_path, adapter: adapter) do |copy|
99
+ IsolatedBoot.call(domain_path, adapter: adapter, database: database, schema: schema) do |copy|
80
100
  runtime = Hecks.boot(copy)
81
101
 
82
102
  refusals = []
83
103
  queries = []
84
104
  dry_runs = []
105
+ dry_run_traces = []
85
106
  fan_outs = []
86
107
  guard_checks = []
87
108
  mutation_traces = []
109
+ outbox_traces = []
88
110
 
89
- # EVERY AGGREGATE A `for_each` COULD EVER QUERY, resolved ONCE
111
+ # Every aggregate a `for_each` could ever query, resolved once
90
112
  # `[domain, aggregate_name]` pairs, gleaned from every loaded
91
113
  # bluebook's own fanning-out policies. Empty for every domain
92
114
  # with no `for_each` at all (every example this corpus ships
@@ -104,17 +126,17 @@ module Hecks
104
126
  args = (step["args"] || {}).transform_keys(&:to_sym)
105
127
 
106
128
  if (question = step["query"])
107
- # THE AD HOC, SINGLE-COMPARATOR FILTER — a "query" step whose
129
+ # **The ad hoc, single-comparator filter** — a "query" step whose
108
130
  # own value is a Hash, not a name: `{aggregate:, field:, op:,
109
- # value:}`, the SAME wire shape kernel/cli.rs's new object-
131
+ # value:}`, the same wire shape kernel/cli.rs's new object-
110
132
  # form "query" step reads on the Rust side (that file's own
111
133
  # header explains why this shape exists at all: it bypasses
112
134
  # the bluebook query DSL entirely, so it needs no generated
113
135
  # per-domain codegen to prove for real). Answered here by
114
- # calling `Ports::Query::InMemory` DIRECTLY — the real
136
+ # calling `Ports::Query::InMemory` directly — the real
115
137
  # production comparator engine, not a second, hand-rewritten
116
138
  # copy of it — against the raw repository, never through
117
- # `runtime.query`, which only ever resolves a NAMED, declared
139
+ # `runtime.query`, which only ever resolves a named, declared
118
140
  # ask.
119
141
  if question.is_a?(Hash)
120
142
  begin
@@ -126,16 +148,16 @@ module Hecks
126
148
  next
127
149
  end
128
150
 
129
- # THE QUERY ORACLETWO INDEPENDENT ENGINES, EACH RUN AND
130
- # CAUGHT ON ITS OWN, never a single shared `begin`/`rescue`
151
+ # **The query oracle**two independent engines, each run and
152
+ # caught on its own, never a single shared `begin`/`rescue`
131
153
  # wrapping both calls. A shared begin/rescue meant `runtime.
132
- # query` raising (native refuses) short-circuited BEFORE
154
+ # query` raising (native refuses) short-circuited before
133
155
  # `runtime.reference_query` ever ran at all — the entry
134
156
  # recorded only `error:`, with no `reference_rows` and no
135
157
  # record of what the reference interpreter would have
136
158
  # answered — and `runtime.reference_query` raising instead
137
159
  # (reference refuses, native already succeeded) landed in the
138
- # SAME rescue, discarding the native `rows` this begin block
160
+ # same rescue, discarding the native `rows` this begin block
139
161
  # had already computed and recording the whole ask as an
140
162
  # ordinary refusal. Either way, "one engine refused and the
141
163
  # other did not" — a real divergence, exactly the shape a
@@ -180,35 +202,86 @@ module Hecks
180
202
 
181
203
  # `{"dry_run": verb, "args": …}` — `Dispatcher#dry_run?`: the command
182
204
  # evaluated hypothetically, nothing saved or emitted, no reaction.
183
- # Recorded, never a refusal: a refused dry run is an ANSWER.
205
+ # Recorded, never a refusal: a refused dry run is an answer.
206
+ #
207
+ # `dry_runs` stays exactly `{verb:, ok:, error?:}` — the same
208
+ # shape it always had, and the same shape `kernel/cli.rs`'s own
209
+ # `dry_run` answers (`{"verb", "ok"}` or `{"verb", "ok": false,
210
+ # "error"}`, that function's own doc comment) — `spec/rust_
211
+ # conformance_spec.rb` compares this array against the compiled
212
+ # binary's own verbatim, so it can never carry a key Rust's own
213
+ # answer does not. The role-gated binding (`as_step_caller`,
214
+ # the same `role:`/`actor_id:` a real dispatch gets below) still
215
+ # applies to the dry-run call itself — only what gets recorded
216
+ # about it is unchanged.
217
+ #
218
+ # `dry_run_traces` — a separate, parallel array (same order,
219
+ # not merged into `dry_runs` above) carrying `before:`/`after:`
220
+ # snapshots of the whole observable store (every instance, the
221
+ # event count) on either side of the hypothetical call, so
222
+ # `Properties.dry_runs_leave_no_trace` can hold `Dispatcher
223
+ # #dry_run?`'s own contract to the store rather than trusting
224
+ # it. Ruby's own oracle data — Rust has nothing to compare it
225
+ # against, so it stays out of the compared surface entirely.
184
226
  if (hypothetical = step["dry_run"])
227
+ before = { instances: snapshot_instances(runtime), events: runtime.events.size }
228
+ entry = { verb: hypothetical }
185
229
  begin
186
- runtime.dry_run?(hypothetical, **args)
187
- dry_runs << { verb: hypothetical, ok: true }
230
+ as_step_caller(step) { runtime.dry_run?(hypothetical, **args) }
231
+ entry[:ok] = true
188
232
  rescue *Runtime::DOMAIN_REFUSALS, Bluebook::Expression::EvaluationError => e
189
- dry_runs << { verb: hypothetical, ok: false, error: e.message }
233
+ entry.merge!(ok: false, error: e.message)
190
234
  end
235
+ after = { instances: snapshot_instances(runtime), events: runtime.events.size }
236
+ dry_runs << entry
237
+ dry_run_traces << entry.merge(before: before, after: after)
191
238
  next
192
239
  end
193
240
 
194
241
  begin
195
- # THE FAN-OUT ORACLE'S OWN LOW-WATER MARK — taken before
196
- # dispatch, so any reaction this ONE step's own announced
242
+ # **The fan-out oracle's own low-water mark** — taken before
243
+ # dispatch, so any reaction this one step's own announced
197
244
  # events produce (`reaction_log` grows in place, the same
198
245
  # Array `runtime.reactions` already exposes) can be sliced
199
- # out after, and matched against an INDEPENDENT recomputation
246
+ # out after, and matched against an independent recomputation
200
247
  # of what a `for_each` policy should have fanned out over —
201
248
  # the query oracle's own shape (two engines, compared, never
202
249
  # one graded against itself), aimed at fan-out instead of a
203
250
  # named ask.
204
251
  reaction_mark = runtime.reactions.size
205
252
 
206
- # THE SNAPSHOT A `for_each` QUERY WOULD HAVE SEEN — taken
207
- # BEFORE this step's own dispatch, not after. The real
208
- # `deliver_for_each` runs its query SYNCHRONOUSLY, inside
209
- # this SAME dispatch, before this call even returns so an
210
- # oracle that re-reads the live repository AFTER `dispatch`
211
- # answers sees whatever the fan-out's OWN dispatched
253
+ # **The outbox oracle's own low-water marks** — taken before
254
+ # dispatch, same idiom as `reaction_mark` right above:
255
+ # `saga_log_mark` slices `runtime.sagas` (a single flat
256
+ # array, safe to index into directly) the identical way
257
+ # `reaction_mark` already slices `runtime.reactions`.
258
+ # `outbox_before_ids` is a set of delivery_ids, not a
259
+ # size — `runtime.outbox.rows` concatenates every bound
260
+ # repository's own array in a fixed per-store order
261
+ # (`Outbox::Relay#rows`, `stores.flat_map`), so a row a
262
+ # different step's dispatch enqueues into an
263
+ # earlier-iterated store would land in the middle of
264
+ # that concatenated list, not at its tail — a plain
265
+ # "grew from N to M, take the tail" slice (the shape
266
+ # `reaction_mark`/`saga_log_mark` both get away with,
267
+ # since `reaction_log`/`saga_log` are each already one
268
+ # flat array irrespective of domain) would silently miss
269
+ # or misattribute rows the moment more than one
270
+ # repository has an outbox. `delivery_id` is unique per
271
+ # store by construction (`Row#to_h`'s own header;
272
+ # `hecks_outbox`'s `UNIQUE` column in the Postgres/Sqlite
273
+ # DDL, `outbox_enqueue`'s own de-dup check in Memory), so
274
+ # membership in this set is exactly "existed before this
275
+ # step's own dispatch ran."
276
+ saga_log_mark = runtime.sagas.size
277
+ outbox_before_ids = runtime.outbox.rows.map(&:delivery_id)
278
+
279
+ # The snapshot a `for_each` query would have seen — taken
280
+ # before this step's own dispatch, not after. The real
281
+ # `deliver_for_each` runs its query synchronously, inside
282
+ # this same dispatch, before this call even returns — so an
283
+ # oracle that re-reads the live repository after `dispatch`
284
+ # answers sees whatever the fan-out's own dispatched
212
285
  # commands already mutated (an Account a `Review` leg just
213
286
  # moved out of "open," say), not what the query actually
214
287
  # matched. A measured bug, not a hypothetical one — this
@@ -222,59 +295,78 @@ module Hecks
222
295
  runtime.registry.repository(fdomain, aggregate).all.to_h { |record| [record.id, record.state.dup] }
223
296
  end
224
297
 
225
- # THE GUARD ORACLE'S OWN PRE-DISPATCH READ — same idiom,
298
+ # **The guard oracle's own pre-dispatch read** — same idiom,
226
299
  # same placement, same reason as fan_out_snapshot right
227
300
  # above: `Admissibility#enforce_givens` (which itself calls
228
301
  # `#enforce_lifecycle_guard` when `declaring:` is passed)
229
- # is called a SECOND time here, independently, against the
302
+ # is called a second time here, independently, against the
230
303
  # record exactly as CommandInterpreter#hydrate's own acting
231
304
  # branch would find it (`repository.find(id).dup` — the
232
305
  # identical three-tier id fallback, Identity.of/.from,
233
- # reproduced read-only) — BEFORE this step's real dispatch
306
+ # reproduced read-only) — before this step's real dispatch
234
307
  # can mutate anything a cross-aggregate given dereferences
235
308
  # (`customer.status`). A pure predicate read, side-effect
236
309
  # free, so calling it twice changes nothing this step
237
310
  # itself observes.
238
311
  guard_check = build_guard_check(runtime, step["verb"], args)
239
312
 
240
- # THE MUTATION ORACLE'S OWN PRE-DISPATCH READ — same
241
- # idiom again: an ENTITY-DISPATCHED command's own
313
+ # **The mutation oracle's own pre-dispatch read** — same
314
+ # idiom again: an entity-dispatched command's own
242
315
  # `append`/`remove`/`multiply`/`clamp` mutations (S17's
243
316
  # fixture, spec/fixtures/entity_list_mutations, now a real
244
- # bootable domain) act on the entity's OWN attributes, so
317
+ # bootable domain) act on the entity's own attributes, so
245
318
  # the element addressed by this step's own identity args
246
- # is snapshotted BEFORE dispatch, materialized to plain
319
+ # is snapshotted before dispatch, materialized to plain
247
320
  # data — `nil` for anything out of scope (an aggregate-
248
321
  # level command, an entity command with no mutations at
249
322
  # all, or one whose identity args don't resolve).
250
323
  mutation_trace = build_mutation_trace(runtime, step["verb"], args)
251
324
 
252
- # `role:` — an OPTIONAL per-step key, absent on every one of
253
- # the 231 existing `spec/corpus/*.json` steps (their own
325
+ # `role:`/`actor_id:` — optional per-step keys, absent on every
326
+ # one of the 231 existing `spec/corpus/*.json` steps (their own
254
327
  # unwrapped `runtime.dispatch` call, unchanged, so nothing
255
- # already pinned changes behavior). Binds the SAME ambient
328
+ # already pinned changes behavior). Binds the same ambient
256
329
  # caller `refuse_role_mismatch` reads (`Hecks.as_caller`,
257
330
  # `Runtime::Caller.as`) for exactly the one dispatch this
258
331
  # step makes, then unbinds — mirrors `Caller.as`'s own
259
332
  # `ensure`-restore, so back-to-back steps with different (or
260
- # no) `role:` never leak into each other.
261
- result = if step["role"]
262
- Hecks.as_caller(role: step["role"]) { runtime.dispatch(step["verb"], **args) }
263
- else
264
- runtime.dispatch(step["verb"], **args)
265
- end
333
+ # no) `role:` never leak into each other. `actor_id:` is the
334
+ # sibling `kernel/cli.rs` already read (its own comment on
335
+ # the key): with it, a Governance-attached domain runs the
336
+ # real `holds_role?` lookup instead of the string fallback.
337
+ result = as_step_caller(step) { runtime.dispatch_flat(step["verb"], args) }
266
338
 
267
339
  fan_outs.concat(fan_out_findings(runtime, fan_out_snapshot, result.events, runtime.reactions[reaction_mark..]))
340
+
341
+ # **The outbox oracle's own capture** — every outbox row this
342
+ # step's own dispatch newly wrote (across every bound
343
+ # repository, including any a reaction cascade touched —
344
+ # `outbox_before_ids` was taken before `dispatch`, which
345
+ # is the same call that runs the whole cascade
346
+ # synchronously, `reenter` included), paired with the
347
+ # `reaction_log`/`saga_log` rows that same dispatch
348
+ # produced. Skipped entirely when empty — a step whose
349
+ # own aggregate has no outbox enqueues nothing here, and
350
+ # there is nothing for `Properties.outbox_rows_match_
351
+ # reactions` to check for it (its own reactions, if any,
352
+ # went through the direct, pre-outbox path instead).
353
+ outbox_new_rows = runtime.outbox.rows.reject { |row| outbox_before_ids.include?(row.delivery_id) }
354
+ if outbox_new_rows.any?
355
+ outbox_traces << { verb: step["verb"], rows: outbox_new_rows.map(&:to_h),
356
+ reactions: runtime.reactions[reaction_mark..].dup,
357
+ sagas: runtime.sagas[saga_log_mark..].dup }
358
+ end
359
+
268
360
  guard_checks << guard_check.merge(actual_refused: false, actual_kind: nil) if guard_check
269
- # AFTER — only on SUCCESS ; a refused step mutated nothing,
361
+ # After — only on success ; a refused step mutated nothing,
270
362
  # so there is no "after" to compare (and #build_mutation_
271
363
  # trace already skipped anything with no mutations to
272
364
  # trace in the first place).
273
365
  mutation_traces << mutation_trace.merge(after: read_mutation_after(runtime, mutation_trace)) if mutation_trace
274
366
  rescue *Runtime::DOMAIN_REFUSALS, Bluebook::Expression::EvaluationError => e
275
- # `kind:` — the RAISED CLASS, not re-derived from the message.
367
+ # `kind:` — the raised class, not re-derived from the message.
276
368
  # `GivenNotMet`/`EnsuresNotMet` share their exact wording
277
- # ("<command> refused — <description>") with FOUR other
369
+ # ("<command> refused — <description>") with four other
278
370
  # refusal templates (Vocabulary's own LifecycleRefused/
279
371
  # TypeMismatch/Unauthorized entries) — a property that told
280
372
  # a guard refusal apart by pattern-matching the string alone
@@ -282,21 +374,21 @@ module Hecks
282
374
  # one of those. The class is unambiguous where the string
283
375
  # is not.
284
376
  refusals << { verb: step["verb"], error: e.message, kind: refusal_kind(e) }
285
- # ONLY a refusal raised BY THE GUARD ITSELF counts here —
377
+ # Only a refusal raised by the guard itself counts here —
286
378
  # measured, not assumed: a step whose args were simply
287
379
  # incomplete (AbsentArgument, from normalize_args — which
288
- # runs BEFORE enforce_givens in DISPATCH_ORDER) never
380
+ # runs before enforce_givens in DISPATCH_ORDER) never
289
381
  # reached the guard at all, and this oracle's own first
290
382
  # live run against real generated pizzas data caught
291
383
  # exactly that case as a false positive (a malformed-args
292
384
  # step the generator deliberately produces, `amount:`
293
385
  # dropped entirely) before this comment existed. Whether a
294
- # refusal from a stage AFTER enforce_givens (TypeMismatch
386
+ # refusal from a stage after enforce_givens (TypeMismatch
295
387
  # on a mutation, EnsuresNotMet, InvariantViolation) proves
296
388
  # the guard passed can't be told apart from a same-shaped
297
- # refusal from a stage BEFORE it by class alone (TypeMismatch
389
+ # refusal from a stage before it by class alone (TypeMismatch
298
390
  # can come from either), so anything that isn't one of the
299
- # two guard classes is left OUT of guard_checks entirely —
391
+ # two guard classes is left out of guard_checks entirely —
300
392
  # inconclusive, not a claimed pass.
301
393
  if guard_check && GUARD_REFUSAL_CLASSES.include?(e.class)
302
394
  guard_checks << guard_check.merge(actual_refused: true,
@@ -311,9 +403,9 @@ module Hecks
311
403
  { name: event.name, aggregate: event.aggregate, id: event.id, payload: event.payload }
312
404
  end
313
405
 
314
- # THE LIVE PROCESS-MANAGER STORE, materialised to inert data —
406
+ # The live process-manager store, materialised to inert data —
315
407
  # `{ pm_name => { correlation => { state:, memory: } } }`, the
316
- # SAME shape SagaInterpreter#checkpoint hands its persistence
408
+ # same shape SagaInterpreter#checkpoint hands its persistence
317
409
  # adapter (state plus a `Value.materialize`d memory, which is
318
410
  # exactly what `deep_copy` there serialises). Captured here
319
411
  # because Replay returns the history, not the runtime, and the
@@ -321,7 +413,7 @@ module Hecks
321
413
  # property (Properties.sagas_rehydrate_cleanly) reads this rather
322
414
  # than reaching into a store the Memory rebind leaves as the
323
415
  # no-op NULL_SAGA_STORE. Materialised, not raw, so the history
324
- # stays plain data AND the round-trip check sees exactly the
416
+ # stays plain data and the round-trip check sees exactly the
325
417
  # bytes a real adapter would have persisted.
326
418
  saga_instances = runtime.registry.saga_instances.each_with_object({}) do |(pm_name, conversations), out|
327
419
  out[pm_name] = conversations.each_with_object({}) do |(correlation, instance), rows|
@@ -335,34 +427,52 @@ module Hecks
335
427
  # Free: no second boot, just the object the first one already
336
428
  # built.
337
429
  #
338
- # `bluebook:` (singular) stays the FIRST-loaded chapter — every
430
+ # `bluebook:` (singular) stays the first-loaded chapter — every
339
431
  # existing property scopes itself to "only what we have the
340
432
  # grammar for" against exactly this one, deliberately (see
341
433
  # lifecycle_values_are_declared's own comment). `bluebooks:`
342
- # (plural) is the FULL map, keyed by domain name — a domain
434
+ # (plural) is the full map, keyed by domain name — a domain
343
435
  # under fuzz commonly composes more than one bluebook (banking
344
436
  # alone loads Banking + Governance + Identity), and a refusal
345
- # or an event can legitimately come from ANY of them, not only
437
+ # or an event can legitimately come from any of them, not only
346
438
  # whichever one happened to load first. A property that needs
347
- # to resolve a verb back to its OWN declaring bluebook — not
439
+ # to resolve a verb back to its own declaring bluebook — not
348
440
  # "the" bluebook — reads this instead.
349
- { instances: instances, events: events, refusals: refusals,
350
- reactions: runtime.reactions, sagas: runtime.sagas, saga_instances: saga_instances,
351
- queries: queries, dry_runs: dry_runs, fan_outs: fan_outs, guard_checks: guard_checks,
352
- mutation_traces: mutation_traces,
353
- saga_dispatches: runtime.saga_dispatches, policy_dispatches: runtime.policy_dispatches,
354
- bluebook: runtime.registry.bluebooks.values.first,
355
- bluebooks: runtime.registry.bluebooks.dup }
441
+ history = { instances: instances, events: events, refusals: refusals,
442
+ reactions: runtime.reactions, sagas: runtime.sagas, saga_instances: saga_instances,
443
+ queries: queries, dry_runs: dry_runs, dry_run_traces: dry_run_traces,
444
+ fan_outs: fan_outs, guard_checks: guard_checks,
445
+ mutation_traces: mutation_traces, outbox_traces: outbox_traces,
446
+ saga_dispatches: runtime.saga_dispatches, policy_dispatches: runtime.policy_dispatches,
447
+ bluebook: runtime.registry.bluebooks.values.first,
448
+ bluebooks: runtime.registry.bluebooks.dup }
449
+
450
+ # `runtime` is still live here — this is the one and only place
451
+ # it is. See `SelfConsistency`'s own header for why this needs
452
+ # to happen now, against the same registry/repositories this
453
+ # replay's own dispatch loop just populated, not a second boot.
454
+ history[:self_consistency] = SelfConsistency.check(runtime, history) if self_consistency
455
+
456
+ history
356
457
  end
357
458
  end
358
459
 
359
- # THE GUARD ORACLE'S OWN RESOLUTION "which record, if any, is
460
+ # A step with no `role:` dispatches exactly as every corpus step
461
+ # always has — bare, no caller bound at all (`Caller.current` nil,
462
+ # so `refuse_role_mismatch` returns before checking anything).
463
+ def as_step_caller(step, &)
464
+ return yield unless step["role"]
465
+
466
+ Hecks.as_caller(role: step["role"], actor_id: step["actor_id"], &)
467
+ end
468
+
469
+ # The guard oracle's own resolution — "which record, if any, is
360
470
  # this step about, and would enforce_givens/enforce_lifecycle_guard
361
- # have refused it against that record's PRE-DISPATCH state" —
471
+ # have refused it against that record's pre-dispatch state" —
362
472
  # reproduced read-only from already-public pieces
363
473
  # (Naming.split_verb, registry.bluebook/.aggregate/.command,
364
474
  # Runtime::Identity.of/.from, repository.find), the exact same
365
- # three-tier fallback CommandInterpreter#hydrate's OWN acting
475
+ # three-tier fallback CommandInterpreter#hydrate's own acting
366
476
  # branch uses, minus its creating/duplicate-checking logic (a
367
477
  # creating command has no pre-existing record to snapshot, and
368
478
  # every real target this closes — Debit/CloseAccount/Credit/
@@ -379,21 +489,21 @@ module Hecks
379
489
  #
380
490
  # Never lets a resolution surprise (a malformed verb, a dangling
381
491
  # reference) become the step's own real dispatch outcome — this
382
- # is a SEPARATE, best-effort read, not part of the step's own
492
+ # is a separate, best-effort read, not part of the step's own
383
493
  # control flow.
384
- # THE SAME SHAPE `call`'s own end-of-replay block used to build
494
+ # The same shape `call`'s own end-of-replay block used to build
385
495
  # inline — every persisted record, keyed the way `query_eligible_rows`/
386
- # `#eligible_rows` (properties.rb) already expect. Now ALSO called
387
- # once PER QUERY STEP (see `call`, above), not only once at the very
388
- # end: a query asked at step 1 of a script whose LATER steps go on
496
+ # `#eligible_rows` (properties.rb) already expect. Now also called
497
+ # once per query step (see `call`, above), not only once at the very
498
+ # end: a query asked at step 1 of a script whose later steps go on
389
499
  # to create more records was being checked, by every property that
390
- # independently recomputes "the eligible rows," against the FINAL
391
- # snapshot — the records that existed AFTER the whole replay, not
500
+ # independently recomputes "the eligible rows," against the final
501
+ # snapshot — the records that existed after the whole replay, not
392
502
  # the ones that existed when the query actually ran. Found live:
393
503
  # `Banking.accounts_by_kind`, asked as literally the first step of a
394
504
  # 3-step script, correctly answered against zero accounts (none
395
505
  # existed yet) while `group_by_matches_recompute`'s own independent
396
- # recompute claimed "1 eligible row" — the ONE account the script's
506
+ # recompute claimed "1 eligible row" — the one account the script's
397
507
  # later two steps went on to create. Each query step now carries
398
508
  # its own `instances_at:` snapshot, taken at the moment it ran, so
399
509
  # every property that recomputes against "the eligible rows" reads
@@ -428,7 +538,7 @@ module Hecks
428
538
  command = aggregate&.command(command_name)
429
539
  return nil unless aggregate && command && !command.creates?
430
540
 
431
- # NOTHING TO CHECK, genuinely — not "nothing THIS reproduces yet".
541
+ # Nothing to check, genuinely — not "nothing THIS reproduces yet".
432
542
  # A transition-only guard (no per-command `from:`, no `given`,
433
543
  # only an aggregate `lifecycle do transition ... end` block
434
544
  # naming this command — `Admit`/`Reject`'s own shape) still
@@ -451,20 +561,20 @@ module Hecks
451
561
  recomputed_kind = begin
452
562
  rules.enforce_givens(record.dup, command, args, domain: domain_name, declaring: aggregate)
453
563
 
454
- # A SECOND, SEPARATE DISPATCH_ORDER STEP — `enforce_givens`
455
- # (just above) only ever checks a per-COMMAND `from:` clause
564
+ # A second, separate DISPATCH_ORDER step — `enforce_givens`
565
+ # (just above) only ever checks a per-command `from:` clause
456
566
  # (its own trailing `enforce_lifecycle_guard(declaring, ...)
457
567
  # if declaring` call) — the aggregate's own `lifecycle do
458
- # transition "X" => Y, from: Z end` block is a WHOLLY separate
568
+ # transition "X" => Y, from: Z end` block is a wholly separate
459
569
  # method (`admissible_transition`), called as its own later
460
570
  # DISPATCH_ORDER step (`:enforce_givens` then
461
571
  # `:admissible_transition` — Vocabulary.symbols
462
572
  # ("AggregateDispatchOrder")), not reached from inside
463
573
  # `enforce_givens` at all. Missing this call meant a command
464
- # declared with NO per-command `from:` of its own — every real
574
+ # declared with no per-command `from:` of its own — every real
465
575
  # transition-guarded command in this corpus, `Admit`/`Reject`
466
576
  # included — always recomputed "admitted" no matter the
467
- # record's actual state, because the ONE check that would
577
+ # record's actual state, because the one check that would
468
578
  # have refused it was never run. Found live: `Expression::
469
579
  # Expression.Admit`, fuzzed against `lib/hecks/grammar`
470
580
  # (a domain the property's own hand-verification — Banking,
@@ -484,22 +594,22 @@ module Hecks
484
594
  nil
485
595
  end
486
596
 
487
- # THE MUTATION ORACLE'S OWN PRE-DISPATCH READ — scoped, on
488
- # purpose, to ENTITY-DISPATCHED commands only (a dotted
597
+ # The mutation oracle's own pre-dispatch read — scoped, on
598
+ # purpose, to entity-dispatched commands only (a dotted
489
599
  # command_name): the one place `append`/`remove`/`multiply`/
490
- # `clamp` are known to act on an entity's OWN attributes
600
+ # `clamp` are known to act on an entity's own attributes
491
601
  # (spec/fixtures/entity_list_mutations' own TaggedList — `tags`
492
602
  # a value-object list, `count` a VO-typed scalar), never on
493
- # ANOTHER nested entity list — so this never needs to reproduce
603
+ # another nested entity list — so this never needs to reproduce
494
604
  # `MutationApplier#entity_element`'s own auto-mint/collision logic
495
- # (item 1's own fix) at all. An aggregate-level command whose OWN
496
- # mutation appends an ENTITY (`Board.AddList`, `SafeDepositBox.
497
- # LogVisit`) is a DIFFERENT, already-covered case — item 1's own
605
+ # (item 1's own fix) at all. An aggregate-level command whose own
606
+ # mutation appends an entity (`Board.AddList`, `SafeDepositBox.
607
+ # LogVisit`) is a different, already-covered case — item 1's own
498
608
  # collision property, not this one.
499
609
  #
500
610
  # `nil` for anything out of scope: an aggregate-level command, an
501
611
  # entity command with no mutations at all, or one whose identity
502
- # args (parent OR element) don't resolve.
612
+ # args (parent or element) don't resolve.
503
613
  # Same shape as build_guard_check just above: one early-return chain
504
614
  # resolving the entity/element this step's args address (see the
505
615
  # comment above), each step depending on the previous one's
@@ -550,7 +660,7 @@ module Hecks
550
660
  nil
551
661
  end
552
662
 
553
- # THE SAME ELEMENT, RE-LOCATED, AFTER dispatch — by identity, not
663
+ # The same element, re-located, after dispatch — by identity, not
554
664
  # position (an append could have changed the array's own length
555
665
  # or order relative to it). `nil` if it somehow vanished (not
556
666
  # expected for any op this fixture declares — none of them
@@ -571,10 +681,10 @@ module Hecks
571
681
  nil
572
682
  end
573
683
 
574
- # THE FAN-OUT ORACLE — one finding per (event, for_each policy) this
684
+ # The fan-out oracle — one finding per (event, for_each policy) this
575
685
  # step's own announced events could have triggered, independent of
576
- # `PolicyInterpreter#deliver_for_each`: the SAME `where` evaluator
577
- # every given/ensures already runs through, but the QUERY answered
686
+ # `PolicyInterpreter#deliver_for_each`: the same `where` evaluator
687
+ # every given/ensures already runs through, but the query answered
578
688
  # by `Ports::Query::InMemory.holds?` directly against the live
579
689
  # repository (`Replay.run_filter`'s own idiom), never by calling
580
690
  # `QueryInterpreter` — sharing that call would make this oracle
@@ -589,7 +699,7 @@ module Hecks
589
699
  def fan_out_findings(runtime, snapshot, announced, reactions_since)
590
700
  announced.each_with_object([]) do |event, findings|
591
701
  # `event.aggregate` is domain-qualified ("Banking::Account" —
592
- # see command_rules/emission.rb's own Event.new) — the SAME
702
+ # see command_rules/emission.rb's own Event.new) — the same
593
703
  # source `PolicyInterpreter#policies_for` reads, split the
594
704
  # same two ways: `Naming.demodulise` for the emitting
595
705
  # aggregate's bare name, plain `split("::")` for the domain.
@@ -621,8 +731,8 @@ module Hecks
621
731
  { policy: policy.name, on: event.name, expected_row_ids: expected, actual_row_ids: actual }
622
732
  end
623
733
 
624
- # THE INDEPENDENT RECOMPUTATION — `policy.for_each`'s declared query,
625
- # answered against the PRE-DISPATCH snapshot (see the snapshot's
734
+ # The independent recomputation — `policy.for_each`'s declared query,
735
+ # answered against the pre-dispatch snapshot (see the snapshot's
626
736
  # own comment at its capture site: the real fan-out's query runs
627
737
  # synchronously, before its own dispatched commands can mutate
628
738
  # anything the query would have matched, so this has to read the
@@ -650,24 +760,24 @@ module Hecks
650
760
  matched.keys.map(&:to_s).sort
651
761
  end
652
762
 
653
- # Answers ONE ad hoc filter step for real — the mirror image of
763
+ # Answers one ad hoc filter step for real — the mirror image of
654
764
  # kernel/cli.rs's own `run_filter`, deliberately calling the exact
655
- # SAME production module that method's Rust port stands in for
765
+ # same production module that method's Rust port stands in for
656
766
  # (`Ports::Query::InMemory`, lib/hecks/ports/query/in_memory.rb)
657
767
  # rather than re-deriving comparator behavior by hand. `field` walks
658
768
  # through `QuerySpecification::FieldPath.dig` (the same reading a
659
769
  # declared where-clause gets), `comparable`/`holds?` are the same
660
770
  # two calls `InMemory.execute` itself makes per candidate record —
661
771
  # this is that method's own filter/select step, inlined, because
662
- # there is no DECLARED `Query` object here to hand `execute` (an ad
772
+ # there is no declared `Query` object here to hand `execute` (an ad
663
773
  # hoc filter has no `order_by`/`limit`/`offset` at all, so nothing
664
774
  # about `execute`'s own ordering/paging logic even applies).
665
775
  # Sorted by id ascending regardless — `Ports::Query::Ordering`'s own
666
776
  # header explains why an ask with no declared order still needs
667
777
  # this tier ("the identity tier is what makes an ask total").
668
- # THE OUTCOME CLASS a recorded refusal row names (C8.2/C8.3,
778
+ # The outcome class a recorded refusal row names (C8.2/C8.3,
669
779
  # docs/semantics/bluebook-semantics.md): a domain refusal is its own
670
- # class; an evaluation FAULT — the language refusing to interpret a
780
+ # class; an evaluation fault — the language refusing to interpret a
671
781
  # broken rule or input — is `"Fault"`, the same word the Rust kernel
672
782
  # emits (`Refusal::Fault`), never a refusal class and never a raw
673
783
  # Ruby exception name.
@@ -681,7 +791,7 @@ module Hecks
681
791
  op = filter["op"].to_s
682
792
  value = filter["value"]
683
793
 
684
- # A malformed ad-hoc ask is a FAULT (C8.3), not a bare RuntimeError.
794
+ # A malformed ad-hoc ask is a fault (C8.3), not a bare RuntimeError.
685
795
  unless FILTER_COMPARATORS.include?(op)
686
796
  raise Bluebook::Expression::EvaluationError, "unknown query comparator #{op.inspect}"
687
797
  end
@@ -700,9 +810,9 @@ module Hecks
700
810
  matched.sort_by { |record| record.id.to_s }.map { |record| { id: record.id }.merge(record.state) }
701
811
  end
702
812
 
703
- # The `refusals` entry's own "verb" column for a REFUSED ad hoc
813
+ # The `refusals` entry's own "verb" column for a refused ad hoc
704
814
  # filter — there is no real verb to report (a filter step carries
705
- # none), so this builds the SAME descriptive label kernel/cli.rs's
815
+ # none), so this builds the same descriptive label kernel/cli.rs's
706
816
  # own `filter_label` builds from the same three raw fields, tolerant
707
817
  # of any of them being missing (Ruby's own nil-to-"" interpolation)
708
818
  # the same way that Rust port is.