hecks 1.3.0 → 1.5.0

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