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
@@ -3,151 +3,36 @@ require_relative "model_check/client_profile"
3
3
 
4
4
  module Hecks
5
5
  module Bluebook
6
- # Lightweight formal methods over the IR — the same family as TLA+/
7
- # Alloy/P: every lifecycle is a declared finite state machine and
8
- # every process manager a declared protocol, and both are already
9
- # data, not code, so they can be model-checked rather than merely
10
- # executed. Static analysis only — no bluebook boots twice, no
11
- # runtime is touched — over what meta_validator/judge.rb and the
12
- # builders' own `validate!` methods leave uncovered (an undeclared
13
- # transition target, a dispatch to nowhere, a compensation nothing
14
- # can ever reach).
15
- #
16
- # **The rare property this rests on**: the model is the implementation.
17
- # A checker over TLA+ verifies a spec a human keeps in sync with
18
- # code by hand ; this verifies the same IR the runtime dispatches
19
- # against, so there is no second copy to drift.
6
+ # Static formal checks over the assembled bluebook IR: lifecycles as state
7
+ # machines, process managers as protocols, checked as data without booting a runtime.
20
8
  module ModelCheck
21
9
  Finding = Struct.new(:kind, :severity, :subject, :message, keyword_init: true) do
22
10
  def to_s = "#{severity.to_s.upcase.ljust(7)} #{kind.to_s.ljust(20)} #{subject} — #{message}"
23
11
  end
24
12
 
25
- # **A finding shipped, not silenced** — the coverage-gate idiom, empty
26
- # allowlists enforced both directions (spec/model_check_spec.rb holds
27
- # this exact table: an error the checker reports and this does not
28
- # name is a regression, an entry the checker does not report is
29
- # stale and must be deleted). bin/model_check reads this same
30
- # constant, so the tool and the spec can never drift apart.
31
- #
32
- # "banking"/ExternalSettlement — found on the first real run:
33
- # ExternalSettlement declares `ends_on "ExternalTransferSent"`, and
34
- # ExternalTransfer.Send genuinely emits it — the event is real, and
35
- # the aggregate reaches "sent" (its own, separate lifecycle) — but
36
- # the saga's protocol has no `on "ExternalTransferSent"` handler, so
37
- # its own `state "sent"` is unreachable through the chain the
38
- # checker walks, and the saga's own bookkeeping (saga_log, ends_on)
39
- # never closes it. Real domain activity is unaffected; the saga's
40
- # own tracking of it is not. Left named rather than redesigning a
41
- # corpus fixture that is not this checker's to redesign.
42
- # S7, ADR 0025 — an ExternalSettlement finding once lived here as an
43
- # allowlist entry and is gone now, not just quieted: its "sent" state
44
- # was a `state "x"` line never named by any handler's own from:/to:, a
45
- # pure declaration-drift artifact. States are derived from the
46
- # transitions that name them now (ProcessManagerBuilder#derived_
47
- # states), so a state nothing ever transitions into or out of does
48
- # not exist to be unreachable — the finding that entry allowlisted
49
- # cannot occur, by construction.
50
- #
51
- # "banking"/NotifyOnClosure, FlagKeyReturn — gone from here, moved
52
- # to banking. `across "Notifications"` names a domain that does not
53
- # exist anywhere in this repo, deliberately (it exercises the
54
- # undelivered-reaction runtime path — `spec/runtime/policy_spec.rb`,
55
- # "records a reaction it cannot deliver rather than swallowing it").
56
- # That expectation is now declared on the two policies themselves —
57
- # `across "Notifications", expect_undelivered: true` — and
58
- # `expected_undelivered_findings` below holds it in both directions:
59
- # the unknown-target and unacknowledged-relationship findings are
60
- # expected, and a declaration whose target turns out reachable is a
61
- # `stale_undelivered_expectation` error. A domain's own allowance
62
- # lives in its own source, never in a core table keyed by its name.
63
- #
64
- # Pinned empty (spec/model_check_spec.rb), the way `bin/fuzz`'s
65
- # `KNOWN_FUZZ_FINDINGS` is: a finding a domain means to keep belongs
66
- # in that domain's own declaration.
67
- ALLOWED_FINDINGS = {
68
- # QualityControl was the first domain in this corpus to trigger an
69
- # `asks`/`tells` port operation from a `policy`, and once carried
70
- # two entries here for it — both gone now, not just quieted:
71
- #
72
- # `deaf_policy` (ClearOnPass, RefuseOnFail, RecordTheIssue,
73
- # RecordTheRefusal) went first: `emitted_events` below now reads an
74
- # outbound operation's `.answers`/`.refuses` the same way it already
75
- # read a command's `.emits`, so `Clearance.SuitePassed`/`SuiteFailed`
76
- # and `Ticket.IssueFiled`/`IssueFilingRefused` enter the known-emits
77
- # set for real — the same fix `bin/qa_pr_check`'s own move to
78
- # dispatching through the CI port (rather than `Clearance::Passed`/
79
- # `Failed` directly) needed to make these two policies actually fire.
80
- #
81
- # `unknown_trigger` (FileWhenSubmitted, AskOnceMore) — BUG#23 — was
82
- # never actually a `Naming`/`PolicyBuilder` defect, confirmed by
83
- # tracing the real dispatch path rather than trusting a stale
84
- # comment that once sat here: `Naming.command_ref`'s bare-constant
85
- # rewrite does leave `trigger Ticket::IssueTracker::File` (aggregate,
86
- # port, operation) as "Ticket::IssueTracker.File", a leftover `::`
87
- # past the aggregate — but `PolicyInterpreter#deliver` re-qualifies
88
- # every trigger with this domain's own name before dispatch
89
- # ("QualityControl::Ticket::IssueTracker.File"), and `Naming.
90
- # split_verb` already folds that reintroduced `::` into the
91
- # dot-joined tail correctly (the same fix `ReactionInvocation#
92
- # resolve_target` already carries) — confirmed live: a real
93
- # dispatch through `Ticket.Submit` fires
94
- # `IssueFiled`/`TicketFiled` exactly as declared. The actual gap was
95
- # entirely in this checker: `verbs_of` never enumerated a port
96
- # operation as a triggerable verb at all, and `policy_findings`
97
- # compared raw strings instead of `Naming.split_verb` triples the
98
- # way `handler_findings`'s own `unknown_dispatch` check already does
99
- # (BUG#6). Fixed with `port_verbs_of`/`triggerable_verbs`, scoped
100
- # entirely to this file — no change to `Naming` or `PolicyBuilder`
101
- # was needed or made.
102
- }.freeze
13
+ # Findings a domain deliberately allows, enforced in both directions by
14
+ # spec/model_check_spec.rb: an error this checker reports but isn't listed
15
+ # here is a regression; an entry listed but never reported is stale. Pinned
16
+ # empty — a domain's own allowance belongs on its own declaration. (ADR 0025)
17
+ ALLOWED_FINDINGS = {}.freeze
103
18
 
104
19
  # The profiles `call` accepts; `nil` is the default, unprofiled run.
105
20
  PROFILES = %i[client].freeze
106
21
 
107
22
  module_function
108
23
 
109
- # Runs every static model check over `bluebook` and returns what it
110
- # finds.
111
- #
112
- # `hecksagon:`/`known_domains:` — both optional, both `nil`-safe
113
- # (every existing caller with no sibling hecksagon, or checking one
114
- # domain in isolation, behaves exactly as before). `hecksagon` is
115
- # this bluebook's own sibling wiring file, if the caller loaded one
116
- # (see `emitted_events`'s own comment on why a caller that didn't
117
- # simply finds none, correctly). `known_domains` is the caller's
118
- # own corpus-wide view — every bluebook/hecksagon name it has
119
- # booted anywhere, across every domain it has looked at, not just
120
- # this one — used only to catch a typo'd `across`/`uses_framework`
121
- # target; see `cross_domain_policy_findings`'s own comment for why
122
- # this can only ever be a corpus-scoped heuristic, never a general
123
- # correctness guarantee.
124
- #
125
- # `rust_target:`/`strict:` — both default false, both only change the
126
- # severity of `rust_reserved_name` findings (see
127
- # `rust_reserved_name_findings`); every other finding is unaffected.
24
+ # Runs every static model check over `bluebook` and returns what it finds.
128
25
  #
129
26
  # @param bluebook [Bluebook::Chapter] the assembled chapter to check
130
- # @param hecksagon [Bluebook::Hecksagon, nil] this bluebook's own
131
- # sibling wiring file, if the caller loaded one
132
- # @param known_domains [Set<String>, nil] every bluebook/hecksagon
133
- # name the caller has booted anywhere in this corpus scan
134
- # @param global_emitted_events [Set<String>, nil] every bare event
135
- # name any domain the caller has booted this run emits — lets a
136
- # same-domain-target policy (e.g. one built by `translates`)
137
- # reacting to a genuinely foreign event avoid a false `deaf_policy`
138
- # finding; `nil` (every pre-existing call site) keeps today's
139
- # single-domain-only check
140
- # @param rust_target [Boolean] whether this domain has a real Rust
141
- # target, raising `rust_reserved_name` findings to error
142
- # @param strict [Boolean] whether to raise every `rust_reserved_name`
143
- # finding to error regardless of `rust_target`
144
- # @param profile [Symbol, nil] `:client` adds `ClientProfile`'s error
145
- # findings for constructs known to answer wrongly without refusing;
146
- # `nil` (the default) adds nothing and leaves every other finding as
147
- # it was
148
- # @return [Array<Finding>] every finding this bluebook triggers,
149
- # across its lifecycles, sagas, policies, and Rust-reserved names
150
- # @raise [ArgumentError] if `profile` is neither `nil` nor `:client`
27
+ # @param hecksagon [Bluebook::Hecksagon, nil] this bluebook's own sibling wiring
28
+ # file, if the caller loaded one
29
+ # @param known_domains [Set<String>, nil] every bluebook/hecksagon name booted
30
+ # anywhere in this corpus scan, checked to catch a typo'd across/uses_framework target
31
+ # @param global_emitted_events [Set<String>, nil] every event emitted anywhere in
32
+ # the corpus, checked as a fallback so a translates reaction isn't flagged deaf
33
+ # @param profile [Symbol, nil] :client adds ClientProfile's error findings
34
+ # @return [Array<Finding>] every finding this bluebook triggers
35
+ # @raise [ArgumentError] if profile is neither nil nor :client
151
36
  def call(bluebook, hecksagon: nil, known_domains: nil, global_emitted_events: nil, rust_target: false, strict: false,
152
37
  profile: nil)
153
38
  unless profile.nil? || PROFILES.include?(profile)
@@ -166,45 +51,43 @@ module Hecks
166
51
  findings.concat(rust_reserved_name_findings(domain_name: bluebook.name,
167
52
  aggregate_names: bluebook.aggregates.map(&:hecks_name),
168
53
  rust_target: rust_target, strict: strict))
54
+ findings.concat(external_query_findings(bluebook, rust_target: rust_target))
169
55
  findings.concat(ClientProfile.call(bluebook, hecksagon: hecksagon)) if profile == :client
170
56
  findings
171
57
  end
172
58
 
173
- # ── Rust reserved names ───────────────────────────────────────────
174
- #
175
- # A name that becomes a bare Rust module identifier with no `r#`
176
- # escape hatch: an aggregate (`pub mod <name.downcase>;` plus its
177
- # `<name.downcase>.rs` file) and a domain (`pub mod <name>;` and a
178
- # Cargo `[features]` key). Field names are not checked — both
179
- # generators already raw-escape those (`rust_ident_field`).
180
- #
181
- # The words come from the `RustReservedWord`/`CargoReservedName`
182
- # vocabularies, the same tables `rust/project/naming.rb` and
183
- # hecks-codegen's generated `reserved_names.rs` read. Both Rust
184
- # generators refuse through this check (`Projector.
185
- # reserved_name_refusal`, and its hecks-codegen port in `naming.rs`).
59
+ # Flags a query the hecksagon binds to a port's adapter. Only the Ruby runtime asks an
60
+ # adapter for an answer; a Rust host would find nothing behind the query.
186
61
  #
187
- # Severity: a domain that only ever runs in Ruby is fine with an
188
- # aggregate named `Match`, so this warns by default. It is an error
189
- # when the caller says the domain has a Rust target (`rust_target:` —
190
- # `bin/model_check` reads it off the domain's Cargo feature, the
191
- # generators always pass it) or asks for strictness (`strict:`,
192
- # `bin/model_check --strict`).
193
- #
194
- # The module-name transform is `downcase`, the one both generators
195
- # apply to an aggregate name and to an attached chapter's name.
62
+ # @param bluebook [Bluebook::Chapter] the assembled chapter, its hecksagon's ports attached
63
+ # @param rust_target [Boolean] whether this domain has a real Rust target; a domain without
64
+ # one is not checked, since nothing but the Ruby runtime serves it
65
+ # @return [Array<Finding>] one :external_query error per bound query, none off a Rust target
66
+ def external_query_findings(bluebook, rust_target: false)
67
+ return [] unless rust_target
68
+
69
+ bluebook.aggregates.flat_map do |aggregate|
70
+ aggregate.ports.flat_map do |port|
71
+ port.answered_queries.map do |answer|
72
+ Finding.new(kind: :external_query, severity: :error,
73
+ subject: "#{aggregate.hecks_name}.#{answer.name}",
74
+ message: "the #{port.name} port's adapter answers this query, and only the Ruby " \
75
+ "runtime asks an adapter — the Rust host cannot serve it")
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ # Flags an aggregate or domain name that collides with a Rust keyword or reserved
82
+ # Cargo key — both become bare module identifiers with no `r#` escape hatch.
196
83
  #
197
- # @param domain_name [String, Symbol, nil] the domain's own name, or
198
- # `nil` to skip the domain-level check
199
- # @param aggregate_names [Array<String, Symbol>] every aggregate name
200
- # to check
201
- # @param rust_target [Boolean] whether this domain has a real Rust
202
- # target, raising severity to error
203
- # @param strict [Boolean] whether to raise severity to error
204
- # regardless of `rust_target`
205
- # @return [Array<Finding>] one `:rust_reserved_name` finding per
206
- # aggregate or domain name that collides with a Rust or Cargo
207
- # reserved word
84
+ # @param domain_name [String, Symbol, nil] the domain's own name, or nil to skip
85
+ # the domain-level check
86
+ # @param aggregate_names [Array<String, Symbol>] every aggregate name to check
87
+ # @param rust_target [Boolean] raises severity to error when this domain has a
88
+ # real Rust target
89
+ # @param strict [Boolean] raises severity to error regardless of rust_target
90
+ # @return [Array<Finding>] one :rust_reserved_name finding per colliding name
208
91
  def rust_reserved_name_findings(domain_name: nil, aggregate_names: [], rust_target: false, strict: false)
209
92
  severity = rust_target || strict ? :error : :warning
210
93
  keywords = Hecks::Vocabulary.fetch("RustReservedWord")
@@ -221,17 +104,7 @@ module Hecks
221
104
  findings
222
105
  end
223
106
 
224
- # Checks the domain's own module/Cargo-feature name, one level up
225
- # from `rust_reserved_name_findings`'s own per-aggregate check.
226
- #
227
- # @param domain_name [String, Symbol] the domain's own name
228
- # @param keywords [Array<String>] the admitted `RustReservedWord`
229
- # vocabulary rows
230
- # @param severity [Symbol] the finding severity to use, `:error` or
231
- # `:warning`
232
- # @return [Array<Finding>] a single-element array holding a
233
- # `:rust_reserved_name` finding, or `[]` if `domain_name` collides
234
- # with no reserved word or Cargo key
107
+ # The domain-level half of rust_reserved_name_findings — same tables, one name.
235
108
  def domain_reserved_name_findings(domain_name, keywords, severity)
236
109
  module_name = rust_module_name(domain_name)
237
110
  table = if keywords.include?(module_name)
@@ -246,27 +119,10 @@ module Hecks
246
119
  "rename the domain")]
247
120
  end
248
121
 
249
- # Derives the Rust module identifier both generators derive from a
250
- # name.
251
- #
252
- # @param name [String, Symbol] an aggregate or domain name
253
- # @return [String] the Rust module identifier both generators derive
254
- # from `name`
255
122
  def rust_module_name(name) = name.to_s.downcase
256
123
 
257
- # ── lifecycles (aggregate and entity — a piece may declare one too) ──
258
-
259
- # Runs every lifecycle-shaped model check for one declaring
260
- # construct's own lifecycle, if it has one.
261
- #
262
- # @param aggregate [Bluebook::Aggregate] the root aggregate `declaring`
263
- # belongs to
264
- # @param declaring [Bluebook::Aggregate, Bluebook::Entity] the
265
- # construct whose own lifecycle to check — `aggregate` itself, or
266
- # one of its entities
267
- # @return [Array<Finding>] `[]` if `declaring` has no lifecycle;
268
- # otherwise every `unknown_command`/`unreachable_state`/
269
- # `dead_transition`/`stuck_state` finding its lifecycle triggers
124
+ # Runs every lifecycle-shaped check for one declaring construct — the aggregate
125
+ # itself, or one of its entities — if it declares a lifecycle at all.
270
126
  def lifecycle_findings(aggregate, declaring)
271
127
  lifecycle = declaring.lifecycle
272
128
  return [] unless lifecycle
@@ -285,15 +141,7 @@ module Hecks
285
141
  findings
286
142
  end
287
143
 
288
- # Finds every declared state a lifecycle's own reachability walk
289
- # never reaches.
290
- #
291
- # @param lifecycle [Bluebook::Lifecycle] the lifecycle being checked
292
- # @param full [Array<String>] every state `full_states` declares
293
- # @param reached [Set<String>] every state `reachable_states` reaches
294
- # @param subject [String] the finding's own subject label
295
- # @return [Array<Finding>] one `:unreachable_state` finding per
296
- # declared state that `reached` does not cover
144
+ # Finds every declared state the lifecycle's own reachability walk never reaches.
297
145
  def unreachable_state_findings(lifecycle, full, reached, subject)
298
146
  (full - reached.to_a).map do |state|
299
147
  Finding.new(kind: :unreachable_state, severity: :error, subject: subject,
@@ -302,13 +150,7 @@ module Hecks
302
150
  end
303
151
  end
304
152
 
305
- # Finds every constrained transition that can never fire.
306
- #
307
- # @param lifecycle [Bluebook::Lifecycle] the lifecycle being checked
308
- # @param reached [Set<String>] every state `reachable_states` reaches
309
- # @param subject [String] the finding's own subject label
310
- # @return [Array<Finding>] one `:dead_transition` finding per
311
- # constrained transition whose `from:` states are all unreached
153
+ # Finds every constrained transition whose from: states are all unreached.
312
154
  def dead_transition_findings(lifecycle, reached, subject)
313
155
  lifecycle.transitions.filter_map do |command, transition|
314
156
  next unless transition.constrained?
@@ -320,14 +162,8 @@ module Hecks
320
162
  end
321
163
  end
322
164
 
323
- # Finds every reached state with no transition ever leaving it.
324
- #
325
- # @param lifecycle [Bluebook::Lifecycle] the lifecycle being checked
326
- # @param reached [Set<String>] every state `reachable_states` reaches
327
- # @param subject [String] the finding's own subject label
328
- # @return [Array<Finding>] one `:stuck_state` finding per reached,
329
- # non-exempt state with no transition ever leaving it (skipped
330
- # entirely when any transition is unconstrained)
165
+ # Finds every reached state with no transition ever leaving it, unless any
166
+ # transition in this lifecycle is unconstrained.
331
167
  def stuck_state_findings(lifecycle, reached, subject)
332
168
  any_unconstrained = lifecycle.transitions.any? { |_, t| !t.constrained? }
333
169
  (reached - terminal_exempt(lifecycle)).filter_map do |state|
@@ -340,15 +176,7 @@ module Hecks
340
176
  end
341
177
  end
342
178
 
343
- # Finds every transition named after a command this construct does
344
- # not declare.
345
- #
346
- # @param lifecycle [Bluebook::Lifecycle] the lifecycle being checked
347
- # @param commands [Array<String>] every command name the declaring
348
- # construct actually declares
349
- # @param subject [String] the finding's own subject label
350
- # @return [Array<Finding>] one `:unknown_command` finding per
351
- # transition named after a command `commands` does not list
179
+ # Finds every transition named after a command the construct doesn't declare.
352
180
  def unknown_transition_commands(lifecycle, commands, subject)
353
181
  lifecycle.transitions.filter_map do |command, _transition|
354
182
  next if commands.include?(command)
@@ -358,14 +186,11 @@ module Hecks
358
186
  end
359
187
  end
360
188
 
361
- # default, every declared target, and every declared from — a
362
- # from-only state (declared nowhere as a target) is real and is
363
- # exactly the hole `Lifecycle#states` leaves: it answers default
364
- # plus targets only.
189
+ # Every state `lifecycle` declares in any role — default, target, or from,
190
+ # unique.
365
191
  #
366
192
  # @param lifecycle [Bluebook::Lifecycle] the lifecycle being checked
367
- # @return [Array<String>] every state `lifecycle` declares, in any
368
- # role (default, target, or from), unique
193
+ # @return [Array<String>] every state lifecycle declares, unique
369
194
  def full_states(lifecycle)
370
195
  (
371
196
  [lifecycle.default] +
@@ -374,21 +199,14 @@ module Hecks
374
199
  ).uniq
375
200
  end
376
201
 
377
- # Least fixpoint from the default state: an unconstrained
378
- # transition always fires, from wherever the machine is ; a
379
- # constrained one fires once any of its named sources is reached.
380
- #
381
- # @param lifecycle [Bluebook::Lifecycle] the lifecycle being checked
382
- # @return [Set<String>] every state reachable from `lifecycle`'s own
383
- # default state
202
+ # Least fixpoint from the default state: an unconstrained transition always
203
+ # fires; a constrained one fires once any of its named sources is reached.
384
204
  def reachable_states(lifecycle)
385
205
  reached = Set.new([lifecycle.default])
386
206
  loop do
387
207
  grown = false
388
- # `transitions` is an Array of [from, transition]-shaped entries
389
- # (Lifecycle#transitions), not a Hash — Style/HashEachMethods'
390
- # `each_value` rewrite assumed otherwise from the block shape
391
- # alone. False positive.
208
+ # `transitions` is an Array of [from, transition] pairs, not a Hash, so
209
+ # Style/HashEachMethods' each_value rewrite is a false positive here.
392
210
  # rubocop:disable-next Style/HashEachMethods
393
211
  lifecycle.transitions.each do |_, transition|
394
212
  next if reached.include?(transition.target)
@@ -402,26 +220,10 @@ module Hecks
402
220
  reached
403
221
  end
404
222
 
405
- # A state with no outgoing declared path at all is exempt from the
406
- # stuck-state warning for a different reason than "it fires an
407
- # unconstrained transition" — the default state of a lifecycle
408
- # with only constrained transitions is legitimately allowed to sit
409
- # forever, since nothing about *entering* it via default implies
410
- # anything must eventually move it, unlike a state a transition
411
- # explicitly delivered somewhere.
412
- #
413
- # Scoped to `default` alone, and only when the lifecycle actually
414
- # declares real transitions elsewhere: an empty lifecycle (no
415
- # transitions at all) doesn't get this exemption — that's not "a
416
- # machine whose entry point deliberately awaits external action,"
417
- # it's much more likely a lifecycle nobody finished wiring, and
418
- # should still warn (see spec/fixtures/model_check/lifecycle_
419
- # findings.bluebook's own Widget::Part, which stays warned on
420
- # purpose).
421
- #
422
- # @param lifecycle [Bluebook::Lifecycle] the lifecycle being checked
423
- # @return [Array<String>] `[lifecycle.default]` when it is exempt from
424
- # the stuck-state warning, `[]` otherwise
223
+ # Exempts only the lifecycle's own default state, and only when the lifecycle
224
+ # declares real transitions elsewhere: entering default implies nothing about
225
+ # ever leaving it, unlike a state some transition explicitly delivered to. An
226
+ # empty lifecycle still warns — that reads as unfinished wiring, not a deliberate rest.
425
227
  def terminal_exempt(lifecycle)
426
228
  return [] if lifecycle.transitions.empty?
427
229
 
@@ -429,24 +231,12 @@ module Hecks
429
231
  outgoing_sources.include?(lifecycle.default) ? [] : [lifecycle.default]
430
232
  end
431
233
 
432
- # ── process managers / sagas ──────────────────────────────────────
433
-
434
- # Runs every saga-shaped model check for one process manager.
435
- #
436
- # @param bluebook [Bluebook::Chapter] the assembled chapter
437
- # `process_manager` belongs to
438
- # @param process_manager [Bluebook::ProcessManager] the process
439
- # manager to check
440
- # @return [Array<Finding>] every `deaf_trigger`/`unreachable_pm_state`/
441
- # `deaf_handler`/`unknown_dispatch`/`unarmed_compensation`/
442
- # `dead_compensation` finding this process manager triggers
234
+ # Runs every saga-shaped check for one process manager.
443
235
  def saga_findings(bluebook, process_manager)
444
236
  emitted = emitted_events(bluebook)
445
- # (domain, aggregate, command) triples, not raw strings — see
446
- # `handler_findings`'s own comment on the dispatch side for why:
447
- # `Naming.split_verb` is what makes an entity verb's two legitimate
448
- # spellings (`Naming.command_ref`'s own `::`-then-`.` rewrite vs.
449
- # `verbs_of`'s own all-`.` one) compare equal.
237
+ # Parsed as (domain, aggregate, command) triples via Naming.split_verb, not
238
+ # compared as raw strings — that's what makes an entity verb's two legitimate
239
+ # spellings compare equal (see handler_findings' unknown_dispatch check).
450
240
  verbs = verbs_of(bluebook).map { |verb| Naming.split_verb(verb) }
451
241
  reached = pm_reachable_states(process_manager, emitted)
452
242
 
@@ -460,15 +250,7 @@ module Hecks
460
250
  findings
461
251
  end
462
252
 
463
- # Finds every declared `starts_on`/`ends_on` event this domain never
464
- # emits.
465
- #
466
- # @param process_manager [Bluebook::ProcessManager] the process
467
- # manager being checked
468
- # @param emitted [Array<String>] every bare event name this domain
469
- # actually emits
470
- # @return [Array<Finding>] one `:deaf_trigger` finding per declared
471
- # `starts_on`/`ends_on` event this domain never emits
253
+ # Finds every declared starts_on/ends_on event this domain never emits.
472
254
  def deaf_trigger_findings(process_manager, emitted)
473
255
  [process_manager.starts_on, process_manager.ends_on].compact.filter_map do |event|
474
256
  next if emitted.include?(bare(event))
@@ -480,13 +262,6 @@ module Hecks
480
262
  end
481
263
 
482
264
  # Finds every declared state no handler chain ever reaches.
483
- #
484
- # @param process_manager [Bluebook::ProcessManager] the process
485
- # manager being checked
486
- # @param reached [Set<String>] every state `pm_reachable_states`
487
- # reaches
488
- # @return [Array<Finding>] one `:unreachable_pm_state` finding per
489
- # declared state that `reached` does not cover
490
265
  def unreachable_pm_state_findings(process_manager, reached)
491
266
  (Array(process_manager.states) - reached.to_a).map do |state|
492
267
  Finding.new(kind: :unreachable_pm_state, severity: :error, subject: process_manager.name,
@@ -495,33 +270,12 @@ module Hecks
495
270
  end
496
271
  end
497
272
 
498
- # One handler's own findings — deaf_handler, unknown_dispatch (one
499
- # per dispatch), and unarmed_compensation (one per compensating
500
- # dispatch), pulled out of saga_findings' own handler loop; each
501
- # check reads only this handler plus the domain-wide emitted/verbs
502
- # sets saga_findings already resolved once, no state shared between
503
- # handlers.
504
- #
505
- # @param bluebook [Bluebook::Chapter] the assembled chapter
506
- # `process_manager` belongs to
507
- # @param process_manager [Bluebook::ProcessManager] the process
508
- # manager `handler` belongs to
509
- # @param emitted [Array<String>] every bare event name this domain
510
- # actually emits
511
- # @param verbs [Array<Array(String, String, String)>] every command
512
- # this domain declares, parsed as `Naming.split_verb`
513
- # `[domain, aggregate, command]` triples
514
- # @param handler [Bluebook::ProcessManagerHandler] the handler to
515
- # check
516
- # @return [Array<Finding>] every `deaf_handler`/`unknown_dispatch`/
517
- # `unarmed_compensation` finding this handler triggers
273
+ # One handler's own deaf_handler/unknown_dispatch/unarmed_compensation findings.
518
274
  def handler_findings(bluebook, process_manager, emitted, verbs, handler)
519
275
  findings = []
520
276
 
521
- # The compensating leg answers `REFUSED`, a synthetic trigger no
522
- # command ever emits by name (`ProcessManager::REFUSED`) — not
523
- # a deaf handler, the one handler this domain's own events can
524
- # never satisfy on purpose.
277
+ # The compensating leg answers `REFUSED`, a synthetic trigger no command
278
+ # ever emits by name — deliberately exempt from the deaf-handler check.
525
279
  if handler.event_type != ProcessManager::REFUSED && !emitted.include?(bare(handler.event_type))
526
280
  findings << Finding.new(kind: :deaf_handler, severity: :error, subject: process_manager.name,
527
281
  message: "a handler answers #{handler.event_type.inspect}, which no command " \
@@ -529,37 +283,9 @@ module Hecks
529
283
  end
530
284
 
531
285
  handler.dispatches.each do |dispatch|
532
- # **Always this domain** — same fix, same reason, as `SagaInterpreter
533
- # #qualified` (BUG#6). Guessing instead — reading a dispatch whose
534
- # own `command_name` still carries a leftover `::` after `Naming.
535
- # command_ref`'s own rewrite as "already qualified" and leaving it
536
- # alone — would hit the exact same string-shape ambiguity that
537
- # `SagaInterpreter#qualified`'s own comment explains at length
538
- # (a same-domain entity command reference and a genuinely
539
- # cross-domain one are textually indistinguishable after that
540
- # rewrite). Confirmed against the entire corpus, same as that
541
- # fix: no saga anywhere ever dispatches genuinely cross-domain,
542
- # so this checker qualifies exactly the way the runtime
543
- # actually dispatches — unconditionally against `bluebook.name`
544
- # — instead of maintaining its own, independently-wrong copy of
545
- # the same guess.
546
- #
547
- # Compared as a triple, not a string — `Naming.command_ref`'s
548
- # own rewrite of an entity reference (`Manifest::Slot::Fill`)
549
- # collapses to "Manifest::Slot.Fill" (`::` between aggregate and
550
- # entity, `.` before the command); `verbs_of`'s own entity
551
- # spelling, below, joins aggregate/entity/command all with `.`
552
- # instead (matching `fuzzing/sequence_generator/catalog.rb`'s own
553
- # independent convention, its comment's own "the same spelling"
554
- # claim). Both are legitimate, and `Naming.split_verb` already
555
- # parses either to the identical (domain, aggregate, command)
556
- # triple (its own comment: "past the already-resolved domain
557
- # boundary, any leftover `::` is unambiguous... folding it into
558
- # the dot-joined tail") — the same reading `ReactionInvocation.
559
- # resolve_target` relies on at runtime. A bare string `include?`
560
- # would falsely flag every entity dispatch as unknown_dispatch
561
- # even once correctly domain-qualified, comparing two spellings
562
- # of the same verb as though they were different ones.
286
+ # Always qualified against this domain's own name (no saga dispatches
287
+ # cross-domain) and compared as a parsed triple, not a string, so an
288
+ # entity verb's two legitimate spellings compare equal.
563
289
  qualified = Naming.split_verb("#{bluebook.name}::#{dispatch.command_name}")
564
290
  next if verbs.include?(qualified)
565
291
 
@@ -569,14 +295,9 @@ module Hecks
569
295
  "checker's scope, same as CommandRules#resolve_references")
570
296
  end
571
297
 
572
- # A `compensates` declared with nowhere to ever fire — the exact
573
- # shape of the real bug this whole feature closes ("the
574
- # reversal was written and never armed"), caught at build/
575
- # model-check time instead of discovered in production. No
576
- # handler anywhere answers `REFUSED` (`process_manager.saga?` false) means
577
- # `SagaInterpreter#unwind` never runs for this process
578
- # manager at all, so a declared `compensates` is structurally
579
- # unreachable — not a warning about style, a dead declaration.
298
+ # No handler anywhere answers `REFUSED` means SagaInterpreter#unwind never
299
+ # runs for this process manager, so a declared compensates is structurally
300
+ # unreachable — a dead declaration, not a style warning.
580
301
  if !process_manager.saga? && handler.dispatches.any?(&:compensates)
581
302
  handler.dispatches.select(&:compensates).each do |dispatch|
582
303
  findings << Finding.new(kind: :unarmed_compensation, severity: :error, subject: process_manager.name,
@@ -589,16 +310,7 @@ module Hecks
589
310
  findings
590
311
  end
591
312
 
592
- # Checks whether a saga's own compensation leaves an unreachable
593
- # state.
594
- #
595
- # @param process_manager [Bluebook::ProcessManager] the process
596
- # manager being checked
597
- # @param reached [Set<String>] every state `pm_reachable_states`
598
- # reaches
599
- # @return [Array<Finding>] a single-element array holding a
600
- # `:dead_compensation` finding, or `[]` if `process_manager` has no
601
- # saga or its own compensation leaves a reached state
313
+ # Checks whether a saga's own compensation leaves an unreachable state.
602
314
  def dead_compensation_findings(process_manager, reached)
603
315
  return [] unless process_manager.saga? && !reached.include?(process_manager.saga.from_state)
604
316
 
@@ -607,22 +319,10 @@ module Hecks
607
319
  "handler chain ever reaches — a refusal here can never fire it")]
608
320
  end
609
321
 
610
- # A handler edge is only usable in the closure if it can actually
611
- # fire — `REFUSED` always can (it is a compensation trigger, not an
612
- # event), and any other handler needs its event genuinely emitted.
613
- # Without this, a deaf handler's declared from_state -> to_state
614
- # pair reads as connected even though nothing can ever traverse
615
- # it, which would hide exactly the states this walk exists to
616
- # catch (a state only "reachable" through a handler that itself
617
- # never fires).
618
- #
619
- # @param process_manager [Bluebook::ProcessManager] the process
620
- # manager being checked
621
- # @param emitted [Array<String>] every bare event name this domain
622
- # actually emits
623
- # @return [Set<String>] every state reachable from
624
- # `process_manager`'s own first declared state, through handlers
625
- # that can actually fire
322
+ # Only a handler that can actually fire extends the closure — `REFUSED` always
323
+ # can (it's a compensation trigger, not an event); any other handler needs its
324
+ # event genuinely emitted. Otherwise a deaf handler's edge would read as
325
+ # connected even though nothing can ever traverse it.
626
326
  def pm_reachable_states(process_manager, emitted)
627
327
  return Set.new if Array(process_manager.states).empty?
628
328
 
@@ -642,80 +342,25 @@ module Hecks
642
342
  reached
643
343
  end
644
344
 
645
- # ── policies ───────────────────────────────────────────────────────
646
-
647
345
  # Runs every same-domain policy check, or defers to
648
- # `cross_domain_policy_findings` for a policy that targets another
649
- # domain.
650
- #
651
- # @param bluebook [Bluebook::Chapter] the assembled chapter `policy`
652
- # belongs to
653
- # @param policy [Bluebook::Policy] the policy to check
654
- # @param hecksagon [Bluebook::Hecksagon, nil] this bluebook's own
655
- # sibling wiring file, if the caller loaded one
656
- # @param known_domains [Set<String>, nil] every bluebook/hecksagon
657
- # name the caller has booted anywhere in this corpus scan
658
- # @param global_emitted_events [Set<String>, nil] every event name emitted anywhere in the
659
- # corpus, consulted only as a fallback for a `translates` reaction; nil when the caller
660
- # has no cross-domain set to offer
661
- # @return [Array<Finding>] every `deaf_policy`/`unknown_trigger`
662
- # finding this policy triggers, or `cross_domain_policy_findings`'s
663
- # own return for a cross-domain policy
346
+ # cross_domain_policy_findings for a cross-domain policy.
664
347
  def policy_findings(bluebook, policy, hecksagon, known_domains, global_emitted_events = nil)
665
348
  return cross_domain_policy_findings(policy, hecksagon, known_domains) if policy.target_domain
666
349
 
667
350
  emitted = emitted_events(bluebook)
668
351
  findings = []
669
352
 
670
- # `policy.event_name` (Naming.unqualified) — not `bare`, which only
671
- # strips a "::" domain qualifier. An aggregate-scoped policy's
672
- # `on_event` carries a "." aggregate qualifier instead (PolicyBuilder
673
- # stores whatever was typed, verbatim — see `on "Account.
674
- # AccountFrozen"`), and `bare` left it untouched, silently comparing
675
- # "Account.AccountFrozen" against a list of bare emitted names that
676
- # can never contain it. Latent until now : banking's one aggregate-
677
- # scoped same-domain policy check would have caught it, but its only
678
- # prior aggregate-scoped policy (ReviewOnFreeze) is also cross-domain
679
- # (`across "Compliance"`), which exits this method one line above
680
- # before the mismatch is ever reached.
681
- # `global_emitted_events` — a `translates` (hecksagon-level)
682
- # reaction is built as the exact same same-domain-target `Policy`
683
- # an ordinary `policy` block is (no `target_domain`, so it never
684
- # reaches `cross_domain_policy_findings` above), but its whole
685
- # point is to react to a foreign domain's own event — this
686
- # domain's own `emitted_events(bluebook)` was never going to
687
- # contain it. Checked only as a fallback, after the local check
688
- # already failed, so a real same-domain typo still gets flagged
689
- # exactly as before whenever the caller has no cross-domain set
690
- # to offer (nil — every existing call site, unchanged) or the
691
- # event genuinely isn't emitted anywhere this run examined.
353
+ # `policy.event_name`, not `bare` — an aggregate-scoped on_event carries a
354
+ # "." qualifier bare doesn't strip. `global_emitted_events` is a fallback for
355
+ # a translates reaction, whose whole point is reacting to a foreign event.
692
356
  unless emitted.include?(policy.event_name) || global_emitted_events&.include?(policy.event_name)
693
357
  findings << Finding.new(kind: :deaf_policy, severity: :error, subject: policy.name,
694
358
  message: "on #{policy.on_event.inspect}, which no command in this domain emits")
695
359
  end
696
360
 
697
- # `trigger` is spelled "Aggregate.Command" (or "Entity.Command" one
698
- # level down), completed to an FQN by PolicyInterpreter#deliver as
699
- # "#{domain}::#{trigger_command}" — the same join `verbs_of` builds
700
- # independently, so the two spellings have to be compared as FQNs.
701
- #
702
- # Compared as a triple, not a string — `handler_findings`'s own
703
- # `unknown_dispatch` check (BUG#6) already applies this fix for a
704
- # saga's dispatch; a policy's `trigger` needed the identical one. A
705
- # policy triggering an `asks`/`tells` port operation (`Aggregate::
706
- # Port::Operation`, three colon-joined segments — `Naming.command_ref`'s
707
- # bare-constant rewrite turns this into "Aggregate::Port.Operation",
708
- # a leftover `::` past the aggregate) is a real, working dispatch —
709
- # `PolicyInterpreter#deliver` qualifies it with this domain's own
710
- # name before `Naming.split_verb` ever sees it, and `split_verb`
711
- # already folds that leftover `::` into the dot-joined tail
712
- # correctly (fixed for `ReactionInvocation#resolve_target`, PR
713
- # #520) — but this check compared raw strings against `verbs_of`,
714
- # which never enumerated port operations at all, so it reported
715
- # every port-operation trigger as unknown regardless. `triggerable_
716
- # verbs` now includes both, and both sides are parsed through
717
- # `Naming.split_verb` before comparing, the same reading
718
- # `resolve_target` relies on at runtime.
361
+ # Completed to the same FQN verbs_of builds, then compared as a parsed
362
+ # triple — not a string — so a port-operation trigger's spelling matches
363
+ # the same way an entity command's does (see handler_findings above).
719
364
  qualified = Naming.split_verb("#{bluebook.name}::#{policy.trigger_command}")
720
365
  unless qualified && triggerable_verbs(bluebook).include?(qualified)
721
366
  findings << Finding.new(kind: :unknown_trigger, severity: :error, subject: policy.name,
@@ -726,35 +371,9 @@ module Hecks
726
371
  findings
727
372
  end
728
373
 
729
- # ── cross-domain policies (Context Mapping) ───────────────────────
730
- #
731
- # `uses_framework "X"` already is a Shared Kernel relationship — it
732
- # merges X's own bluebook into this registry, no boundary. A cross-
733
- # domain `policy ... across: "X"` already is a Customer/Supplier
734
- # relationship — it dispatches into X over real cross-Lambda RPC in
735
- # the Rust host (`rust/host/src/lambda_client.rs`). Neither is a new
736
- # word; this makes the choice between them checked instead of a
737
- # prose comment nobody enforces (`examples/banking/bluebook/
738
- # banking.hecksagon`'s own hand-written note explaining why
739
- # Compliance is reached via `across`, never `uses_framework`).
740
- #
741
- # No new keyword anywhere — ADR 0025 principle 1 ("one idea, one
742
- # spelling") refuses a `relationship:`/`as:` argument that would
743
- # just restate, as a string, the fact the chosen keyword (
744
- # `uses_framework` vs `across`) already states completely. The
745
- # DDD vocabulary (Shared Kernel, Customer/Supplier) lives here, in
746
- # the finding's own name and this comment, and in prose docs — not
747
- # in the grammar.
748
- #
749
- # @param policy [Bluebook::Policy] the cross-domain policy to check
750
- # @param hecksagon [Bluebook::Hecksagon, nil] this bluebook's own
751
- # sibling wiring file, if the caller loaded one
752
- # @param known_domains [Set<String>, nil] every bluebook/hecksagon
753
- # name the caller has booted anywhere in this corpus scan
754
- # @return [Array<Finding>] `expected_undelivered_findings`'s own
755
- # return for a policy declared `expect_undelivered`; otherwise every
756
- # `contradictory_relationship`/`unacknowledged_relationship`/
757
- # `unknown_target_domain` finding this policy triggers
374
+ # `uses_framework` is a Shared Kernel relationship (X merges in, no boundary);
375
+ # `across` is Customer/Supplier (real cross-Lambda RPC). This checks the choice
376
+ # between them instead of leaving it as an unenforced comment. (ADR 0025)
758
377
  def cross_domain_policy_findings(policy, hecksagon, known_domains)
759
378
  return expected_undelivered_findings(policy, hecksagon, known_domains) if policy.expect_undelivered
760
379
  # No sibling hecksagon loaded — nothing to check a relationship against.
@@ -764,12 +383,9 @@ module Hecks
764
383
  findings = []
765
384
 
766
385
  if hecksagon.framework_members.include?(target)
767
- # Shared kernel and customer/supplier are mutually exclusive
768
- # claims about the same target — `uses_framework` means "X is
769
- # loaded in-process, right here"; `across` means "X is a
770
- # separate deployment, reached only by RPC." Declaring both is
771
- # either a pointless RPC to a domain already local, or a
772
- # `uses_framework` that isn't really doing what its name says.
386
+ # Shared Kernel and Customer/Supplier are mutually exclusive claims about
387
+ # the same target — declaring both means either a pointless RPC to a
388
+ # domain already local, or a uses_framework that isn't doing its job.
773
389
  findings << Finding.new(kind: :contradictory_relationship, severity: :error, subject: policy.name,
774
390
  message: "across #{target.inspect} dispatches over RPC (Customer/Supplier), " \
775
391
  "but this hecksagon also uses_framework #{target.inspect} (Shared " \
@@ -777,11 +393,8 @@ module Hecks
777
393
  "relationship declarations contradict each other for the same " \
778
394
  "target domain")
779
395
  elsif hecksagon.subscriptions.none? { |subscribed| Naming.qualifier(subscribed) == target }
780
- # This is what finally gives `subscribe` real teeth — checked
781
- # here, at model-check time, still never routed at runtime
782
- # (nothing dispatches off a `subscribe` line; see hecksagon.md's
783
- # own "checked, not routed" section). ADR 0025 names `subscribe`
784
- # by number as failing the corpus-use bar; this is the real use.
396
+ # Checked here at model-check time; subscribe itself is never routed at
397
+ # runtime (see hecksagon.md's own "checked, not routed" section).
785
398
  findings << Finding.new(kind: :unacknowledged_relationship, severity: :error, subject: policy.name,
786
399
  message: "across #{target.inspect} declares a Customer/Supplier " \
787
400
  "relationship, but nothing in this hecksagon records the " \
@@ -790,17 +403,9 @@ module Hecks
790
403
  "attach it in-process instead")
791
404
  end
792
405
 
793
- # **Typo detection, deliberately weaker** — `known_domains` can only
794
- # ever be a monorepo-scoped heuristic: a real external hecks
795
- # consumer's own domain (this repo's own embryonaut client-
796
- # shaped case) lives in a genuinely separate repository this
797
- # corpus scan can never see, so a target this check cannot find
798
- # is "unknown to THIS corpus," never proof of a typo. A target
799
- # undefined by design (the corpus's own "Notifications", used
800
- # deliberately to exercise the undelivered-reaction runtime path)
801
- # declares so on its own policy — `expect_undelivered: true`,
802
- # checked by `expected_undelivered_findings` — rather than being
803
- # named in a core allowlist.
406
+ # A heuristic, not proof: an external hecks consumer's own domain lives in a
407
+ # repo this corpus scan can't see, so "unknown here" isn't necessarily a typo.
408
+ # A domain undefined on purpose declares so via expect_undelivered instead.
804
409
  if known_domains && !known_domains.include?(target)
805
410
  findings << Finding.new(kind: :unknown_target_domain, severity: :error, subject: policy.name,
806
411
  message: "across #{target.inspect} names a domain nowhere in the corpus " \
@@ -812,25 +417,10 @@ module Hecks
812
417
  findings
813
418
  end
814
419
 
815
- # A declared undelivered target, held to its declaration. The two
816
- # findings an unreachable `across` target raises (unknown target,
817
- # unacknowledged relationship) are what the policy declared it
818
- # expects, so they are not raised. What is raised is the declaration
819
- # going stale: the target is a domain this corpus actually booted,
820
- # or the sibling hecksagon attaches or subscribes to it — either way
821
- # the reaction can be delivered, and the declaration is now a lie.
822
- # `known_domains` is nil for a single-target run, which can then only
823
- # check the hecksagon half.
824
- #
825
- # @param policy [Bluebook::Policy] the `expect_undelivered` policy to
826
- # check
827
- # @param hecksagon [Bluebook::Hecksagon, nil] this bluebook's own
828
- # sibling wiring file, if the caller loaded one
829
- # @param known_domains [Set<String>, nil] every bluebook/hecksagon
830
- # name the caller has booted anywhere in this corpus scan
831
- # @return [Array<Finding>] a single-element array holding a
832
- # `:stale_undelivered_expectation` finding, or `[]` if the target
833
- # remains genuinely unreachable
420
+ # Holds an expect_undelivered declaration to its word: the two findings an
421
+ # unreachable target would otherwise raise are suppressed (that's the point of
422
+ # the declaration), but if the target turns out reachable after all, the stale
423
+ # declaration itself is now the error.
834
424
  def expected_undelivered_findings(policy, hecksagon, known_domains)
835
425
  target = policy.target_domain
836
426
  reached = []
@@ -849,33 +439,12 @@ module Hecks
849
439
  "what reaches #{target}")]
850
440
  end
851
441
 
852
- # ── shared enumeration ────────────────────────────────────────────
853
-
854
- # A port operation emits too — the primary/driving port an adapter
855
- # outside the bluebook calls through (see hecksagon_builder.rb) is a
856
- # second, real source of events, alongside a command's own `emits`.
857
- # Ports attach to the aggregate/bluebook from the sibling `.hecksagon`
858
- # file, not this one — a caller that boots only the `.bluebook` (as
859
- # the fixtures under spec/fixtures/model_check/ do, having no
860
- # hecksagon at all) simply finds none, which is correct : nothing
861
- # can be deaf to an event that isn't even wired up yet.
442
+ # Every bare event name this domain's commands and port operations emit,
443
+ # answer, or refuse — an outbound `asks` port operation has no `.emits`.
862
444
  #
863
- # An outbound operation (`asks`) emits through a different door — it
864
- # declares no `.emits` at all (`PortOperationBuilder#refuse_wrong_
865
- # words!` refuses one that tries), naming its two real endings
866
- # `.answers`/`.refuses` instead (`PortOperation#initialize`). Reading
867
- # only `.emits` left every `asks`'s own two events invisible to this
868
- # method — real, live events a policy genuinely reacts to
869
- # (`Clearance.SuitePassed`/`SuiteFailed`, `Ticket.IssueFiled`/
870
- # `IssueFilingRefused`), reported as `deaf_policy` findings until this
871
- # read both. `.compact` because an inbound operation's `.answers`/
872
- # `.refuses` are always nil (there is no channel back to tell), which
873
- # would otherwise seed every emitted-events set with a stray nil.
874
- #
875
- # @param bluebook [Bluebook::Chapter] the assembled chapter to
876
- # enumerate
877
- # @return [Array<String>] every bare event name this domain's
878
- # commands and port operations emit, answer, or refuse, unique
445
+ # @param bluebook [Bluebook::Chapter] the assembled chapter to enumerate
446
+ # @return [Array<String>] every bare event name emitted, answered, or refused
447
+ # across this domain, unique
879
448
  def emitted_events(bluebook)
880
449
  aggregate_emits = bluebook.aggregates.flat_map do |aggregate|
881
450
  aggregate.commands.map(&:emits) +
@@ -887,32 +456,14 @@ module Hecks
887
456
  (aggregate_emits + chapter_emits).flatten.compact.uniq
888
457
  end
889
458
 
890
- # One operation, either of its own sources of events — an inbound
891
- # `tells` names its own via `.emits`; an outbound `asks` has none
892
- # (`PortOperationBuilder#refuse_wrong_words!` refuses one that
893
- # tries) and names its two real endings `.answers`/`.refuses`
894
- # instead. Pulled out of `emitted_events` above purely to keep that
895
- # method's own branching low enough to read at a glance — every
896
- # port, aggregate-owned or chapter-level, asks this the same way.
897
- #
898
- # @param ports [Array<Bluebook::DomainPort>] the ports to enumerate
899
- # @return [Array<String, nil>] every event name each operation emits,
900
- # answers, or refuses; `nil` entries included (compacted by the
901
- # caller)
459
+ # One port's own emitted/answered/refused event names — an inbound operation's
460
+ # answers/refuses are always nil, compacted by the caller.
902
461
  def port_operation_events(ports)
903
462
  ports.flat_map { |port| port.operations.flat_map { |op| [*op.emits, op.answers, op.refuses] } }
904
463
  end
905
464
 
906
- # Fully-qualified, the same spelling DispatchSpec#command_name
907
- # carries and fuzzing/sequence_generator/catalog.rb builds
908
- # independently for the same reason: a saga dispatch and a fuzzer
909
- # step both have to name a verb the same way the door does.
910
- #
911
- # @param bluebook [Bluebook::Chapter] the assembled chapter to
912
- # enumerate
913
- # @return [Array<String>] every command's own fully-qualified verb,
914
- # `"Domain::Aggregate.Command"` or
915
- # `"Domain::Aggregate.Entity.Command"`
465
+ # Every command's own fully-qualified verb: "Domain::Aggregate.Command" or
466
+ # "Domain::Aggregate.Entity.Command".
916
467
  def verbs_of(bluebook)
917
468
  bluebook.aggregates.flat_map do |aggregate|
918
469
  verbs = aggregate.commands.map { |command| "#{bluebook.name}::#{aggregate.hecks_name}.#{command.hecks_name}" }
@@ -924,20 +475,9 @@ module Hecks
924
475
  end
925
476
  end
926
477
 
927
- # An aggregate-owned port operation is a triggerable verb too —
928
- # `ReactionInvocation#resolve_target`'s own port-operation branch
929
- # resolves one by the exact same two-segment tail shape ("Aggregate::
930
- # Port.Operation", the aggregate then the port then the operation,
931
- # dot-joined past the domain) an entity command uses, checked first,
932
- # same order `Dispatcher#dispatch` already resolves a live verb in.
933
- # Only an aggregate's own ports (`aggregate.ports`) are in scope here
934
- # — a policy's `trigger` always names one aggregate, never a chapter-
935
- # level port with no owner to address through.
936
- #
937
- # @param bluebook [Bluebook::Chapter] the assembled chapter to
938
- # enumerate
939
- # @return [Array<String>] every aggregate-owned port operation's own
940
- # fully-qualified verb, `"Domain::Aggregate.Port.Operation"`
478
+ # Every aggregate-owned port operation's own fully-qualified verb — only an
479
+ # aggregate's own ports are in scope; a policy trigger never names a
480
+ # chapter-level port directly.
941
481
  def port_verbs_of(bluebook)
942
482
  bluebook.aggregates.flat_map do |aggregate|
943
483
  aggregate.ports.flat_map do |port|
@@ -948,27 +488,13 @@ module Hecks
948
488
  end
949
489
  end
950
490
 
951
- # Every triggerable verb, as a triple — `verbs_of` (ordinary/entity
952
- # commands) plus `port_verbs_of` (port operations), each parsed
953
- # through `Naming.split_verb` so a caller never has to compare two
954
- # spellings of the same verb as strings (see `policy_findings`'s own
955
- # `unknown_trigger` check for why that comparison has to happen this
956
- # way, not as `include?` on a raw string).
957
- #
958
- # @param bluebook [Bluebook::Chapter] the assembled chapter to
959
- # enumerate
960
- # @return [Set<Array(String, String, String)>] every triggerable
961
- # verb, parsed as `Naming.split_verb` `[domain, aggregate,
962
- # command]` triples
491
+ # Every triggerable verb — ordinary/entity commands plus port operations —
492
+ # parsed through Naming.split_verb so callers never compare raw spellings.
963
493
  def triggerable_verbs(bluebook)
964
494
  (verbs_of(bluebook) + port_verbs_of(bluebook)).to_set { |verb| Naming.split_verb(verb) }
965
495
  end
966
496
 
967
497
  # Strips a domain qualifier off an event name.
968
- #
969
- # @param event [String, Symbol] an event name, qualified
970
- # (`"Domain::Event"`) or bare
971
- # @return [String] `event`'s own bare name, past the last `::`
972
498
  def bare(event) = event.to_s.split("::").last
973
499
  end
974
500
  end