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,62 @@
1
+ require "optparse"
2
+ require_relative "suite"
3
+ require_relative "report"
4
+
5
+ module Hecks
6
+ module Bench
7
+ # The command line behind `hecks bench`: parses flags, runs the `Suite`, prints the report.
8
+ # Lives in `lib/` so a spec can drive it without shelling out.
9
+ module CLI
10
+ # The defaults `hecks bench` runs with when no flag says otherwise.
11
+ DEFAULTS = { domains: %w[pizzas banking], targets: Suite::TARGETS, warmup: 200, iterations: 1000,
12
+ runs: 3 }.freeze
13
+
14
+ module_function
15
+
16
+ # Runs the benchmark described by `argv` and prints its report.
17
+ #
18
+ # @return [Integer] the process exit status: 0 on success, 2 for a usage error
19
+ def run(argv, out: $stdout, err: $stderr)
20
+ options = parse(argv)
21
+ result = Suite.call(options.fetch(:config), log: err)
22
+ out.puts(options[:format] == "json" ? Report.json(result) : Report.markdown(result))
23
+ File.write(options[:output], Report.json(result)) if options[:output]
24
+ 0
25
+ rescue OptionParser::ParseError, ArgumentError => e
26
+ err.puts "hecks bench: #{e.message}"
27
+ 2
28
+ end
29
+
30
+ # Turns command-line arguments into a configuration and output choices.
31
+ #
32
+ # @raise [OptionParser::ParseError] on an unknown or malformed flag
33
+ def parse(argv)
34
+ values = DEFAULTS.dup
35
+ extras = { format: "markdown", output: nil, rust_binary: nil }
36
+ parser(values, extras).parse(argv.dup)
37
+ { config: Suite::Config.new(**values, rust_binary: extras[:rust_binary]),
38
+ format: extras[:format], output: extras[:output] }
39
+ end
40
+
41
+ def parser(values, extras)
42
+ OptionParser.new do |opts|
43
+ opts.banner = "usage: hecks bench [options]"
44
+ opts.on("--domain NAMES", Array, "pizzas, banking (default: both)") { |v| values[:domains] = v }
45
+ opts.on("--targets NAMES", Array, "#{Suite::TARGETS.join(', ')} (default: all)") { |v| values[:targets] = v }
46
+ opts.on("--iterations N", Integer, "timed cycles per run (default #{DEFAULTS[:iterations]})") do |v|
47
+ values[:iterations] = v
48
+ end
49
+ opts.on("--warmup N", Integer, "discarded cycles before timing (default #{DEFAULTS[:warmup]})") do |v|
50
+ values[:warmup] = v
51
+ end
52
+ opts.on("--runs N", Integer, "fresh boots per target, median reported (default #{DEFAULTS[:runs]})") do |v|
53
+ values[:runs] = v
54
+ end
55
+ opts.on("--rust-binary PATH", "use this `rust` binary instead of building one") { |v| extras[:rust_binary] = v }
56
+ opts.on("--format FORMAT", %w[markdown json], "markdown (default) or json") { |v| extras[:format] = v }
57
+ opts.on("--output PATH", "also write the full JSON result here") { |v| extras[:output] = v }
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,71 @@
1
+ require "etc"
2
+ require "open3"
3
+
4
+ module Hecks
5
+ module Bench
6
+ # Describes the machine and toolchain a benchmark ran on.
7
+ # Each probe is best effort: a missing tool yields `"unknown"`, not an error.
8
+ module Environment
9
+ module_function
10
+
11
+ def describe
12
+ {
13
+ cpu: cpu, cores: Etc.nprocessors, memory_gib: memory_gib, os: os,
14
+ ruby: RUBY_DESCRIPTION, yjit: yjit?,
15
+ rustc: capture("rustc", "-V"), cargo: capture("cargo", "-V"),
16
+ hecks: Hecks::VERSION, commit: commit, load_average: load_average
17
+ }
18
+ end
19
+
20
+ # Read before and after a run to show whether something else was busy.
21
+ def load_average
22
+ text = RUBY_PLATFORM.include?("darwin") ? capture("sysctl", "-n", "vm.loadavg").delete("{}") : File.read("/proc/loadavg")
23
+ text.split.first.to_f.round(2)
24
+ rescue SystemCallError
25
+ nil
26
+ end
27
+
28
+ def cpu
29
+ return capture("sysctl", "-n", "machdep.cpu.brand_string") if RUBY_PLATFORM.include?("darwin")
30
+
31
+ model = File.foreach("/proc/cpuinfo").find { |line| line.start_with?("model name") }
32
+ model ? model.split(":", 2).last.strip : "unknown"
33
+ rescue SystemCallError
34
+ "unknown"
35
+ end
36
+
37
+ def memory_gib
38
+ bytes =
39
+ if RUBY_PLATFORM.include?("darwin")
40
+ capture("sysctl", "-n", "hw.memsize").to_i
41
+ else
42
+ File.read("/proc/meminfo")[/MemTotal:\s+(\d+)/, 1].to_i * 1024
43
+ end
44
+ bytes.zero? ? "unknown" : (bytes / (1024.0**3)).round(1)
45
+ rescue SystemCallError
46
+ "unknown"
47
+ end
48
+
49
+ def os
50
+ return "macOS #{capture('sw_vers', '-productVersion')} (#{capture('uname', '-m')})" if RUBY_PLATFORM.include?("darwin")
51
+
52
+ capture("uname", "-sr")
53
+ end
54
+
55
+ def yjit?
56
+ defined?(RubyVM::YJIT) ? RubyVM::YJIT.enabled? : false
57
+ end
58
+
59
+ def commit
60
+ capture("git", "-C", File.expand_path("../../..", __dir__), "rev-parse", "--short", "HEAD")
61
+ end
62
+
63
+ def capture(*command)
64
+ out, status = Open3.capture2(*command, err: File::NULL)
65
+ status.success? && !out.strip.empty? ? out.lines.first.strip : "unknown"
66
+ rescue SystemCallError
67
+ "unknown"
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,30 @@
1
+ module Hecks
2
+ module Bench
3
+ # Decides whether the Postgres targets can run at all.
4
+ # A missing server or `pg` gem skips the target with a reason; settings come from libpq's env.
5
+ module PostgresProbe
6
+ module_function
7
+
8
+ def unavailable_reason
9
+ require "pg"
10
+ PG.connect(dbname: "postgres", connect_timeout: 2).close
11
+ nil
12
+ rescue LoadError
13
+ "the `pg` gem is not installed"
14
+ rescue PG::Error => e
15
+ "no Postgres server is reachable (#{e.message.lines.first.to_s.strip}); start one, or point " \
16
+ "PGHOST, PGPORT and PGUSER at one"
17
+ end
18
+
19
+ def server_version
20
+ require "pg"
21
+ connection = PG.connect(dbname: "postgres", connect_timeout: 2)
22
+ connection.exec("SHOW server_version").getvalue(0, 0)
23
+ rescue LoadError, PG::Error
24
+ "unknown"
25
+ ensure
26
+ connection&.close
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,76 @@
1
+ require "json"
2
+
3
+ module Hecks
4
+ module Bench
5
+ # Renders a `Suite.call` result as Markdown or JSON.
6
+ # The Markdown carries machine, counts and skips so a pasted table is never a bare number.
7
+ module Report
8
+ module_function
9
+
10
+ def markdown(result)
11
+ [environment_block(result[:environment]), configuration_block(result[:config]),
12
+ table(result[:results]), floor_block(result[:results]), skipped_block(result[:skipped])]
13
+ .compact.join("\n\n") << "\n"
14
+ end
15
+
16
+ def json(result)
17
+ JSON.pretty_generate(result)
18
+ end
19
+
20
+ def table(results)
21
+ header = ["| domain | target | commands/s | range across runs | p50 (us) | p99 (us) | drift |",
22
+ "|---|---|---:|---:|---:|---:|---:|"]
23
+ (header + results.map { |entry| row(entry) }).join("\n")
24
+ end
25
+
26
+ def row(entry)
27
+ median = entry[:median]
28
+ low, high = entry[:throughput_range]
29
+ cells = [entry[:domain], entry[:target], number(median[:throughput_per_s], 0),
30
+ "#{number(low, 0)} to #{number(high, 0)}", number(median[:p50_us], 1),
31
+ number(median[:p99_us], 1), number(median[:drift], 2)]
32
+ "| #{cells.join(' | ')} |"
33
+ end
34
+
35
+ def number(value, places)
36
+ return "n/a" if value.nil?
37
+
38
+ whole, fraction = format("%.#{places}f", value).split(".")
39
+ [whole.gsub(/(\d)(?=(\d{3})+\z)/, '\1,'), fraction].compact.join(".")
40
+ end
41
+
42
+ def environment_block(environment)
43
+ load = environment[:load_average]
44
+ lines = [
45
+ "- CPU: #{environment[:cpu]} (#{environment[:cores]} cores), #{environment[:memory_gib]} GiB",
46
+ "- OS: #{environment[:os]}",
47
+ "- Ruby: #{environment[:ruby]} (YJIT #{environment[:yjit] ? 'on' : 'off'})",
48
+ "- Rust: #{environment[:rustc]}; #{environment[:cargo]}",
49
+ "- Hecks: #{environment[:hecks]} at #{environment[:commit]}",
50
+ "- Load average (1 min) before and after: #{load[:before]} and #{load[:after]}"
51
+ ]
52
+ lines << "- Postgres: #{environment[:postgres]}" if environment[:postgres]
53
+ lines.join("\n")
54
+ end
55
+
56
+ def configuration_block(config)
57
+ "Warmup #{config[:warmup]} cycles, then #{config[:iterations]} timed cycles per run, " \
58
+ "median of #{config[:runs]} run(s), domains: #{config[:domains].join(', ')}."
59
+ end
60
+
61
+ def floor_block(results)
62
+ floors = results.select { |entry| entry[:target] == "rust" }
63
+ .map { |entry| "#{entry[:domain]} #{number(entry[:median][:roundtrip_floor_p50_us], 1)} us" }
64
+ return nil if floors.empty?
65
+
66
+ "Rust pipe round-trip floor (p50, a step refused at once): #{floors.join(', ')}."
67
+ end
68
+
69
+ def skipped_block(skipped)
70
+ return nil if skipped.empty?
71
+
72
+ (["Skipped:"] + skipped.map { |entry| "- #{entry[:target]}: #{entry[:reason]}" }).join("\n")
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,93 @@
1
+ require_relative "run"
2
+
3
+ module Hecks
4
+ module Bench
5
+ # Times the Ruby runtime's `dispatch_flat`, in-process, on one persistence adapter.
6
+ # Boots a throwaway copy via `Fuzzing::IsolatedBoot` so every run starts from an empty store.
7
+ module RubyRunner
8
+ ADAPTERS = %i[memory sqlite postgres postgres_era].freeze
9
+
10
+ POSTGRES_ADAPTERS = %i[postgres postgres_era].freeze
11
+
12
+ DATABASE = "hecks_bench".freeze
13
+
14
+ module_function
15
+
16
+ def call(workload, adapter:, warmup:, iterations:)
17
+ unless ADAPTERS.include?(adapter)
18
+ raise ArgumentError, "unknown adapter #{adapter.inspect} — one of #{ADAPTERS.join(', ')}"
19
+ end
20
+
21
+ load_dependencies
22
+ schema = "bench_#{Process.pid}_#{rand(1 << 32).to_s(16)}"
23
+ quietly_for_postgres do
24
+ boot(workload, adapter, schema) do |runtime|
25
+ workload.setup.each { |step| dispatch(runtime, step) }
26
+ measure(runtime, workload, warmup: warmup, iterations: iterations)
27
+ end
28
+ end
29
+ ensure
30
+ drop_schema(schema) if schema && POSTGRES_ADAPTERS.include?(adapter)
31
+ end
32
+
33
+ def boot(workload, adapter, schema)
34
+ Hecks::Fuzzing::IsolatedBoot.call(workload.domain_path, adapter: adapter, database: DATABASE,
35
+ schema: schema, scratch: { database: DATABASE, schema: schema }) do |copy|
36
+ yield Hecks.boot(copy, install_doors: false)
37
+ end
38
+ end
39
+
40
+ # Every aggregate opens its own connection and each repeats the `schema already exists`
41
+ # notice, burying progress lines; `PGOPTIONS` is restored afterwards.
42
+ def quietly_for_postgres
43
+ previous = ENV.fetch("PGOPTIONS", nil)
44
+ ENV["PGOPTIONS"] = [previous, "-c client_min_messages=warning"].compact.join(" ")
45
+ yield
46
+ ensure
47
+ ENV["PGOPTIONS"] = previous
48
+ end
49
+
50
+ def measure(runtime, workload, warmup:, iterations:)
51
+ warmup.times { |n| workload.cycle(n).each { |step| dispatch(runtime, step) } }
52
+ GC.start
53
+ samples = []
54
+ started = now
55
+ iterations.times do |n|
56
+ workload.cycle(warmup + n).each { |step| samples << [step.verb, time { dispatch(runtime, step) }] }
57
+ end
58
+ Run.new(samples: samples, wall_seconds: now - started)
59
+ end
60
+
61
+ def dispatch(runtime, step)
62
+ runtime.dispatch_flat(step.verb, step.ruby_args)
63
+ end
64
+
65
+ def now
66
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
67
+ end
68
+
69
+ def time
70
+ started = now
71
+ yield
72
+ now - started
73
+ end
74
+
75
+ def load_dependencies
76
+ require "hecks"
77
+ require "hecks/fuzzing"
78
+ require "hecks/ports/persistence/plugins/era"
79
+ end
80
+
81
+ def drop_schema(schema)
82
+ require "pg"
83
+ connection = PG.connect(dbname: DATABASE, connect_timeout: 2)
84
+ connection.exec("SET client_min_messages = warning")
85
+ connection.exec("DROP SCHEMA IF EXISTS #{connection.quote_ident(schema)} CASCADE")
86
+ rescue LoadError, PG::Error
87
+ nil
88
+ ensure
89
+ connection&.close
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,55 @@
1
+ module Hecks
2
+ module Bench
3
+ # The raw timings of one measured pass over a workload on one runtime.
4
+ #
5
+ # Runners produce these and know nothing about reporting. {#summary} is the only
6
+ # place raw samples become the figures a report prints.
7
+ class Run
8
+ # @return [Array<Array(String, Float)>] each timed command's verb and latency in
9
+ # seconds, in dispatch order
10
+ attr_reader :samples
11
+
12
+ # @return [Float] seconds from the first timed command's start to the last one's end
13
+ attr_reader :wall_seconds
14
+
15
+ # @return [Hash{Symbol => Object}] facts a runner wants reported alongside the timings
16
+ attr_reader :extras
17
+
18
+ # Builds a run.
19
+ #
20
+ # @param samples [Array<Array(String, Float)>] verb and latency seconds per command
21
+ # @param wall_seconds [Float] the wall-clock duration of the whole timed pass
22
+ # @param extras [Hash{Symbol => Object}] runner-specific facts, such as a round-trip floor
23
+ def initialize(samples:, wall_seconds:, extras: {})
24
+ @samples = samples
25
+ @wall_seconds = wall_seconds
26
+ @extras = extras
27
+ end
28
+
29
+ # Reduces the samples to the figures a report prints.
30
+ #
31
+ # @return [Hash{Symbol => Object}] `:commands`, `:wall_seconds`, `:throughput_per_s`,
32
+ # the `Stats.latency` figures, `:drift`, `:by_verb` (each verb's count, p50 and p99),
33
+ # and any `extras`
34
+ def summary
35
+ seconds = samples.map(&:last)
36
+ {
37
+ commands: samples.size,
38
+ wall_seconds: wall_seconds.round(4),
39
+ throughput_per_s: (samples.size / wall_seconds).round(1),
40
+ **Stats.latency(seconds),
41
+ drift: Stats.drift(seconds),
42
+ by_verb: by_verb
43
+ }.merge(extras)
44
+ end
45
+
46
+ private
47
+
48
+ def by_verb
49
+ samples.group_by(&:first).transform_values do |group|
50
+ Stats.latency(group.map(&:last)).slice(:count, :p50_us, :p99_us)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,101 @@
1
+ require "fileutils"
2
+ require "open3"
3
+ require_relative "run"
4
+
5
+ module Hecks
6
+ module Bench
7
+ # Times the native Rust binary through `rust --serve`, one JSON step per line.
8
+ # Timings include two pipe crossings and the binary's own JSON parse; start-up is excluded.
9
+ module RustRunner
10
+ # HECKS_RUST_DIR names the workspace to build in; unset, this checkout's own rust/.
11
+ RUST_DIR = ENV.fetch("HECKS_RUST_DIR") { File.expand_path("../../../rust", __dir__) }
12
+
13
+ FLOOR_SAMPLES = 500
14
+
15
+ class BuildFailed < StandardError; end
16
+
17
+ class StepRefused < StandardError; end
18
+
19
+ module_function
20
+
21
+ def unavailable_reason(binary: nil)
22
+ return "the binary #{binary} does not exist or is not executable" if binary && !File.executable?(binary)
23
+ return nil if binary || Environment.capture("cargo", "-V") != "unknown"
24
+
25
+ "`cargo` is not on PATH and no --rust-binary was given"
26
+ end
27
+
28
+ # The pinned copy has its own name so building another domain's feature cannot replace it.
29
+ def build(feature, rust_dir: RUST_DIR)
30
+ output, status = Open3.capture2e("cargo", "build", "--release", "--no-default-features",
31
+ "--features", feature, chdir: rust_dir)
32
+ unless status.success?
33
+ raise BuildFailed,
34
+ "cargo build --release --features #{feature} failed:\n#{output.lines.last(15).join}"
35
+ end
36
+
37
+ target = File.join(ENV.fetch("CARGO_TARGET_DIR", File.join(rust_dir, "target")), "release")
38
+ pinned = File.join(target, "rust-bench-#{feature}")
39
+ FileUtils.cp(File.join(target, "rust"), pinned)
40
+ pinned
41
+ end
42
+
43
+ def call(workload, binary:, warmup:, iterations:)
44
+ IO.popen([binary, "--serve"], "r+") do |pipe|
45
+ pipe.sync = true
46
+ workload.setup.each { |step| ask(pipe, step.to_json_line) }
47
+ warmup.times { |n| workload.cycle(n).each { |step| ask(pipe, step.to_json_line) } }
48
+ run = measure(pipe, workload, warmup: warmup, iterations: iterations)
49
+ Run.new(samples: run.samples, wall_seconds: run.wall_seconds,
50
+ extras: { roundtrip_floor_p50_us: floor(pipe) })
51
+ ensure
52
+ pipe.close_write unless pipe.closed?
53
+ end
54
+ end
55
+
56
+ def measure(pipe, workload, warmup:, iterations:)
57
+ samples = []
58
+ started = now
59
+ iterations.times do |n|
60
+ workload.cycle(warmup + n).each do |step|
61
+ line = step.to_json_line
62
+ began = now
63
+ answer = round_trip(pipe, line)
64
+ samples << [step.verb, now - began]
65
+ check(answer, step.verb)
66
+ end
67
+ end
68
+ Run.new(samples: samples, wall_seconds: now - started)
69
+ end
70
+
71
+ # An empty step is refused without touching the store: the cost of the pipes alone.
72
+ def floor(pipe)
73
+ seconds = Array.new(FLOOR_SAMPLES) do
74
+ began = now
75
+ round_trip(pipe, "{}")
76
+ now - began
77
+ end
78
+ Stats.micros(Stats.percentile(seconds, 0.5))
79
+ end
80
+
81
+ def ask(pipe, line)
82
+ check(round_trip(pipe, line), line[0, 80])
83
+ end
84
+
85
+ def round_trip(pipe, line)
86
+ pipe.write(line, "\n")
87
+ pipe.gets.to_s
88
+ end
89
+
90
+ def check(answer, what)
91
+ return if answer.start_with?('{"ok":true')
92
+
93
+ raise StepRefused, "#{what} was not accepted: #{answer.strip.empty? ? 'the binary closed its output' : answer.strip}"
94
+ end
95
+
96
+ def now
97
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,46 @@
1
+ module Hecks
2
+ module Bench
3
+ # Order statistics over latency samples.
4
+ # Percentiles are nearest-rank, never interpolated, so every figure is a latency that happened.
5
+ module Stats
6
+ module_function
7
+
8
+ def percentile(samples, fraction)
9
+ return nil if samples.empty?
10
+
11
+ sorted = samples.sort
12
+ sorted[[(fraction * sorted.size).ceil - 1, 0].max]
13
+ end
14
+
15
+ def median(values)
16
+ return nil if values.empty?
17
+
18
+ sorted = values.sort
19
+ middle = sorted.size / 2
20
+ sorted.size.odd? ? sorted[middle].to_f : (sorted[middle - 1] + sorted[middle]) / 2.0
21
+ end
22
+
23
+ def latency(seconds)
24
+ {
25
+ count: seconds.size,
26
+ p50_us: micros(percentile(seconds, 0.50)),
27
+ p99_us: micros(percentile(seconds, 0.99)),
28
+ mean_us: micros(seconds.sum / seconds.size),
29
+ max_us: micros(seconds.max)
30
+ }
31
+ end
32
+
33
+ # A ratio well above 1.0 means latency grows as the store fills, which a whole-run p50 hides.
34
+ def drift(seconds)
35
+ tenth = seconds.size / 10
36
+ return nil if tenth.zero?
37
+
38
+ (percentile(seconds.last(tenth), 0.5) / percentile(seconds.first(tenth), 0.5)).round(2)
39
+ end
40
+
41
+ def micros(seconds)
42
+ seconds && (seconds * 1_000_000).round(1)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,98 @@
1
+ module Hecks
2
+ module Bench
3
+ # Runs every requested target against every requested domain and aggregates the runs.
4
+ # Runs are interleaved across targets so unrelated machine load lands on all of them.
5
+ module Suite
6
+ TARGETS = %w[ruby:memory ruby:sqlite ruby:postgres ruby:postgres_era rust].freeze
7
+
8
+ # What to measure; `rust_binary` needs a single domain, since a binary embeds exactly one.
9
+ Config = Struct.new(:domains, :targets, :warmup, :iterations, :runs, :rust_binary, keyword_init: true)
10
+
11
+ module_function
12
+
13
+ def call(config, log: $stderr)
14
+ validate(config)
15
+ load_before = Environment.load_average
16
+ skipped = unavailable_targets(config)
17
+ active = config.targets - skipped.map { |entry| entry[:target] }
18
+ skipped.each { |entry| log.puts "skip #{entry[:target]}: #{entry[:reason]}" }
19
+ results = config.domains.flat_map { |domain| measure_domain(Workload.fetch(domain), active, config, log) }
20
+ environment = Environment.describe
21
+ environment[:load_average] = { before: load_before, after: environment[:load_average] }
22
+ { environment: environment.merge(postgres: postgres_version(active)),
23
+ config: config.to_h, results: results, skipped: skipped }
24
+ end
25
+
26
+ def validate(config)
27
+ unknown = config.targets - TARGETS
28
+ raise ArgumentError, "unknown target #{unknown.first.inspect} — one of #{TARGETS.join(', ')}" if unknown.any?
29
+
30
+ config.domains.each { |domain| Workload.fetch(domain) }
31
+ if config.iterations < 1 || config.runs < 1 || config.warmup.negative?
32
+ raise ArgumentError, "iterations and runs must be at least 1, and warmup at least 0"
33
+ end
34
+ return unless config.rust_binary && config.domains.size > 1
35
+
36
+ raise ArgumentError, "--rust-binary embeds one domain, so it needs exactly one --domain"
37
+ end
38
+
39
+ def unavailable_targets(config)
40
+ postgres = PostgresProbe.unavailable_reason if config.targets.any? { |t| t.start_with?("ruby:postgres") }
41
+ rust = RustRunner.unavailable_reason(binary: config.rust_binary) if config.targets.include?("rust")
42
+ config.targets.filter_map do |target|
43
+ reason = target == "rust" ? rust : (postgres if target.start_with?("ruby:postgres"))
44
+ { target: target, reason: reason } if reason
45
+ end
46
+ end
47
+
48
+ def measure_domain(workload, targets, config, log)
49
+ binaries = {}
50
+ summaries = Hash.new { |hash, target| hash[target] = [] }
51
+ config.runs.times do |index|
52
+ targets.each do |target|
53
+ run = measure_once(workload, target, config, binaries)
54
+ summaries[target] << run.summary
55
+ log.puts progress_line("#{workload.name} #{target}", "run #{index + 1}/#{config.runs}", summaries[target].last)
56
+ end
57
+ end
58
+ targets.map { |target| aggregate(workload.name, target, summaries[target]) }
59
+ end
60
+
61
+ def progress_line(label, position, summary)
62
+ "#{label.ljust(27)} #{position} #{summary[:throughput_per_s].round.to_s.rjust(8)} cmds/s " \
63
+ "p50 #{summary[:p50_us].to_s.rjust(9)} us p99 #{summary[:p99_us].to_s.rjust(9)} us"
64
+ end
65
+
66
+ def measure_once(workload, target, config, binaries)
67
+ if target == "rust"
68
+ binary = config.rust_binary || (binaries[workload.name] ||= RustRunner.build(workload.name))
69
+ RustRunner.call(workload, binary: binary, warmup: config.warmup, iterations: config.iterations)
70
+ else
71
+ RubyRunner.call(workload, adapter: target.delete_prefix("ruby:").to_sym,
72
+ warmup: config.warmup, iterations: config.iterations)
73
+ end
74
+ end
75
+
76
+ def aggregate(domain, target, summaries)
77
+ rates = summaries.map { |summary| summary[:throughput_per_s] }
78
+ median = %i[throughput_per_s p50_us p99_us mean_us max_us drift].to_h do |key|
79
+ [key, Stats.median(summaries.filter_map { |summary| summary[key] })]
80
+ end
81
+ median[:roundtrip_floor_p50_us] = Stats.median(summaries.filter_map { |s| s[:roundtrip_floor_p50_us] })
82
+ median[:by_verb] = median_by_verb(summaries)
83
+ { domain: domain, target: target, runs: summaries, median: median.compact,
84
+ throughput_range: rates.minmax }
85
+ end
86
+
87
+ def median_by_verb(summaries)
88
+ summaries.first[:by_verb].keys.to_h do |verb|
89
+ [verb, %i[p50_us p99_us].to_h { |key| [key, Stats.median(summaries.map { |s| s[:by_verb][verb][key] })] }]
90
+ end
91
+ end
92
+
93
+ def postgres_version(active)
94
+ PostgresProbe.server_version if active.any? { |target| target.start_with?("ruby:postgres") }
95
+ end
96
+ end
97
+ end
98
+ end