hecks 2.9.0 → 3.0.1

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 +13 -4
  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 +15 -8
  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 +15 -5
  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,246 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../tools"
4
+
5
+ module Hecks
6
+ module Tools
7
+ # Hoists `given`s that two or more commands under one owner repeat verbatim into a single
8
+ # owner-level `given`, leaving bare references behind. Single-owner groups in example domains
9
+ # only.
10
+ #
11
+ # It is not a `Hecks::Codemod::Runner`: hoisting adds an owner-level precondition, so the
12
+ # exported IR cannot stay byte-identical. The invariant checked instead is each command's
13
+ # effective rules.
14
+ #
15
+ # The `given` is inserted right after the owner's opening line: `command` calls are deferred
16
+ # to `drain_pending!`, so the position of `given` inside the owner does not matter (ADR 0028).
17
+ #
18
+ # hecks hoist_local_givens [--dry-run]
19
+ module HoistLocalGivens
20
+ # One rule several commands of an owner repeat.
21
+ Candidate = Struct.new(:description, :canonical, :owner, :locations, keyword_init: true)
22
+
23
+ module_function
24
+
25
+ # Rewrites every example domain that has a candidate, and prints one line for each.
26
+ #
27
+ # @param argv [Array<String>] `--dry-run` to restore every file after the rewrite is checked
28
+ # @param root [String] the checkout (unused: the examples come from `Codemod`)
29
+ # @return [Integer] 0
30
+ def main(argv, root: Tools::ROOT)
31
+ _ = root
32
+ require "hecks"
33
+ require "hecks/codemod"
34
+ require "hecks/query_ir"
35
+ dry_run = argv.include?("--dry-run")
36
+
37
+ results = Hecks::Codemod::EXAMPLE_ROOTS.filter_map do |domain_dir|
38
+ bluebook_files = Dir.glob(File.join(domain_dir, "bluebook", "*.bluebook"))
39
+ next if bluebook_files.empty?
40
+
41
+ run_files(bluebook_files, dry_run: dry_run)
42
+ end
43
+
44
+ report(results, dry_run)
45
+ 0
46
+ end
47
+
48
+ # @param results [Array<Hash>] one outcome for each example domain
49
+ # @param dry_run [Boolean] whether nothing was kept
50
+ # @return [void]
51
+ def report(results, dry_run)
52
+ label = ->(c) { "#{c.owner}##{c.description.inspect} (#{c.locations.size} locations)" }
53
+
54
+ puts "== results (#{dry_run ? 'DRY RUN — nothing written' : 'applied'}) =="
55
+ results.each do |r|
56
+ case r[:status]
57
+ when :clean then puts "clean (no candidates): #{r[:file]}"
58
+ when :applied then puts "APPLIED #{r[:file]}: #{r[:candidates].map(&label).join(', ')}"
59
+ when :skipped then puts "SKIPPED #{r[:file]} (#{r[:reason]}): #{r[:candidates].map(&label).join(', ')}"
60
+ end
61
+ end
62
+ end
63
+
64
+ # Grouped by owner as well as description: identical rules recur across unrelated
65
+ # aggregates, and a cross-owner match must not block hoisting the single-owner ones.
66
+ #
67
+ # @param registry [Object] the booted bluebooks
68
+ # @return [Array<Candidate>] the rules to hoist
69
+ def find_candidates(registry)
70
+ Hecks::QueryIR.collect_rules(registry)
71
+ .select { |r| r.kind == "given" }
72
+ .group_by { |r| [r.description, r.canonical, Hecks::QueryIR.owner_of(r.location)] }
73
+ .filter_map do |(description, canonical, owner), rules|
74
+ next if rules.size < 2
75
+ # An owner-level "(declared)" rule is already hoisted for this owner.
76
+ next if rules.any? { |r| r.location.end_with?(" (declared)") }
77
+
78
+ Candidate.new(description: description, canonical: canonical, owner: owner,
79
+ locations: rules.map(&:location))
80
+ end
81
+ end
82
+
83
+ # The owner's own window: its opening line to the next `end` at the same indentation.
84
+ # `collect_rules` yields a dotted owner ("Account.LedgerEntry") but the source spells the
85
+ # bare name, so the last segment is matched; a name that is not unique in the file is
86
+ # skipped.
87
+ #
88
+ # @param text [String] a bluebook's source
89
+ # @param owner [String] the owner's dotted name
90
+ # @return [Array(MatchData, Integer), nil] the opening line's match and the closing offset
91
+ def owner_window(text, owner)
92
+ bare_name = owner.split(".").last
93
+ open_re = /^([ \t]*)(?:aggregate|entity)\s+"#{Regexp.escape(bare_name)}"\s+do\n/
94
+ return nil if text.scan(open_re).size != 1
95
+
96
+ open_match = text.match(open_re)
97
+ indent = open_match[1]
98
+ close_at = text.index(/^#{indent}end\n/, open_match.end(0))
99
+ return nil unless close_at
100
+
101
+ [open_match, close_at]
102
+ end
103
+
104
+ # Nested entity blocks are carved out of the scan: an entity may declare a `given` with the
105
+ # same description but a different predicate.
106
+ #
107
+ # @param window [String] the owner's source
108
+ # @return [Array<Array(String, Boolean)>] each stretch of source, and whether it is nested
109
+ def strip_nested_entities(window)
110
+ entity_re = /^([ \t]*)entity\s+"[^"]+"\s+do\n/
111
+ segments = []
112
+ pos = 0
113
+
114
+ while (m = window.match(entity_re, pos))
115
+ end_match = window.match(/^#{m[1]}end\n/, m.end(0))
116
+ break unless end_match
117
+
118
+ segments << [window[pos...m.begin(0)], false]
119
+ segments << [window[m.begin(0)...end_match.end(0)], true]
120
+ pos = end_match.end(0)
121
+ end
122
+ segments << [window[pos..], false]
123
+ segments
124
+ end
125
+
126
+ # A match needs the predicate to equal the candidate's canonical, not just the description:
127
+ # the same words can carry a different rule (`disputed_by.status` vs
128
+ # `account.customer.status`).
129
+ #
130
+ # @param predicate_src [String] a `given`'s block source
131
+ # @param candidate [Candidate] the rule being hoisted
132
+ # @return [Boolean] whether it is the same rule
133
+ def matching_occurrence?(predicate_src, candidate)
134
+ predicate_src.strip == candidate.canonical
135
+ end
136
+
137
+ # @param description [String] a `given`'s description
138
+ # @return [Regexp] the line that declares it with a block
139
+ def desc_pattern_for(description)
140
+ /^([ \t]*)given\(#{Regexp.escape(description.inspect)}\)\s*\{([^\n}]*)\}\n/
141
+ end
142
+
143
+ # @param text [String] a bluebook's source
144
+ # @param candidate [Candidate] the rule to hoist
145
+ # @return [Array(String, Boolean)] the new source and whether the rule was hoisted
146
+ def apply_candidate(text, candidate)
147
+ bounds = owner_window(text, candidate.owner)
148
+ return [text, false] unless bounds
149
+
150
+ open_match, close_at = bounds
151
+ window = text[open_match.end(0)...close_at]
152
+ segments = strip_nested_entities(window)
153
+ pattern = desc_pattern_for(candidate.description)
154
+
155
+ outside_matches = segments.flat_map { |seg, nested| nested ? [] : seg.scan(pattern) }
156
+ .select { |_, predicate_src| matching_occurrence?(predicate_src, candidate) }
157
+ return [text, false] if outside_matches.size < 2
158
+
159
+ # Indent one level inside the owner, not as deep as the occurrence in a command block.
160
+ child_indent = "#{open_match[1]} "
161
+ new_given_line = "#{child_indent}given(#{candidate.description.inspect}) { #{candidate.canonical} }\n"
162
+
163
+ bare_window = segments.map do |seg, nested|
164
+ next seg if nested
165
+
166
+ seg.gsub(pattern) do
167
+ indent = Regexp.last_match(1)
168
+ predicate_src = Regexp.last_match(2)
169
+ if matching_occurrence?(predicate_src, candidate)
170
+ "#{indent}given(#{candidate.description.inspect})\n"
171
+ else
172
+ Regexp.last_match(0)
173
+ end
174
+ end
175
+ end.join
176
+
177
+ new_text = text[0...open_match.end(0)] + new_given_line + bare_window + text[close_at..]
178
+ [new_text, true]
179
+ end
180
+
181
+ # Every command's effective rule set (block-declared or referenced) must be unchanged.
182
+ # Owner-level "(declared)" entries are excluded: they are what is meant to differ.
183
+ #
184
+ # @param registry [Object] the booted bluebooks
185
+ # @return [Hash{String => Array}] each command's rules, by location
186
+ def command_rule_map(registry)
187
+ Hecks::QueryIR.collect_rules(registry)
188
+ .reject { |r| r.location.end_with?(" (declared)") }
189
+ .group_by(&:location)
190
+ .transform_values { |rules| rules.map { |r| [r.kind, r.description, r.canonical] }.sort }
191
+ end
192
+
193
+ # Kept in one method: `originals` and `before_rules` must be captured before any candidate
194
+ # is applied, and splitting would mean threading that snapshot through parameters.
195
+ #
196
+ # @param files [Array<String>] one example domain's bluebook files
197
+ # @param dry_run [Boolean] whether to judge the edit in memory and write nothing
198
+ # @return [Hash] `file`, `status` (`:clean`, `:applied`, `:skipped`), and the candidates
199
+ # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
200
+ def run_files(files, dry_run:)
201
+ files = files.sort
202
+ originals = files.to_h { |file| [file, File.read(file)] }
203
+ before_rules = command_rule_map(Hecks::Codemod.load_bluebook(files))
204
+ candidates = find_candidates(Hecks::Codemod.load_bluebook(files))
205
+ result_path = files.one? ? files.first : File.dirname(files.first)
206
+
207
+ return { file: result_path, status: :clean } if candidates.empty?
208
+
209
+ texts = originals.transform_values(&:dup)
210
+ applied = []
211
+ candidates.each do |c|
212
+ target = files.find { |file| apply_candidate(texts[file], c).last }
213
+ next unless target
214
+
215
+ text, changed = apply_candidate(texts[target], c)
216
+ texts[target] = text
217
+ applied << c if changed
218
+ end
219
+
220
+ if applied.empty?
221
+ return { file: result_path, status: :skipped,
222
+ reason: "no candidate matched its own source text", candidates: [] }
223
+ end
224
+
225
+ texts.each { |file, text| Hecks::Codemod.stage(file, text, dry_run: dry_run) }
226
+ after_rules, error = Hecks::Codemod.safely do
227
+ command_rule_map(Hecks::Codemod.load_bluebook(files))
228
+ end
229
+ if dry_run || after_rules != before_rules
230
+ originals.each { |file, text| Hecks::Codemod.unstage(file, text, dry_run: dry_run) }
231
+ end
232
+
233
+ if after_rules == before_rules
234
+ { file: result_path, status: :applied, candidates: applied }
235
+ else
236
+ reason = if error
237
+ "reboot raised after edit (#{error}) — reverted"
238
+ else
239
+ "a command's own effective rule set changed after edit — reverted"
240
+ end
241
+ { file: result_path, status: :skipped, reason: reason, candidates: applied }
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hecks"
4
+ require_relative "../tools"
5
+
6
+ module Hecks
7
+ module Tools
8
+ # Compacts a Postgres- or Sqlite-backed aggregate's journal: deletes entries already projected
9
+ # into its own aggregate table (its own `checkpoint`), so a `:refresh` projection rebuild never
10
+ # needs them. A `:strict` projection catch-up refuses loudly on its own if it is ever behind
11
+ # what this deletes (see `Ports::Projection::Worker#catch_up!`).
12
+ #
13
+ # hecks compact <domain> [aggregate_name ...] # dry run
14
+ # hecks compact <domain> [aggregate_name ...] --force # apply
15
+ module JournalCompaction
16
+ module_function
17
+
18
+ # Reports, or with `--force` applies, the compaction of each matching aggregate's journal.
19
+ #
20
+ # @param argv [Array<String>] the domain directory, aggregate names and `--force`
21
+ # @param root [String] unused: the domain is read from the path given
22
+ # @return [Integer] 0, or 1 when the domain directory does not exist
23
+ def main(argv, **)
24
+ argv = argv.dup
25
+ force = argv.delete("--force")
26
+ domain = argv.shift
27
+ if domain.nil? || !Dir.exist?(domain)
28
+ warn "hecks compact: no such domain #{domain.inspect}"
29
+ warn "usage: hecks compact <domain> [aggregate_name ...] [--force]"
30
+ return 1
31
+ end
32
+ wanted = argv.dup
33
+
34
+ registry = Hecks.boot(domain).registry
35
+ candidates = candidates(registry, wanted)
36
+ if candidates.empty?
37
+ puts "hecks compact: no Postgres/Sqlite-backed aggregate matched " \
38
+ "#{wanted.empty? ? '(any)' : wanted.inspect} in #{domain}"
39
+ return 0
40
+ end
41
+
42
+ candidates.each { |candidate| compact_candidate(candidate, force: force) }
43
+ 0
44
+ end
45
+
46
+ # @param registry [Hecks::Runtime::Registry] the booted registry
47
+ # @param wanted [Array<String>] aggregate names, or empty for every aggregate
48
+ # @return [Array<Hash>] each compactable aggregate and its adapter
49
+ def candidates(registry, wanted)
50
+ registry.bluebooks.each_with_object([]) do |(domain_name, bluebook), all|
51
+ bluebook.aggregates.each do |aggregate|
52
+ next unless wanted.empty? || wanted.include?(aggregate.hecks_name) || wanted.include?(aggregate.storage_name)
53
+
54
+ repository = registry.repository(domain_name, aggregate)
55
+ next unless repository.is_a?(Hecks::Ports::Persistence::AppendOnly)
56
+
57
+ adapter = repository.adapter
58
+ next unless adapter.respond_to?(:compact_entries!) && adapter.respond_to?(:checkpoint)
59
+
60
+ all << { aggregate: aggregate, adapter: adapter }
61
+ end
62
+ end
63
+ end
64
+
65
+ # @return [void]
66
+ def compact_candidate(candidate, force:)
67
+ aggregate = candidate[:aggregate]
68
+ adapter = candidate[:adapter]
69
+ through = adapter.checkpoint
70
+
71
+ if through <= adapter.compacted_through
72
+ puts "SKIP #{aggregate.storage_name}: nothing new to compact (already compacted through #{through})"
73
+ return
74
+ end
75
+
76
+ unless force
77
+ puts "DRY RUN #{aggregate.storage_name}: would delete journal entries through sequence " \
78
+ "#{through} (compacted_through is currently #{adapter.compacted_through}); current " \
79
+ "state (#{adapter.count} records) is unaffected. Re-run with --force to apply."
80
+ return
81
+ end
82
+
83
+ removed = adapter.compact_entries!(through: through)
84
+ puts "COMPACTED #{aggregate.storage_name}: deleted #{removed} journal entries through " \
85
+ "sequence #{through}; #{adapter.count} records preserved"
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hecks"
4
+ require_relative "../tools"
5
+
6
+ module Hecks
7
+ module Tools
8
+ # Projects each domain's OIDC client and scope manifest (`Hecks::Projections::OIDC`) into an
9
+ # `oidc.json` beside the domain.
10
+ #
11
+ # A domain that has no bluebook is skipped and one that cannot be projected is reported, so a
12
+ # sweep over the whole checkout carries on past either. `hecks deploy project_oidc` loads
13
+ # `bundler/setup` first, which pins the `json` gem: newer releases pretty-print an empty array
14
+ # as `[\n\n]`, a diff spec/oidc_manifest_spec.rb would report as drift.
15
+ module OidcManifests
16
+ # Directories that hold a `.hecksagon` but are never domains.
17
+ NOT_DOMAINS = %r{\A(rust|deploy|tmp|coverage)/}
18
+
19
+ module_function
20
+
21
+ # Projects the manifests and prints one line for each domain.
22
+ #
23
+ # @param argv [Array<String>] domain directories, relative to `root`; every domain of the
24
+ # checkout when empty
25
+ # @param root [String] the checkout
26
+ # @return [Integer] 0
27
+ def main(argv, root: Tools::ROOT)
28
+ wanted = argv.empty? ? domains(root) : argv.map { |path| path.delete_prefix("#{root}/").chomp("/") }
29
+ wanted.each { |path| project(root, path) }
30
+ 0
31
+ end
32
+
33
+ # Every `.hecksagon` names a domain root; rust/, deploy/, tmp/ and coverage/ are never
34
+ # domains.
35
+ #
36
+ # @param root [String] the checkout
37
+ # @return [Array<String>] the domain directories, relative to `root`, sorted
38
+ def domains(root)
39
+ folder = Hecks::Adapters::Folder.new
40
+ Dir.glob(File.join(root, "**/*.hecksagon"))
41
+ .map { |path| folder.domain_root(File.dirname(path)) }
42
+ .compact
43
+ .map { |path| path.delete_prefix("#{root}/") }
44
+ .grep_v(NOT_DOMAINS)
45
+ .uniq.sort
46
+ end
47
+
48
+ # @param root [String] the checkout
49
+ # @param path [String] a domain directory, relative to `root`
50
+ # @return [void]
51
+ def project(root, path)
52
+ runtime = Hecks.boot(File.join(root, path), install_doors: false)
53
+ name = runtime.registry.bluebooks.keys.first
54
+
55
+ unless name
56
+ warn " #{path}: no bluebook found — skipped"
57
+ return
58
+ end
59
+
60
+ bluebook = runtime.registry.bluebook(name)
61
+ out = File.join(root, path, "oidc.json")
62
+
63
+ Hecks::Projector.write(Hecks::Projector.call(:oidc, bluebook: bluebook), out)
64
+ puts " #{path}/oidc.json <- #{name}"
65
+ rescue StandardError => e
66
+ warn " #{path}: cannot project — #{e.message.lines.first.strip}"
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../tools"
4
+
5
+ module Hecks
6
+ module Tools
7
+ # Structured queries against the language's own IR: construct diffs, duplicate rules, impact.
8
+ # A thin front end over `Hecks::QueryIR`, which `hecks serve_query_ir_mcp` shares; needs no
9
+ # Postgres.
10
+ #
11
+ # hecks query_ir constructs [Name ...]
12
+ # hecks query_ir duplicates [--meta] [domain_dir ...]
13
+ # hecks query_ir impact Name field (advisory, not a gate)
14
+ module QueryIrRun
15
+ USAGE = <<~USAGE
16
+ Usage:
17
+ hecks query_ir constructs [Name ...]
18
+ hecks query_ir duplicates [--meta] [domain_dir ...]
19
+ hecks query_ir impact Name field
20
+ USAGE
21
+
22
+ module_function
23
+
24
+ # Prints the answer to one query.
25
+ #
26
+ # @param argv [Array<String>] `constructs`, `duplicates` or `impact`, then its arguments
27
+ # @param root [String] the checkout (unused: the queries read the loaded language)
28
+ # @return [Integer] 0, or 1 when the arguments are refused
29
+ def main(argv, root: Tools::ROOT)
30
+ _ = root
31
+ require "hecks"
32
+ require "hecks/query_ir"
33
+ args = argv.dup
34
+ case args.shift
35
+ when "constructs" then constructs(args)
36
+ when "duplicates" then duplicates(args)
37
+ when "impact" then impact(args)
38
+ else
39
+ warn USAGE
40
+ 1
41
+ end
42
+ end
43
+
44
+ # @param args [Array<String>] construct names, none for all
45
+ # @return [Integer] the exit status
46
+ def constructs(args)
47
+ puts Hecks::QueryIR.format_constructs(Hecks::QueryIR.constructs(args))
48
+ 0
49
+ rescue ArgumentError => e
50
+ warn e.message
51
+ 1
52
+ end
53
+
54
+ # @param args [Array<String>] `--meta` and domain directories
55
+ # @return [Integer] the exit status
56
+ def duplicates(args)
57
+ use_meta = args.delete("--meta") || args.empty?
58
+ domains = args.empty? ? nil : args
59
+
60
+ puts Hecks::QueryIR.format_duplicates(Hecks::QueryIR.duplicates(domains: domains, include_meta: use_meta))
61
+ 0
62
+ end
63
+
64
+ # @param args [Array<String>] a construct's name and a field's
65
+ # @return [Integer] the exit status
66
+ def impact(args)
67
+ name, field = args
68
+ unless name && field
69
+ warn "usage: hecks query_ir impact Name field"
70
+ return 1
71
+ end
72
+
73
+ puts Hecks::QueryIR.format_impact_preview(Hecks::QueryIR.impact_preview(name, field))
74
+ 0
75
+ rescue ArgumentError => e
76
+ warn e.message
77
+ 1
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "open3"
5
+ require "tmpdir"
6
+ require_relative "../tools"
7
+ require_relative "../corpus"
8
+
9
+ module Hecks
10
+ module Tools
11
+ # Regenerates every corpus domain's committed Rust output with `hecks project_rust`, or checks
12
+ # that the committed output is what a regeneration would write.
13
+ #
14
+ # The domain list comes from `Hecks::Corpus.rust_regen_order`, never a hand-kept list. The
15
+ # order is fixed (sorted by relative path): domains that share governance/, identity/,
16
+ # Cargo.toml and generated/mod.rs stamp them with whichever ran last, so it must not flap. If
17
+ # the domain set or the sort rule changes, re-run and commit the output; never hand-edit stamps.
18
+ #
19
+ # hecks regenerate_corpus # discover, regenerate, print the plan
20
+ # hecks regenerate_corpus --check # regenerate into a scratch crate; fail on any difference
21
+ #
22
+ # `--check` never writes the working tree: it runs beside the parallel suite.
23
+ module RegenerationRun
24
+ module_function
25
+
26
+ # Regenerates the corpus, or checks it.
27
+ #
28
+ # @param argv [Array<String>] `--check` to project into a scratch crate and compare
29
+ # @param root [String] the checkout
30
+ # @return [Integer] 0, or 1 when a check finds a difference
31
+ # @raise [SystemExit] when no corpus domain is found or a domain fails to regenerate
32
+ def main(argv, root: Tools::ROOT)
33
+ Dir.chdir(root) do
34
+ domains = plan(root)
35
+ # A check projects into a scratch copy of the crate; the forked children inherit
36
+ # HECKS_RUST_DIR.
37
+ scratch = argv.include?("--check") && scratch_crate(root)
38
+ saved = ENV.fetch("HECKS_RUST_DIR", nil)
39
+ ENV["HECKS_RUST_DIR"] = scratch if scratch
40
+ begin
41
+ begin
42
+ regenerate(domains)
43
+ ensure
44
+ saved ? ENV["HECKS_RUST_DIR"] = saved : ENV.delete("HECKS_RUST_DIR")
45
+ end
46
+ scratch ? verdict(scratch) : 0
47
+ ensure
48
+ FileUtils.rm_rf(scratch) if scratch
49
+ end
50
+ end
51
+ end
52
+
53
+ # `meta` is never in the list: `Hecks::Corpus::RUST_ELSEWHERE` routes it to the check that
54
+ # owns it instead.
55
+ #
56
+ # @param root [String] the checkout
57
+ # @return [Array<String>] the domains to regenerate, relative to `root`, in run order
58
+ # @raise [SystemExit] when there are none
59
+ def plan(root)
60
+ domains = Hecks::Corpus.rust_regen_order.map { |domain| domain.dir.delete_prefix("#{root}/") }
61
+
62
+ if domains.empty?
63
+ abort "hecks regenerate_corpus: discovered ZERO domains with committed rust/src/generated/ " \
64
+ "output in Hecks::Corpus — that almost certainly means this script is running from the " \
65
+ "wrong directory, or something upstream deleted rust/src/generated/ entirely. Refusing " \
66
+ "to silently treat that as \"nothing to regenerate.\""
67
+ end
68
+
69
+ puts "hecks regenerate_corpus: regenerating #{domains.size} domain(s), in this fixed order:"
70
+ domains.each { |d| puts " #{d}" }
71
+ puts
72
+ domains
73
+ end
74
+
75
+ # Builds the memoized grammar registry once and forks for each domain, one at a time and in
76
+ # order, because the shared outputs depend on which domain ran last.
77
+ #
78
+ # @param domains [Array<String>] the domains, in run order
79
+ # @return [void]
80
+ def regenerate(domains)
81
+ require "hecks"
82
+ require "hecks/bluebook/meta_validator"
83
+ require "hecks/rust_build"
84
+ Hecks::Bluebook::MetaValidator.grammar_registry
85
+ domains.each { |domain| project_rust_in_fork(domain) }
86
+ end
87
+
88
+ # Forks so the child inherits the memoized grammar registry instead of rebuilding it. The
89
+ # child's output goes through a pipe and is printed here, so it reaches whoever captures this
90
+ # process's stdout.
91
+ #
92
+ # @param domain [String] the domain, relative to the checkout
93
+ # @return [void]
94
+ # @raise [SystemExit] when the child fails
95
+ def project_rust_in_fork(domain)
96
+ puts "== hecks project_rust #{domain} =="
97
+ reader, writer = IO.pipe
98
+ pid = fork { run_child(reader, writer, domain) }
99
+ writer.close
100
+ $stdout.write(reader.read)
101
+ reader.close
102
+ _, status = Process.wait2(pid)
103
+ abort "hecks regenerate_corpus: hecks project_rust #{domain} failed (#{status.inspect})" unless status.success?
104
+ puts
105
+ end
106
+
107
+ # @param reader [IO] the parent's end of the pipe, closed here
108
+ # @param writer [IO] the child's end, which becomes its stdout and stderr
109
+ # @param domain [String] the domain
110
+ # @return [void] never returns: the child leaves without the parent's exit handlers
111
+ def run_child(reader, writer, domain)
112
+ reader.close
113
+ # The real descriptors, not the globals: a caller may have swapped those for a buffer.
114
+ STDOUT.reopen(writer) # rubocop:disable Style/GlobalStdStream
115
+ STDERR.reopen(writer) # rubocop:disable Style/GlobalStdStream
116
+ $stdout = STDOUT
117
+ $stderr = STDERR
118
+ status = begin
119
+ Hecks::RustBuild.run("project_rust", [domain], out: STDOUT, err: STDERR) # rubocop:disable Style/GlobalStdStream
120
+ rescue SystemExit => e
121
+ e.status
122
+ rescue StandardError => e
123
+ warn "#{e.class}: #{e.message}"
124
+ 1
125
+ end
126
+ $stdout.flush
127
+ exit!(status)
128
+ end
129
+
130
+ # A scratch crate: copies of the generated tree and Cargo.toml, all `hecks project_rust`
131
+ # writes.
132
+ #
133
+ # @param root [String] the checkout
134
+ # @return [String] the scratch directory
135
+ def scratch_crate(root)
136
+ scratch = Dir.mktmpdir("regen-codegen-check")
137
+ FileUtils.mkdir_p(File.join(scratch, "src"))
138
+ FileUtils.cp_r(File.join(root, "rust/src/generated"), File.join(scratch, "src/generated"))
139
+ FileUtils.cp(File.join(root, "rust/Cargo.toml"), scratch)
140
+ scratch
141
+ end
142
+
143
+ # The gate: the scratch projection must match the generated sources and Cargo.toml on disk.
144
+ #
145
+ # @param scratch [String] the scratch crate, removed here
146
+ # @return [Integer] 0 when it matches, 1 after printing the difference
147
+ def verdict(scratch)
148
+ same = matches_scratch?(scratch)
149
+ FileUtils.rm_rf(scratch)
150
+ same ? 0 : 1
151
+ end
152
+
153
+ # @param scratch [String] the scratch crate
154
+ # @return [Boolean] whether it matches the tree, printing `git diff` output when it does not
155
+ def matches_scratch?(scratch)
156
+ pairs = [["rust/src/generated", File.join(scratch, "src/generated")],
157
+ ["rust/Cargo.toml", File.join(scratch, "Cargo.toml")]]
158
+ pairs.map do |on_disk, projected|
159
+ out, status = Open3.capture2e("git", "diff", "--no-index", "--exit-code", "--", on_disk, projected)
160
+ print out
161
+ status.success?
162
+ end.all?
163
+ end
164
+ end
165
+ end
166
+ end