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,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require_relative "console_capture"
5
+
6
+ module Hecks
7
+ module Adapters
8
+ # The `PgAdmin` port's adapter: the Postgres administration that happens outside the
9
+ # persistence adapters, namely roles and scratch databases.
10
+ #
11
+ # Connections are made to the maintenance database (`postgres`) or to the database being
12
+ # administered, as the current operating-system user, exactly as `psql` would. Nothing here
13
+ # creates the ledger database itself: PostgresEra provisions the tables it needs on first
14
+ # connect, and an operator makes the database with `createdb`.
15
+ class PgAdmin
16
+ # The role the QA ledger connects as when none is named.
17
+ DEFAULT_ROLE = "hecks_qa"
18
+
19
+ # What every database this adapter drops must start with, and be followed by a name.
20
+ SCRATCH = /\Ascratch_\w+\z/
21
+
22
+ # Databases that are never dropped, whatever their name: the maintenance and template
23
+ # databases.
24
+ SYSTEM_DATABASES = %w[postgres template0 template1].freeze
25
+
26
+ # The environment variables that name the ledger or production database in use.
27
+ CONFIGURED_DATABASES = %w[PGDATABASE HECKS_LEDGER_DATABASE HECKS_DATABASE].freeze
28
+
29
+ # Relation kinds in `pg_class` mapped to the `ALTER` keyword that changes their owner.
30
+ # Partitions are their own rows, so `ALTER TABLE ... OWNER` on a parent does not reach them.
31
+ KINDS = { "r" => "TABLE", "p" => "TABLE", "S" => "SEQUENCE", "v" => "VIEW",
32
+ "m" => "MATERIALIZED VIEW" }.freeze
33
+
34
+ class << self
35
+ # @return [#call, nil] opens a connection given `dbname:` and answers an object with
36
+ # `exec`, `exec_params`, `quote_ident` and `close`; a spec replaces it so no server is
37
+ # reached. The `pg` gem is required only when this is nil.
38
+ attr_accessor :connector
39
+ end
40
+
41
+ # Accepts the arguments every driven adapter is built with and keeps none of them.
42
+ #
43
+ # @param aggregate [Object, nil] unused
44
+ # @param settings [Hash] unused
45
+ # @param root [String, nil] unused
46
+ def initialize(aggregate: nil, settings: {}, root: nil); end
47
+
48
+ # Creates the ordinary role the QA ledger connects as and makes it own the database, its
49
+ # schema, and everything in it. Safe to repeat: what is already right is reported, not redone.
50
+ #
51
+ # A superuser or BYPASSRLS role is refused, because the era write-fence cannot bind it.
52
+ #
53
+ # @param held [Hash] `database` (required) and `role` (`hecks_qa` when absent)
54
+ # @return [Hash{Symbol => Hash}] `report:` what was done and what already held
55
+ # @raise [ConsoleCapture::Failure] if no database is named or it does not exist, or the role
56
+ # exists with privileges the fence cannot bind
57
+ def create_ledger_role(**held)
58
+ database = named(held)
59
+ role = plain(held[:role]) || DEFAULT_ROLE
60
+
61
+ done = []
62
+ skipped = []
63
+ with(dbname: "postgres") do |admin|
64
+ ensure_role(admin, role, done, skipped)
65
+ hand_over_database(admin, database, role, done, skipped)
66
+ end
67
+ with(dbname: database) { |db| hand_over_contents(db, role, done) }
68
+
69
+ { report: { value: ledger_report(database, role, done, skipped) } }
70
+ end
71
+
72
+ # Creates an empty database, for a scratch run that needs one. Repeating it reports that the
73
+ # database exists.
74
+ #
75
+ # @param held [Hash] `database` (required)
76
+ # @return [Hash{Symbol => Hash}] `report:` what happened
77
+ # @raise [ConsoleCapture::Failure] if no database is named
78
+ def create_database(**held)
79
+ database = named(held)
80
+ with(dbname: "postgres") do |admin|
81
+ if exists?(admin, database)
82
+ { report: { value: "database #{database} already exists" } }
83
+ else
84
+ admin.exec("CREATE DATABASE #{admin.quote_ident(database)}")
85
+ { report: { value: "created database #{database}" } }
86
+ end
87
+ end
88
+ end
89
+
90
+ # Drops a scratch database if it is there, ending other sessions on it first so one left
91
+ # open by a crashed run does not block the drop. Ending every session on a database is
92
+ # only safe for a throwaway one, so a name must start with `scratch_` and must not be the
93
+ # ledger or production database the environment names.
94
+ #
95
+ # @param held [Hash] `database` (required)
96
+ # @return [Hash{Symbol => Hash}] `report:` what happened
97
+ # @raise [ConsoleCapture::Failure] if no database is named, it is not a scratch name, or it
98
+ # is a configured ledger or production database
99
+ def drop_database(**held)
100
+ database = named(held)
101
+ refuse_to_drop!(database)
102
+ with(dbname: "postgres") do |admin|
103
+ if exists?(admin, database)
104
+ admin.exec_params("SELECT pg_terminate_backend(pid) FROM pg_stat_activity " \
105
+ "WHERE datname = $1 AND pid <> pg_backend_pid()", [database])
106
+ admin.exec("DROP DATABASE #{admin.quote_ident(database)}")
107
+ { report: { value: "dropped database #{database}" } }
108
+ else
109
+ { report: { value: "no database #{database}" } }
110
+ end
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ def refuse_to_drop!(database)
117
+ if SYSTEM_DATABASES.include?(database.downcase) || configured_databases.include?(database)
118
+ raise ConsoleCapture::Failure, "refusing to drop #{database}: it is the ledger, production or " \
119
+ "a system database"
120
+ end
121
+ return if database.match?(SCRATCH)
122
+
123
+ raise ConsoleCapture::Failure, "refusing to drop #{database}: only databases named scratch_<name> " \
124
+ "are dropped"
125
+ end
126
+
127
+ def configured_databases
128
+ named = CONFIGURED_DATABASES.filter_map { |variable| ENV.fetch(variable, nil) }
129
+ url = ENV.fetch("DATABASE_URL", nil)
130
+ named << URI.parse(url).path.to_s.delete_prefix("/") if url && !url.empty?
131
+ named.reject(&:empty?)
132
+ rescue URI::InvalidURIError
133
+ named
134
+ end
135
+
136
+ def named(held)
137
+ database = plain(held[:database])
138
+ raise ConsoleCapture::Failure, "no database named" if database.to_s.empty?
139
+
140
+ database
141
+ end
142
+
143
+ def ensure_role(admin, role, done, skipped)
144
+ found = admin.exec_params("SELECT rolsuper, rolbypassrls FROM pg_roles WHERE rolname = $1", [role])
145
+ if found.ntuples.zero?
146
+ # CREATE ROLE has no IF NOT EXISTS, and concurrent creates can lose on the catalog
147
+ # index (unique_violation) as well as on duplicate_object, so the block rescues both.
148
+ admin.exec(<<~SQL)
149
+ DO $$ BEGIN
150
+ CREATE ROLE #{admin.quote_ident(role)} LOGIN NOSUPERUSER NOBYPASSRLS NOCREATEDB NOCREATEROLE;
151
+ EXCEPTION WHEN duplicate_object OR unique_violation THEN NULL;
152
+ END $$
153
+ SQL
154
+ done << "created role #{role} (LOGIN, no SUPERUSER, no BYPASSRLS)"
155
+ elsif found[0]["rolsuper"] == "t" || found[0]["rolbypassrls"] == "t"
156
+ kind = found[0]["rolsuper"] == "t" ? "a superuser" : "a BYPASSRLS role"
157
+ raise ConsoleCapture::Failure,
158
+ "role #{role} already exists as #{kind}: the era write-fence cannot bind it. " \
159
+ "Pick another role, or ALTER ROLE #{role} NOSUPERUSER NOBYPASSRLS first."
160
+ else
161
+ skipped << "role #{role} exists, ordinary"
162
+ end
163
+ end
164
+
165
+ def hand_over_database(admin, database, role, done, skipped)
166
+ owner = admin.exec_params(
167
+ "SELECT pg_get_userbyid(datdba) AS owner FROM pg_database WHERE datname = $1", [database]
168
+ )
169
+ if owner.ntuples.zero?
170
+ raise ConsoleCapture::Failure,
171
+ "no database #{database}: createdb it first (PostgresEra provisions every table it " \
172
+ "needs on first connect, never the database itself)"
173
+ elsif owner[0]["owner"] == role
174
+ skipped << "database #{database} already owned by #{role}"
175
+ else
176
+ admin.exec("ALTER DATABASE #{admin.quote_ident(database)} OWNER TO #{admin.quote_ident(role)}")
177
+ done << "database #{database}: owner #{owner[0]['owner']} -> #{role}"
178
+ end
179
+ end
180
+
181
+ def hand_over_contents(db, role, done)
182
+ quoted = db.quote_ident(role)
183
+ schema = db.exec("SELECT pg_get_userbyid(nspowner) AS owner FROM pg_namespace WHERE nspname = 'public'")
184
+ if schema.ntuples.positive? && schema[0]["owner"] != role
185
+ db.exec("ALTER SCHEMA public OWNER TO #{quoted}")
186
+ done << "schema public: owner #{schema[0]['owner']} -> #{role}"
187
+ end
188
+
189
+ relations = db.exec_params(<<~SQL, [role])
190
+ SELECT c.relname, c.relkind, pg_get_userbyid(c.relowner) AS owner
191
+ FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
192
+ WHERE n.nspname = 'public' AND c.relkind IN ('r', 'p', 'S', 'v', 'm') AND pg_get_userbyid(c.relowner) <> $1
193
+ ORDER BY c.relkind, c.relname
194
+ SQL
195
+ relations.each do |row|
196
+ db.exec("ALTER #{KINDS.fetch(row['relkind'])} #{db.quote_ident(row['relname'])} OWNER TO #{quoted}")
197
+ end
198
+ done << "#{relations.ntuples} relation(s) in public -> #{role}" if relations.ntuples.positive?
199
+
200
+ functions = db.exec_params(<<~SQL, [role])
201
+ SELECT p.proname, pg_get_function_identity_arguments(p.oid) AS args
202
+ FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
203
+ WHERE n.nspname = 'public' AND pg_get_userbyid(p.proowner) <> $1
204
+ ORDER BY p.proname
205
+ SQL
206
+ functions.each do |row|
207
+ db.exec("ALTER FUNCTION #{db.quote_ident(row['proname'])}(#{row['args']}) OWNER TO #{quoted}")
208
+ end
209
+ done << "#{functions.ntuples} function(s) in public -> #{role}" if functions.ntuples.positive?
210
+ end
211
+
212
+ def ledger_report(database, role, done, skipped)
213
+ lines = ["#{database} is #{role}'s"]
214
+ done.each { |line| lines << " did: #{line}" }
215
+ skipped.each { |line| lines << " already: #{line}" }
216
+ lines << " bind it: database \"postgres://#{role}@localhost/#{database}\"" if done.any?
217
+ lines.join("\n")
218
+ end
219
+
220
+ def exists?(admin, database)
221
+ admin.exec_params("SELECT 1 FROM pg_database WHERE datname = $1", [database]).ntuples.positive?
222
+ end
223
+
224
+ def with(dbname:)
225
+ connection = connect(dbname)
226
+ yield connection
227
+ rescue ConsoleCapture::Failure
228
+ raise
229
+ rescue StandardError => e
230
+ raise ConsoleCapture::Failure, "postgres: #{e.message.strip}"
231
+ ensure
232
+ connection&.close
233
+ end
234
+
235
+ def connect(dbname)
236
+ return self.class.connector.call(dbname: dbname) if self.class.connector
237
+
238
+ require "pg"
239
+ PG.connect(dbname: dbname)
240
+ end
241
+
242
+ def plain(argument) = argument.is_a?(Hash) ? argument[:value] : argument
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,7 @@
1
+ # Required by name, not relative to this file: a fuzz or model check boots a copy of this directory,
2
+ # and the implementation must load once, from `lib/`, not from the copy.
3
+ require "hecks/hecks/adapters/process_pool"
4
+
5
+ Hecks.adapter "ProcessPool" do
6
+ port "ProcessPool"
7
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+ require "tempfile"
5
+ require_relative "console_capture"
6
+ require_relative "rust_workspace"
7
+
8
+ module Hecks
9
+ module Adapters
10
+ # The `ProcessPool` port's adapter: starts a long-running child (a fuzz sweep forks its own
11
+ # workers) and stays with it until it ends.
12
+ #
13
+ # The child runs in its own process group, and an interrupt or termination sent to this
14
+ # process is passed on to the whole group, so stopping a launcher stops the workers it
15
+ # started instead of leaving them running. Output goes to a temporary file, not a pipe, so a
16
+ # child that writes a lot never blocks on a reader that is waiting for it to exit.
17
+ class ProcessPool
18
+ # What a child said and how it ended.
19
+ #
20
+ # @!attribute [r] output
21
+ # @return [String] what it wrote to stdout and stderr, in order
22
+ # @!attribute [r] status
23
+ # @return [Process::Status] how it ended
24
+ Finished = Struct.new(:output, :status) do
25
+ # @return [Boolean] whether the child ended with status 0
26
+ def ok? = status.success?
27
+ end
28
+
29
+ # The library the sweep child loads `Hecks::Tools` from.
30
+ LIB = File.expand_path("../../..", __dir__)
31
+
32
+ # What the sweep child runs: the `fuzz` tool, which takes the flags that follow.
33
+ ENTRY = 'require "hecks/tools"; Hecks::Tools.script("fuzz", ARGV)'
34
+
35
+ # Signals passed on to a running child's process group.
36
+ FORWARDED = %w[INT TERM HUP QUIT].freeze
37
+
38
+ class << self
39
+ # @return [#call, nil] runs a child given `(command, env, chdir)` and answers a `Finished`;
40
+ # a spec replaces it so nothing is started
41
+ attr_accessor :starter
42
+ end
43
+
44
+ # Accepts the arguments every driven adapter is built with and keeps none of them.
45
+ #
46
+ # @param aggregate [Object, nil] unused
47
+ # @param settings [Hash] unused
48
+ # @param root [String, nil] unused
49
+ def initialize(aggregate: nil, settings: {}, root: nil); end
50
+
51
+ # Runs a fuzz sweep over one domain, or over every domain of the checkout when none is named.
52
+ #
53
+ # @param held [Hash] the `Fuzzing` record: `domain`, `seeds`, `steps`, `workers`, `adapter`
54
+ # @return [Hash{Symbol => Hash}] `report:` what the sweep printed
55
+ # @raise [ConsoleCapture::Failure] when the sweep found something, or could not run
56
+ def sweep(**held)
57
+ if plain(held[:domain]).nil? && !RustWorkspace.new.checkout?
58
+ raise ConsoleCapture::Failure, "name a domain (hecks fuzz <domain>): sweeping every domain " \
59
+ "needs a hecks checkout"
60
+ end
61
+
62
+ command = [RbConfig.ruby, "-I", LIB, "-e", ENTRY, "--"]
63
+ command << plain(held[:domain]) if plain(held[:domain])
64
+ { "--seeds" => :seeds, "--steps" => :steps, "--workers" => :workers, "--adapter" => :adapter }.each do |flag, key|
65
+ command.push(flag, plain(held[key]).to_s) unless plain(held[key]).nil?
66
+ end
67
+
68
+ answer(run(command))
69
+ end
70
+
71
+ # Starts a child, forwards interrupts to it, and waits for it to end.
72
+ #
73
+ # @param command [Array<String>] the program, then its arguments
74
+ # @param env [Hash{String => String}] variables to set for it
75
+ # @param chdir [String, nil] the directory to run it in
76
+ # @return [Finished] what it said and how it ended
77
+ def run(command, env: {}, chdir: nil)
78
+ starter = self.class.starter
79
+ return starter.call(command, env, chdir) if starter
80
+
81
+ output = Tempfile.new("process-pool")
82
+ options = { pgroup: true, out: output, err: output }
83
+ options[:chdir] = chdir if chdir
84
+ # Traps go in before the spawn, so a signal that lands while the child is starting is held
85
+ # and passed on the moment its pid is known, instead of orphaning it.
86
+ pid = nil
87
+ held = []
88
+ previous = FORWARDED.to_h do |name|
89
+ [name, trap(name) { pid ? forward(name, pid) : held << name }]
90
+ end
91
+ pid = spawn(env, *command, **options)
92
+ held.each { |name| forward(name, pid) }
93
+ _, status = Process.wait2(pid)
94
+ Finished.new(File.read(output.path), status)
95
+ rescue Errno::ENOENT => e
96
+ Finished.new(e.message, Struct.new(:success?, :exitstatus).new(false, 127))
97
+ ensure
98
+ previous&.each { |name, handler| trap(name, handler) }
99
+ output&.close!
100
+ end
101
+
102
+ private
103
+
104
+ def forward(name, pid)
105
+ Process.kill(name, -pid)
106
+ rescue Errno::ESRCH
107
+ nil
108
+ end
109
+
110
+ def answer(finished)
111
+ raise ConsoleCapture::Failure, finished.output.strip unless finished.ok?
112
+
113
+ { report: { value: finished.output } }
114
+ end
115
+
116
+ def plain(argument) = argument.is_a?(Hash) ? argument[:value] : argument
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,7 @@
1
+ # Required by name, not relative to this file: a fuzz or model check boots a copy of this directory,
2
+ # and the implementation must load once, from `lib/`, not from the copy.
3
+ require "hecks/hecks/adapters/rust_toolchain"
4
+
5
+ Hecks.adapter "RustToolchain" do
6
+ port "RustToolchain"
7
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+ require_relative "console_capture"
5
+ require_relative "rust_workspace"
6
+ require_relative "process_pool"
7
+ require "hecks/rust_build"
8
+
9
+ module Hecks
10
+ module Adapters
11
+ # The `RustToolchain` port's adapter: generates a domain's Rust source, compiles it (native,
12
+ # WASI, browser) and checks the build against the Ruby engine.
13
+ #
14
+ # Each ask runs one of the `Hecks::RustBuild` tools in this process, in the workspace
15
+ # `RustWorkspace` names: the checkout's own `rust/`, or a copy of the packaged workspace under
16
+ # the client's `.hecks/rust/<version>/`. The tool is told that workspace and a `target/`
17
+ # beside it, so a build never writes into the installed gem. A tool that ends non-zero is a
18
+ # refusal whose reason is what it printed. The benchmark is the exception: it can run for
19
+ # minutes, so it starts as a child through `ProcessPool`.
20
+ class RustToolchain
21
+ # The `Hecks::RustBuild` tool each ask runs.
22
+ TOOLS = { generate: "project_rust", wasm: "project_wasm", browser_wasm: "project_wasm_browser",
23
+ conform: "rust_conformance", replay: "rust_conformance_fuzz",
24
+ coverage: "rust_coverage" }.freeze
25
+
26
+ # The load path the benchmark child runs with: this checkout's or gem's `lib/`.
27
+ LIB = File.expand_path("../../..", __dir__)
28
+
29
+ # The program the benchmark child runs: `hecks bench`'s command line.
30
+ BENCH = 'require "hecks"; require "hecks/bench"; exit Hecks::Bench::CLI.run(ARGV)'
31
+
32
+ # `Hecks::Bench::CLI`'s flag for each `FuzzRun` field a benchmark takes.
33
+ BENCH_FLAGS = { "--domain" => :domains, "--targets" => :targets, "--iterations" => :iterations,
34
+ "--warmup" => :warmup, "--runs" => :runs, "--rust-binary" => :rust_binary,
35
+ "--format" => :format, "--output" => :output }.freeze
36
+
37
+ class << self
38
+ # @return [#run, nil] starts the long-running benchmark; a `ProcessPool` when nil
39
+ attr_accessor :pool
40
+
41
+ # @return [#capture, nil] runs each tool, as `capture(tool, argv, env:)` answering an
42
+ # object with `out`, `err`, `status` and `ok?`; `Hecks::RustBuild` when nil. A spec
43
+ # replaces it, so no toolchain is needed to test what is asked of it.
44
+ attr_accessor :runner
45
+
46
+ # @return [RustWorkspace, nil] the workspace to build in; `RustWorkspace.new` when nil
47
+ attr_accessor :workspace
48
+ end
49
+
50
+ # Accepts the arguments every driven adapter is built with and keeps none of them.
51
+ #
52
+ # @param aggregate [Object, nil] unused
53
+ # @param settings [Hash] unused
54
+ # @param root [String, nil] unused
55
+ def initialize(aggregate: nil, settings: {}, root: nil); end
56
+
57
+ # Generates the domain's Rust source into the workspace, with a Cargo feature for it.
58
+ #
59
+ # @param held [Hash] the `Build` record: `domain` (its directory)
60
+ # @return [Hash{Symbol => Hash}] `output:` what the generator reported
61
+ # @raise [ConsoleCapture::Failure] when the domain cannot be generated, or the install has no
62
+ # Rust workspace to build in
63
+ def generate(**held)
64
+ child(:generate, [plain(held[:domain])])
65
+ end
66
+
67
+ # Generates the domain, then compiles it to a `wasm32-wasip1` module.
68
+ #
69
+ # @param held [Hash] the `Build` record: `domain`
70
+ # @return [Hash{Symbol => Hash}] `output:` where the module was written
71
+ # @raise [ConsoleCapture::Failure] when the wasm target or the build is missing
72
+ def wasm(**held)
73
+ child(:wasm, [plain(held[:domain])])
74
+ end
75
+
76
+ # Generates the domain, then compiles it to an ES module for a browser through wasm-bindgen.
77
+ #
78
+ # @param held [Hash] the `Build` record: `domain`
79
+ # @return [Hash{Symbol => Hash}] `output:` where the module was written
80
+ # @raise [ConsoleCapture::Failure] when the target, the wasm-bindgen version or the build is
81
+ # missing
82
+ def browser_wasm(**held)
83
+ child(:browser_wasm, [plain(held[:domain])])
84
+ end
85
+
86
+ # Replays a step script through the Ruby engine and, when an artifact is named, through that
87
+ # artifact too, and compares what each did.
88
+ #
89
+ # @param held [Hash] the `Build` record: `domain`, `script` (a JSON file of steps) and
90
+ # `artifact` (`native`, `build`, a `.wasm` file or a binary; Ruby's own result when absent)
91
+ # @return [Hash{Symbol => Hash}] `output:` the comparison, or Ruby's result as JSON
92
+ # @raise [ConsoleCapture::Failure] when the two disagree, or the artifact cannot be run
93
+ def conform(**held)
94
+ child(:conform, [plain(held[:domain]), plain(held[:script]), plain(held[:artifact])].compact)
95
+ end
96
+
97
+ # Replays generated step sequences through the Ruby engine and an artifact.
98
+ #
99
+ # @param held [Hash] the `Build` record: `domain`, `artifact`, `seeds` (10 when absent) and
100
+ # `steps` (25 when absent)
101
+ # @return [Hash{Symbol => Hash}] `output:` how many sequences matched
102
+ # @raise [ConsoleCapture::Failure] at the first seed that diverges, naming how to reproduce it
103
+ def replay(**held)
104
+ argv = [plain(held[:domain]), plain(held[:artifact]), (plain(held[:seeds]) || 10).to_s,
105
+ (plain(held[:steps]) || 25).to_s]
106
+ child(:replay, argv)
107
+ end
108
+
109
+ # Checks that every rule of the coverage allowlist still excuses a real gap.
110
+ #
111
+ # @param _held [Hash] the `Build` record, which holds nothing this needs
112
+ # @return [Hash{Symbol => Hash}] `output:` the check's verdict
113
+ # @raise [ConsoleCapture::Failure] when a rule excuses nothing
114
+ def audit(**_held)
115
+ child(:coverage, ["--check-allowlist"])
116
+ end
117
+
118
+ # Measures command throughput and latency for each target, building the Rust binary in the
119
+ # workspace when it is a target and none was named. The benchmark can run for minutes, so it
120
+ # starts through `ProcessPool`, which passes an interrupt on to it.
121
+ #
122
+ # @param held [Hash] the `FuzzRun` record: `domains`, `targets`, `iterations`, `warmup`,
123
+ # `runs`, `rust_binary`, `format` and `output` (a file the full JSON is also written to)
124
+ # @return [Hash{Symbol => Hash}] `report:` what the benchmark printed
125
+ # @raise [ConsoleCapture::Failure] when it could not run, or the install has no Rust workspace
126
+ def measure(**held)
127
+ flags = BENCH_FLAGS.filter_map { |flag, key| [flag, plain(held[key]).to_s] unless plain(held[key]).nil? }
128
+ env = (self.class.workspace || RustWorkspace.new).environment.merge("HECKS_NO_3_0_NOTICE" => "1")
129
+ command = [RbConfig.ruby, "-I", LIB, "-e", BENCH, "--", *flags.flatten]
130
+ finished = (self.class.pool || ProcessPool.new).run(command, env: env)
131
+ raise ConsoleCapture::Failure, finished.output.strip unless finished.ok?
132
+
133
+ { report: { value: finished.output } }
134
+ rescue RustWorkspace::Unavailable => e
135
+ raise ConsoleCapture::Failure, e.message
136
+ end
137
+
138
+ # Reports whether each construct of a generated module has a routed implementation.
139
+ #
140
+ # @param module_name [Hash, String] the generated module (a domain's directory name)
141
+ # @param codegen [Hash, String, nil] `ruby` (the default) or `rust`: which generator's
142
+ # manifest to read
143
+ # @return [Hash] `text:` the report, gaps included: a gap is what it says, not a refusal
144
+ # @raise [ConsoleCapture::Failure] when there is no such module, or no report could be made
145
+ def rust_coverage(module_name:, codegen: nil)
146
+ argv = [plain(module_name)]
147
+ argv << "--codegen=#{plain(codegen)}" if plain(codegen)
148
+ result = run(:coverage, argv)
149
+ return { text: result.out } if result.ok? || result.out.start_with?("=" * 8)
150
+
151
+ raise ConsoleCapture::Failure, message_of(result)
152
+ end
153
+
154
+ private
155
+
156
+ def child(ask, argv)
157
+ result = run(ask, argv)
158
+ raise ConsoleCapture::Failure, message_of(result) unless result.ok?
159
+
160
+ { output: { value: result.out } }
161
+ end
162
+
163
+ def run(ask, argv)
164
+ space = self.class.workspace || RustWorkspace.new
165
+ env = space.environment
166
+ (self.class.runner || RustBuild).capture(TOOLS.fetch(ask), argv, env: env)
167
+ rescue RustWorkspace::Unavailable => e
168
+ raise ConsoleCapture::Failure, e.message
169
+ end
170
+
171
+ def message_of(result)
172
+ text = [result.err, result.out].map(&:strip).reject(&:empty?).join("\n")
173
+ text.empty? ? "the build ended with status #{result.status}" : text
174
+ end
175
+
176
+ def plain(argument) = argument.is_a?(Hash) ? argument[:value] : argument
177
+ end
178
+ end
179
+ end