hecks 2.9.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 (600) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +51 -6
  3. data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
  4. data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
  5. data/lib/hecks/adapters/driven/lambda.rb +1 -1
  6. data/lib/hecks/adapters/driven/memory.rb +16 -3
  7. data/lib/hecks/adapters/driven/postgres/reconnect.rb +6 -2
  8. data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
  9. data/lib/hecks/adapters/driven/postgres.rb +4 -2
  10. data/lib/hecks/adapters/driven/prism.rb +41 -8
  11. data/lib/hecks/adapters/driven/tenant_provisioner.rb +73 -16
  12. data/lib/hecks/behaviors/expectations.rb +1 -1
  13. data/lib/hecks/behaviors.rb +1 -1
  14. data/lib/hecks/bench/cli.rb +62 -0
  15. data/lib/hecks/bench/environment.rb +71 -0
  16. data/lib/hecks/bench/postgres_probe.rb +30 -0
  17. data/lib/hecks/bench/report.rb +76 -0
  18. data/lib/hecks/bench/ruby_runner.rb +93 -0
  19. data/lib/hecks/bench/run.rb +55 -0
  20. data/lib/hecks/bench/rust_runner.rb +101 -0
  21. data/lib/hecks/bench/stats.rb +46 -0
  22. data/lib/hecks/bench/suite.rb +98 -0
  23. data/lib/hecks/bench/workload.rb +89 -0
  24. data/lib/hecks/bench.rb +17 -0
  25. data/lib/hecks/bluebook/assembly/contracts.rb +2 -0
  26. data/lib/hecks/bluebook/attribute.rb +1 -1
  27. data/lib/hecks/bluebook/behaviour/aggregate.rb +30 -1
  28. data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -0
  29. data/lib/hecks/bluebook/chapter.rb +6 -2
  30. data/lib/hecks/bluebook/domain_port.rb +36 -5
  31. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +10 -0
  32. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +8 -0
  33. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -1
  34. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +31 -3
  35. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +122 -3
  36. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +1 -1
  37. data/lib/hecks/bluebook/dsl/query_builder.rb +27 -1
  38. data/lib/hecks/bluebook/dsl/rule_reference.rb +1 -1
  39. data/lib/hecks/bluebook/dsl/world_builder.rb +29 -2
  40. data/lib/hecks/bluebook/expression/canonical_form.rb +1 -1
  41. data/lib/hecks/bluebook/expression/resolver.rb +3 -2
  42. data/lib/hecks/bluebook/hexagon.rb +13 -5
  43. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -0
  44. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +4 -1
  45. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
  46. data/lib/hecks/bluebook/meta_validator.rb +9 -4
  47. data/lib/hecks/bluebook/model_check.rb +23 -0
  48. data/lib/hecks/bluebook/pattern_subset.rb +38 -0
  49. data/lib/hecks/bluebook/query.rb +20 -5
  50. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  51. data/lib/hecks/canonical_json.rb +30 -0
  52. data/lib/hecks/chapters.rb +102 -0
  53. data/lib/hecks/cli/behaviors.rb +61 -0
  54. data/lib/hecks/cli/canonicalise.rb +25 -0
  55. data/lib/hecks/cli/check_engine_agreement.rb +32 -0
  56. data/lib/hecks/cli/check_era.rb +88 -0
  57. data/lib/hecks/cli/console.rb +66 -0
  58. data/lib/hecks/cli/deploy_template_diff.rb +40 -0
  59. data/lib/hecks/cli/doc_coverage.rb +32 -0
  60. data/lib/hecks/cli/document.rb +2 -2
  61. data/lib/hecks/cli/expression_projection.rb +41 -0
  62. data/lib/hecks/cli/follow.rb +88 -0
  63. data/lib/hecks/cli/history.rb +44 -0
  64. data/lib/hecks/cli/ir.rb +1 -1
  65. data/lib/hecks/cli/mcp.rb +3 -3
  66. data/lib/hecks/cli/model_check.rb +7 -4
  67. data/lib/hecks/cli/pattern_cases.rb +47 -0
  68. data/lib/hecks/cli/present.rb +39 -0
  69. data/lib/hecks/cli/project_cli.rb +224 -32
  70. data/lib/hecks/cli/project_diagrams.rb +4 -3
  71. data/lib/hecks/cli/project_glossary.rb +44 -0
  72. data/lib/hecks/cli/reference.rb +46 -0
  73. data/lib/hecks/cli/refresh_projections.rb +44 -0
  74. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
  75. data/lib/hecks/cli/release.rb +70 -0
  76. data/lib/hecks/cli/release_gem.rb +59 -0
  77. data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
  78. data/lib/hecks/cli/rspec_shard_files.rb +66 -0
  79. data/lib/hecks/cli/run.rb +3 -3
  80. data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
  81. data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
  82. data/lib/hecks/cli/shape.rb +70 -0
  83. data/lib/hecks/cli/smoke_http.rb +230 -0
  84. data/lib/hecks/cli/smoke_test.rb +5 -3
  85. data/lib/hecks/cli/spec_example.rb +27 -0
  86. data/lib/hecks/cli/statements.rb +32 -0
  87. data/lib/hecks/cli/stores.rb +4 -5
  88. data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
  89. data/lib/hecks/cli.rb +201 -52
  90. data/lib/hecks/codemod.rb +324 -0
  91. data/lib/hecks/corpus.rb +423 -0
  92. data/lib/hecks/deploy/bluebook/deploy.bluebook +591 -23
  93. data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -4
  94. data/lib/hecks/deploy/oidc.json +82 -2
  95. data/lib/hecks/doc/reference.rb +351 -0
  96. data/lib/hecks/doors/cli_door.rb +196 -0
  97. data/lib/hecks/doors/cli_runner.rb +297 -0
  98. data/lib/hecks/{facade → doors}/command_request.rb +1 -1
  99. data/lib/hecks/{facade → doors}/handle.rb +2 -2
  100. data/lib/hecks/{facade → doors}/json_door.rb +4 -4
  101. data/lib/hecks/doors/launcher_options.rb +129 -0
  102. data/lib/hecks/{cli → doors}/mcp_door.rb +7 -7
  103. data/lib/hecks/doors/mcp_door_scope.rb +97 -0
  104. data/lib/hecks/{facade/surface → doors/ruby_door}/aggregate_door.rb +8 -3
  105. data/lib/hecks/{facade/surface → doors/ruby_door}/chapter.rb +3 -3
  106. data/lib/hecks/{facade/surface.rb → doors/ruby_door.rb} +36 -5
  107. data/lib/hecks/doors.rb +61 -0
  108. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +1 -1
  109. data/lib/hecks/embryonaut_bluebook.rb +1 -1
  110. data/lib/hecks/engine_agreement.rb +156 -0
  111. data/lib/hecks/facade.rb +24 -37
  112. data/lib/hecks/forms/app.rb +6 -6
  113. data/lib/hecks/forms/banking_presentation.rb +50 -0
  114. data/lib/hecks/forms/field_shape.rb +2 -1
  115. data/lib/hecks/forms/port_argument.rb +1 -1
  116. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
  117. data/lib/hecks/fuzzing/combination_miner.rb +86 -0
  118. data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
  119. data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
  120. data/lib/hecks/fuzzing/differential.rb +156 -0
  121. data/lib/hecks/fuzzing/domain_generator.rb +615 -0
  122. data/lib/hecks/fuzzing/era_boundary.rb +71 -0
  123. data/lib/hecks/fuzzing/form_census.rb +136 -0
  124. data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
  125. data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
  126. data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
  127. data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
  128. data/lib/hecks/fuzzing/outside_world.rb +45 -0
  129. data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
  130. data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
  131. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
  132. data/lib/hecks/fuzzing/properties/guards.rb +190 -0
  133. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
  134. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
  135. data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
  136. data/lib/hecks/fuzzing/properties/querying.rb +175 -0
  137. data/lib/hecks/fuzzing/properties.rb +162 -0
  138. data/lib/hecks/fuzzing/qa_settings.rb +123 -0
  139. data/lib/hecks/fuzzing/replay.rb +505 -0
  140. data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
  141. data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
  142. data/lib/hecks/fuzzing/self_consistency.rb +339 -0
  143. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
  144. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
  145. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
  146. data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
  147. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
  148. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
  149. data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
  150. data/lib/hecks/fuzzing/shrinker.rb +166 -0
  151. data/lib/hecks/fuzzing/structural_skips.rb +33 -0
  152. data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
  153. data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
  154. data/lib/hecks/fuzzing/value_generator.rb +216 -0
  155. data/lib/hecks/fuzzing.rb +23 -0
  156. data/lib/hecks/grammar/evolve.rb +336 -0
  157. data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
  158. data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
  159. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
  160. data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
  161. data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
  162. data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
  163. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
  164. data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
  165. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
  166. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
  167. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
  168. data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
  169. data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
  170. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
  171. data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
  172. data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
  173. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
  174. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
  175. data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
  176. data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
  177. data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
  178. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
  179. data/lib/hecks/hecks/adapters/git.adapter +7 -0
  180. data/lib/hecks/hecks/adapters/git.rb +102 -0
  181. data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
  182. data/lib/hecks/hecks/adapters/host_http.rb +65 -0
  183. data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
  184. data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
  185. data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
  186. data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
  187. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
  188. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
  189. data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
  190. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
  191. data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
  192. data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
  193. data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
  194. data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
  195. data/lib/hecks/hecks/adapters/local_files.rb +46 -0
  196. data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
  197. data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
  198. data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
  199. data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
  200. data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
  201. data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
  202. data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
  203. data/lib/hecks/hecks/adapters/shell.adapter +7 -0
  204. data/lib/hecks/hecks/adapters/shell.rb +49 -0
  205. data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
  206. data/lib/hecks/hecks/adapters/terminal.rb +74 -0
  207. data/lib/hecks/hecks/codebase.behaviors +172 -0
  208. data/lib/hecks/hecks/codebase.bluebook +2453 -0
  209. data/lib/hecks/hecks/context_map.hecksagon +13 -0
  210. data/lib/hecks/hecks/custodian.behaviors +179 -0
  211. data/lib/hecks/hecks/custodian.bluebook +1847 -0
  212. data/lib/hecks/hecks/deploy.behaviors +74 -0
  213. data/lib/hecks/hecks/deploy.world +12 -0
  214. data/lib/hecks/hecks/environments/memory.world +10 -0
  215. data/lib/hecks/hecks/governance.world +7 -0
  216. data/lib/hecks/hecks/hecks.behaviors +62 -0
  217. data/lib/hecks/hecks/hecks.bluebook +124 -0
  218. data/lib/hecks/hecks/hecks.hecksagon +390 -0
  219. data/lib/hecks/hecks/hecks.world +42 -0
  220. data/lib/hecks/kernel_capabilities.rb +194 -0
  221. data/lib/hecks/language/bluebook/bluebook.bluebook +11 -0
  222. data/lib/hecks/language/bluebook/query.bluebook +7 -0
  223. data/lib/hecks/language/bluebook/vocabulary.bluebook +4 -4
  224. data/lib/hecks/language/hecksagon/domain_port.bluebook +10 -0
  225. data/lib/hecks/language/hecksagon/hecksagon.bluebook +20 -0
  226. data/lib/hecks/mcp_stdio_guard.rb +2 -2
  227. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
  228. data/lib/hecks/persistence_legacy_fixture.rb +113 -0
  229. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +8 -5
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +4 -1
  231. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +15 -2
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +1 -1
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +1 -1
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +32 -15
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +6 -3
  237. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
  238. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +1 -1
  239. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +43 -24
  240. data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
  241. data/lib/hecks/ports/persistence/state_codec.rb +24 -5
  242. data/lib/hecks/projection_files.rb +285 -0
  243. data/lib/hecks/projections/bootstrap_table.rb +1 -1
  244. data/lib/hecks/projections/deploy/fargate.rb +5 -5
  245. data/lib/hecks/projections/deploy/lambda.rb +43 -32
  246. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  247. data/lib/hecks/projections/deploy/preview/template.rb +1 -1
  248. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
  249. data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
  250. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
  251. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
  252. data/lib/hecks/projections/deploy/shared.rb +1 -1
  253. data/lib/hecks/projections/diagrams.rb +3 -3
  254. data/lib/hecks/projections/model/deviations.rb +1 -1
  255. data/lib/hecks/projections/parser_table.rb +4 -4
  256. data/lib/hecks/projections/reference.rb +2 -3
  257. data/lib/hecks/projections/rust_vocabulary.rb +1 -1
  258. data/lib/hecks/projector/cli_projector.rb +106 -37
  259. data/lib/hecks/projector.rb +1 -1
  260. data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
  261. data/lib/hecks/quality_control/adapters/agent.rb +58 -0
  262. data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
  263. data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
  264. data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
  265. data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
  266. data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
  267. data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
  268. data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
  269. data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
  270. data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
  271. data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
  272. data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
  273. data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
  274. data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
  275. data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
  276. data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
  277. data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
  278. data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
  279. data/lib/hecks/quality_control/cli/child.rb +45 -0
  280. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
  281. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
  282. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
  283. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
  284. data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
  285. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
  286. data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
  287. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
  288. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
  289. data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
  290. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
  291. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
  292. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
  293. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
  294. data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
  295. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
  296. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
  297. data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
  298. data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
  299. data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
  300. data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
  301. data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
  302. data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
  303. data/lib/hecks/query_ir.rb +346 -0
  304. data/lib/hecks/query_ir_mcp.rb +198 -0
  305. data/lib/hecks/release/runner/ci_publisher.rb +2 -2
  306. data/lib/hecks/release/runner/clean_tree.rb +42 -0
  307. data/lib/hecks/release/runner/console.rb +6 -0
  308. data/lib/hecks/release/runner/gem_publisher.rb +14 -7
  309. data/lib/hecks/release/runner/npm_publisher.rb +6 -36
  310. data/lib/hecks/release/runner/preflight.rb +26 -2
  311. data/lib/hecks/release/runner/published.rb +8 -26
  312. data/lib/hecks/release/runner/tagger.rb +30 -15
  313. data/lib/hecks/release/runner.rb +57 -6
  314. data/lib/hecks/router/namespace_installer.rb +3 -3
  315. data/lib/hecks/runtime/adapter_lookup.rb +124 -0
  316. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +4 -0
  317. data/lib/hecks/runtime/dependency_planning.rb +20 -2
  318. data/lib/hecks/runtime/dispatcher.rb +53 -3
  319. data/lib/hecks/runtime/interpreting.rb +3 -0
  320. data/lib/hecks/runtime/loader.rb +65 -12
  321. data/lib/hecks/runtime/outbox.rb +1 -1
  322. data/lib/hecks/runtime/policy_interpreter.rb +5 -2
  323. data/lib/hecks/runtime/port_operation_interpreter.rb +2 -10
  324. data/lib/hecks/runtime/query_interpreter.rb +77 -0
  325. data/lib/hecks/runtime/reaction_outcome.rb +89 -0
  326. data/lib/hecks/runtime/refusal_wording.rb +23 -1
  327. data/lib/hecks/runtime/registry/verification.rb +163 -4
  328. data/lib/hecks/runtime/registry.rb +57 -3
  329. data/lib/hecks/runtime/remote_dispatcher.rb +31 -2
  330. data/lib/hecks/runtime/value/coercion.rb +23 -135
  331. data/lib/hecks/runtime/value/field_checks.rb +172 -0
  332. data/lib/hecks/runtime/value.rb +4 -1
  333. data/lib/hecks/runtime.rb +20 -6
  334. data/lib/hecks/rust_build/conformance.rb +158 -0
  335. data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
  336. data/lib/hecks/rust_build/coverage.rb +218 -0
  337. data/lib/hecks/rust_build/native_build.rb +124 -0
  338. data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
  339. data/lib/hecks/rust_build/project_rust.rb +332 -0
  340. data/lib/hecks/rust_build/wasm.rb +105 -0
  341. data/lib/hecks/rust_build/wasm_browser.rb +113 -0
  342. data/lib/hecks/rust_build.rb +144 -0
  343. data/lib/hecks/storehouse.rb +16 -16
  344. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +2 -2
  345. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +1 -1
  346. data/lib/hecks/three_zero/forms.yml +127 -0
  347. data/lib/hecks/three_zero.rb +12 -0
  348. data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
  349. data/lib/hecks/tools/comment_style.rb +1070 -0
  350. data/lib/hecks/tools/corpus_report.rb +87 -0
  351. data/lib/hecks/tools/deploy_recipe.rb +255 -0
  352. data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
  353. data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
  354. data/lib/hecks/tools/era_projection_backfill.rb +94 -0
  355. data/lib/hecks/tools/era_reattest.rb +146 -0
  356. data/lib/hecks/tools/evolve_run.rb +189 -0
  357. data/lib/hecks/tools/fuzz_sweep.rb +373 -0
  358. data/lib/hecks/tools/heki_compaction.rb +100 -0
  359. data/lib/hecks/tools/hoist_local_givens.rb +246 -0
  360. data/lib/hecks/tools/journal_compaction.rb +89 -0
  361. data/lib/hecks/tools/oidc_manifests.rb +70 -0
  362. data/lib/hecks/tools/query_ir_run.rb +81 -0
  363. data/lib/hecks/tools/regeneration_run.rb +166 -0
  364. data/lib/hecks/tools/rust_comment_style.rb +642 -0
  365. data/lib/hecks/tools/sequence_script.rb +62 -0
  366. data/lib/hecks/tools/tail_merge.rb +117 -0
  367. data/lib/hecks/tools/tenant_provisioning.rb +142 -0
  368. data/lib/hecks/tools/translation_audit.rb +208 -0
  369. data/lib/hecks/tools/translation_scaffold.rb +131 -0
  370. data/lib/hecks/tools.rb +86 -0
  371. data/lib/hecks/version.rb +1 -1
  372. data/lib/hecks.rb +32 -15
  373. data/qa/settings.yml +66 -0
  374. data/rust/.cargo/config.toml +6 -0
  375. data/rust/.gitignore +2 -0
  376. data/rust/Cargo.lock +54 -0
  377. data/rust/Cargo.toml +61 -0
  378. data/rust/build/.cargo/config.toml +3 -0
  379. data/rust/build/.gitignore +1 -0
  380. data/rust/build/Cargo.lock +7 -0
  381. data/rust/build/Cargo.toml +19 -0
  382. data/rust/build/src/build_artifact.rs +47 -0
  383. data/rust/build/src/cargo_sync.rs +248 -0
  384. data/rust/build/src/json.rs +421 -0
  385. data/rust/build/src/lineage_pass.rs +285 -0
  386. data/rust/build/src/main.rs +51 -0
  387. data/rust/build/src/optional_pass.rs +150 -0
  388. data/rust/build/src/pipeline.rs +264 -0
  389. data/rust/build/src/reserved_names.rs +76 -0
  390. data/rust/build/src/resolve.rs +124 -0
  391. data/rust/build/src/root.rs +25 -0
  392. data/rust/build/src/sidecars.rs +73 -0
  393. data/rust/build/src/subprocess.rs +51 -0
  394. data/rust/build/src/tmp.rs +31 -0
  395. data/rust/codegen/.cargo/config.toml +3 -0
  396. data/rust/codegen/.gitignore +1 -0
  397. data/rust/codegen/Cargo.lock +7 -0
  398. data/rust/codegen/Cargo.toml +20 -0
  399. data/rust/codegen/src/attr.rs +32 -0
  400. data/rust/codegen/src/bridging.rs +481 -0
  401. data/rust/codegen/src/commands.rs +1226 -0
  402. data/rust/codegen/src/constraints.rs +109 -0
  403. data/rust/codegen/src/dependency_planning.rs +209 -0
  404. data/rust/codegen/src/domain_generator.rs +1288 -0
  405. data/rust/codegen/src/exemplar.rs +230 -0
  406. data/rust/codegen/src/expr_emitter.rs +87 -0
  407. data/rust/codegen/src/fielded.rs +192 -0
  408. data/rust/codegen/src/hecks_naming.rs +33 -0
  409. data/rust/codegen/src/json.rs +335 -0
  410. data/rust/codegen/src/json_codec.rs +697 -0
  411. data/rust/codegen/src/literal.rs +207 -0
  412. data/rust/codegen/src/main.rs +290 -0
  413. data/rust/codegen/src/manifest.rs +227 -0
  414. data/rust/codegen/src/mutations.rs +784 -0
  415. data/rust/codegen/src/naming.rs +286 -0
  416. data/rust/codegen/src/ports.rs +208 -0
  417. data/rust/codegen/src/prelude.rs +116 -0
  418. data/rust/codegen/src/queries.rs +786 -0
  419. data/rust/codegen/src/reactions.rs +532 -0
  420. data/rust/codegen/src/read_models.rs +697 -0
  421. data/rust/codegen/src/reference_specs.rs +40 -0
  422. data/rust/codegen/src/registry.rs +966 -0
  423. data/rust/codegen/src/reserved_names.rs +76 -0
  424. data/rust/codegen/src/shared.rs +93 -0
  425. data/rust/codegen/src/skip_reason.rs +25 -0
  426. data/rust/codegen/src/types.rs +362 -0
  427. data/rust/host/.cargo/config.toml +3 -0
  428. data/rust/host/.gitignore +1 -0
  429. data/rust/host/Cargo.lock +4297 -0
  430. data/rust/host/Cargo.toml +61 -0
  431. data/rust/host/HECKS_RELEASE +1 -0
  432. data/rust/host/rds-ca-bundle.pem +2736 -0
  433. data/rust/host/src/api.rs +1596 -0
  434. data/rust/host/src/approval.rs +652 -0
  435. data/rust/host/src/auth.rs +1287 -0
  436. data/rust/host/src/bin/expr_harness.rs +46 -0
  437. data/rust/host/src/bin/lineage_harness.rs +119 -0
  438. data/rust/host/src/bin/mint_harness.rs +135 -0
  439. data/rust/host/src/checkout.rs +265 -0
  440. data/rust/host/src/dispatch.rs +1443 -0
  441. data/rust/host/src/expr_json.rs +1117 -0
  442. data/rust/host/src/field_hints.rs +52 -0
  443. data/rust/host/src/ir.rs +733 -0
  444. data/rust/host/src/journal.rs +874 -0
  445. data/rust/host/src/lambda_client.rs +378 -0
  446. data/rust/host/src/log.rs +147 -0
  447. data/rust/host/src/main.rs +474 -0
  448. data/rust/host/src/mint.rs +1865 -0
  449. data/rust/host/src/payments/keystore.rs +330 -0
  450. data/rust/host/src/payments/tests.rs +1400 -0
  451. data/rust/host/src/payments.rs +767 -0
  452. data/rust/host/src/presentation.rs +698 -0
  453. data/rust/host/src/presentation_write.rs +1431 -0
  454. data/rust/host/src/query_step.rs +149 -0
  455. data/rust/host/src/rate_limit.rs +904 -0
  456. data/rust/host/src/reference_transform.rs +241 -0
  457. data/rust/host/src/reference_validate.rs +338 -0
  458. data/rust/host/src/resend.rs +296 -0
  459. data/rust/host/src/secrets.rs +106 -0
  460. data/rust/host/src/server.rs +663 -0
  461. data/rust/host/src/storage_shape.rs +175 -0
  462. data/rust/host/src/test_pg.rs +40 -0
  463. data/rust/host/src/ui_schema.rs +1351 -0
  464. data/rust/host/src/wasm_runner.rs +191 -0
  465. data/rust/host/src/web/newsletter/cooldown.rs +126 -0
  466. data/rust/host/src/web/newsletter/tests.rs +104 -0
  467. data/rust/host/src/web/newsletter.rs +310 -0
  468. data/rust/host/src/web/newsletter_send.rs +357 -0
  469. data/rust/host/src/web/registrations.rs +1505 -0
  470. data/rust/host/src/web.rs +2937 -0
  471. data/rust/lsp/.gitignore +1 -0
  472. data/rust/lsp/Cargo.lock +7 -0
  473. data/rust/lsp/Cargo.toml +16 -0
  474. data/rust/lsp/README.md +133 -0
  475. data/rust/lsp/src/diagnostics.rs +165 -0
  476. data/rust/lsp/src/json.rs +385 -0
  477. data/rust/lsp/src/main.rs +399 -0
  478. data/rust/lsp/src/outline.rs +177 -0
  479. data/rust/lsp/src/rpc.rs +67 -0
  480. data/rust/parser/.cargo/config.toml +5 -0
  481. data/rust/parser/.gitignore +1 -0
  482. data/rust/parser/Cargo.lock +7 -0
  483. data/rust/parser/Cargo.toml +21 -0
  484. data/rust/parser/src/build/closed_sets.rs +27 -0
  485. data/rust/parser/src/build/identity.rs +276 -0
  486. data/rust/parser/src/build/mod.rs +12 -0
  487. data/rust/parser/src/build/naming.rs +141 -0
  488. data/rust/parser/src/build/pattern_subset.rs +161 -0
  489. data/rust/parser/src/build/query_derive.rs +72 -0
  490. data/rust/parser/src/build/query_inference.rs +269 -0
  491. data/rust/parser/src/build/query_options.rs +100 -0
  492. data/rust/parser/src/build/read_model.rs +94 -0
  493. data/rust/parser/src/build/references.rs +32 -0
  494. data/rust/parser/src/canonical.rs +114 -0
  495. data/rust/parser/src/diag.rs +55 -0
  496. data/rust/parser/src/emit.rs +953 -0
  497. data/rust/parser/src/expr/ast_json.rs +147 -0
  498. data/rust/parser/src/expr/evaluator.rs +81 -0
  499. data/rust/parser/src/expr/mod.rs +84 -0
  500. data/rust/parser/src/expr/resolver.rs +497 -0
  501. data/rust/parser/src/ir.rs +387 -0
  502. data/rust/parser/src/keywords.rs +585 -0
  503. data/rust/parser/src/lex.rs +662 -0
  504. data/rust/parser/src/main.rs +252 -0
  505. data/rust/parser/src/parse/aggregate.rs +612 -0
  506. data/rust/parser/src/parse/chapter.rs +517 -0
  507. data/rust/parser/src/parse/command.rs +637 -0
  508. data/rust/parser/src/parse/domain_port.rs +127 -0
  509. data/rust/parser/src/parse/entity.rs +527 -0
  510. data/rust/parser/src/parse/file.rs +67 -0
  511. data/rust/parser/src/parse/hecksagon.rs +231 -0
  512. data/rust/parser/src/parse/lifecycle.rs +162 -0
  513. data/rust/parser/src/parse/mod.rs +1262 -0
  514. data/rust/parser/src/parse/policy.rs +87 -0
  515. data/rust/parser/src/parse/process_manager.rs +305 -0
  516. data/rust/parser/src/parse/query.rs +100 -0
  517. data/rust/parser/src/parse/read_model.rs +214 -0
  518. data/rust/parser/src/parse/value_object.rs +250 -0
  519. data/rust/parser/src/ruby_value.rs +422 -0
  520. data/rust/project/bridging.rb +257 -0
  521. data/rust/project/commands.rb +877 -0
  522. data/rust/project/constraints.rb +86 -0
  523. data/rust/project/dependency_planning.rb +178 -0
  524. data/rust/project/domain_generator.rb +980 -0
  525. data/rust/project/exemplar.rb +148 -0
  526. data/rust/project/expr_emitter.rb +72 -0
  527. data/rust/project/fielded.rb +141 -0
  528. data/rust/project/json_codec.rb +641 -0
  529. data/rust/project/mutations.rb +721 -0
  530. data/rust/project/naming.rb +205 -0
  531. data/rust/project/ports.rb +65 -0
  532. data/rust/project/queries.rb +493 -0
  533. data/rust/project/reactions.rb +317 -0
  534. data/rust/project/read_models.rb +690 -0
  535. data/rust/project/reference_specs.rb +27 -0
  536. data/rust/project/registry.rb +396 -0
  537. data/rust/project/skip_reason.rb +29 -0
  538. data/rust/project/types.rb +225 -0
  539. data/rust/project/write_if_changed.rb +83 -0
  540. data/rust/project.rb +30 -0
  541. data/rust/project_rust_pipeline.rb +383 -0
  542. data/rust/src/exemplar/commands.rs +279 -0
  543. data/rust/src/exemplar/constraints.rs +18 -0
  544. data/rust/src/exemplar/fielded.rs +280 -0
  545. data/rust/src/exemplar/json.rs +277 -0
  546. data/rust/src/exemplar/mod.rs +17 -0
  547. data/rust/src/exemplar/mutations.rs +80 -0
  548. data/rust/src/exemplar/queries.rs +33 -0
  549. data/rust/src/exemplar/reactions.rs +88 -0
  550. data/rust/src/exemplar/read_models.rs +48 -0
  551. data/rust/src/exemplar/registry.rs +166 -0
  552. data/rust/src/exemplar/types.rs +44 -0
  553. data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
  554. data/rust/src/kernel/attribute_shapes/list.rs +18 -0
  555. data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
  556. data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
  557. data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
  558. data/rust/src/kernel/cli.rs +585 -0
  559. data/rust/src/kernel/dispatch.rs +1090 -0
  560. data/rust/src/kernel/expr.rs +458 -0
  561. data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
  562. data/rust/src/kernel/expression_operators/comparison.rs +48 -0
  563. data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
  564. data/rust/src/kernel/expression_operators/logical.rs +13 -0
  565. data/rust/src/kernel/expression_operators/membership.rs +18 -0
  566. data/rust/src/kernel/expression_operators/mod.rs +68 -0
  567. data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
  568. data/rust/src/kernel/expression_operators/positional.rs +126 -0
  569. data/rust/src/kernel/expression_operators/presence.rs +101 -0
  570. data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
  571. data/rust/src/kernel/expression_operators/sized.rs +35 -0
  572. data/rust/src/kernel/expression_operators/text.rs +79 -0
  573. data/rust/src/kernel/expression_operators/to_string.rs +18 -0
  574. data/rust/src/kernel/json.rs +779 -0
  575. data/rust/src/kernel/mod.rs +133 -0
  576. data/rust/src/kernel/named_query.rs +219 -0
  577. data/rust/src/kernel/orchestrate.rs +1810 -0
  578. data/rust/src/kernel/pattern.rs +307 -0
  579. data/rust/src/kernel/query_comparators.rs +206 -0
  580. data/rust/src/kernel/query_ordering.rs +103 -0
  581. data/rust/src/kernel/read_model.rs +914 -0
  582. data/rust/src/kernel/reference_lookup.rs +149 -0
  583. data/rust/src/kernel/repository.rs +468 -0
  584. data/rust/src/kernel/routing.rs +612 -0
  585. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
  586. data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
  587. data/rust/src/kernel/vocab/field_hint.rs +70 -0
  588. data/rust/src/kernel/vocab/mod.rs +20 -0
  589. data/rust/src/kernel/vocab/query_comparator.rs +65 -0
  590. data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
  591. data/rust/src/lib.rs +8 -0
  592. data/rust/src/main.rs +16 -0
  593. data/rust/web/.gitignore +1 -0
  594. data/rust/web/Cargo.lock +119 -0
  595. data/rust/web/Cargo.toml +18 -0
  596. data/rust/web/src/lib.rs +8 -0
  597. metadata +477 -12
  598. data/lib/hecks/facade/cli_door.rb +0 -96
  599. data/lib/hecks/facade/cli_runner.rb +0 -127
  600. data/lib/hecks/mcp_door_scope.rb +0 -95
@@ -0,0 +1,218 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+ require "open3"
6
+ require "tmpdir"
7
+ require_relative "../rust_build"
8
+
9
+ module Hecks
10
+ module RustBuild
11
+ # Reports whether each construct in a domain's IR has a real, routed implementation under
12
+ # `<workspace>/src/generated/<domain>` (ADR 0054a).
13
+ #
14
+ # Coverage.call([domain, "--codegen=ruby|rust"])
15
+ # Coverage.call(["--check-allowlist"])
16
+ #
17
+ # Ground truth is the generated module's `ir.json` and its `manifest.json`; a construct in the
18
+ # IR that the manifest omits is a synthetic `unaccounted` finding. The exit status is 1 when a
19
+ # gap remains that the allowlist does not excuse.
20
+ module Coverage
21
+ USAGE = "usage: hecks rust_coverage <generated-module-name> [--codegen=ruby|rust] " \
22
+ "(e.g. pizzas, banking, governance, identity)\n hecks rust_coverage --check-allowlist"
23
+
24
+ # Deliberate, documented gaps only, matched by kind, gap class and construct (never a regex
25
+ # over `reason` prose). The list only shrinks: `--check-allowlist` fails once a rule excuses
26
+ # no gap anywhere.
27
+ ALLOWLIST = [].freeze
28
+
29
+ module_function
30
+
31
+ # @param argv [Array<String>] the generated module's name and optional `--codegen=` flag,
32
+ # or `--check-allowlist`
33
+ # @return [Integer] 0 when nothing is a gap, 1 otherwise
34
+ # @raise [Failure] when the module, its `ir.json` or its manifest is missing
35
+ def call(argv)
36
+ flags, positional = argv.partition { |arg| arg.start_with?("--codegen") }
37
+ codegen = flags.empty? ? "ruby" : flags.last.delete_prefix("--codegen=")
38
+ raise Failure, "--codegen must be `ruby` or `rust` (got #{flags.last.inspect})" unless %w[ruby rust].include?(codegen)
39
+
40
+ target = positional.first or raise Failure, USAGE
41
+ return check_allowlist if target == "--check-allowlist"
42
+
43
+ report(target, codegen)
44
+ end
45
+
46
+ def rust_dir = RustBuild.rust_dir
47
+
48
+ def generated_root = File.join(rust_dir, "src/generated")
49
+
50
+ # Entity-scoped query ids at any depth (`Domain::Aggregate.Entity.Query`); mirrors the
51
+ # generator's own entity query entries.
52
+ def entity_query_ids(owner_id, entities)
53
+ entities.flat_map do |entity|
54
+ entity_id = "#{owner_id}.#{entity.fetch(:name)}"
55
+ entity.fetch(:queries, []).map { |q| "#{entity_id}.#{q.fetch(:name)}" } +
56
+ entity_query_ids(entity_id, entity.fetch(:entities, []))
57
+ end
58
+ end
59
+
60
+ # The [kind, id] pairs the IR implies, independent of the manifest.
61
+ def expected_from_ir(payload)
62
+ domain_name = payload.fetch(:name)
63
+ expected = payload.fetch(:aggregates).flat_map { |aggregate| aggregate_constructs(domain_name, aggregate) }
64
+ payload.fetch(:read_models).each { |rm| expected << [:read_model, "#{domain_name}::#{rm.fetch(:name)}"] }
65
+ payload.fetch(:policies).each { |p| expected << [:policy, "#{domain_name}::#{p.fetch(:name)}"] }
66
+ payload.fetch(:process_managers).each { |pm| expected << [:process_manager, "#{domain_name}::#{pm.fetch(:name)}"] }
67
+ # An `ir.json` from before the `lineage` key existed has none: nothing to expect.
68
+ payload.fetch(:lineage, {}).fetch(:capable_aggregates, []).each do |aggregate|
69
+ expected << [:lineage_aggregate, "#{domain_name}::#{aggregate.fetch(:name)}"]
70
+ end
71
+ expected
72
+ end
73
+
74
+ def aggregate_constructs(domain_name, aggregate)
75
+ id = "#{domain_name}::#{aggregate.fetch(:name)}"
76
+ found = [[:aggregate, id]]
77
+ aggregate.fetch(:commands).each { |c| found << [:command, "#{id}.#{c.fetch(:name)}"] }
78
+ aggregate.fetch(:queries).each { |q| found << [:query, "#{id}.#{q.fetch(:name)}"] }
79
+ entity_query_ids(id, aggregate.fetch(:entities)).each { |query_id| found << [:query, query_id] }
80
+ aggregate.fetch(:entities).each do |entity|
81
+ entity_id = "#{id}.#{entity.fetch(:name)}"
82
+ found << [:entity, entity_id]
83
+ entity.fetch(:commands).each { |c| found << [:entity_command, "#{entity_id}.#{c.fetch(:name)}"] }
84
+ end
85
+ aggregate.fetch(:ports).each do |port|
86
+ port.fetch(:operations).each { |op| found << [:port_operation, "#{id}.#{port.fetch(:name)}.#{op.fetch(:name)}"] }
87
+ end
88
+ found
89
+ end
90
+
91
+ # Parses a generated module's `ir.json` and `manifest.json`, adding a synthetic `unaccounted`
92
+ # finding for any IR construct the manifest omits.
93
+ def coverage_findings(mod_dir)
94
+ ir_path = File.join(mod_dir, "ir.json")
95
+ File.exist?(ir_path) or raise Failure, "#{ir_path}: missing — this generated tree predates ir.json " \
96
+ "(hecks project_rust writes it now); re-run hecks project_rust for this domain"
97
+ ir = JSON.parse(File.read(ir_path), symbolize_names: true)
98
+ manifest_path = File.join(mod_dir, "manifest.json")
99
+ File.exist?(manifest_path) or raise Failure, "#{manifest_path}: missing — re-run hecks project_rust for this " \
100
+ "domain to write it"
101
+ manifest = JSON.parse(File.read(manifest_path), symbolize_names: true)
102
+ [ir, manifest + unaccounted_findings(ir, manifest)]
103
+ end
104
+
105
+ def allowlist_rule_matches?(rule, finding)
106
+ rule[:kind] == finding[:kind].to_s &&
107
+ rule[:gap_class] == finding[:gap_class].to_s &&
108
+ rule[:construct] == finding[:construct].to_s
109
+ end
110
+
111
+ def allowlisted(finding) = ALLOWLIST.find { |rule| allowlist_rule_matches?(rule, finding) }
112
+
113
+ # A synthetic finding for each expected construct with no manifest entry at all: a gap in
114
+ # the manifest writer itself.
115
+ def unaccounted_findings(payload, manifest)
116
+ ids = Hash.new { |hash, kind| hash[kind] = Set.new }
117
+ manifest.each { |entry| ids[entry[:kind].to_sym] << entry[:id] }
118
+ expected_from_ir(payload).reject { |kind, id| ids[kind].include?(id) }.map do |kind, id|
119
+ { kind: kind.to_s, id: id, generated: false, gap_class: "unaccounted", construct: "unaccounted",
120
+ reason: "expected per ir.json, but manifest.json has NO entry for it at all — not even a " \
121
+ "recorded skip; the generator's own accounting missed this construct entirely" }
122
+ end
123
+ end
124
+
125
+ # A real functional gap: not generated, or generated but unrouted.
126
+ def functional_gap?(finding) = finding[:generated] == false || finding[:routed] == false
127
+
128
+ # Every rule must still excuse a real gap somewhere under `src/generated/`.
129
+ def check_allowlist
130
+ gaps = Dir.glob(File.join(generated_root, "*/ir.json")).flat_map do |ir_path|
131
+ coverage_findings(File.dirname(ir_path))[1].select { |finding| functional_gap?(finding) }
132
+ end
133
+ stale = ALLOWLIST.reject { |rule| gaps.any? { |finding| allowlist_rule_matches?(rule, finding) } }
134
+ if stale.empty?
135
+ puts "hecks rust_coverage --check-allowlist: all #{ALLOWLIST.size} rules still excuse a real gap"
136
+ return 0
137
+ end
138
+ stale.each do |rule|
139
+ warn "STALE ALLOWLIST RULE: #{rule.slice(:kind, :gap_class, :construct)} matches no gap in any generated " \
140
+ "module — the gap it excused is gone; delete the rule"
141
+ end
142
+ 1
143
+ end
144
+
145
+ def report(domain, codegen)
146
+ mod_dir = File.join(generated_root, domain)
147
+ Dir.exist?(mod_dir) or raise Failure, "#{mod_dir}: no such generated module — run hecks project_rust first, " \
148
+ "or check rust/src/generated/ for the actual directory name"
149
+ return print_report(domain, mod_dir) unless codegen == "rust"
150
+
151
+ Dir.mktmpdir("rust-coverage-codegen-") do |scratch|
152
+ print_report(domain, regenerated(mod_dir, domain, scratch))
153
+ end
154
+ end
155
+
156
+ # Regenerates the domain's manifest through `hecks-codegen` into a scratch directory. `cargo
157
+ # run` rather than a fixed target path, so `CARGO_TARGET_DIR` and rust-toolchain.toml apply.
158
+ def regenerated(mod_dir, domain, scratch)
159
+ ir_path = File.join(mod_dir, "ir.json")
160
+ File.exist?(File.join(mod_dir, "manifest.json")) or raise Failure, "#{mod_dir}: no committed manifest.json — " \
161
+ "re-run hecks project_rust for this domain to write it"
162
+ out_dir = File.join(scratch, domain)
163
+ output, status = Open3.capture2e("cargo", "run", "--quiet", "--", "domain", ir_path, domain, domain, out_dir,
164
+ chdir: File.join(rust_dir, "codegen"))
165
+ status.success? or raise Failure, "hecks-codegen domain failed for #{domain}:\n#{output}"
166
+ # `hecks-codegen domain` writes no ir.json sidecar; the cross-check reads the committed IR
167
+ # the generator was just handed.
168
+ FileUtils.cp(ir_path, File.join(out_dir, "ir.json"))
169
+ out_dir
170
+ end
171
+
172
+ def print_report(domain, mod_dir)
173
+ ir, findings = coverage_findings(mod_dir)
174
+ # A construct is implemented only when generated and routed (where routing applies): a
175
+ # `dispatch_*` that compiles but nothing can call is not working.
176
+ implemented, remainder = findings.partition { |f| f[:generated] && f[:routed] != false }
177
+ deferred, gap = remainder.partition { |f| allowlisted(f) }
178
+ puts "=" * 72
179
+ puts "hecks rust_coverage #{domain} (source: manifest.json)"
180
+ puts "=" * 72
181
+ puts "#{ir.fetch(:name)} — #{findings.size} constructs accounted for " \
182
+ "(#{implemented.size} implemented, #{deferred.size} deferred, #{gap.size} GAP)"
183
+ print_bucket("IMPLEMENTED", implemented) unless implemented.empty?
184
+ print_deferred(deferred)
185
+ print_gaps(gap)
186
+ puts
187
+ gap.empty? ? 0 : 1
188
+ end
189
+
190
+ def print_bucket(label, entries)
191
+ puts "\n#{label} (#{entries.size})"
192
+ sorted(entries).each { |entry| print_entry(entry) }
193
+ end
194
+
195
+ def print_deferred(deferred)
196
+ puts "\nDEFERRED (#{deferred.size}) — on the allowlist, with its cited reason"
197
+ sorted(deferred).each do |entry|
198
+ puts " [#{entry[:kind]}] #{entry[:id]}"
199
+ puts " allowlist: #{allowlisted(entry)[:doc]}"
200
+ end
201
+ end
202
+
203
+ def print_gaps(gap)
204
+ puts "\nGAP (#{gap.size}) — missing, and NOT on the allowlist: a real, actionable finding"
205
+ sorted(gap).each { |entry| print_entry(entry) }
206
+ end
207
+
208
+ def print_entry(entry)
209
+ line = " [#{entry[:kind]}] #{entry[:id]}"
210
+ line += " (#{entry[:gap_class]})" if entry[:gap_class]
211
+ puts line
212
+ puts " #{entry[:reason]}" if entry[:reason]
213
+ end
214
+
215
+ def sorted(entries) = entries.sort_by { |entry| [entry[:kind].to_s, entry[:id]] }
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "fileutils"
5
+ require "open3"
6
+
7
+ module Hecks
8
+ module RustBuild
9
+ # Builds the native binary for one generated domain's Cargo feature, once per workspace.
10
+ #
11
+ # Examples interleave, and alternating `--features` builds forces a full recompile each time,
12
+ # so a built binary is cached per `[rust_dir, feature, sources]`, where `sources` is a digest of
13
+ # the workspace's Rust sources and manifests. Regenerating the domain changes the digest, so the
14
+ # next call rebuilds instead of answering with a stale binary or a stale failure.
15
+ module NativeBuild
16
+ # Raised when a declared feature fails to build; nil is reserved for "feature not declared".
17
+ class BuildFailed < StandardError; end
18
+
19
+ @cache = {}
20
+
21
+ class << self
22
+ # @return [Hash{Array => String, BuildFailed, nil}] each build's answer, keyed by
23
+ # `[rust_dir, feature, sources_digest]`
24
+ attr_reader :cache
25
+ end
26
+
27
+ module_function
28
+
29
+ # Builds the binary for `domain_feature` once per (rust_dir, feature, sources) and
30
+ # memoizes the result.
31
+ #
32
+ # @param domain_feature [String] the Cargo feature, a domain's directory basename
33
+ # @param rust_dir [String] the workspace to build in
34
+ # @return [String, nil] the pinned binary, or nil when Cargo.toml declares no such feature
35
+ # @raise [BuildFailed] when the build fails (memoized too)
36
+ def build_rust_for(domain_feature, rust_dir)
37
+ cache = NativeBuild.cache
38
+ key = [rust_dir, domain_feature, sources_digest(rust_dir)]
39
+ if cache.key?(key)
40
+ raise cache[key] if cache[key].is_a?(BuildFailed)
41
+
42
+ return cache[key]
43
+ end
44
+
45
+ manifest = File.read(File.join(rust_dir, "Cargo.toml"))
46
+ return cache[key] = nil unless manifest =~ /^#{Regexp.escape(domain_feature)}\s*=\s*\[\]/
47
+
48
+ begin
49
+ cache[key] = build_and_pin(domain_feature, rust_dir)
50
+ rescue BuildFailed => e
51
+ cache[key] = e
52
+ raise
53
+ end
54
+ end
55
+
56
+ # Fingerprints what a build reads: every `.rs`, `.toml` and `Cargo.lock` file under the
57
+ # workspace outside `target/`, by path, size and modification time.
58
+ #
59
+ # @param rust_dir [String] the workspace
60
+ # @return [String] a hex digest that changes when any of those files does
61
+ def sources_digest(rust_dir)
62
+ digest = Digest::SHA256.new
63
+ Dir.glob(File.join(rust_dir, "**", "*.{rs,toml}"), File::FNM_DOTMATCH).sort.each do |path|
64
+ digest_file(digest, path, rust_dir)
65
+ end
66
+ lock = File.join(rust_dir, "Cargo.lock")
67
+ digest_file(digest, lock, rust_dir) if File.file?(lock)
68
+ digest.hexdigest
69
+ end
70
+
71
+ def digest_file(digest, path, rust_dir)
72
+ relative = path.delete_prefix("#{rust_dir}/")
73
+ return if relative.start_with?("target/") || !File.file?(path)
74
+
75
+ stat = File.stat(path)
76
+ digest << "#{relative}\0#{stat.size}\0#{stat.mtime.to_i}.#{stat.mtime.nsec}\n"
77
+ end
78
+
79
+ # Holds an exclusive flock from `cargo build` through the copy-out: parallel sweeps share
80
+ # `target/debug/rust`, and another build could overwrite it before it is pinned. The lock
81
+ # file is left on disk.
82
+ #
83
+ # @param domain_feature [String] the Cargo feature
84
+ # @param rust_dir [String] the workspace to build in
85
+ # @return [String] the binary, pinned to `target/debug/rust-<feature>`
86
+ # @raise [BuildFailed] when the build fails or leaves no binary
87
+ def build_and_pin(domain_feature, rust_dir)
88
+ lock_path = File.join(rust_dir, "target", ".build_rust_for.lock")
89
+ FileUtils.mkdir_p(File.dirname(lock_path))
90
+
91
+ File.open(lock_path, File::CREAT | File::RDWR) do |lock|
92
+ lock.flock(File::LOCK_EX)
93
+ command = ["cargo", "build", "--no-default-features", "--features", domain_feature]
94
+ cargo(command, rust_dir, domain_feature)
95
+ pin(rust_dir, domain_feature, command)
96
+ end
97
+ end
98
+
99
+ def cargo(command, rust_dir, domain_feature)
100
+ _stdout, stderr, status = Open3.capture3(*command, chdir: rust_dir)
101
+ return if status.success?
102
+
103
+ raise BuildFailed, "`#{command.join(' ')}` failed in #{rust_dir} (exit #{status.exitstatus}) — " \
104
+ "#{domain_feature} is declared in Cargo.toml, so this is a build failure, " \
105
+ "not a missing feature:\n#{stderr}"
106
+ rescue SystemCallError => e
107
+ raise BuildFailed, "`#{command.join(' ')}` could not run in #{rust_dir}: #{e.message}"
108
+ end
109
+
110
+ # `cargo build` always writes the same path; it is pinned per feature while the lock is held.
111
+ def pin(rust_dir, domain_feature, command)
112
+ binary = File.join(rust_dir, "target", "debug", "rust")
113
+ unless File.executable?(binary)
114
+ raise BuildFailed, "`#{command.join(' ')}` succeeded in #{rust_dir} but left no executable at #{binary}"
115
+ end
116
+
117
+ pinned = File.join(rust_dir, "target", "debug", "rust-#{domain_feature}")
118
+ FileUtils.cp(binary, pinned)
119
+ File.chmod(0o755, pinned)
120
+ pinned
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module RustBuild
5
+ class ProjectRust
6
+ # Writes `src/generated/mod.rs`: a `pub mod` per generated directory, the feature-gated
7
+ # `active` re-export of the selected domain, and a `compile_error!` for each pair of
8
+ # non-default domain features enabled together.
9
+ class RootMod
10
+ # @param file [IO] where the module is written
11
+ # @param directories [Array<String>] every directory under `src/generated`
12
+ # @param domains [Array<String>] the directories that carry their own `merged.rs`
13
+ # @param default [String] the domain this run generated, whose feature is the default
14
+ def initialize(file, directories, domains, default)
15
+ @file = file
16
+ @directories = directories
17
+ @domains = domains
18
+ @default = default
19
+ end
20
+
21
+ # @return [void]
22
+ def write
23
+ banner
24
+ modules
25
+ active_notes
26
+ reexports
27
+ conflict_guards
28
+ end
29
+
30
+ private
31
+
32
+ def banner
33
+ @file.puts "// GENERATED by hecks project_rust — re-run it to refresh this list."
34
+ @file.puts "//"
35
+ @file.puts "// BUG#25 — every DOMAIN (an entry in `domains`, below: a directory"
36
+ @file.puts "// carrying its own merged.rs and therefore its own Cargo feature) is"
37
+ @file.puts "// declared behind that SAME feature, so a broken or absent domain's"
38
+ @file.puts "// generated code is never even compiled into a build that selects a"
39
+ @file.puts "// DIFFERENT domain — `cargo build --features waybill` used to pull in"
40
+ @file.puts "// every OTHER domain's `pub mod` unconditionally, so one domain's own"
41
+ @file.puts "// non-compiling generated code (a `has_many` field, before this fix)"
42
+ @file.puts "// broke every feature's build, not just its own. A shared FRAMEWORK"
43
+ @file.puts "// chapter (governance/identity — no merged.rs of its own, so absent"
44
+ @file.puts "// from `domains`) stays unconditional: it has no single feature of its"
45
+ @file.puts "// own to gate behind, and is compiled in by whichever domain(s) attach"
46
+ @file.puts "// it via `uses_framework`."
47
+ end
48
+
49
+ def modules
50
+ @directories.each do |name|
51
+ @file.puts "#[cfg(feature = #{name.inspect})]" if @domains.include?(name)
52
+ @file.puts "pub mod #{name};"
53
+ end
54
+ end
55
+
56
+ def active_notes
57
+ @file.puts
58
+ @file.puts "// `active` is whichever ONE domain's own merged Store/dispatch table"
59
+ @file.puts "// is selected by Cargo feature (rust/Cargo.toml, kept in sync with this"
60
+ @file.puts "// list by hecks project_rust) — kernel/cli.rs imports"
61
+ @file.puts "// `crate::generated::active::{dispatch_by_name, Store, ...}` unchanged"
62
+ @file.puts "// no matter which domain that resolves to."
63
+ @file.puts "//"
64
+ @file.puts "// DOMAIN FEATURES ARE MUTUALLY EXCLUSIVE (R5) — Cargo has no native"
65
+ @file.puts "// concept of that, and `default = [#{@default.inspect}]` in"
66
+ @file.puts "// Cargo.toml stays enabled unless a build passes"
67
+ @file.puts "// `--no-default-features`, so a plain `cargo build --features banking`"
68
+ @file.puts "// used to enable BOTH the default domain's feature and banking's,"
69
+ @file.puts "// producing two `pub use ... as active;` imports and a hard E0252."
70
+ @file.puts "// Fixed two ways below, entirely mechanically from `domains`/"
71
+ @file.puts "// `target_mod_name` (this run's new default) — never hand-maintained:"
72
+ @file.puts "//"
73
+ @file.puts "// 1. THE DEFAULT DOMAIN'S OWN re-export backs off whenever any OTHER"
74
+ @file.puts "// domain feature is also enabled, so it never contends for `active`"
75
+ @file.puts "// by accident — `cargo build --features banking` (still implicitly"
76
+ @file.puts "// carrying the default feature) now resolves to banking alone,"
77
+ @file.puts "// same as if `--no-default-features` had been passed explicitly."
78
+ @file.puts "// 2. Two NON-default domain features enabled TOGETHER (a genuine,"
79
+ @file.puts "// not-accidental conflict — nothing makes that combination"
80
+ @file.puts "// meaningful) still fail the build, but with an explicit"
81
+ @file.puts "// `compile_error!` naming both features instead of a cryptic"
82
+ @file.puts "// \"defined multiple times\"."
83
+ end
84
+
85
+ def reexports
86
+ @domains.each do |name|
87
+ other_domains = @domains - [name]
88
+ if name == @default && !other_domains.empty?
89
+ guard = other_domains.map { |o| "feature = #{o.inspect}" }.join(", ")
90
+ @file.puts "#[cfg(all(feature = #{name.inspect}, not(any(#{guard}))))]"
91
+ else
92
+ @file.puts "#[cfg(feature = #{name.inspect})]"
93
+ end
94
+ @file.puts "pub use #{name}::merged as active;"
95
+ end
96
+ end
97
+
98
+ def conflict_guards
99
+ non_default_domains = @domains - [@default]
100
+ if non_default_domains.size > 1
101
+ @file.puts
102
+ @file.puts "// Explicit conflict guard — see point 2 above. Only fires when TWO"
103
+ @file.puts "// non-default domain features are both turned on; the default"
104
+ @file.puts "// domain's own feature never reaches here (point 1 already excludes"
105
+ @file.puts "// it whenever any other domain feature is present)."
106
+ non_default_domains.combination(2).each do |a, b|
107
+ @file.puts "#[cfg(all(feature = #{a.inspect}, feature = #{b.inspect}))]"
108
+ # Plain `a`/`b`/`domains.join` here, never `.inspect` — this whole
109
+ # line already sits inside one Rust string literal
110
+ # (`compile_error!("...")`), and `.inspect`'s own quoted form
111
+ # would nest unescaped double quotes and produce invalid Rust.
112
+ @file.puts "compile_error!(\"domain features are mutually exclusive — enable only one of: #{@domains.join(', ')} " \
113
+ "(both #{a} and #{b} are enabled)\");"
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end