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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f6baf48801bf4f341c3a39a05215c2a0ce3f345b22068f84d0e04c91c3c76f6
4
- data.tar.gz: 6eda1623f06f2607400ff7a2b13c10775ed60416c91f36f7d4a36bdb9d6adf39
3
+ metadata.gz: c04beedd84550f06db74d93d28c907eee4a0a913f6dfb619d84a1844faefeb13
4
+ data.tar.gz: 7b3cf71a37bbc83fa80c14eace35831f4b9bceaa789affbda6e2b1158e1ef39e
5
5
  SHA512:
6
- metadata.gz: c8d8670a30de7d7318a84cf6a25f78b394c6974fb42f61697fbe20d0bc5642455740c644fd055cfe7f347d0bed254ab93fd42ce596fc444af6de8bcde86e7291
7
- data.tar.gz: dc3b18d73758d235d369c0158729b8186e8d6a6d8c2c5040d26f772876b43d8ff8eb29a784e166585af1d890bf5620de0fa456934bf6568ba96d1318858e9079
6
+ metadata.gz: fdf38f7f5ece37807699026ff6166408f948a827aa59d4cec712ad48a686a90d932ec49ac083853a141654a50bfbc1ade18a0c3538126821ecb187128966c155
7
+ data.tar.gz: 03ccb78bc4b99292abc6e39ae74b35179254583fb41f735f116da2cacec4709971c8eb36c02fdc0e0ebe13b742b58311fe8e01dd1731e2c40efcaa0a430dcbf4
data/exe/hecks CHANGED
@@ -1,11 +1,56 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # The `hecks` command the gem installs: run, read, check and serve a domain.
4
- # Routes to `Hecks::CLI` (lib/hecks/cli.rb); run with no arguments to list subcommands.
3
+ # Hecks's command line — GENERATED by hecks project_cli. Do not edit.
4
+ #
5
+ # A launcher, not a CLI: the verbs, their arguments and their refusals are
6
+ # projected from the bluebook beside it every time this runs, so a change
7
+ # to the chapter shows up here without re-minting anything.
8
+ #
9
+ # exe/hecks every verb, and every question
10
+ # exe/hecks <verb> --help what it wants, and how it refuses
11
+
12
+ Encoding.default_external = Encoding::UTF_8
13
+ Encoding.default_internal = Encoding::UTF_8
5
14
 
6
- # The gem's own `lib` goes first, as for every `bin/` script, so this also
7
- # runs straight from a checkout.
8
15
  $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
9
- require "hecks/cli"
10
16
 
11
- exit Hecks::CLI.start(ARGV)
17
+ # The names the gem has always shipped keep their positional forms.
18
+ LEGACY = %w[run docs narrate ir stores model_check smoke_test project_diagrams project_cli mcp].freeze
19
+ if LEGACY.include?(ARGV.first)
20
+ require "hecks/cli"
21
+ exit Hecks::CLI.start(ARGV) unless Hecks::CLI.launcher_form?(ARGV)
22
+ end
23
+
24
+ require "hecks"
25
+
26
+ # Usage is answered from the projected chapter alone: no adapter is bound and no
27
+ # database is opened. Only a line that runs a verb boots the domain.
28
+ domain = File.expand_path("../lib/hecks/hecks", __dir__)
29
+ program = "hecks"
30
+ described = begin
31
+ Hecks.describe(domain)
32
+ rescue StandardError => e
33
+ abort "cannot open Hecks: #{e.message.lines.first.strip}"
34
+ end
35
+
36
+ text, status, reason = Hecks::Doors::CliRunner.usage(
37
+ runtime: described, argv: ARGV, program: program
38
+ )
39
+ unless text
40
+ runtime = begin
41
+ Hecks.boot(domain, install_doors: false)
42
+ rescue StandardError => e
43
+ abort "cannot open Hecks: #{e.message.lines.first.strip}"
44
+ end
45
+ text, status, reason = Hecks::Doors::CliRunner.call(
46
+ runtime: runtime, argv: ARGV, program: program
47
+ )
48
+ end
49
+ # A `--wait` run that failed answers its settled record on stdout, so `| jq` reads it
50
+ # either way, and says why on stderr.
51
+ if reason
52
+ puts text
53
+ warn reason
54
+ exit status
55
+ end
56
+ status.zero? ? puts(text) : abort(text)
@@ -24,7 +24,7 @@ module Hecks
24
24
 
25
25
  # Folds the journal into the snapshot and truncates it. Opt-in; never run after a save.
26
26
  #
27
- # This discards the history `entries` returns, which projections and `bin/history`
27
+ # This discards the history `entries` returns, which projections and `hecks history`
28
28
  # read in full, so use it only on an aggregate nothing projects from.
29
29
  #
30
30
  # The snapshot is written durably before the journal is truncated; a crash between
@@ -7,7 +7,7 @@ module Hecks
7
7
  # Runtime::RemoteDispatcher's writes: one Lambda invoke, one JSON round trip.
8
8
  #
9
9
  # The function name defaults to `"hecks-#{domain.downcase}"`, matching
10
- # `bin/project_deploy`'s `stack_name` computation. A domain whose stack
10
+ # `hecks deploy project`'s `stack_name` computation. A domain whose stack
11
11
  # name doesn't follow that pattern must pass `function` explicitly, via
12
12
  # a `.world`'s `persisted_by("Lambda")`/`dispatched_by("Lambda")` block.
13
13
  class Client
@@ -38,7 +38,7 @@ module Hecks
38
38
  function = setting(settings, :function, nil)
39
39
  # `domain` only prefixes the instances lookup; the function to call is named by
40
40
  # DOMAIN_NAME (set in a deployed Lambda, where root is always /var/task), else by
41
- # root's basename, which matches bin/project_deploy's stack name on a local boot.
41
+ # root's basename, which matches hecks deploy project's stack name on a local boot.
42
42
  function_domain = ENV["DOMAIN_NAME"] || (root ? File.basename(root) : domain)
43
43
  @client = Client.new(domain: function_domain, region: region, function: function)
44
44
  @prefix = "#{domain}::#{aggregate.hecks_name}#"
@@ -34,6 +34,7 @@ module Hecks
34
34
  @events = []
35
35
  @entries = []
36
36
  @outbox = []
37
+ @outbox_deliveries = {}
37
38
  end
38
39
 
39
40
  # Looks up the current record for one aggregate identity.
@@ -162,6 +163,7 @@ module Hecks
162
163
  @events = []
163
164
  @entries = []
164
165
  @outbox = []
166
+ @outbox_deliveries = {}
165
167
  self
166
168
  end
167
169
 
@@ -185,10 +187,11 @@ module Hecks
185
187
  # was a duplicate
186
188
  def outbox_enqueue(rows)
187
189
  rows.filter_map do |row|
188
- next nil if @outbox.any? { |held| held.delivery_id == row.delivery_id }
190
+ next nil if @outbox_deliveries.key?(row.delivery_id)
189
191
 
190
192
  row.id = @outbox.size + 1
191
193
  @outbox << row
194
+ @outbox_deliveries[row.delivery_id] = true
192
195
  row
193
196
  end
194
197
  end
@@ -199,7 +202,7 @@ module Hecks
199
202
  # @return [Boolean] true when the row was pending and is now claimed; false when it is
200
203
  # unknown or not pending
201
204
  def outbox_claim(id) # rubocop:disable Naming/PredicateMethod
202
- row = @outbox.find { |held| held.id == id }
205
+ row = outbox_row(id)
203
206
  return false unless row&.pending?
204
207
 
205
208
  row.status = "claimed"
@@ -215,7 +218,7 @@ module Hecks
215
218
  # @param error [String, nil] the failure description, or nil to clear it
216
219
  # @return [Boolean] true when the row exists and was updated; false when no row has `id`
217
220
  def outbox_settle(id, status:, error: nil) # rubocop:disable Naming/PredicateMethod
218
- row = @outbox.find { |held| held.id == id } or return false
221
+ row = outbox_row(id) or return false
219
222
  row.status = status.to_s
220
223
  row.error = error
221
224
  true
@@ -233,6 +236,16 @@ module Hecks
233
236
 
234
237
  private
235
238
 
239
+ # Finds a held outbox row by id in constant time.
240
+ #
241
+ # Ids are assigned as the 1-based position at enqueue and rows are never
242
+ # removed, so the id is the row's index plus one.
243
+ def outbox_row(id)
244
+ return nil unless id.is_a?(Integer) && id >= 1
245
+
246
+ @outbox[id - 1]
247
+ end
248
+
236
249
  # Skips the codec copy and rehydration round trip while judging the
237
250
  # self-hosted grammar (S17, ADR 0026), where re-validating already-valid
238
251
  # entity state costs O(N) per save and O(N^2) across an N-row table.
@@ -38,8 +38,12 @@ module Hecks
38
38
  # The failing call still raises and is never retried: it may have reached the server.
39
39
  # A failed reconnect is swallowed so it does not mask the original error.
40
40
  def reconnect!
41
- @db = self.class.connect_for(@aggregate.name, @settings)
42
- rescue PG::Error
41
+ if @db.respond_to?(:reconnect!)
42
+ @db.reconnect!
43
+ else
44
+ @db = self.class.connect_for(@aggregate.name, @settings)
45
+ end
46
+ rescue PG::Error, Runtime::WiringError
43
47
  nil
44
48
  end
45
49
  end
@@ -0,0 +1,180 @@
1
+ require "monitor"
2
+
3
+ module Hecks
4
+ module Adapters
5
+ # One Postgres connection shared by every `Postgres` or `PostgresEra` adapter of a process
6
+ # that targets the same database and schema, so a domain's connection count does not grow
7
+ # with its aggregates. Each adapter class shares only with its own kind, because each opens
8
+ # its connection its own way.
9
+ #
10
+ # Every call runs under one re-entrant monitor, and a transaction holds it from BEGIN to
11
+ # COMMIT, so a second thread's statements never interleave into another thread's
12
+ # transaction. The owning thread re-enters freely, so a nested `transaction` joins the open
13
+ # one, whichever aggregate's adapter opened it.
14
+ class PostgresSharedConnection
15
+ REGISTRY = {} # rubocop:disable Style/MutableConstant
16
+ REGISTRY_LOCK = Mutex.new
17
+ private_constant :REGISTRY, :REGISTRY_LOCK
18
+
19
+ # Runs in a forked child right after the fork, before any of its code.
20
+ #
21
+ # A child inherits its parent's sockets, and libpq sends a Terminate on the socket when
22
+ # the child's copy of the connection is finalized at exit, which would end the parent's
23
+ # session. The child's copy of each socket fd is pointed at the null device, so that
24
+ # Terminate goes nowhere, and the child forgets the handles without `finish`.
25
+ #
26
+ # @return [void]
27
+ def self.disown_inherited!
28
+ REGISTRY.each_value(&:disown!)
29
+ REGISTRY.clear
30
+ end
31
+
32
+ # Prepended to `Process` so every `fork`, `Process.fork` and `Kernel#fork` disowns the
33
+ # shared connections in the child.
34
+ module ForkGuard
35
+ # @return [Integer, nil] the child's pid in the parent, nil in the child
36
+ def _fork
37
+ pid = super
38
+ PostgresSharedConnection.disown_inherited! if pid.zero?
39
+ pid
40
+ end
41
+ end
42
+ Process.singleton_class.prepend(ForkGuard)
43
+
44
+ # Returns the process's shared connection for a database and schema, opening it on first use.
45
+ #
46
+ # The key includes the pid, so a forked child opens its own instead of sharing its
47
+ # parent's socket.
48
+ #
49
+ # @param name [String] the domain or aggregate name, used only in refusal messages
50
+ # @param settings [Hash{Symbol, String => Object}] `database` and optional `schema`
51
+ # @param connector [#connect_for] the adapter class that opens the connection
52
+ # @return [PostgresSharedConnection] the shared handle, connected
53
+ # @raise [Runtime::WiringError] if `database` is missing or the connection is refused
54
+ def self.for(name, settings, connector:)
55
+ key = [Process.pid, connector, setting(settings, :database), setting(settings, :schema)]
56
+ REGISTRY_LOCK.synchronize do
57
+ handle = REGISTRY[key] ||= new(name, settings, connector: connector)
58
+ handle.reconnect! if handle.dead?
59
+ handle
60
+ end
61
+ end
62
+
63
+ # Reads one setting under its Symbol or String spelling as a string.
64
+ #
65
+ # @param settings [Hash{Symbol, String => Object}] the binding's settings
66
+ # @param key [Symbol] the setting's name
67
+ # @return [String] the value, empty when absent
68
+ def self.setting(settings, key)
69
+ (settings.key?(key) ? settings[key] : settings[key.to_s]).to_s
70
+ end
71
+ private_class_method :setting
72
+
73
+ # Counts the shared connections this process holds open.
74
+ #
75
+ # @return [Integer] how many database and schema pairs have a live shared connection
76
+ def self.open_count = REGISTRY_LOCK.synchronize { REGISTRY.values.count { |handle| !handle.dead? } }
77
+
78
+ # Closes every shared connection and forgets it; the next `for` opens a fresh one.
79
+ #
80
+ # @return [void]
81
+ def self.close_all!
82
+ REGISTRY_LOCK.synchronize do
83
+ REGISTRY.each_value(&:close)
84
+ REGISTRY.clear
85
+ end
86
+ end
87
+
88
+ # @param name [String] the domain or aggregate name, used only in refusal messages
89
+ # @param settings [Hash{Symbol, String => Object}] `database` and optional `schema`
90
+ # @param connector [#connect_for] the adapter class that opens the connection
91
+ # @raise [Runtime::WiringError] if `database` is missing or the connection is refused
92
+ def initialize(name, settings, connector:)
93
+ @name = name
94
+ @settings = settings
95
+ @connector = connector
96
+ @monitor = Monitor.new
97
+ @conn = connector.connect_for(name, settings)
98
+ end
99
+
100
+ # Runs one parameterless statement under the connection's monitor.
101
+ #
102
+ # @param sql [String] the statement
103
+ # @return [PG::Result] the statement's result
104
+ def exec(sql, &) = @monitor.synchronize { @conn.exec(sql, &) }
105
+
106
+ # Runs one statement with bind parameters under the connection's monitor.
107
+ #
108
+ # @param sql [String] the statement, with `$1`-style placeholders
109
+ # @param binds [Array<Object>] one value per placeholder
110
+ # @return [PG::Result] the statement's result
111
+ def exec_params(sql, binds = [], *rest, &) = @monitor.synchronize { @conn.exec_params(sql, binds, *rest, &) }
112
+
113
+ # Reports the server-side transaction state; blocks while another thread's transaction
114
+ # holds the connection, so a caller never mistakes it for its own.
115
+ #
116
+ # @return [Integer] a `PG::PQTRANS_*` constant
117
+ def transaction_status = @monitor.synchronize { @conn.transaction_status }
118
+
119
+ # Runs the block in one transaction, joining one the calling thread already holds.
120
+ #
121
+ # @yield the writes to commit together; an exception rolls the outermost one back
122
+ # @return [Object] the block's own value
123
+ def transaction(&block)
124
+ @monitor.synchronize do
125
+ next yield if @conn.transaction_status != PG::PQTRANS_IDLE
126
+
127
+ @conn.transaction(&block)
128
+ end
129
+ end
130
+
131
+ # Reports the server process id serving the connection.
132
+ #
133
+ # @return [Integer] the backend pid
134
+ def backend_pid = @monitor.synchronize { @conn.backend_pid }
135
+
136
+ # Quotes an identifier; needs no connection round trip.
137
+ #
138
+ # @param name [String, Symbol] the identifier
139
+ # @return [String] the double-quoted identifier
140
+ def quote_ident(name) = PG::Connection.quote_ident(name.to_s)
141
+
142
+ # Reports whether the underlying socket is closed or broken.
143
+ #
144
+ # @return [Boolean] true when the connection cannot run statements
145
+ def dead? = @monitor.synchronize { @conn.finished? || @conn.status != PG::CONNECTION_OK }
146
+
147
+ # Replaces the connection when it is dead; a no-op when another caller already did.
148
+ #
149
+ # @return [void]
150
+ # @raise [Runtime::WiringError] if the new connection is refused
151
+ def reconnect!
152
+ @monitor.synchronize do
153
+ next unless dead?
154
+
155
+ @conn.close unless @conn.finished?
156
+ @conn = @connector.connect_for(@name, @settings)
157
+ end
158
+ end
159
+
160
+ # Detaches this handle's socket from the parent's session in a forked child: the child's
161
+ # fd is repointed at the null device, so nothing the child's copy of the connection writes
162
+ # (statements, Terminate) reaches the parent's server session. Takes no lock, since the
163
+ # child holds none of the parent's monitors.
164
+ #
165
+ # @return [void]
166
+ def disown!
167
+ return if @conn.finished?
168
+
169
+ IO.for_fd(@conn.socket, autoclose: false).reopen(File::NULL, "r+")
170
+ rescue StandardError
171
+ nil
172
+ end
173
+
174
+ # Closes the underlying connection.
175
+ #
176
+ # @return [void]
177
+ def close = @monitor.synchronize { @conn.close unless @conn.finished? }
178
+ end
179
+ end
180
+ end
@@ -11,6 +11,7 @@ require_relative "../../runtime/errors"
11
11
  require_relative "../../runtime/event"
12
12
  require_relative "postgres/outbox"
13
13
  require_relative "postgres/reconnect"
14
+ require_relative "postgres/shared_connection"
14
15
  require_relative "../../runtime/instance"
15
16
 
16
17
  module Hecks
@@ -79,7 +80,8 @@ module Hecks
79
80
  "cannot bind Postgres at #{declared} for #{name}: #{e.message.strip}"
80
81
  end
81
82
 
82
- # Connects and creates the aggregate, journal, event, saga and outbox tables if absent.
83
+ # Joins the process's shared connection for the declared database and schema, then
84
+ # creates the aggregate, journal, event, saga and outbox tables if absent.
83
85
  #
84
86
  # @param aggregate [Bluebook::Aggregate] the aggregate whose table this adapter owns
85
87
  # @param settings [Hash{Symbol, String => Object}] world settings for the binding:
@@ -93,7 +95,7 @@ module Hecks
93
95
  def initialize(aggregate:, settings: {}, root: nil)
94
96
  @aggregate = aggregate
95
97
  @settings = settings
96
- @db = self.class.connect_for(aggregate.name, settings)
98
+ @db = PostgresSharedConnection.for(aggregate.name, settings, connector: self.class)
97
99
  # Scopes saga rows; falls back to the aggregate's storage name when
98
100
  # settings gives no domain (e.g. a directly-instantiated adapter).
99
101
  @domain = (
@@ -1,3 +1,4 @@
1
+ require "monitor"
1
2
  require "prism"
2
3
  require_relative "../../bluebook/expression/canonical_form"
3
4
 
@@ -14,6 +15,16 @@ module Hecks
14
15
  # rubocop:disable-next Style/MutableConstant
15
16
  TREES = {}
16
17
 
18
+ # The first block node starting on each line, per parsed tree (by identity), built by one
19
+ # walk of the tree the first time a block in its file is looked up. Dropped with its tree
20
+ # by #forget/#forget_all.
21
+ BLOCKS_BY_LINE = {}.compare_by_identity
22
+
23
+ # Guards `TREES` and `BLOCKS_BY_LINE` together, so a parse or an index build never
24
+ # interleaves with a `forget` that drops the same tree. Reentrant: `block_node_at`
25
+ # calls `tree_for` under it.
26
+ LOCK = Monitor.new
27
+
17
28
  module_function
18
29
 
19
30
  # Recovers a block's body as canonical source text.
@@ -47,14 +58,25 @@ module Hecks
47
58
  # @return [Prism::BlockNode, nil] the first block node found starting on `line`, or nil
48
59
  # if none does
49
60
  def block_node_at(file, line)
50
- found = nil
51
- walk(tree_for(file)) do |node|
61
+ LOCK.synchronize do
62
+ tree = tree_for(file)
63
+ BLOCKS_BY_LINE[tree] ||= blocks_by_line(tree)
64
+ BLOCKS_BY_LINE[tree][line]
65
+ end
66
+ end
67
+
68
+ # Indexes a tree's block nodes by the line they start on, keeping the first in walk order.
69
+ #
70
+ # @param tree [Prism::ProgramNode] a parsed file's root node
71
+ # @return [Hash{Integer => Prism::BlockNode}] block node by 1-based start line
72
+ def blocks_by_line(tree)
73
+ index = {}
74
+ walk(tree) do |node|
52
75
  next unless node.is_a?(::Prism::BlockNode)
53
- next unless node.location.start_line == line
54
76
 
55
- found ||= node
77
+ index[node.location.start_line] ||= node
56
78
  end
57
- found
79
+ index
58
80
  end
59
81
 
60
82
  # Parses a file with `prism`, caching the result for the life of the process.
@@ -65,7 +87,7 @@ module Hecks
65
87
  # ::Prism.parse_file reads the file at the C-extension level, bypassing
66
88
  # Ruby's File/IO layer — invisible to tools that virtualize the filesystem
67
89
  # there (e.g. tebako's memfs). Read through File.read first so they intercept it.
68
- TREES[file] ||= ::Prism.parse(File.read(file)).value
90
+ LOCK.synchronize { TREES[file] ||= ::Prism.parse(File.read(file)).value }
69
91
  end
70
92
 
71
93
  # Drops one file from the process-wide parse cache, for a caller (`Hecks::Codemod`)
@@ -74,12 +96,23 @@ module Hecks
74
96
  # @param file [String] the file path to drop from the cache
75
97
  # @return [Prism::ProgramNode, nil] the cached tree that was removed, or nil if
76
98
  # nothing was cached for `file`
77
- def forget(file) = TREES.delete(file)
99
+ def forget(file)
100
+ LOCK.synchronize do
101
+ tree = TREES.delete(file)
102
+ BLOCKS_BY_LINE.delete(tree)
103
+ tree
104
+ end
105
+ end
78
106
 
79
107
  # Drops every cached parse tree.
80
108
  #
81
109
  # @return [Hash] the now-empty cache
82
- def forget_all = TREES.clear
110
+ def forget_all
111
+ LOCK.synchronize do
112
+ BLOCKS_BY_LINE.clear
113
+ TREES.clear
114
+ end
115
+ end
83
116
 
84
117
  # Visits `node` and every descendant depth-first, calling `visit` on each.
85
118
  #
@@ -5,8 +5,21 @@ require "fileutils"
5
5
  module Hecks
6
6
  module Adapters
7
7
  # The imperative half of tenant provisioning, behind `Deploy::Tenant.port
8
- # "TenantProvisioning"`; booting the domain and its capability gate stay in bin/project_tenant.
8
+ # "TenantProvisioning"`, for `Tenant.Provision` and for hecks deploy provision; booting the
9
+ # domain and its capability gate stay in hecks deploy provision.
9
10
  class TenantProvisioner
11
+ # Raised when a value is outside its allow-list or the overlay path leaves `environments/`.
12
+ class Refused < ArgumentError; end
13
+
14
+ # Each argument's allow-list. `\A...\z` anchors: `$` would let a newline end the value early.
15
+ IDENTIFIER = /\A[a-z][a-z0-9_]*\z/
16
+ LABEL = /\A[A-Za-z0-9][A-Za-z0-9_ .:-]*\z/
17
+ CONSTANT = /\A[A-Z][A-Za-z0-9]*\z/
18
+ CONNECTION = /\A[^\x00-\x20\x7f]+\z/
19
+
20
+ RULES = { slug: IDENTIFIER, schema: IDENTIFIER, domain: LABEL, realm: LABEL,
21
+ database: CONNECTION, adapter: CONSTANT }.freeze
22
+
10
23
  # Accepts the arguments every driven adapter is built with and keeps none of them.
11
24
  #
12
25
  # @param aggregate [Object, nil] unused
@@ -14,30 +27,74 @@ module Hecks
14
27
  # @param root [String, nil] unused
15
28
  def initialize(aggregate: nil, settings: {}, root: nil); end
16
29
 
17
- # Writes the tenant's environment overlay file under its domain directory.
30
+ # Writes the tenant's environment overlay under `<directory>/environments/`.
18
31
  #
19
- # `directory:` must be passed explicitly — a domain's declared name is not a
20
- # filesystem path. Each argument arrives either bare or wrapped as `{ value: }`.
32
+ # Each argument arrives bare or wrapped as `{ value: }`; the record's other fields are
33
+ # ignored. Values are allow-listed and written as Ruby string literals.
21
34
  #
22
35
  # @param slug [String, Hash] names the overlay file
23
- # @param directory [String, Hash] the domain's own directory on disk
24
- # @return [Hash{Symbol => Object}] `:slug`, `:domain`, `:realm` and `:schema`, as given
25
- def provision(slug:, domain:, realm:, schema:, database:, adapter:, directory:)
26
- domain_directory = File.expand_path(directory[:value] || directory)
36
+ # @param directory [String, Hash] the domain's own directory on disk (not its declared name)
37
+ # @param adapter [String, Hash, nil] the adapter the overlay binds; PostgresEra when none
38
+ # @return [Hash{Symbol => Object}] `:slug`, `:domain`, `:realm`, `:schema` and `:output`
39
+ # @raise [Refused] when a value is outside its allow-list or the path leaves `environments/`
40
+ def write_overlay(slug:, domain:, realm:, schema:, database:, directory:, adapter: nil, **)
41
+ values = { slug: unwrap(slug), domain: unwrap(domain), realm: unwrap(realm),
42
+ schema: unwrap(schema), database: unwrap(database),
43
+ adapter: adapter_name(adapter) }
44
+ values.each { |name, value| check(name, value) }
27
45
 
28
- overlay_path = File.join(domain_directory, "environments", "#{slug[:value] || slug}.world")
46
+ overlay_path = overlay_path_for(unwrap(directory), values[:slug])
47
+ refuse_missing_directory(unwrap(directory))
29
48
  FileUtils.mkdir_p(File.dirname(overlay_path))
30
- File.write(overlay_path, <<~WORLD)
31
- Hecks.world "#{domain[:value] || domain}" do
32
- realm "#{realm[:value] || realm}"
33
- persisted_by("#{adapter[:value] || adapter}") do
34
- database "#{database[:value] || database}"
35
- schema "#{schema[:value] || schema}"
49
+ File.write(overlay_path, render(values))
50
+
51
+ { slug: slug, domain: domain, realm: realm, schema: schema,
52
+ output: { value: "wrote #{overlay_path}\n" } }
53
+ end
54
+
55
+ private
56
+
57
+ # A tenant overlay belongs to a domain that exists: a missing directory is a mistyped path,
58
+ # not a place to create.
59
+ def refuse_missing_directory(directory)
60
+ return if File.directory?(directory.to_s)
61
+
62
+ raise Refused, "directory #{directory.to_s.inspect} is not an existing domain directory"
63
+ end
64
+
65
+ def render(values)
66
+ <<~WORLD
67
+ Hecks.world #{values[:domain].inspect} do
68
+ realm #{values[:realm].inspect}
69
+ persisted_by(#{values[:adapter].inspect}) do
70
+ database #{values[:database].inspect}
71
+ schema #{values[:schema].inspect}
36
72
  end
37
73
  end
38
74
  WORLD
75
+ end
76
+
77
+ def unwrap(argument) = argument.is_a?(Hash) ? argument[:value] : argument
78
+
79
+ def check(name, value)
80
+ return if value.is_a?(String) && RULES.fetch(name).match?(value)
81
+
82
+ raise Refused, "#{name} #{value.inspect} is not an accepted #{name}"
83
+ end
84
+
85
+ def overlay_path_for(directory, slug)
86
+ raise Refused, "directory is required" unless directory.is_a?(String) && !directory.empty?
87
+
88
+ environments = File.join(File.expand_path(directory), "environments")
89
+ path = File.expand_path("#{slug}.world", environments)
90
+ return path if path.start_with?("#{environments}#{File::SEPARATOR}")
91
+
92
+ raise Refused, "overlay path #{path} is outside #{environments}"
93
+ end
39
94
 
40
- { slug: slug, domain: domain, realm: realm, schema: schema }
95
+ def adapter_name(adapter)
96
+ name = unwrap(adapter)
97
+ name.nil? || name.to_s.empty? ? "PostgresEra" : name
41
98
  end
42
99
  end
43
100
  end
@@ -81,7 +81,7 @@ module Hecks
81
81
  # Memory-only-guarded runtime
82
82
  # @raise [Malformed] if any aggregate is bound to a non-Memory adapter
83
83
  def boot_and_guard(files)
84
- runtime = Hecks::Runtime::Loader.boot_files(files, install_facade: false)
84
+ runtime = Hecks::Runtime::Loader.boot_files(files, install_doors: false)
85
85
  guard_memory_only!(runtime)
86
86
  runtime
87
87
  end
@@ -2,7 +2,7 @@ require_relative "behaviors/dsl"
2
2
  require_relative "behaviors/expectations"
3
3
  require_relative "behaviors/runner"
4
4
 
5
- # The `.behaviors` toolkit driven by `bin/behaviors` and `hecks/behaviors/rspec`.
5
+ # The `.behaviors` toolkit driven by `hecks run_behaviors` and `hecks/behaviors/rspec`.
6
6
  # Opt-in: not required by lib/hecks.rb, like `Fuzzing`.
7
7
  # lib/hecks/fuzzing.rb, the shape this file mirrors) is opt-in too.
8
8
  module Hecks