hecks 2.8.0 → 3.0.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 (886) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +50 -7
  3. data/lib/hecks/adapters/driven/claude_code.rb +4 -104
  4. data/lib/hecks/adapters/driven/d1.rb +44 -237
  5. data/lib/hecks/adapters/driven/folder.rb +17 -76
  6. data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
  7. data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
  8. data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
  9. data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
  10. data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
  11. data/lib/hecks/adapters/driven/heki.rb +13 -42
  12. data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
  15. data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
  16. data/lib/hecks/adapters/driven/lambda.rb +10 -84
  17. data/lib/hecks/adapters/driven/local_storage.rb +5 -40
  18. data/lib/hecks/adapters/driven/memory.rb +32 -100
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +10 -32
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
  24. data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
  25. data/lib/hecks/adapters/driven/postgres.rb +149 -136
  26. data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
  27. data/lib/hecks/adapters/driven/prism.rb +48 -38
  28. data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
  29. data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
  30. data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
  31. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
  32. data/lib/hecks/adapters/driven/sqlite.rb +128 -65
  33. data/lib/hecks/adapters/driven/tenant_provisioner.rb +76 -62
  34. data/lib/hecks/adapters/driven.rb +5 -36
  35. data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
  36. data/lib/hecks/behaviors/dsl.rb +7 -25
  37. data/lib/hecks/behaviors/expectations.rb +32 -142
  38. data/lib/hecks/behaviors/ir.rb +7 -20
  39. data/lib/hecks/behaviors/rspec.rb +6 -17
  40. data/lib/hecks/behaviors/runner.rb +10 -34
  41. data/lib/hecks/behaviors.rb +4 -8
  42. data/lib/hecks/bench/cli.rb +62 -0
  43. data/lib/hecks/bench/environment.rb +71 -0
  44. data/lib/hecks/bench/postgres_probe.rb +30 -0
  45. data/lib/hecks/bench/report.rb +76 -0
  46. data/lib/hecks/bench/ruby_runner.rb +93 -0
  47. data/lib/hecks/bench/run.rb +55 -0
  48. data/lib/hecks/bench/rust_runner.rb +101 -0
  49. data/lib/hecks/bench/stats.rb +46 -0
  50. data/lib/hecks/bench/suite.rb +98 -0
  51. data/lib/hecks/bench/workload.rb +89 -0
  52. data/lib/hecks/bench.rb +17 -0
  53. data/lib/hecks/bluebook/aggregate.rb +9 -75
  54. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
  55. data/lib/hecks/bluebook/assembly/build.rb +4 -15
  56. data/lib/hecks/bluebook/assembly/contract.rb +15 -136
  57. data/lib/hecks/bluebook/assembly/contracts.rb +56 -174
  58. data/lib/hecks/bluebook/assembly/marks.rb +31 -223
  59. data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
  60. data/lib/hecks/bluebook/assembly.rb +6 -45
  61. data/lib/hecks/bluebook/attribute.rb +12 -61
  62. data/lib/hecks/bluebook/behaviour/aggregate.rb +39 -39
  63. data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
  64. data/lib/hecks/bluebook/behaviour/chapter.rb +14 -42
  65. data/lib/hecks/bluebook/behaviour/command.rb +14 -66
  66. data/lib/hecks/bluebook/behaviour/domain_port.rb +11 -20
  67. data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
  68. data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
  69. data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
  70. data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
  71. data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
  72. data/lib/hecks/bluebook/behaviour/read_model.rb +25 -31
  73. data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
  74. data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
  75. data/lib/hecks/bluebook/capabilities.rb +17 -49
  76. data/lib/hecks/bluebook/chapter.rb +16 -50
  77. data/lib/hecks/bluebook/command.rb +23 -81
  78. data/lib/hecks/bluebook/domain_port.rb +49 -67
  79. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
  80. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
  81. data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
  82. data/lib/hecks/bluebook/dsl/binding_proxy.rb +16 -69
  83. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +96 -708
  84. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +63 -169
  85. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +3 -1
  86. data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
  87. data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
  88. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +42 -89
  89. data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
  90. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
  91. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +148 -197
  92. data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
  93. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
  94. data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
  95. data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
  96. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
  97. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
  98. data/lib/hecks/bluebook/dsl/query_builder.rb +38 -43
  99. data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
  100. data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
  101. data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
  102. data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
  103. data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
  104. data/lib/hecks/bluebook/dsl/world_builder.rb +46 -111
  105. data/lib/hecks/bluebook/entity.rb +13 -71
  106. data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
  107. data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
  108. data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
  109. data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
  110. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
  111. data/lib/hecks/bluebook/expression/resolver.rb +39 -661
  112. data/lib/hecks/bluebook/hexagon.rb +18 -33
  113. data/lib/hecks/bluebook/lifecycle.rb +5 -16
  114. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
  115. data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
  116. data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
  117. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
  118. data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
  119. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +25 -176
  120. data/lib/hecks/bluebook/meta_validator/shapes.rb +53 -267
  121. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -306
  122. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
  123. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
  124. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
  125. data/lib/hecks/bluebook/meta_validator.rb +87 -352
  126. data/lib/hecks/bluebook/model_check/client_profile.rb +6 -21
  127. data/lib/hecks/bluebook/model_check.rb +122 -596
  128. data/lib/hecks/bluebook/pattern_subset.rb +49 -69
  129. data/lib/hecks/bluebook/process_manager.rb +13 -83
  130. data/lib/hecks/bluebook/project_loader.rb +3 -39
  131. data/lib/hecks/bluebook/project_register.rb +12 -60
  132. data/lib/hecks/bluebook/query.rb +27 -51
  133. data/lib/hecks/bluebook/read_model.rb +57 -81
  134. data/lib/hecks/bluebook/reference.rb +7 -41
  135. data/lib/hecks/bluebook/smoke_test.rb +9 -99
  136. data/lib/hecks/bluebook/synthesizer.rb +13 -62
  137. data/lib/hecks/bluebook/translation.rb +26 -62
  138. data/lib/hecks/bluebook/value_object.rb +11 -36
  139. data/lib/hecks/bluebook.rb +5 -22
  140. data/lib/hecks/cache_dir.rb +4 -23
  141. data/lib/hecks/canonical_json.rb +30 -0
  142. data/lib/hecks/chapters.rb +102 -0
  143. data/lib/hecks/cli/behaviors.rb +61 -0
  144. data/lib/hecks/cli/canonicalise.rb +25 -0
  145. data/lib/hecks/cli/check_engine_agreement.rb +32 -0
  146. data/lib/hecks/cli/check_era.rb +88 -0
  147. data/lib/hecks/cli/console.rb +66 -0
  148. data/lib/hecks/cli/deploy_template_diff.rb +40 -0
  149. data/lib/hecks/cli/doc_coverage.rb +32 -0
  150. data/lib/hecks/cli/document.rb +5 -23
  151. data/lib/hecks/cli/expression_projection.rb +41 -0
  152. data/lib/hecks/cli/follow.rb +88 -0
  153. data/lib/hecks/cli/history.rb +44 -0
  154. data/lib/hecks/cli/ir.rb +4 -6
  155. data/lib/hecks/cli/mcp.rb +5 -6
  156. data/lib/hecks/cli/model_check.rb +24 -88
  157. data/lib/hecks/cli/pattern_cases.rb +47 -0
  158. data/lib/hecks/cli/present.rb +39 -0
  159. data/lib/hecks/cli/project_cli.rb +223 -64
  160. data/lib/hecks/cli/project_diagrams.rb +4 -10
  161. data/lib/hecks/cli/project_glossary.rb +44 -0
  162. data/lib/hecks/cli/reference.rb +46 -0
  163. data/lib/hecks/cli/refresh_projections.rb +44 -0
  164. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
  165. data/lib/hecks/cli/release.rb +70 -0
  166. data/lib/hecks/cli/release_gem.rb +59 -0
  167. data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
  168. data/lib/hecks/cli/rspec_shard_files.rb +66 -0
  169. data/lib/hecks/cli/run.rb +15 -61
  170. data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
  171. data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
  172. data/lib/hecks/cli/shape.rb +70 -0
  173. data/lib/hecks/cli/smoke_http.rb +230 -0
  174. data/lib/hecks/cli/smoke_test.rb +7 -19
  175. data/lib/hecks/cli/spec_example.rb +27 -0
  176. data/lib/hecks/cli/statements.rb +32 -0
  177. data/lib/hecks/cli/stores.rb +4 -6
  178. data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
  179. data/lib/hecks/cli.rb +216 -73
  180. data/lib/hecks/codemod.rb +324 -0
  181. data/lib/hecks/construct.rb +8 -34
  182. data/lib/hecks/corpus.rb +423 -0
  183. data/lib/hecks/deploy/bluebook/deploy.bluebook +617 -158
  184. data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -11
  185. data/lib/hecks/deploy/oidc.json +82 -2
  186. data/lib/hecks/doc/reference.rb +351 -0
  187. data/lib/hecks/doors/cli_door.rb +196 -0
  188. data/lib/hecks/doors/cli_runner.rb +297 -0
  189. data/lib/hecks/{facade → doors}/command_request.rb +14 -34
  190. data/lib/hecks/doors/handle.rb +206 -0
  191. data/lib/hecks/doors/json_door.rb +132 -0
  192. data/lib/hecks/doors/launcher_options.rb +129 -0
  193. data/lib/hecks/{cli → doors}/mcp_door.rb +25 -120
  194. data/lib/hecks/doors/mcp_door_scope.rb +97 -0
  195. data/lib/hecks/doors/ruby_door/aggregate_door.rb +162 -0
  196. data/lib/hecks/doors/ruby_door/chapter.rb +72 -0
  197. data/lib/hecks/doors/ruby_door.rb +77 -0
  198. data/lib/hecks/doors.rb +61 -0
  199. data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
  200. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
  201. data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
  202. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
  203. data/lib/hecks/embryonaut_bluebook.rb +5 -72
  204. data/lib/hecks/engine_agreement.rb +156 -0
  205. data/lib/hecks/facade.rb +23 -51
  206. data/lib/hecks/forms/app.rb +21 -76
  207. data/lib/hecks/forms/banking_presentation.rb +50 -0
  208. data/lib/hecks/forms/command_form_renderer.rb +10 -29
  209. data/lib/hecks/forms/field_renderer.rb +17 -156
  210. data/lib/hecks/forms/field_shape.rb +31 -222
  211. data/lib/hecks/forms/html.rb +12 -47
  212. data/lib/hecks/forms/index_renderer.rb +1 -4
  213. data/lib/hecks/forms/page.rb +3 -9
  214. data/lib/hecks/forms/params.rb +14 -60
  215. data/lib/hecks/forms/port_argument.rb +2 -19
  216. data/lib/hecks/forms/query_form_renderer.rb +4 -79
  217. data/lib/hecks/forms/record_renderer.rb +8 -75
  218. data/lib/hecks/forms/record_table.rb +7 -50
  219. data/lib/hecks/forms/reference_options.rb +3 -30
  220. data/lib/hecks/forms/value_object_shape.rb +8 -35
  221. data/lib/hecks/forms.rb +5 -33
  222. data/lib/hecks/fqn.rb +5 -12
  223. data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
  224. data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
  225. data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
  226. data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
  227. data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
  228. data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
  229. data/lib/hecks/framework.rb +11 -80
  230. data/lib/hecks/freezer.rb +5 -39
  231. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
  232. data/lib/hecks/fuzzing/combination_miner.rb +86 -0
  233. data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
  234. data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
  235. data/lib/hecks/fuzzing/differential.rb +156 -0
  236. data/lib/hecks/fuzzing/domain_generator.rb +615 -0
  237. data/lib/hecks/fuzzing/era_boundary.rb +71 -0
  238. data/lib/hecks/fuzzing/form_census.rb +136 -0
  239. data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
  240. data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
  241. data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
  242. data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
  243. data/lib/hecks/fuzzing/outside_world.rb +45 -0
  244. data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
  245. data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
  246. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
  247. data/lib/hecks/fuzzing/properties/guards.rb +190 -0
  248. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
  249. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
  250. data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
  251. data/lib/hecks/fuzzing/properties/querying.rb +175 -0
  252. data/lib/hecks/fuzzing/properties.rb +162 -0
  253. data/lib/hecks/fuzzing/qa_settings.rb +123 -0
  254. data/lib/hecks/fuzzing/replay.rb +505 -0
  255. data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
  256. data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
  257. data/lib/hecks/fuzzing/self_consistency.rb +339 -0
  258. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
  259. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
  260. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
  261. data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
  262. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
  263. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
  264. data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
  265. data/lib/hecks/fuzzing/shrinker.rb +166 -0
  266. data/lib/hecks/fuzzing/structural_skips.rb +33 -0
  267. data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
  268. data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
  269. data/lib/hecks/fuzzing/value_generator.rb +216 -0
  270. data/lib/hecks/fuzzing.rb +23 -0
  271. data/lib/hecks/grammar/evolve.rb +336 -0
  272. data/lib/hecks/grammar/expression.bluebook +15 -40
  273. data/lib/hecks/grammar/translation.bluebook +3 -6
  274. data/lib/hecks/grammar.rb +16 -41
  275. data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
  276. data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
  277. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
  278. data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
  279. data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
  280. data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
  281. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
  282. data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
  283. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
  284. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
  285. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
  286. data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
  287. data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
  288. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
  289. data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
  290. data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
  291. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
  292. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
  293. data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
  294. data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
  295. data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
  296. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
  297. data/lib/hecks/hecks/adapters/git.adapter +7 -0
  298. data/lib/hecks/hecks/adapters/git.rb +102 -0
  299. data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
  300. data/lib/hecks/hecks/adapters/host_http.rb +65 -0
  301. data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
  302. data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
  303. data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
  304. data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
  305. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
  306. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
  307. data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
  308. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
  309. data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
  310. data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
  311. data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
  312. data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
  313. data/lib/hecks/hecks/adapters/local_files.rb +46 -0
  314. data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
  315. data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
  316. data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
  317. data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
  318. data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
  319. data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
  320. data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
  321. data/lib/hecks/hecks/adapters/shell.adapter +7 -0
  322. data/lib/hecks/hecks/adapters/shell.rb +49 -0
  323. data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
  324. data/lib/hecks/hecks/adapters/terminal.rb +74 -0
  325. data/lib/hecks/hecks/codebase.behaviors +172 -0
  326. data/lib/hecks/hecks/codebase.bluebook +2453 -0
  327. data/lib/hecks/hecks/context_map.hecksagon +13 -0
  328. data/lib/hecks/hecks/custodian.behaviors +179 -0
  329. data/lib/hecks/hecks/custodian.bluebook +1847 -0
  330. data/lib/hecks/hecks/deploy.behaviors +74 -0
  331. data/lib/hecks/hecks/deploy.world +12 -0
  332. data/lib/hecks/hecks/environments/memory.world +10 -0
  333. data/lib/hecks/hecks/governance.world +7 -0
  334. data/lib/hecks/hecks/hecks.behaviors +62 -0
  335. data/lib/hecks/hecks/hecks.bluebook +124 -0
  336. data/lib/hecks/hecks/hecks.hecksagon +390 -0
  337. data/lib/hecks/hecks/hecks.world +42 -0
  338. data/lib/hecks/ir.rb +14 -83
  339. data/lib/hecks/kernel_capabilities.rb +194 -0
  340. data/lib/hecks/language/adapter.bluebook +2 -7
  341. data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
  342. data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
  343. data/lib/hecks/language/bluebook/bluebook.bluebook +26 -83
  344. data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
  345. data/lib/hecks/language/bluebook/command.bluebook +27 -144
  346. data/lib/hecks/language/bluebook/entity.bluebook +24 -97
  347. data/lib/hecks/language/bluebook/policy.bluebook +16 -67
  348. data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
  349. data/lib/hecks/language/bluebook/projection.bluebook +126 -60
  350. data/lib/hecks/language/bluebook/query.bluebook +19 -43
  351. data/lib/hecks/language/bluebook/shape.bluebook +23 -73
  352. data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
  353. data/lib/hecks/language/bluebook/vocabulary.bluebook +83 -198
  354. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
  355. data/lib/hecks/language/hecksagon/domain_port.bluebook +14 -19
  356. data/lib/hecks/language/hecksagon/hecksagon.bluebook +26 -25
  357. data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
  358. data/lib/hecks/language/oidc.json +35 -0
  359. data/lib/hecks/language/port.bluebook +4 -17
  360. data/lib/hecks/language/translation/translation.bluebook +3 -9
  361. data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
  362. data/lib/hecks/language/world/wiring.bluebook +1 -3
  363. data/lib/hecks/language/world/world.bluebook +5 -7
  364. data/lib/hecks/literal.rb +8 -50
  365. data/lib/hecks/mcp_stdio_guard.rb +4 -41
  366. data/lib/hecks/naming.rb +29 -137
  367. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
  368. data/lib/hecks/persistence_legacy_fixture.rb +113 -0
  369. data/lib/hecks/ports/access_control.rb +7 -23
  370. data/lib/hecks/ports/agent/answers.rb +5 -23
  371. data/lib/hecks/ports/agent.rb +47 -172
  372. data/lib/hecks/ports/authentication.rb +12 -39
  373. data/lib/hecks/ports/authorization.rb +16 -43
  374. data/lib/hecks/ports/clock.rb +5 -45
  375. data/lib/hecks/ports/extraction.rb +7 -12
  376. data/lib/hecks/ports/identity_assignment.rb +8 -29
  377. data/lib/hecks/ports/identity_generation.rb +6 -29
  378. data/lib/hecks/ports/identity_resolution.rb +2 -10
  379. data/lib/hecks/ports/key_vault.rb +9 -45
  380. data/lib/hecks/ports/persistence/append_only.rb +61 -64
  381. data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
  382. data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
  383. data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
  384. data/lib/hecks/ports/persistence/plugin.rb +13 -42
  385. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +44 -144
  386. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
  387. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +20 -85
  388. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +6 -25
  389. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +18 -24
  390. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
  391. data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
  392. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
  393. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
  394. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
  395. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
  396. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
  397. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
  398. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +19 -76
  399. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
  400. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
  401. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
  402. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
  403. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
  404. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +43 -127
  405. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
  406. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +131 -555
  407. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
  408. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
  409. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
  410. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
  411. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
  412. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
  413. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
  414. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +50 -47
  415. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -124
  416. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
  417. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
  418. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
  419. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
  420. data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
  421. data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
  422. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
  423. data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
  424. data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
  425. data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
  426. data/lib/hecks/ports/persistence/state_codec.rb +47 -108
  427. data/lib/hecks/ports/persistence.rb +7 -14
  428. data/lib/hecks/ports/projection.rb +42 -84
  429. data/lib/hecks/ports/query/in_memory.rb +10 -58
  430. data/lib/hecks/ports/query/ordering.rb +11 -32
  431. data/lib/hecks/ports/query.rb +14 -37
  432. data/lib/hecks/ports.rb +2 -8
  433. data/lib/hecks/projection_files.rb +285 -0
  434. data/lib/hecks/projections/bootstrap_table.rb +12 -44
  435. data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
  436. data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
  437. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
  438. data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
  439. data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
  440. data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
  441. data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
  442. data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
  443. data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
  444. data/lib/hecks/projections/deploy/fargate.rb +40 -207
  445. data/lib/hecks/projections/deploy/lambda.rb +191 -708
  446. data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
  447. data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
  448. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  449. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
  450. data/lib/hecks/projections/deploy/preview/script.rb +2 -14
  451. data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
  452. data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
  453. data/lib/hecks/projections/deploy/preview/template.rb +3 -59
  454. data/lib/hecks/projections/deploy/preview.rb +2 -67
  455. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
  456. data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
  457. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
  458. data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
  459. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
  460. data/lib/hecks/projections/deploy/scripts.rb +4 -57
  461. data/lib/hecks/projections/deploy/shared.rb +48 -147
  462. data/lib/hecks/projections/deploy/smoke.rb +4 -61
  463. data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
  464. data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
  465. data/lib/hecks/projections/deploy/template_diff.rb +5 -57
  466. data/lib/hecks/projections/diagrams.rb +22 -584
  467. data/lib/hecks/projections/glossary/html.rb +14 -130
  468. data/lib/hecks/projections/glossary/markdown.rb +8 -69
  469. data/lib/hecks/projections/glossary/mermaid.rb +9 -66
  470. data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
  471. data/lib/hecks/projections/glossary/sentences.rb +14 -159
  472. data/lib/hecks/projections/glossary.rb +30 -222
  473. data/lib/hecks/projections/ir.rb +3 -10
  474. data/lib/hecks/projections/model/deviations.rb +16 -43
  475. data/lib/hecks/projections/model.rb +12 -51
  476. data/lib/hecks/projections/oidc.rb +12 -56
  477. data/lib/hecks/projections/parser_table.rb +13 -32
  478. data/lib/hecks/projections/reference.rb +4 -18
  479. data/lib/hecks/projections/rust_vocabulary.rb +13 -251
  480. data/lib/hecks/projections/shape.rb +3 -29
  481. data/lib/hecks/projections/statements.rb +7 -86
  482. data/lib/hecks/projections/vocabulary.rb +6 -22
  483. data/lib/hecks/projections.rb +2 -16
  484. data/lib/hecks/projector/cli_projector.rb +154 -400
  485. data/lib/hecks/projector/docs_projector.rb +10 -201
  486. data/lib/hecks/projector/exporter.rb +42 -134
  487. data/lib/hecks/projector/ir_projector.rb +2 -9
  488. data/lib/hecks/projector/narrate_projector.rb +15 -189
  489. data/lib/hecks/projector/target.rb +13 -76
  490. data/lib/hecks/projector.rb +30 -121
  491. data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
  492. data/lib/hecks/quality_control/adapters/agent.rb +58 -0
  493. data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
  494. data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
  495. data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
  496. data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
  497. data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
  498. data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
  499. data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
  500. data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
  501. data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
  502. data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
  503. data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
  504. data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
  505. data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
  506. data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
  507. data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
  508. data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
  509. data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
  510. data/lib/hecks/quality_control/cli/child.rb +45 -0
  511. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
  512. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
  513. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
  514. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
  515. data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
  516. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
  517. data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
  518. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
  519. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
  520. data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
  521. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
  522. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
  523. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
  524. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
  525. data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
  526. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
  527. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
  528. data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
  529. data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
  530. data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
  531. data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
  532. data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
  533. data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
  534. data/lib/hecks/query_ir.rb +346 -0
  535. data/lib/hecks/query_ir_mcp.rb +198 -0
  536. data/lib/hecks/query_specification/common/comparators.rb +5 -26
  537. data/lib/hecks/query_specification/common/comparison.rb +35 -143
  538. data/lib/hecks/query_specification/common/dsl.rb +8 -24
  539. data/lib/hecks/query_specification/common/null_policy.rb +20 -70
  540. data/lib/hecks/query_specification/common/options.rb +2 -6
  541. data/lib/hecks/query_specification/common/where_clause.rb +2 -10
  542. data/lib/hecks/query_specification/field_path.rb +37 -77
  543. data/lib/hecks/query_specification/hop_path.rb +12 -76
  544. data/lib/hecks/release/gem_pin.rb +2 -26
  545. data/lib/hecks/release/runner/ci_publisher.rb +5 -8
  546. data/lib/hecks/release/runner/clean_tree.rb +42 -0
  547. data/lib/hecks/release/runner/commands.rb +2 -4
  548. data/lib/hecks/release/runner/console.rb +6 -0
  549. data/lib/hecks/release/runner/gem_publisher.rb +15 -9
  550. data/lib/hecks/release/runner/npm_publisher.rb +9 -53
  551. data/lib/hecks/release/runner/preflight.rb +29 -20
  552. data/lib/hecks/release/runner/published.rb +8 -26
  553. data/lib/hecks/release/runner/tagger.rb +31 -23
  554. data/lib/hecks/release/runner.rb +60 -38
  555. data/lib/hecks/rendering.rb +4 -26
  556. data/lib/hecks/router/namespace_installer.rb +15 -39
  557. data/lib/hecks/router.rb +4 -8
  558. data/lib/hecks/runtime/adapter_lookup.rb +124 -0
  559. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  560. data/lib/hecks/runtime/boot_gates.rb +3 -18
  561. data/lib/hecks/runtime/caller.rb +19 -59
  562. data/lib/hecks/runtime/capability_graph.rb +5 -21
  563. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  564. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +27 -160
  565. data/lib/hecks/runtime/command_interpreter.rb +124 -306
  566. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  567. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  568. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  569. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  570. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  571. data/lib/hecks/runtime/dependency_planning.rb +50 -104
  572. data/lib/hecks/runtime/dispatcher.rb +134 -243
  573. data/lib/hecks/runtime/entity_element.rb +40 -462
  574. data/lib/hecks/runtime/entity_interpreter.rb +64 -219
  575. data/lib/hecks/runtime/errors.rb +32 -115
  576. data/lib/hecks/runtime/event.rb +3 -25
  577. data/lib/hecks/runtime/identity.rb +18 -154
  578. data/lib/hecks/runtime/instance.rb +18 -150
  579. data/lib/hecks/runtime/interpreting.rb +14 -70
  580. data/lib/hecks/runtime/invocation.rb +28 -164
  581. data/lib/hecks/runtime/loader.rb +109 -194
  582. data/lib/hecks/runtime/outbox.rb +39 -262
  583. data/lib/hecks/runtime/policy_interpreter.rb +43 -202
  584. data/lib/hecks/runtime/port_operation_interpreter.rb +28 -97
  585. data/lib/hecks/runtime/query_interpreter.rb +122 -187
  586. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  587. data/lib/hecks/runtime/reaction_outcome.rb +89 -0
  588. data/lib/hecks/runtime/read_model_interpreter.rb +117 -208
  589. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  590. data/lib/hecks/runtime/reference_hop.rb +8 -81
  591. data/lib/hecks/runtime/refusal_wording.rb +28 -87
  592. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  593. data/lib/hecks/runtime/registry/verification.rb +213 -227
  594. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  595. data/lib/hecks/runtime/registry.rb +156 -247
  596. data/lib/hecks/runtime/remote_dispatcher.rb +68 -154
  597. data/lib/hecks/runtime/routing.rb +4 -15
  598. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  599. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  600. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  601. data/lib/hecks/runtime/tenant_check.rb +6 -68
  602. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  603. data/lib/hecks/runtime/value/admission.rb +16 -67
  604. data/lib/hecks/runtime/value/coercion.rb +162 -760
  605. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  606. data/lib/hecks/runtime/value/field_checks.rb +172 -0
  607. data/lib/hecks/runtime/value.rb +15 -84
  608. data/lib/hecks/runtime.rb +35 -69
  609. data/lib/hecks/rust_build/conformance.rb +158 -0
  610. data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
  611. data/lib/hecks/rust_build/coverage.rb +218 -0
  612. data/lib/hecks/rust_build/native_build.rb +124 -0
  613. data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
  614. data/lib/hecks/rust_build/project_rust.rb +332 -0
  615. data/lib/hecks/rust_build/wasm.rb +105 -0
  616. data/lib/hecks/rust_build/wasm_browser.rb +113 -0
  617. data/lib/hecks/rust_build.rb +144 -0
  618. data/lib/hecks/storehouse.rb +155 -582
  619. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +10 -34
  620. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  621. data/lib/hecks/three_zero/forms.yml +127 -0
  622. data/lib/hecks/three_zero.rb +12 -0
  623. data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
  624. data/lib/hecks/tools/comment_style.rb +1070 -0
  625. data/lib/hecks/tools/corpus_report.rb +87 -0
  626. data/lib/hecks/tools/deploy_recipe.rb +255 -0
  627. data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
  628. data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
  629. data/lib/hecks/tools/era_projection_backfill.rb +94 -0
  630. data/lib/hecks/tools/era_reattest.rb +146 -0
  631. data/lib/hecks/tools/evolve_run.rb +189 -0
  632. data/lib/hecks/tools/fuzz_sweep.rb +373 -0
  633. data/lib/hecks/tools/heki_compaction.rb +100 -0
  634. data/lib/hecks/tools/hoist_local_givens.rb +246 -0
  635. data/lib/hecks/tools/journal_compaction.rb +89 -0
  636. data/lib/hecks/tools/oidc_manifests.rb +70 -0
  637. data/lib/hecks/tools/query_ir_run.rb +81 -0
  638. data/lib/hecks/tools/regeneration_run.rb +166 -0
  639. data/lib/hecks/tools/rust_comment_style.rb +642 -0
  640. data/lib/hecks/tools/sequence_script.rb +62 -0
  641. data/lib/hecks/tools/tail_merge.rb +117 -0
  642. data/lib/hecks/tools/tenant_provisioning.rb +142 -0
  643. data/lib/hecks/tools/translation_audit.rb +208 -0
  644. data/lib/hecks/tools/translation_scaffold.rb +131 -0
  645. data/lib/hecks/tools.rb +86 -0
  646. data/lib/hecks/vendoring/git_environment.rb +2 -8
  647. data/lib/hecks/vendoring/git_source.rb +4 -10
  648. data/lib/hecks/vendoring.rb +7 -46
  649. data/lib/hecks/version.rb +3 -14
  650. data/lib/hecks.rb +52 -90
  651. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  652. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  653. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  654. data/qa/settings.yml +66 -0
  655. data/rust/.cargo/config.toml +6 -0
  656. data/rust/.gitignore +2 -0
  657. data/rust/Cargo.lock +54 -0
  658. data/rust/Cargo.toml +61 -0
  659. data/rust/build/.cargo/config.toml +3 -0
  660. data/rust/build/.gitignore +1 -0
  661. data/rust/build/Cargo.lock +7 -0
  662. data/rust/build/Cargo.toml +19 -0
  663. data/rust/build/src/build_artifact.rs +47 -0
  664. data/rust/build/src/cargo_sync.rs +248 -0
  665. data/rust/build/src/json.rs +421 -0
  666. data/rust/build/src/lineage_pass.rs +285 -0
  667. data/rust/build/src/main.rs +51 -0
  668. data/rust/build/src/optional_pass.rs +150 -0
  669. data/rust/build/src/pipeline.rs +264 -0
  670. data/rust/build/src/reserved_names.rs +76 -0
  671. data/rust/build/src/resolve.rs +124 -0
  672. data/rust/build/src/root.rs +25 -0
  673. data/rust/build/src/sidecars.rs +73 -0
  674. data/rust/build/src/subprocess.rs +51 -0
  675. data/rust/build/src/tmp.rs +31 -0
  676. data/rust/codegen/.cargo/config.toml +3 -0
  677. data/rust/codegen/.gitignore +1 -0
  678. data/rust/codegen/Cargo.lock +7 -0
  679. data/rust/codegen/Cargo.toml +20 -0
  680. data/rust/codegen/src/attr.rs +32 -0
  681. data/rust/codegen/src/bridging.rs +481 -0
  682. data/rust/codegen/src/commands.rs +1226 -0
  683. data/rust/codegen/src/constraints.rs +109 -0
  684. data/rust/codegen/src/dependency_planning.rs +209 -0
  685. data/rust/codegen/src/domain_generator.rs +1288 -0
  686. data/rust/codegen/src/exemplar.rs +230 -0
  687. data/rust/codegen/src/expr_emitter.rs +87 -0
  688. data/rust/codegen/src/fielded.rs +192 -0
  689. data/rust/codegen/src/hecks_naming.rs +33 -0
  690. data/rust/codegen/src/json.rs +335 -0
  691. data/rust/codegen/src/json_codec.rs +697 -0
  692. data/rust/codegen/src/literal.rs +207 -0
  693. data/rust/codegen/src/main.rs +290 -0
  694. data/rust/codegen/src/manifest.rs +227 -0
  695. data/rust/codegen/src/mutations.rs +784 -0
  696. data/rust/codegen/src/naming.rs +286 -0
  697. data/rust/codegen/src/ports.rs +208 -0
  698. data/rust/codegen/src/prelude.rs +116 -0
  699. data/rust/codegen/src/queries.rs +786 -0
  700. data/rust/codegen/src/reactions.rs +532 -0
  701. data/rust/codegen/src/read_models.rs +697 -0
  702. data/rust/codegen/src/reference_specs.rs +40 -0
  703. data/rust/codegen/src/registry.rs +966 -0
  704. data/rust/codegen/src/reserved_names.rs +76 -0
  705. data/rust/codegen/src/shared.rs +93 -0
  706. data/rust/codegen/src/skip_reason.rs +25 -0
  707. data/rust/codegen/src/types.rs +362 -0
  708. data/rust/host/.cargo/config.toml +3 -0
  709. data/rust/host/.gitignore +1 -0
  710. data/rust/host/Cargo.lock +4297 -0
  711. data/rust/host/Cargo.toml +61 -0
  712. data/rust/host/HECKS_RELEASE +1 -0
  713. data/rust/host/rds-ca-bundle.pem +2736 -0
  714. data/rust/host/src/api.rs +1596 -0
  715. data/rust/host/src/approval.rs +652 -0
  716. data/rust/host/src/auth.rs +1287 -0
  717. data/rust/host/src/bin/expr_harness.rs +46 -0
  718. data/rust/host/src/bin/lineage_harness.rs +119 -0
  719. data/rust/host/src/bin/mint_harness.rs +135 -0
  720. data/rust/host/src/checkout.rs +265 -0
  721. data/rust/host/src/dispatch.rs +1443 -0
  722. data/rust/host/src/expr_json.rs +1117 -0
  723. data/rust/host/src/field_hints.rs +52 -0
  724. data/rust/host/src/ir.rs +733 -0
  725. data/rust/host/src/journal.rs +874 -0
  726. data/rust/host/src/lambda_client.rs +378 -0
  727. data/rust/host/src/log.rs +147 -0
  728. data/rust/host/src/main.rs +474 -0
  729. data/rust/host/src/mint.rs +1865 -0
  730. data/rust/host/src/payments/keystore.rs +330 -0
  731. data/rust/host/src/payments/tests.rs +1400 -0
  732. data/rust/host/src/payments.rs +767 -0
  733. data/rust/host/src/presentation.rs +698 -0
  734. data/rust/host/src/presentation_write.rs +1431 -0
  735. data/rust/host/src/query_step.rs +149 -0
  736. data/rust/host/src/rate_limit.rs +904 -0
  737. data/rust/host/src/reference_transform.rs +241 -0
  738. data/rust/host/src/reference_validate.rs +338 -0
  739. data/rust/host/src/resend.rs +296 -0
  740. data/rust/host/src/secrets.rs +106 -0
  741. data/rust/host/src/server.rs +663 -0
  742. data/rust/host/src/storage_shape.rs +175 -0
  743. data/rust/host/src/test_pg.rs +40 -0
  744. data/rust/host/src/ui_schema.rs +1351 -0
  745. data/rust/host/src/wasm_runner.rs +191 -0
  746. data/rust/host/src/web/newsletter/cooldown.rs +126 -0
  747. data/rust/host/src/web/newsletter/tests.rs +104 -0
  748. data/rust/host/src/web/newsletter.rs +310 -0
  749. data/rust/host/src/web/newsletter_send.rs +357 -0
  750. data/rust/host/src/web/registrations.rs +1505 -0
  751. data/rust/host/src/web.rs +2937 -0
  752. data/rust/lsp/.gitignore +1 -0
  753. data/rust/lsp/Cargo.lock +7 -0
  754. data/rust/lsp/Cargo.toml +16 -0
  755. data/rust/lsp/README.md +133 -0
  756. data/rust/lsp/src/diagnostics.rs +165 -0
  757. data/rust/lsp/src/json.rs +385 -0
  758. data/rust/lsp/src/main.rs +399 -0
  759. data/rust/lsp/src/outline.rs +177 -0
  760. data/rust/lsp/src/rpc.rs +67 -0
  761. data/rust/parser/.cargo/config.toml +5 -0
  762. data/rust/parser/.gitignore +1 -0
  763. data/rust/parser/Cargo.lock +7 -0
  764. data/rust/parser/Cargo.toml +21 -0
  765. data/rust/parser/src/build/closed_sets.rs +27 -0
  766. data/rust/parser/src/build/identity.rs +276 -0
  767. data/rust/parser/src/build/mod.rs +12 -0
  768. data/rust/parser/src/build/naming.rs +141 -0
  769. data/rust/parser/src/build/pattern_subset.rs +161 -0
  770. data/rust/parser/src/build/query_derive.rs +72 -0
  771. data/rust/parser/src/build/query_inference.rs +269 -0
  772. data/rust/parser/src/build/query_options.rs +100 -0
  773. data/rust/parser/src/build/read_model.rs +94 -0
  774. data/rust/parser/src/build/references.rs +32 -0
  775. data/rust/parser/src/canonical.rs +114 -0
  776. data/rust/parser/src/diag.rs +55 -0
  777. data/rust/parser/src/emit.rs +953 -0
  778. data/rust/parser/src/expr/ast_json.rs +147 -0
  779. data/rust/parser/src/expr/evaluator.rs +81 -0
  780. data/rust/parser/src/expr/mod.rs +84 -0
  781. data/rust/parser/src/expr/resolver.rs +497 -0
  782. data/rust/parser/src/ir.rs +387 -0
  783. data/rust/parser/src/keywords.rs +585 -0
  784. data/rust/parser/src/lex.rs +662 -0
  785. data/rust/parser/src/main.rs +252 -0
  786. data/rust/parser/src/parse/aggregate.rs +612 -0
  787. data/rust/parser/src/parse/chapter.rs +517 -0
  788. data/rust/parser/src/parse/command.rs +637 -0
  789. data/rust/parser/src/parse/domain_port.rs +127 -0
  790. data/rust/parser/src/parse/entity.rs +527 -0
  791. data/rust/parser/src/parse/file.rs +67 -0
  792. data/rust/parser/src/parse/hecksagon.rs +231 -0
  793. data/rust/parser/src/parse/lifecycle.rs +162 -0
  794. data/rust/parser/src/parse/mod.rs +1262 -0
  795. data/rust/parser/src/parse/policy.rs +87 -0
  796. data/rust/parser/src/parse/process_manager.rs +305 -0
  797. data/rust/parser/src/parse/query.rs +100 -0
  798. data/rust/parser/src/parse/read_model.rs +214 -0
  799. data/rust/parser/src/parse/value_object.rs +250 -0
  800. data/rust/parser/src/ruby_value.rs +422 -0
  801. data/rust/project/bridging.rb +257 -0
  802. data/rust/project/commands.rb +877 -0
  803. data/rust/project/constraints.rb +86 -0
  804. data/rust/project/dependency_planning.rb +178 -0
  805. data/rust/project/domain_generator.rb +980 -0
  806. data/rust/project/exemplar.rb +148 -0
  807. data/rust/project/expr_emitter.rb +72 -0
  808. data/rust/project/fielded.rb +141 -0
  809. data/rust/project/json_codec.rb +641 -0
  810. data/rust/project/mutations.rb +721 -0
  811. data/rust/project/naming.rb +205 -0
  812. data/rust/project/ports.rb +65 -0
  813. data/rust/project/queries.rb +493 -0
  814. data/rust/project/reactions.rb +317 -0
  815. data/rust/project/read_models.rb +690 -0
  816. data/rust/project/reference_specs.rb +27 -0
  817. data/rust/project/registry.rb +396 -0
  818. data/rust/project/skip_reason.rb +29 -0
  819. data/rust/project/types.rb +225 -0
  820. data/rust/project/write_if_changed.rb +83 -0
  821. data/rust/project.rb +30 -0
  822. data/rust/project_rust_pipeline.rb +383 -0
  823. data/rust/src/exemplar/commands.rs +279 -0
  824. data/rust/src/exemplar/constraints.rs +18 -0
  825. data/rust/src/exemplar/fielded.rs +280 -0
  826. data/rust/src/exemplar/json.rs +277 -0
  827. data/rust/src/exemplar/mod.rs +17 -0
  828. data/rust/src/exemplar/mutations.rs +80 -0
  829. data/rust/src/exemplar/queries.rs +33 -0
  830. data/rust/src/exemplar/reactions.rs +88 -0
  831. data/rust/src/exemplar/read_models.rs +48 -0
  832. data/rust/src/exemplar/registry.rs +166 -0
  833. data/rust/src/exemplar/types.rs +44 -0
  834. data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
  835. data/rust/src/kernel/attribute_shapes/list.rs +18 -0
  836. data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
  837. data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
  838. data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
  839. data/rust/src/kernel/cli.rs +585 -0
  840. data/rust/src/kernel/dispatch.rs +1090 -0
  841. data/rust/src/kernel/expr.rs +458 -0
  842. data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
  843. data/rust/src/kernel/expression_operators/comparison.rs +48 -0
  844. data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
  845. data/rust/src/kernel/expression_operators/logical.rs +13 -0
  846. data/rust/src/kernel/expression_operators/membership.rs +18 -0
  847. data/rust/src/kernel/expression_operators/mod.rs +68 -0
  848. data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
  849. data/rust/src/kernel/expression_operators/positional.rs +126 -0
  850. data/rust/src/kernel/expression_operators/presence.rs +101 -0
  851. data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
  852. data/rust/src/kernel/expression_operators/sized.rs +35 -0
  853. data/rust/src/kernel/expression_operators/text.rs +79 -0
  854. data/rust/src/kernel/expression_operators/to_string.rs +18 -0
  855. data/rust/src/kernel/json.rs +779 -0
  856. data/rust/src/kernel/mod.rs +133 -0
  857. data/rust/src/kernel/named_query.rs +219 -0
  858. data/rust/src/kernel/orchestrate.rs +1810 -0
  859. data/rust/src/kernel/pattern.rs +307 -0
  860. data/rust/src/kernel/query_comparators.rs +206 -0
  861. data/rust/src/kernel/query_ordering.rs +103 -0
  862. data/rust/src/kernel/read_model.rs +914 -0
  863. data/rust/src/kernel/reference_lookup.rs +149 -0
  864. data/rust/src/kernel/repository.rs +468 -0
  865. data/rust/src/kernel/routing.rs +612 -0
  866. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
  867. data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
  868. data/rust/src/kernel/vocab/field_hint.rs +70 -0
  869. data/rust/src/kernel/vocab/mod.rs +20 -0
  870. data/rust/src/kernel/vocab/query_comparator.rs +65 -0
  871. data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
  872. data/rust/src/lib.rs +8 -0
  873. data/rust/src/main.rs +16 -0
  874. data/rust/web/.gitignore +1 -0
  875. data/rust/web/Cargo.lock +119 -0
  876. data/rust/web/Cargo.toml +18 -0
  877. data/rust/web/src/lib.rs +8 -0
  878. metadata +478 -12
  879. data/lib/hecks/facade/cli_door.rb +0 -178
  880. data/lib/hecks/facade/cli_runner.rb +0 -271
  881. data/lib/hecks/facade/handle.rb +0 -293
  882. data/lib/hecks/facade/json_door.rb +0 -247
  883. data/lib/hecks/facade/surface/aggregate_door.rb +0 -222
  884. data/lib/hecks/facade/surface/chapter.rb +0 -116
  885. data/lib/hecks/facade/surface.rb +0 -61
  886. data/lib/hecks/mcp_door_scope.rb +0 -176
@@ -2,52 +2,21 @@ module Hecks
2
2
  module Bluebook
3
3
  module DSL
4
4
  class BluebookBuilder
5
- # The self.validate_*/self.correlation_*/self.event_*/self.walk_*
6
- # cluster — every whole-chapter, cross-aggregate check `#build`
7
- # runs once a chapter is fully assembled (reference targets, event
8
- # shapes, correlation keys, query hops, projected fields,
9
- # bidirectional-reference cycles), plus each one's own private
10
- # helpers. Split out of bluebook_builder.rb (which keeps the DSL
11
- # surface — chapter/aggregate/read_model/policy declaration,
12
- # `#build`, and `self.build`) for the same reason
13
- # AggregateBuilder::Sealing exists: one cohesive concern, filed by
14
- # responsibility rather than by an arbitrary line count.
15
- #
16
- # `extend`ed onto BluebookBuilder rather than `include`d — every
17
- # one of these is a pure function of its own explicit arguments
18
- # (an assembled `Bluebook::Chapter`, an aggregate list, ...), never
19
- # builder-instance state (no `@ivar` read anywhere in this
20
- # module), and every caller reaches these as
21
- # `BluebookBuilder.validate_assembled!(...)` class-level calls,
22
- # `#build`'s own `self.class.validate_assembled!` included.
23
- # `extend` is `include`'s class-level counterpart: the same "one
24
- # class, its methods, filed across files" pattern
25
- # `Runtime::Registry` already uses (`include Verification`), just
26
- # turning each method into a class (singleton) method instead of
27
- # an instance method, the same role `def self.foo` fills on a
28
- # class that keeps its methods in one file.
5
+ # Whole-chapter, cross-aggregate checks that `#build` runs once a chapter is assembled.
6
+ # Extended onto BluebookBuilder: pure functions of their arguments, no builder state.
29
7
  module Validation
30
- # Runs every whole-chapter check against one fully assembled chapter.
8
+ # Runs every whole-chapter check against one assembled chapter.
9
+ # Public so `MetaValidator.judge_deferred!` can call it with no builder instance.
31
10
  #
32
- # **Every whole-chapter check, in one place** — a pure function of an
33
- # assembled `Bluebook::Chapter`, callable both from `#build` (right
34
- # after assembly) and from `MetaValidator.judge_deferred!` (once, on
35
- # a chapter whose files have all loaded — see `#build`'s own comment
36
- # for why that split exists at all). Public, not
37
- # `private_class_method`'d, for exactly that second caller —
38
- # `MetaValidator` needs to reach this with no builder instance in
39
- # hand, only the chapter `judge_deferred!` already read back out of
40
- # the registry.
41
- #
42
- # @param bluebook [Bluebook::Chapter] the fully assembled chapter to validate
11
+ # @param bluebook [Bluebook::Chapter] the assembled chapter to validate
43
12
  # @return [void]
44
- # @raise [Bluebook::DSL::Malformed] if any of the reference, event-shape, `with:`,
45
- # query-hop, projected-field or provision checks below finds a violation
46
- # @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] if a process
47
- # manager's `correlates_by` resolves to something other than a scalar field
13
+ # @raise [Bluebook::DSL::Malformed] if any check finds a violation
14
+ # @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] if a
15
+ # `correlates_by` resolves to something other than a scalar field
48
16
  def validate_assembled!(bluebook)
49
- # moved to the language: an attribute type is a reference to its Shape,
50
- # so an undeclared value object fails reference resolution
17
+ validate_not_the_gem_module!(bluebook)
18
+ # an attribute type references its Shape, so an undeclared value object
19
+ # fails resolution
51
20
  validate_reference_value_objects!(bluebook.aggregates)
52
21
  validate_correlation_keys!(bluebook.process_managers, bluebook.aggregates)
53
22
  validate_no_bidirectional_references!(bluebook.aggregates)
@@ -56,40 +25,18 @@ module Hecks
56
25
  validate_with_projections!(bluebook.policies, bluebook.process_managers, bluebook.aggregates)
57
26
  end
58
27
 
59
- # Every hop AggregateBuilder#seal_query_field recognised and
60
- # deferred gets checked for real here — the earliest point a
61
- # hop can be checked, for exactly the reason
62
- # validate_no_bidirectional_references! above already gives:
63
- # `Bluebook.new` just stamped `hecks_owner` on every
64
- # aggregate, so `Reference#resolve` finally has a chapter to
65
- # walk. Before this line every target in the file (including
66
- # ones declared above the aggregate doing the asking) would
67
- # have resolved to nil.
28
+ # Hops resolve only now: `Bluebook.new` has just stamped `hecks_owner` on
29
+ # every aggregate.
68
30
  infer_hop_query_arguments!(bluebook)
69
31
  validate_query_hops!(bluebook)
70
32
 
71
- # Same precondition, same reason: a `projects` declaration's
72
- # own reference cannot resolve until every aggregate in the
73
- # chapter is real and owner-stamped (S12, ADR 0025).
33
+ # A `projects` reference needs the same owner-stamped aggregates to resolve (ADR 0025).
74
34
  validate_projected_fields!(bluebook)
75
35
 
76
36
  validate_provisions!(bluebook)
77
37
  end
78
38
 
79
39
  # Checks every `provides` row against `Capabilities::CONTRACTS`.
80
- #
81
- # **What a declared capability must name**. A `provides` row is only
82
- # worth trusting in place of a name check if it is checked: an
83
- # unknown capability, a missing or extra key, or a verb that is
84
- # not this chapter's own command/query of the right kind would
85
- # otherwise wire a role check to nothing, in silence.
86
- #
87
- # @param bluebook [Bluebook::Chapter] the chapter whose `provides` rows to check
88
- # @return [void]
89
- # @raise [Bluebook::DSL::Malformed] if a row names a capability
90
- # `Capabilities::CONTRACTS` does not know, its keys do not match the
91
- # capability's contract exactly, or a verb names no real command/query
92
- # of the declared kind
93
40
  def validate_provisions!(bluebook)
94
41
  bluebook.provides.group_by(&:capability).each do |capability, rows|
95
42
  contract = Capabilities::CONTRACTS.fetch(capability) do
@@ -107,17 +54,6 @@ module Hecks
107
54
  end
108
55
  end
109
56
 
110
- # Checks that one `provides` row's verb names a real command or query.
111
- #
112
- # @param bluebook [Bluebook::Chapter] the chapter the row was declared on
113
- # @param capability [String] the capability the row belongs to, such as
114
- # `"authorization"`
115
- # @param row [Bluebook::Chapter::Provision] the one declared row to check
116
- # @param kind [Symbol] `:command`, `:query` or `:port_operation`, the kind of
117
- # verb this row's key must name (from `Capabilities::CONTRACTS`)
118
- # @return [void]
119
- # @raise [Bluebook::DSL::Malformed] if `row.verb` does not split into a real
120
- # aggregate and a member of the right kind that aggregate declares
121
57
  def validate_provided_verb!(bluebook, capability, row, kind)
122
58
  return validate_provided_port_operation!(bluebook, capability, row) if kind == :port_operation
123
59
 
@@ -129,18 +65,9 @@ module Hecks
129
65
  "which names no #{kind} this chapter declares (spelled \"Aggregate.#{kind.capitalize}\")"
130
66
  end
131
67
 
132
- # Checks the spelling of a `provides` verb that names a hecksagon port
133
- # operation. The port itself is declared in the hecksagon, which attaches
134
- # after this chapter is built, so all that can be checked here is that the
135
- # verb has the `"Aggregate.Port.Operation"` shape and its aggregate is
136
- # this chapter's; `Registry#verify!` checks the operation exists.
137
- #
138
- # @param bluebook [Bluebook::Chapter] the chapter the row was declared on
139
- # @param capability [String] the capability the row belongs to
140
- # @param row [Bluebook::Chapter::Provision] the one declared row to check
141
- # @return [void]
142
- # @raise [Bluebook::DSL::Malformed] if the verb is not `"Aggregate.Port.Operation"`
143
- # or names an aggregate this chapter does not declare
68
+ # Only the verb's shape and aggregate are checkable here: the port is declared
69
+ # in the hecksagon, which attaches after the chapter is built. `Registry#verify!`
70
+ # checks the operation.
144
71
  def validate_provided_port_operation!(bluebook, capability, row)
145
72
  aggregate_name, port, operation = row.verb.split(".", 3)
146
73
  return if bluebook.aggregate(aggregate_name) && port && operation && !operation.include?(".")
@@ -149,44 +76,13 @@ module Hecks
149
76
  "which is not spelled \"Aggregate.Port.Operation\" over an aggregate this chapter declares"
150
77
  end
151
78
 
152
- # Names of the members a `provides` verb of the given kind may address.
153
- #
154
- # @param aggregate [Bluebook::Aggregate] the aggregate the verb is scoped to
155
- # @param kind [Symbol] `:command` or `:query`
156
- # @return [Array<String>] every command's `hecks_name` when `kind` is `:command`,
157
- # else every query's declared `name`
158
79
  def provided_member_names(aggregate, kind)
159
80
  kind == :command ? aggregate.commands.map(&:hecks_name) : aggregate.queries.map(&:name)
160
81
  end
161
82
 
162
- # Refuses an entity command that names itself as its own root.
163
- #
164
- # That is the whole of what is left here, and it needs saying plainly:
165
- # the refusal message once read "references must target aggregate
166
- # heads," a claim broader than what this actually checks.
167
- #
168
- # `CommandBuilder#reference_to` sets `references` only when the target's
169
- # bare name equals the owner's ; anything else becomes a reference
170
- # attribute. So on an aggregate command `references` is always a copy of
171
- # that aggregate's own name, and looking it up in an index of aggregates
172
- # is a tautology — that branch never refused anything and structurally
173
- # could not. Verified across all eight golden chapters before deleting it.
174
- #
175
- # On a piece's command the owner is the entity, and an entity is not a
176
- # head, so what this actually refuses is `reference_to <its own name>`
177
- # written inside `entity do … end`. A piece is reached through its
178
- # aggregate ; a command on one addresses the aggregate, never the piece.
179
- #
180
- # Reference attributes are the language's business now — offered as the
181
- # head's own id and resolved as references, so `Aggregate.Reference` and
182
- # `Command.Reference` refuse an undeclared head with no predicate at all.
183
- #
184
- # @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
185
- # @return [void]
186
- # @raise [Bluebook::DSL::Malformed] if an entity command's `references` names
187
- # something other than one of this chapter's own aggregate heads — which, given
188
- # how `CommandBuilder#reference_to` sets it, only happens when the entity command
189
- # names its own entity as its root
83
+ # Refuses an entity command that names its own entity as its root.
84
+ # `CommandBuilder#reference_to` sets `references` only to the owner's bare name, so on
85
+ # an aggregate command a head lookup is a tautology; only an entity self-reference fails.
190
86
  def validate_reference_value_objects!(aggregates)
191
87
  heads = aggregates.map(&:hecks_name)
192
88
 
@@ -207,27 +103,8 @@ module Hecks
207
103
  "an entity command is addressed through its aggregate; #{violations.uniq.join('; ')}"
208
104
  end
209
105
 
210
- # Events are first-class by convention, not by declaration (ADR
211
- # 0025, "events and reactions" — "a domain event is a value
212
- # object with its own attributes, not a label"). No new `event
213
- # do ... end` construct exists to hand-author and keep in step
214
- # with every emitting command by hand — an event's own known
215
- # shape is whichever command(s) declare `emits` for its name, and
216
- # this is the one thing that has to hold for that convention to
217
- # mean anything: every command that emits a given name has to
218
- # agree on what it carries. An event is one fact; a fact does not
219
- # carry two different truths depending on who is telling it.
220
- #
221
- # Structural fields only (name/type/list/optional) — `pattern:`/
222
- # `admits:`/`default:` are refinements on a field, not a second
223
- # claim about what the payload holds, so two emitting commands
224
- # are free to differ there without actually disagreeing about
225
- # the event's own shape.
226
- #
227
- # @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
228
- # @return [void]
229
- # @raise [Bluebook::DSL::Malformed] if two or more commands `emits` the same event
230
- # name with different structural shapes
106
+ # Every command that emits a given event must agree on its structural shape
107
+ # (name, type, list, optional); `pattern:`/`admits:`/`default:` may differ (ADR 0025).
231
108
  def validate_event_shapes!(aggregates)
232
109
  event_emitters(aggregates).each do |event_name, pairs|
233
110
  next if pairs.size == 1
@@ -242,39 +119,10 @@ module Hecks
242
119
  end
243
120
  end
244
121
 
245
- # The "expensive half" the ADR names: "with: { account: :account }
246
- # projecting into a reaction that has no declared contract ...
247
- # breaks at dispatch rather than at load." Checked here, now that
248
- # `validate_event_shapes!` (above) guarantees at most one real
249
- # shape per event name, and command references being first-class
250
- # (`Naming.command_ref`) means the target side is a real
251
- # resolvable command, not a string that might be a typo.
252
- #
253
- # **Same-chapter only, on purpose** — a `with:` whose source event or
254
- # target command lives outside this chapter (an `across` policy
255
- # reacting to another domain's event entirely) is silently left
256
- # unchecked rather than refused: there is nothing here yet to
257
- # check it against, and "unresolvable" is not the same claim as
258
- # "wrong."
259
- #
260
- # A FOR_EACH policy's source isn't the event at all — a fan-out
261
- # `with:`'s symbols read the query row `for_each` answers
262
- # (FreezeAccountsOnSuspension's own comment: "`account` is the
263
- # key the fan-out merges for each row"), which this has no shape
264
- # for; the source half is skipped for those, the target half
265
- # (does the dispatched command actually declare the field) still
266
- # runs, since that half is true regardless of where the value
267
- # came from.
268
- #
269
- # @param policies [Array<Bluebook::Policy>] every reaction hoisted onto the
270
- # chapter's own aggregates
271
- # @param process_managers [Array<Bluebook::ProcessManager>] the chapter's declared
272
- # process managers
273
- # @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
274
- # @return [void]
275
- # @raise [Bluebook::DSL::Malformed] if a policy's or a saga dispatch's `with:`
276
- # names a field its target command does not declare, or reads a source field
277
- # its event does not carry (see `check_with_spec!` for the full rule)
122
+ # Checks `with:` projections against the source event's shape and the target's fields.
123
+ # Same-chapter only: an unresolvable cross-chapter source or target is skipped.
124
+ # A for_each policy's source is a query row with no known shape, so only its target
125
+ # is checked.
278
126
  def validate_with_projections!(policies, process_managers, aggregates)
279
127
  lookup = command_lookup(aggregates)
280
128
  heads = correlation_heads(process_managers)
@@ -299,47 +147,11 @@ module Hecks
299
147
  end
300
148
  end
301
149
 
302
- # `process_manager:` is present only for a process manager's own dispatch — a
303
- # saga leg's source symbol resolves against the current triggering
304
- # event first, same as a policy, but falls all the way back to the
305
- # saga's own memory when the current event does not carry it
306
- # (`SagaInterpreter#dispatch_args`, its own last `else`) — and
307
- # memory starts as the opening event's payload
308
- # (`SagaInterpreter#instance = { ..., memory: event.payload }`,
309
- # never updated after), never the leg's own. Settlement's own
310
- # comment names exactly this: "the credit leg reads a destination
311
- # no event carried" — `AccountDebited` never declares `:reference`,
312
- # only `TransferRequested` (`pm.starts_on`) does, and that is
313
- # where the value is genuinely still coming from.
314
- # One closed sequence of per-field checks against a fixed set of
315
- # legal `with:` sources (target's own attributes, correlation,
316
- # emitter identity, event/memory shape) — see the comment above
317
- # for why each source is legal. Extracting the loop body would
318
- # mean threading ten-plus already-resolved locals (target,
319
- # source_shape, memory_shape, correlation, identity_sources, ...)
320
- # into a new method for no gain: each `next`/`raise` already
321
- # reads as its own rule at its own site.
322
- #
323
- # @param command_ref [String] the dotted name of the command `with:` dispatches,
324
- # such as `"Account.Debit"` or `"Account.Piece.Move"`
325
- # @param event_name [String, nil] the triggering event's name, or `nil` for a
326
- # FOR_EACH policy (whose source is a query row, not an event)
327
- # @param with_spec [Array<Array(Symbol, Object)>] the declared `with:` pairs,
328
- # field name to source — a Symbol naming a field to read, or a literal value
329
- # @param lookup [Hash{String => Class}] every command in the chapter, keyed by
330
- # `"owner.hecks_name"` (`command_lookup`'s own shape)
331
- # @param label [String] a human-readable description of what declared this
332
- # `with:`, used only in a raised message
333
- # @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
334
- # @param correlation_heads [Array<Symbol>] every saga's own `correlates_by` head
335
- # in this chapter, a legal addressing key alongside a command's own attributes
336
- # @param process_manager [Bluebook::ProcessManager, nil] the process manager this
337
- # `with:` dispatches from, or `nil` for a policy's own trigger
338
- # @return [void]
339
- # @raise [Bluebook::DSL::Malformed] if `with:` names a field the target command
340
- # does not declare, or reads a Symbol source that resolves to neither the
341
- # correlation key, the emitter's identity, nor a field the triggering event or
342
- # (for a saga leg) the starting event's shape carries
150
+ # Checks one `with:` spec: each key must be a field the target command accepts,
151
+ # and each Symbol source must be the correlation key, the emitter's identity, or a
152
+ # field of the event's shape. A saga leg also falls back to the opening event's shape,
153
+ # since saga memory starts as that payload. One closed sequence of per-field checks;
154
+ # splitting it threads many locals.
343
155
  # rubocop:disable-next Metrics/CyclomaticComplexity
344
156
  # rubocop:disable-next Metrics/PerceivedComplexity
345
157
  def check_with_spec!(command_ref, event_name, with_spec, lookup, label, aggregates, correlation_heads,
@@ -348,16 +160,9 @@ module Hecks
348
160
  source_shape = event_name && event_shape_for(event_name, aggregates)
349
161
  memory_shape = process_manager && event_shape_for(process_manager.starts_on, aggregates)
350
162
  correlation = process_manager&.correlates_by && process_manager.correlation_head
351
- # A policy's source also carries the emitter's own identity —
352
- # `PolicyInterpreter#emitter_identity`, the runtime half of this.
353
- # An entity command's event never declares its aggregate's
354
- # identity (it arrives through `reference_to`, not an
355
- # `attribute`), so before this a policy on `KnightCaptured` could
356
- # not spell `with: { label: :label }` at all — "reads :label off
357
- # KnightCaptured, which does not declare it" — and chess's
358
- # AdvancePly grew optional, unread attributes just to survive a
359
- # wholesale forward. Policies only: a saga leg's own source is
360
- # `SagaInterpreter#dispatch_args`, which merges no such thing.
163
+ # A policy's source also carries the emitter's identity
164
+ # (`PolicyInterpreter#emitter_identity`); a saga leg's source
165
+ # (`SagaInterpreter#dispatch_args`) merges no such thing.
361
166
  identity_sources = process_manager.nil? && event_name ? event_identity_heads_for(event_name, aggregates) : []
362
167
 
363
168
  with_spec.each do |field, source|
@@ -380,34 +185,9 @@ module Hecks
380
185
  end
381
186
  end
382
187
 
383
- # Decides whether `field` is a legal `with:` target on `command`.
384
- #
385
- # A command's own `reference_to` (bare, no `as:`) never lands in
386
- # `attributes` — `CommandBuilder#reference_to`'s self-reference
387
- # branch sets `command.references` instead (S2), and mints no new
388
- # field at all. What addresses it is not one name but the same
389
- # set `CommandInterpreter::ArgumentGate#refuse_unknown_arguments`
390
- # already accepts at dispatch time — `:id`, the owning aggregate's
391
- # own `identity_heads` (real corpus proof — `Account.Debit`
392
- # dispatched everywhere as `number: ...`, `Account`'s own
393
- # `identified_by`), and `Naming.reference_key(command.references)`
394
- # (real corpus proof — `FreezeAccountsOnSuspension`'s `for_each`
395
- # fan-out, whose own comment reads "`account` is the key the
396
- # fan-out merges for each row it answers"). Both are simultaneously
397
- # legal there, not context-dependent alternatives, so both are
398
- # legal here : this mirrors that gate rather than re-deriving a
399
- # narrower rule that would refuse one of two real, already-shipped
400
- # dispatch conventions.
401
- #
402
- # @param command [Class<Bluebook::Command>] the command `with:` dispatches
403
- # @param field [Symbol] the `with:` key to check
404
- # @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates,
405
- # searched when `field` might name `command`'s own reference key
406
- # @param correlation_heads [Array<Symbol>] every saga's own `correlates_by` head
407
- # in this chapter
408
- # @return [Boolean] `true` when `field` is one of `command`'s own attributes,
409
- # `:id`, a correlation head, or the identity/reference key of the aggregate
410
- # `command.references` names
188
+ # Mirrors `ArgumentGate#refuse_unknown_arguments`: `:id`, the owning aggregate's
189
+ # `identity_heads` and `Naming.reference_key(command.references)` are all legal keys
190
+ # beside the command's own attributes.
411
191
  def command_declares?(command, field, aggregates, correlation_heads)
412
192
  return true if command.attributes.any? { |a| a.name == field }
413
193
  return true if field == :id
@@ -420,38 +200,14 @@ module Hecks
420
200
  referenced.identity_heads.include?(field) || Naming.reference_key(command.references) == field
421
201
  end
422
202
 
423
- # The fourth addressing key `ArgumentGate#refuse_unknown_arguments`
424
- # accepts, alongside `:id`/`identity_heads`/`reference_key` — every
425
- # saga in this domain's own `correlates_by` head, carried through
426
- # every dispatch as pure passthrough (Settlement's own comment:
427
- # "`reference:` carries the correlation forward... this is pure
428
- # passthrough, not an addressing key"). A command declaring none of
429
- # its attributes named this is not a gap; the correlation key rides
430
- # through commands that never read it, same as it does at runtime.
431
- #
432
- # @param process_managers [Array<Bluebook::ProcessManager>] the chapter's declared
433
- # process managers
434
- # @return [Array<Symbol>] the `correlation_head` of every process manager that
435
- # declares a `correlates_by`
203
+ # A saga's `correlates_by` head is a legal `with:` key: correlation rides
204
+ # through commands that never read it.
436
205
  def correlation_heads(process_managers)
437
206
  process_managers.filter_map { |pm| pm.correlates_by && pm.correlation_head }
438
207
  end
439
208
 
440
- # Yields every command this chapter declares, paired with its owner's name.
441
- #
442
- # Every command this chapter declares, an aggregate's own and
443
- # every entity nested inside one, paired with a name for what
444
- # declares it — shared by `validate_event_shapes!` and
445
- # `validate_with_projections!`'s own command lookup, the same
446
- # reach `HecksagonBuilder#commands_in` needs one level up (S8).
447
- #
448
- # @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
449
- # @yieldparam owner [String] the declaring aggregate's `hecks_name`, or
450
- # `"Aggregate.Entity"` for a command declared on a nested entity
451
- # @yieldparam command [Class<Bluebook::Command>] the command class
452
- # @return [Enumerator<Array(String, Class)>] an enumerator over the same pairs
453
- # when no block is given; otherwise the value of the underlying `each`, unused
454
- # by any caller
209
+ # Yields every command the chapter declares with its owner's name
210
+ # ("Aggregate.Entity" for entities).
455
211
  def each_command(aggregates)
456
212
  return enum_for(:each_command, aggregates) unless block_given?
457
213
 
@@ -463,116 +219,41 @@ module Hecks
463
219
  end
464
220
  end
465
221
 
466
- # Groups every command in the chapter by the event name(s) it emits.
467
- #
468
- # **Not memoised** — caching this as `@event_emitters ||=` on the builder
469
- # instance would be safe for a one-file chapter but wrong for one split
470
- # across several: the first file's `build()` call would compute and cache
471
- # it from whatever `@aggregates` held at that moment, and every later
472
- # file's own validation would keep reading that same stale snapshot,
473
- # silently missing any command a later file adds. Recomputed fresh every
474
- # call instead — this walks the whole chapter once per `#build`, not a
475
- # hot path worth memoising at that cost.
476
- #
477
- # @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
478
- # @return [Hash{String => Array<Array(String, Class)>}] every event name mapped
479
- # to the `[owner, command]` pairs (`each_command`'s own shape) that `emits` it;
480
- # an unqueried event name answers `[]`
222
+ # Not memoised: a chapter split across files is validated once per file, and a
223
+ # cached index would miss the commands later files add.
481
224
  def event_emitters(aggregates)
482
225
  each_command(aggregates).with_object(Hash.new { |h, k| h[k] = [] }) do |(owner, command), index|
483
226
  command.emits.each { |event_name| index[event_name] << [owner, command] }
484
227
  end
485
228
  end
486
229
 
487
- # The comparable shape of one command's attributes, value objects unwrapped.
488
- #
489
- # **Structural, not nominal**. Two commands on two different
490
- # aggregates that both `emits "SameEvent"` are free to type a
491
- # field through two different, locally-scoped wrapper value
492
- # objects (e.g. one aggregate's own `value: SomeText` vs
493
- # another's `value: OtherText`, exactly the per-aggregate "own
494
- # text VO" convention every aggregate in this grammar already
495
- # follows for everything from `RuleText` to `FieldRef`) without
496
- # actually disagreeing about the event's shape — comparing
497
- # `a.type` by name would flag that as a violation for no real
498
- # reason: an event is one fact, and two isomorphic wrapper types
499
- # tell an identical one. So a value-object type is unwrapped to
500
- # its own attribute shape (recursively — a wrapper could itself
501
- # wrap another) before comparing, and only a primitive type
502
- # (nothing left to unwrap) or two VOs that truly differ once
503
- # unwrapped still counts as a real mismatch. `owner` carries the
504
- # type's `value_object` lookup — a command's own attributes only
505
- # know their type's name, never the aggregate that declared it,
506
- # and two sibling aggregates in one chapter each keep a
507
- # same-named VO private to themselves, so the unwrap has to ask
508
- # the same aggregate the field's own command belongs to, never a
509
- # neighbor's.
510
- #
511
- # @param command [Class<Bluebook::Command>] the command whose attribute shape
512
- # to compute
513
- # @param owner [Bluebook::Aggregate, nil] the aggregate whose `value_object`
514
- # lookup resolves any wrapper type by name; `nil` when the owner could not
515
- # be resolved, in which case nothing is unwrapped
516
- # @return [Array<Array>] one `[name, unwrapped_type, list, optional]` tuple per
517
- # attribute, sorted; `unwrapped_type` is a type name String for a primitive
518
- # or an unresolved type, or a recursively-unwrapped Array of the same shape
519
- # for a value object
230
+ # The comparable shape of a command's attributes, value objects unwrapped.
231
+ # Structural, not nominal: aggregates may type a field with differently named
232
+ # wrapper value objects without disagreeing about the event. `owner` supplies the
233
+ # `value_object` lookup, since same-named value objects are private to each aggregate.
520
234
  def event_shape(command, owner)
521
235
  command.attributes.map { |a| [a.name, unwrap_shape(owner, a.type.to_s), a.list?, a.optional?] }.sort
522
236
  end
523
237
 
524
- # Recursively unwraps a type name to a primitive or a value object's own shape.
525
- #
526
- # @param owner [Bluebook::Aggregate, nil] the aggregate whose `value_object`
527
- # lookup resolves `type_name`; `nil` answers `type_name` unchanged
528
- # @param type_name [String] the attribute type to unwrap
529
- # @param seen [Array<String>] type names already unwrapped on this path, so a
530
- # self-referential value object bottoms out on its own name instead of
531
- # recursing forever
532
- # @return [String, Array<Array>] `type_name` itself when `owner` is `nil`, when
533
- # it is already a primitive, when it is already in `seen`, or when it names no
534
- # value object `owner` declares; otherwise the same `[name, unwrapped_type,
535
- # list, optional]` shape `event_shape` returns, one tuple per member
536
238
  def unwrap_shape(owner, type_name, seen = [])
537
- # owner couldn't be resolved, so compare by name: nothing left to unwrap
538
239
  return type_name if owner.nil?
539
240
  return type_name if Attribute::PRIMITIVES.include?(type_name)
540
- # a self-referential VO bottoms out on its own name, not an infinite unwrap
241
+ # a self-referential value object bottoms out on its own name
541
242
  return type_name if seen.include?(type_name)
542
243
 
543
244
  shape = owner.value_object(type_name)
544
- # not this owner's own VO (a reference type, say): nothing further to unwrap
545
245
  return type_name unless shape
546
246
 
547
247
  shape.attributes.map { |a| [a.name, unwrap_shape(owner, a.type.to_s, seen + [type_name]), a.list?, a.optional?] }.sort
548
248
  end
549
249
 
550
- # Finds the aggregate an `each_command` owner string names.
551
- #
552
- # `owner` (from `each_command`) is a plain string — the aggregate's
553
- # `hecks_name` alone, or `"Aggregate.Entity"` for an entity's own
554
- # command. Either way the value objects a command's fields can be
250
+ # The value objects a command's fields can be
555
251
  # typed with are the aggregate's own (`Entity` carries no
556
- # `value_object` lookup of its own — the whole rest of this file
557
- # already resolves hop/type lookups only at the aggregate level,
558
- # e.g. `validate_hop_tail!`'s `target.value_object(type)`), so only
559
- # the first segment ever matters here.
560
- #
561
- # @param owner [String] an `each_command` owner string, such as `"Game"` or
562
- # `"Game.Knight"`
563
- # @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
564
- # @return [Bluebook::Aggregate, nil] the aggregate whose `hecks_name` matches
565
- # `owner`'s first dotted segment, or `nil` if none does
252
+ # `value_object` lookup), so only the owner string's first segment matters.
566
253
  def owner_aggregate(owner, aggregates)
567
254
  aggregates.find { |a| a.hecks_name == owner.to_s.split(".").first }
568
255
  end
569
256
 
570
- # The comparable attribute shape of whichever command first emits `event_name`.
571
- #
572
- # @param event_name [String, Symbol] the event name to look up
573
- # @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
574
- # @return [Array<Array>, nil] the first emitting command's `event_shape`, or `nil`
575
- # if no command in the chapter emits this event
576
257
  def event_shape_for(event_name, aggregates)
577
258
  pairs = event_emitters(aggregates).fetch(event_name.to_s, [])
578
259
  return nil if pairs.empty?
@@ -581,16 +262,8 @@ module Hecks
581
262
  event_shape(command, owner_aggregate(owner_name, aggregates))
582
263
  end
583
264
 
584
- # The identity heads of the aggregate that emits `event_name` — an
585
- # entity's event is stamped with its owning aggregate's identity
586
- # (`Event#id` is the parent's), so an owner spelled "Game.Knight"
587
- # answers Game's heads.
588
- #
589
- # @param event_name [String, Symbol] the event name to look up
590
- # @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
591
- # @return [Array<Symbol>] the emitting aggregate's own `identity_heads`, plus the
592
- # emitting entity's own `identity_heads` when the event is an entity's; `[]` if
593
- # no command in the chapter emits this event, or its owner cannot be resolved
265
+ # An entity's event is stamped with its owning aggregate's identity, so
266
+ # "Game.Knight" answers Game's heads.
594
267
  def event_identity_heads_for(event_name, aggregates)
595
268
  pairs = event_emitters(aggregates).fetch(event_name.to_s, [])
596
269
  return [] if pairs.empty?
@@ -600,57 +273,32 @@ module Hecks
600
273
  return [] unless aggregate
601
274
 
602
275
  heads = aggregate.identity_heads.map(&:to_sym)
603
- # An entity's event also carries the piece's own identity — the
604
- # args a piece was addressed by are the args its event announces
605
- # (`Emission#emit`: `payload: args`), so `id`-shaped heads are
606
- # genuinely there at runtime even though no `attribute` line on
607
- # the entity command declares them.
276
+ # An entity's event also carries the piece's own identity: the args a piece
277
+ # was addressed by are its payload (`Emission#emit`).
608
278
  entity_names = owner_name.to_s.split(".").drop(1)
609
279
  entity = entity_names.reduce(aggregate) { |owner, name| owner&.entities&.find { |e| e.hecks_name == name } }
610
280
  heads + (entity ? entity.identity_heads.map(&:to_sym) : [])
611
281
  end
612
282
 
613
- # Every command in the chapter, keyed by its fully dotted address.
614
- #
615
- # @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
616
- # @return [Hash{String => Class}] `"owner.hecks_name"` (`each_command`'s owner
617
- # string joined with the command's own `hecks_name`) mapped to the command class
618
283
  def command_lookup(aggregates)
619
284
  each_command(aggregates).with_object({}) do |(owner, command), index|
620
285
  index["#{owner}.#{command.hecks_name}"] = command
621
286
  end
622
287
  end
623
288
 
624
- # A reference ring is not a modelling choice, it is a missing one
625
- # — a DDD aggregate is a consistency boundary precisely because
626
- # something outside it can only ever point in, by id, never the
627
- # other way. A caller must be able to reason about one aggregate
628
- # alone ; a ring back to where it started means no aggregate in
629
- # it is a boundary anyone can reason about without the rest of
630
- # the ring, and the whole ring is really one aggregate wearing
631
- # several names.
632
- #
633
- # Checked at the bluebook level, not inside `AggregateBuilder`
634
- # itself, because seeing a cycle needs every end declared — an
635
- # aggregate finishes building long before it can know whether
636
- # some later aggregate in the same file points back at it.
637
- #
638
- # Acyclic within a chapter (ADR 0025, "References") — catches any
639
- # ring, however long (A -> B -> C -> A), not only a direct pair
640
- # (A -> B -> A), the same DFS coloring a reference graph needs
641
- # for any cycle. A cross-chapter reference is unreachable here
642
- # rather than unchecked: `Reference#resolve` is scoped to its
643
- # own chapter by construction, so a target this chapter never
644
- # declares is a dangling name, not an edge — `edges.key?` below
645
- # is what keeps the walk from ever leaving this chapter's own
646
- # aggregates. Self-reference stays legal (`parent.parent.name`
647
- # for a hierarchy is real and safe) — excluded the same way a
648
- # direct pair is.
649
- #
650
- # @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
651
- # @return [void]
652
- # @raise [Bluebook::DSL::Malformed] if the chapter's own aggregate-to-aggregate
653
- # reference graph contains a cycle
289
+ # A reference ring means no aggregate in it is a consistency boundary. Checked at
290
+ # chapter level because seeing a cycle needs every end declared (ADR 0025). Catches
291
+ # any ring length; self-reference stays legal, and a cross-chapter target is a
292
+ # dangling name, not an edge.
293
+ # A chapter's constants install under its name, or its `namespace`; landing in the
294
+ # gem's own `Hecks` module would shadow the gem's modules (ADR 0080), so it refuses.
295
+ def validate_not_the_gem_module!(bluebook)
296
+ return unless (bluebook.namespace || bluebook.name) == "Hecks"
297
+
298
+ raise Malformed, "#{bluebook.name}'s constants would install in the gem's own Hecks module — " \
299
+ "declare a namespace, such as namespace \"Hecks::Domain\", or rename the chapter"
300
+ end
301
+
654
302
  def validate_no_bidirectional_references!(aggregates)
655
303
  edges = aggregates.to_h do |aggregate|
656
304
  [aggregate.hecks_name, aggregate.reference_targets.uniq.reject { |target| target == aggregate.hecks_name }]
@@ -667,14 +315,8 @@ module Hecks
667
315
  "through a query instead of a reference pointing back"
668
316
  end
669
317
 
670
- # Plain DFS with a visiting/done coloring, over the reference
671
- # graph this chapter's own aggregates declare. Returns the ring
672
- # itself (in the order it closes), or nil.
673
- #
674
- # @param edges [Hash{String => Array<String>}] each aggregate's `hecks_name`
675
- # mapped to the names of the aggregates it references
676
- # @return [Array<String>, nil] the aggregate names forming a cycle, in the order
677
- # the walk closes it, or `nil` if the graph has none
318
+ # Plain DFS with a visiting/done coloring; returns the ring in the order it
319
+ # closes, or nil.
678
320
  def find_reference_cycle(edges)
679
321
  state = {}
680
322
 
@@ -686,17 +328,6 @@ module Hecks
686
328
  nil
687
329
  end
688
330
 
689
- # One DFS step of `find_reference_cycle`, from `node`.
690
- #
691
- # @param node [String] the aggregate name to walk from
692
- # @param edges [Hash{String => Array<String>}] the whole reference graph, as
693
- # `find_reference_cycle` receives it
694
- # @param state [Hash{String => Symbol}] each visited node's `:visiting` or
695
- # `:done` color, mutated in place across the whole walk
696
- # @param path [Array<String>] the nodes visited on the current path, mutated
697
- # in place as the walk descends and backtracks
698
- # @return [Array<String>, nil] the cycle closing back on `node`, or `nil` if
699
- # no cycle is reachable from here
700
331
  def reference_cycle_from(node, edges, state, path)
701
332
  return nil if state[node] == :done
702
333
  return path[path.index(node)..] if state[node] == :visiting
@@ -718,36 +349,9 @@ module Hecks
718
349
  end
719
350
 
720
351
  # Resolves and checks every `where` hop deferred at aggregate-seal time.
721
- #
722
- # **The other half of a hop** — AggregateBuilder#seal_query_field
723
- # recognised the head of a dotted where-field that names one of
724
- # its own references and deferred it here, unable to check
725
- # further: it cannot yet resolve what the reference points at.
726
- # This runs once every aggregate exists in one chapter, so it
727
- # can.
728
- #
729
- # Only `where` clauses ever reach here — a hop on ORDER BY is
730
- # refused outright, immediately, back in seal_query_field
731
- # itself (that answer never needed the target's shape).
732
- #
733
- # An entity's own queries do reach `EntityBuilder#reference_to` —
734
- # tier-1 sealing (`AggregateBuilder#query_surfaces`) already
735
- # recognises a hop on an entity's own field and defers it exactly
736
- # like an aggregate's — but nothing walks entity queries at tier 2
737
- # to check the deferral, so a bad hop, or even a well-formed one,
738
- # would otherwise build silently and then match nothing at runtime
739
- # (`QueryInterpreter#entity_rows` reads an element's fields by
740
- # literal hash key, never follows a reference). Refused outright
741
- # here instead of taught to follow the hop for real: no corpus
742
- # member needs an entity query to cross a reference yet, and a
743
- # named refusal beats a runtime that resolves nothing while
744
- # looking like it might.
745
- #
746
- # @param bluebook [Bluebook::Chapter] the fully assembled chapter to check
747
- # @return [void]
748
- # @raise [Bluebook::DSL::Malformed] if an aggregate's `where` hop resolves to
749
- # nothing, chains too deep, or lands somewhere illegal, or if an entity's own
750
- # query hops through a reference at all
352
+ # An entity query that hops through a reference is refused outright: nothing follows
353
+ # the hop at runtime (`QueryInterpreter#entity_rows` reads fields by literal key),
354
+ # so it would match nothing.
751
355
  def validate_query_hops!(bluebook)
752
356
  bluebook.aggregates.each do |aggregate|
753
357
  aggregate.queries.each do |query|
@@ -762,19 +366,8 @@ module Hecks
762
366
  end
763
367
  end
764
368
 
765
- # Mints an implicit query attribute for every unresolved symbolic hop comparison.
766
- #
767
- # The chapter-wide half of AggregateBuilder's local query-argument
768
- # inference. A hop cannot resolve while its aggregate is still being
769
- # built; here every Reference has an owner and target, so a symbolic
770
- # comparison can inherit the type of the scalar it compares without a
771
- # duplicate query-local declaration.
772
- #
773
- # @param bluebook [Bluebook::Chapter] the fully assembled chapter to infer
774
- # query arguments over
775
- # @return [void]
776
- # @raise [Bluebook::DSL::Malformed] if a hop's reference has no `declared_in`
777
- # aggregate to resolve its target through
369
+ # Mints an implicit query attribute for each symbolic hop comparison, typed from
370
+ # the scalar it compares against.
778
371
  def infer_hop_query_arguments!(bluebook)
779
372
  bluebook.aggregates.each do |aggregate|
780
373
  aggregate.queries.each do |query|
@@ -796,23 +389,6 @@ module Hecks
796
389
  end
797
390
  end
798
391
 
799
- # Builds the query attribute a resolved hop plan implies for `name`.
800
- #
801
- # The leaf `infer_hop_query_arguments!` infers for one resolved
802
- # hop plan — a pure function of `plan` and the symbolic `name`
803
- # it is naming, pulled out because it is a self-contained
804
- # computation with no dependency on the enclosing loop's own
805
- # iteration state (it neither reads nor mutates anything about
806
- # `bluebook`/`aggregate`/`query` beyond what `plan` already
807
- # carries).
808
- #
809
- # @param name [Symbol] the symbolic comparison value the query names, and the
810
- # attribute name to mint
811
- # @param plan [QuerySpecification::HopPath::Plan] the resolved hop chain whose
812
- # target's shape `name` is inferred against
813
- # @return [Bluebook::Attribute, nil] a `String`-typed attribute when the tail
814
- # lands on the target's lifecycle field; otherwise an attribute copying the
815
- # resolved leaf's type and list-ness; `nil` when the tail resolves to nothing
816
392
  def inferred_hop_leaf(name, plan)
817
393
  target = plan.hops.last.target
818
394
  head, *nested = plan.tail.to_s.split(".")
@@ -827,14 +403,6 @@ module Hecks
827
403
  end
828
404
  end
829
405
 
830
- # Refuses any `where` clause on an entity's own query that hops through a reference.
831
- #
832
- # @param aggregate [Bluebook::Aggregate] the aggregate `entity` is nested under,
833
- # named only in a raised message
834
- # @param entity [Class<Bluebook::Entity>] the entity whose queries to check
835
- # @return [void]
836
- # @raise [Bluebook::DSL::Malformed] if any of `entity`'s queries has a `where`
837
- # clause that hops through one of the entity's own references
838
406
  def refuse_entity_query_hops!(aggregate, entity)
839
407
  entity.queries.each do |query|
840
408
  query.wheres.each do |clause|
@@ -849,27 +417,13 @@ module Hecks
849
417
  end
850
418
  end
851
419
 
852
- # Resolves one `where` hop's target and checks where its tail lands.
853
- #
854
- # @param aggregate [Bluebook::Aggregate] the querying aggregate, named in a
855
- # raised message
856
- # @param query [Bluebook::Query] the query `clause` belongs to, named in a
857
- # raised message
858
- # @param clause [QuerySpecification::Common::WhereClause] the `where` clause whose
859
- # `field` hops through one of `aggregate`'s own references
860
- # @return [void]
861
- # @raise [Bluebook::DSL::Malformed] if the hop's target is not declared in this
862
- # chapter, the hop chain exceeds `QuerySpecification::HopPath::MAX_HOPS`, or
863
- # the resolved tail fails `validate_hop_tail!`'s own checks
864
420
  def validate_hop_clause!(aggregate, query, clause)
865
421
  plan = QuerySpecification::HopPath.plan(clause.field, aggregate.attributes)
866
422
 
867
423
  case plan.refusal
868
424
  when :unresolvable
869
- # HopPath.plan pushes even an unresolved hop onto `hops`
870
- # before reporting this, specifically so `target_name` —
871
- # real, known at declaration, independent of whether
872
- # `resolve` succeeded — is always here to name.
425
+ # HopPath.plan pushes even an unresolved hop onto `hops`, so `target_name`
426
+ # is always there.
873
427
  raise Malformed,
874
428
  "#{aggregate.hecks_name}.#{query.hecks_name} asks about #{clause.field}, " \
875
429
  "which hops to #{plan.hops.last.target_name}, which this chapter never " \
@@ -888,26 +442,8 @@ module Hecks
888
442
  validate_hop_tail!(aggregate, query, clause, target, plan.tail)
889
443
  end
890
444
 
891
- # Checks that a resolved hop's tail lands on a real scalar field of its target.
892
- #
893
- # The same three-way answer seal_query_field gives for its own
894
- # aggregate's fields — landing on a real scalar (fine), landing
895
- # on a value object (refused by name), or naming nothing at all
896
- # (refused by name) — asked instead of the hop's target aggregate,
897
- # since that is whose shape the tail actually has to answer for.
898
- #
899
- # @param aggregate [Bluebook::Aggregate] the querying aggregate, named in a
900
- # raised message
901
- # @param query [Bluebook::Query] the query `clause` belongs to, named in a
902
- # raised message
903
- # @param clause [QuerySpecification::Common::WhereClause] the `where` clause the
904
- # hop was resolved from, named in a raised message
905
- # @param target [Bluebook::Aggregate] the aggregate the hop resolved to
906
- # @param tail [String] the dotted field path remaining after the hop, such as
907
- # `"status"` or `"amount.currency"`
908
- # @return [void]
909
- # @raise [Bluebook::DSL::Malformed] if `tail` lands on a value object rather
910
- # than a scalar, or names no field `target` declares
445
+ # Same three-way answer as `seal_query_field` (scalar, value object, or
446
+ # nothing), asked of the hop's target.
911
447
  def validate_hop_tail!(aggregate, query, clause, target, tail)
912
448
  name, *nested = tail.to_s.split(".")
913
449
  attribute = target.attributes.find { |candidate| candidate.name.to_s == name }
@@ -933,30 +469,8 @@ module Hecks
933
469
  "not exist matches nothing and refuses nothing"
934
470
  end
935
471
 
936
- # Checks that an ordered comparator over a hopped field lands on a number.
937
- #
938
- # A where hop with an ordered comparator is legitimate ("client
939
- # whose balance > 500") — AggregateBuilder#seal_ordered_comparator
940
- # already deferred this exact check for the same reason every
941
- # other hop check is deferred, and this is where it gets asked,
942
- # against the hop's target instead of the querying aggregate.
943
- #
944
- # @param aggregate [Bluebook::Aggregate] the querying aggregate, named in a
945
- # raised message
946
- # @param query [Bluebook::Query] the query `clause` belongs to, named in a
947
- # raised message
948
- # @param clause [QuerySpecification::Common::WhereClause] the `where` clause
949
- # whose `op` and `field` are named in a raised message
950
- # @param target [Bluebook::Aggregate] the aggregate the hop resolved to, named
951
- # in a raised message
952
- # @param attribute [Bluebook::Attribute, nil] the tail's root attribute on
953
- # `target`, or `nil` when the tail lands on the lifecycle field
954
- # @param nested [Array<String>] the tail's remaining dotted segments past
955
- # `attribute`, `[]` for a bare field
956
- # @return [void]
957
- # @raise [Bluebook::DSL::Malformed] if `clause.op` is one of
958
- # `AggregateBuilder::ORDERED_COMPARATORS` and the field it lands on, after
959
- # `attribute`/`nested`, is not numeric
472
+ # An ordered comparator over a hopped field must land on a number; the check
473
+ # `AggregateBuilder#seal_ordered_comparator` deferred.
960
474
  def validate_hop_comparator!(aggregate, query, clause, target, attribute, nested)
961
475
  return unless AggregateBuilder::ORDERED_COMPARATORS.include?(clause.op.to_s.to_sym)
962
476
  return if attribute &&
@@ -970,26 +484,9 @@ module Hecks
970
484
  "anything else the adapters answer differently or not at all"
971
485
  end
972
486
 
973
- # The target half of `projects` validation (S12, ADR 0025) —
974
- # `AggregateBuilder#seal_projected_fields` already checked the
975
- # local half at declare time (the reference names a real
976
- # `reference_to` on this aggregate); this checks the reference
977
- # actually resolves to a real aggregate in this chapter, and
978
- # that aggregate really declares `remote_field` as a scalar.
979
- #
980
- # Reuses `QuerySpecification::HopPath` rather than re-deriving
981
- # hop resolution a second way — `"reference/remote_field"` is
982
- # the same single-hop shape a query's own `/`-spelled hop
983
- # resolves, even though `projects`'s own DSL spelling is dotted
984
- # (`from: :"customer.status"`): two constructs, two spellings,
985
- # one resolution primitive. A single hop can never reach
986
- # HopPath::MAX_HOPS, so :too_deep is structurally unreachable
987
- # here and is not special-cased.
988
- #
989
- # @param bluebook [Bluebook::Chapter] the fully assembled chapter to check
990
- # @return [void]
991
- # @raise [Bluebook::DSL::Malformed] if any aggregate's `projects` field fails
992
- # `validate_projected_field!`'s own checks
487
+ # Checks each `projects` reference resolves to an aggregate declaring
488
+ # `remote_field` as a scalar. A single hop never reaches HopPath::MAX_HOPS, so
489
+ # :too_deep is not special-cased.
993
490
  def validate_projected_fields!(bluebook)
994
491
  bluebook.aggregates.each do |aggregate|
995
492
  aggregate.projected_fields.each { |field| validate_projected_field!(aggregate, field) }
@@ -997,22 +494,8 @@ module Hecks
997
494
  end
998
495
 
999
496
  # Checks that one `projects` field's reference resolves to a real scalar.
1000
- #
1001
- # A linear decision tree of validation rules over one resolved
1002
- # hop plan, each already explained by its own comment above
1003
- # (the lifecycle fallback, the chained-projection fallback, the
1004
- # final scalar check) — a fixed, closed sequence "resolve, then
1005
- # check each way this could legitimately or illegitimately
1006
- # land," not several unrelated concerns. Splitting it would
1007
- # scatter `plan`/`target`/`remote_attribute` across new methods
1008
- # that would each need most of them anyway.
1009
- #
1010
- # @param aggregate [Bluebook::Aggregate] the aggregate that declared `field`
1011
- # @param field [Bluebook::ProjectedField] the declared `projects` field to check
1012
- # @return [void]
1013
- # @raise [Bluebook::DSL::Malformed] if `field.reference` hops to an aggregate
1014
- # this chapter never declares, or `field.remote_field` names nothing on the
1015
- # target, or names something other than a scalar
497
+ # One closed sequence over a single hop plan; splitting it scatters `plan`/`target`/
498
+ # `remote_attribute` across methods that each need most of them.
1016
499
  # rubocop:disable-next Metrics/AbcSize
1017
500
  def validate_projected_field!(aggregate, field)
1018
501
  plan = QuerySpecification::HopPath.plan("#{field.reference}/#{field.remote_field}", aggregate.attributes)
@@ -1027,30 +510,13 @@ module Hecks
1027
510
  target = plan.hops.last.target
1028
511
  remote_attribute = target.attributes.find { |candidate| candidate.name.to_s == plan.tail }
1029
512
 
1030
- # The worked example itself (ADR 0025) reads through a
1031
- # lifecycle field — banking's Customer.status is `lifecycle
1032
- # :status`, never a plain `attribute` — the same fallback
1033
- # validate_hop_tail! already gives a query's own hop tail. A
1034
- # lifecycle field is always a plain string by construction ;
1035
- # nothing further to check once it matches by name.
513
+ # A lifecycle field is a plain string by construction, so a name match is enough (as in
514
+ # `validate_hop_tail!`).
1036
515
  return if remote_attribute.nil? && target.lifecycle&.field.to_s == plan.tail
1037
516
 
1038
- # A projection may chain through another projection (S12, ADR
1039
- # 0025's own boundary rule, followed through) — `target`'s
1040
- # own projected fields live in `projected_fields`, a
1041
- # separate list from `attributes`, so a match there is
1042
- # invisible to the check above even though it names a real,
1043
- # always-current, stored field. `Transfer.projects
1044
- # :source_customer_status, from: :"source.customer_status"`
1045
- # reads Account's own already-projected `customer_status`
1046
- # this way — Account is one hop from Customer, Transfer is
1047
- # one hop from Account, and neither aggregate needs to know
1048
- # about the other's target two hops away. A projected
1049
- # field's remote value is always a scalar by construction
1050
- # (`RebuildSweep.remote_value` never copies a reference, a
1051
- # value object, or a list), so nothing further to check once
1052
- # it matches by name — same reasoning the lifecycle
1053
- # fallback just above already applies.
517
+ # A projection may chain through another projection; `projected_fields` is
518
+ # separate from `attributes`, and a projected value is always a scalar by
519
+ # construction, so a name match is enough.
1054
520
  return if remote_attribute.nil? && target.projected_fields.any? { |f| f.name.to_s == plan.tail }
1055
521
 
1056
522
  unless remote_attribute
@@ -1067,45 +533,14 @@ module Hecks
1067
533
  "value, never a reference, a value object, or a list"
1068
534
  end
1069
535
 
1070
- # Decides whether a declared attribute is a plain, single scalar value.
1071
- #
1072
- # @param target [Bluebook::Aggregate] the aggregate `attribute` is declared on,
1073
- # whose `value_object` lookup resolves `attribute`'s type
1074
- # @param attribute [Bluebook::Attribute] the attribute to check
1075
- # @return [Boolean] `true` when `attribute` is not a list, not a reference, and
1076
- # its type names no value object `target` declares
1077
536
  def projectable_scalar?(target, attribute)
1078
537
  !attribute.list? && !attribute.reference? && target.value_object(attribute.type).nil?
1079
538
  end
1080
539
 
1081
- # `correlates_by` names a scalar, now checked rather than trusted.
1082
- #
1083
- # ProcessManagerBuilder#validate! already refuses a bare, undotted
1084
- # spelling — a syntactic guarantee that the declaration cannot leave
1085
- # the question open. It cannot go further: a process manager is built
1086
- # in isolation, before this chapter's aggregates exist to check
1087
- # against. Here, with the whole document assembled, the dotted path
1088
- # is walked for real — against whichever command actually emits an
1089
- # event this process manager reacts to — so a path that still lands
1090
- # on a value object is refused before the runtime ever has to decide
1091
- # what a non-scalar correlation key even means: a saga keys off this
1092
- # value directly, and a value object carries no guaranteed-stable
1093
- # identity to key on the way a scalar does.
1094
- #
1095
- # A command that does not declare the path's first segment at all is
1096
- # silently skipped, not refused — correlation has two other fallback
1097
- # tiers below the payload dig (a correlation stamp, then the emitting
1098
- # aggregate's own reference key; saga_interpreter/correlation.rb), so
1099
- # an absent field is not this check's business. Only a field that
1100
- # resolves, and resolves to something other than a scalar, is.
1101
- #
1102
- # @param process_managers [Array<Bluebook::ProcessManager>] the chapter's
1103
- # declared process managers
1104
- # @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
1105
- # @return [void]
1106
- # @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] if a
1107
- # process manager's `correlates_by` path resolves, on any command that emits
1108
- # an event it reacts to, to something other than a scalar
540
+ # `correlates_by` must land on a scalar: the dotted path is walked against each
541
+ # command that emits an event the process manager reacts to. A command lacking the
542
+ # first segment is skipped, since correlation has fallback tiers
543
+ # (saga_interpreter/correlation.rb).
1109
544
  def validate_correlation_keys!(process_managers, aggregates)
1110
545
  process_managers.each do |pm|
1111
546
  next unless pm.correlates_by
@@ -1118,15 +553,6 @@ module Hecks
1118
553
  end
1119
554
  end
1120
555
 
1121
- # Finds why a process manager's `correlates_by` path is not a scalar, if it isn't.
1122
- #
1123
- # @param process_manager [Bluebook::ProcessManager] the process manager whose
1124
- # `correlates_by` to check
1125
- # @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
1126
- # @return [String, nil] the reason the path is not a scalar, on the first
1127
- # emitting command whose first segment resolves; `nil` if every command that
1128
- # emits a reacted-to event either omits the first segment, or resolves to
1129
- # a scalar
1130
556
  def correlation_key_violation(process_manager, aggregates)
1131
557
  head, *rest = process_manager.correlates_by.to_s.split(".")
1132
558
  events = reacted_events(process_manager)
@@ -1142,12 +568,6 @@ module Hecks
1142
568
  nil
1143
569
  end
1144
570
 
1145
- # The demodulised names of every event a process manager reacts to.
1146
- #
1147
- # @param process_manager [Bluebook::ProcessManager] the process manager whose
1148
- # `starts_on`, `ends_on` and handler event types to collect
1149
- # @return [Array<String>] the unique demodulised event names, `REFUSED` (the
1150
- # compensation trigger, not a real event) excluded
1151
571
  def reacted_events(process_manager)
1152
572
  ([process_manager.starts_on, process_manager.ends_on] + process_manager.handlers.map(&:event_type))
1153
573
  .compact
@@ -1156,13 +576,6 @@ module Hecks
1156
576
  .uniq
1157
577
  end
1158
578
 
1159
- # Every command across the chapter that emits one of the given events.
1160
- #
1161
- # @param events [Array<String>] the demodulised event names to match against
1162
- # each command's own `emits`
1163
- # @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
1164
- # @return [Array<Array(Bluebook::Aggregate, Class)>] one `[aggregate, command]`
1165
- # pair per matching command, an aggregate's own or one of its entities'
1166
579
  def emitting_commands(events, aggregates)
1167
580
  aggregates.flat_map do |aggregate|
1168
581
  commands = aggregate.commands + aggregate.entities.flat_map(&:commands)
@@ -1171,16 +584,6 @@ module Hecks
1171
584
  end
1172
585
  end
1173
586
 
1174
- # Decides whether one command attribute can serve as a correlation key.
1175
- #
1176
- # @param owner [Bluebook::Aggregate] the aggregate whose `value_object` lookup
1177
- # resolves `attribute`'s type, for `walk_scalar`
1178
- # @param attribute [Bluebook::Attribute] the command attribute the correlation
1179
- # path's first segment named
1180
- # @param segments [Array<String>] the path's remaining dotted segments, `[]` for
1181
- # a bare field
1182
- # @return [String, nil] why `attribute` cannot serve as a correlation key, or
1183
- # `nil` if it can — see `walk_scalar` for what a non-`nil` reason means
1184
587
  def list_or_scalar_violation(owner, attribute, segments)
1185
588
  if attribute.list?
1186
589
  return "#{attribute.name} is a list — a correlation key must name one instance's own field, " \
@@ -1190,23 +593,8 @@ module Hecks
1190
593
  walk_scalar(owner, attribute.type.to_s, segments)
1191
594
  end
1192
595
 
1193
- # Walks the remaining dotted segments through nested value objects.
1194
- # `type_name` starts as the head attribute's own declared type ; each
1195
- # step either bottoms out at a real scalar (nil — no violation) or
1196
- # names why it cannot: still a value object with no more path left,
1197
- # a value object this domain never declared, a field that value
1198
- # object does not have, or a segment left over after already
1199
- # reaching a scalar.
1200
- #
1201
- # @param owner [Bluebook::Aggregate] the aggregate whose `value_object` lookup
1202
- # resolves each stepped-into type name
1203
- # @param type_name [String] the type to check or step into
1204
- # @param segments [Array<String>] the dotted path segments remaining to walk
1205
- # @return [String, nil] `nil` when the walk bottoms out cleanly on a scalar;
1206
- # otherwise why it cannot: `type_name` is a value object with no segments
1207
- # left, `type_name` is already a scalar with segments left over, `type_name`
1208
- # names no value object `owner` declares, the next segment names no field of
1209
- # that value object, or that field is a list
596
+ # Walks the remaining dotted segments through nested value objects; nil means the walk
597
+ # bottomed out on a scalar, otherwise the string says why it cannot.
1210
598
  def walk_scalar(owner, type_name, segments)
1211
599
  if segments.empty?
1212
600
  return nil if Attribute::PRIMITIVES.include?(type_name)