hecks 2.9.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (600) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +51 -6
  3. data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
  4. data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
  5. data/lib/hecks/adapters/driven/lambda.rb +1 -1
  6. data/lib/hecks/adapters/driven/memory.rb +16 -3
  7. data/lib/hecks/adapters/driven/postgres/reconnect.rb +6 -2
  8. data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
  9. data/lib/hecks/adapters/driven/postgres.rb +4 -2
  10. data/lib/hecks/adapters/driven/prism.rb +41 -8
  11. data/lib/hecks/adapters/driven/tenant_provisioner.rb +73 -16
  12. data/lib/hecks/behaviors/expectations.rb +1 -1
  13. data/lib/hecks/behaviors.rb +1 -1
  14. data/lib/hecks/bench/cli.rb +62 -0
  15. data/lib/hecks/bench/environment.rb +71 -0
  16. data/lib/hecks/bench/postgres_probe.rb +30 -0
  17. data/lib/hecks/bench/report.rb +76 -0
  18. data/lib/hecks/bench/ruby_runner.rb +93 -0
  19. data/lib/hecks/bench/run.rb +55 -0
  20. data/lib/hecks/bench/rust_runner.rb +101 -0
  21. data/lib/hecks/bench/stats.rb +46 -0
  22. data/lib/hecks/bench/suite.rb +98 -0
  23. data/lib/hecks/bench/workload.rb +89 -0
  24. data/lib/hecks/bench.rb +17 -0
  25. data/lib/hecks/bluebook/assembly/contracts.rb +2 -0
  26. data/lib/hecks/bluebook/attribute.rb +1 -1
  27. data/lib/hecks/bluebook/behaviour/aggregate.rb +30 -1
  28. data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -0
  29. data/lib/hecks/bluebook/chapter.rb +6 -2
  30. data/lib/hecks/bluebook/domain_port.rb +36 -5
  31. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +10 -0
  32. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +8 -0
  33. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -1
  34. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +31 -3
  35. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +122 -3
  36. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +1 -1
  37. data/lib/hecks/bluebook/dsl/query_builder.rb +27 -1
  38. data/lib/hecks/bluebook/dsl/rule_reference.rb +1 -1
  39. data/lib/hecks/bluebook/dsl/world_builder.rb +29 -2
  40. data/lib/hecks/bluebook/expression/canonical_form.rb +1 -1
  41. data/lib/hecks/bluebook/expression/resolver.rb +3 -2
  42. data/lib/hecks/bluebook/hexagon.rb +13 -5
  43. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -0
  44. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +4 -1
  45. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
  46. data/lib/hecks/bluebook/meta_validator.rb +9 -4
  47. data/lib/hecks/bluebook/model_check.rb +23 -0
  48. data/lib/hecks/bluebook/pattern_subset.rb +38 -0
  49. data/lib/hecks/bluebook/query.rb +20 -5
  50. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  51. data/lib/hecks/canonical_json.rb +30 -0
  52. data/lib/hecks/chapters.rb +102 -0
  53. data/lib/hecks/cli/behaviors.rb +61 -0
  54. data/lib/hecks/cli/canonicalise.rb +25 -0
  55. data/lib/hecks/cli/check_engine_agreement.rb +32 -0
  56. data/lib/hecks/cli/check_era.rb +88 -0
  57. data/lib/hecks/cli/console.rb +66 -0
  58. data/lib/hecks/cli/deploy_template_diff.rb +40 -0
  59. data/lib/hecks/cli/doc_coverage.rb +32 -0
  60. data/lib/hecks/cli/document.rb +2 -2
  61. data/lib/hecks/cli/expression_projection.rb +41 -0
  62. data/lib/hecks/cli/follow.rb +88 -0
  63. data/lib/hecks/cli/history.rb +44 -0
  64. data/lib/hecks/cli/ir.rb +1 -1
  65. data/lib/hecks/cli/mcp.rb +3 -3
  66. data/lib/hecks/cli/model_check.rb +7 -4
  67. data/lib/hecks/cli/pattern_cases.rb +47 -0
  68. data/lib/hecks/cli/present.rb +39 -0
  69. data/lib/hecks/cli/project_cli.rb +224 -32
  70. data/lib/hecks/cli/project_diagrams.rb +4 -3
  71. data/lib/hecks/cli/project_glossary.rb +44 -0
  72. data/lib/hecks/cli/reference.rb +46 -0
  73. data/lib/hecks/cli/refresh_projections.rb +44 -0
  74. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
  75. data/lib/hecks/cli/release.rb +70 -0
  76. data/lib/hecks/cli/release_gem.rb +59 -0
  77. data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
  78. data/lib/hecks/cli/rspec_shard_files.rb +66 -0
  79. data/lib/hecks/cli/run.rb +3 -3
  80. data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
  81. data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
  82. data/lib/hecks/cli/shape.rb +70 -0
  83. data/lib/hecks/cli/smoke_http.rb +230 -0
  84. data/lib/hecks/cli/smoke_test.rb +5 -3
  85. data/lib/hecks/cli/spec_example.rb +27 -0
  86. data/lib/hecks/cli/statements.rb +32 -0
  87. data/lib/hecks/cli/stores.rb +4 -5
  88. data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
  89. data/lib/hecks/cli.rb +201 -52
  90. data/lib/hecks/codemod.rb +324 -0
  91. data/lib/hecks/corpus.rb +423 -0
  92. data/lib/hecks/deploy/bluebook/deploy.bluebook +591 -23
  93. data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -4
  94. data/lib/hecks/deploy/oidc.json +82 -2
  95. data/lib/hecks/doc/reference.rb +351 -0
  96. data/lib/hecks/doors/cli_door.rb +196 -0
  97. data/lib/hecks/doors/cli_runner.rb +297 -0
  98. data/lib/hecks/{facade → doors}/command_request.rb +1 -1
  99. data/lib/hecks/{facade → doors}/handle.rb +2 -2
  100. data/lib/hecks/{facade → doors}/json_door.rb +4 -4
  101. data/lib/hecks/doors/launcher_options.rb +129 -0
  102. data/lib/hecks/{cli → doors}/mcp_door.rb +7 -7
  103. data/lib/hecks/doors/mcp_door_scope.rb +97 -0
  104. data/lib/hecks/{facade/surface → doors/ruby_door}/aggregate_door.rb +8 -3
  105. data/lib/hecks/{facade/surface → doors/ruby_door}/chapter.rb +3 -3
  106. data/lib/hecks/{facade/surface.rb → doors/ruby_door.rb} +36 -5
  107. data/lib/hecks/doors.rb +61 -0
  108. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +1 -1
  109. data/lib/hecks/embryonaut_bluebook.rb +1 -1
  110. data/lib/hecks/engine_agreement.rb +156 -0
  111. data/lib/hecks/facade.rb +24 -37
  112. data/lib/hecks/forms/app.rb +6 -6
  113. data/lib/hecks/forms/banking_presentation.rb +50 -0
  114. data/lib/hecks/forms/field_shape.rb +2 -1
  115. data/lib/hecks/forms/port_argument.rb +1 -1
  116. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
  117. data/lib/hecks/fuzzing/combination_miner.rb +86 -0
  118. data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
  119. data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
  120. data/lib/hecks/fuzzing/differential.rb +156 -0
  121. data/lib/hecks/fuzzing/domain_generator.rb +615 -0
  122. data/lib/hecks/fuzzing/era_boundary.rb +71 -0
  123. data/lib/hecks/fuzzing/form_census.rb +136 -0
  124. data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
  125. data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
  126. data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
  127. data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
  128. data/lib/hecks/fuzzing/outside_world.rb +45 -0
  129. data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
  130. data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
  131. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
  132. data/lib/hecks/fuzzing/properties/guards.rb +190 -0
  133. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
  134. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
  135. data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
  136. data/lib/hecks/fuzzing/properties/querying.rb +175 -0
  137. data/lib/hecks/fuzzing/properties.rb +162 -0
  138. data/lib/hecks/fuzzing/qa_settings.rb +123 -0
  139. data/lib/hecks/fuzzing/replay.rb +505 -0
  140. data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
  141. data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
  142. data/lib/hecks/fuzzing/self_consistency.rb +339 -0
  143. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
  144. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
  145. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
  146. data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
  147. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
  148. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
  149. data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
  150. data/lib/hecks/fuzzing/shrinker.rb +166 -0
  151. data/lib/hecks/fuzzing/structural_skips.rb +33 -0
  152. data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
  153. data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
  154. data/lib/hecks/fuzzing/value_generator.rb +216 -0
  155. data/lib/hecks/fuzzing.rb +23 -0
  156. data/lib/hecks/grammar/evolve.rb +336 -0
  157. data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
  158. data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
  159. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
  160. data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
  161. data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
  162. data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
  163. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
  164. data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
  165. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
  166. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
  167. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
  168. data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
  169. data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
  170. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
  171. data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
  172. data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
  173. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
  174. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
  175. data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
  176. data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
  177. data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
  178. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
  179. data/lib/hecks/hecks/adapters/git.adapter +7 -0
  180. data/lib/hecks/hecks/adapters/git.rb +102 -0
  181. data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
  182. data/lib/hecks/hecks/adapters/host_http.rb +65 -0
  183. data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
  184. data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
  185. data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
  186. data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
  187. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
  188. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
  189. data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
  190. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
  191. data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
  192. data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
  193. data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
  194. data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
  195. data/lib/hecks/hecks/adapters/local_files.rb +46 -0
  196. data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
  197. data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
  198. data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
  199. data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
  200. data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
  201. data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
  202. data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
  203. data/lib/hecks/hecks/adapters/shell.adapter +7 -0
  204. data/lib/hecks/hecks/adapters/shell.rb +49 -0
  205. data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
  206. data/lib/hecks/hecks/adapters/terminal.rb +74 -0
  207. data/lib/hecks/hecks/codebase.behaviors +172 -0
  208. data/lib/hecks/hecks/codebase.bluebook +2453 -0
  209. data/lib/hecks/hecks/context_map.hecksagon +13 -0
  210. data/lib/hecks/hecks/custodian.behaviors +179 -0
  211. data/lib/hecks/hecks/custodian.bluebook +1847 -0
  212. data/lib/hecks/hecks/deploy.behaviors +74 -0
  213. data/lib/hecks/hecks/deploy.world +12 -0
  214. data/lib/hecks/hecks/environments/memory.world +10 -0
  215. data/lib/hecks/hecks/governance.world +7 -0
  216. data/lib/hecks/hecks/hecks.behaviors +62 -0
  217. data/lib/hecks/hecks/hecks.bluebook +124 -0
  218. data/lib/hecks/hecks/hecks.hecksagon +390 -0
  219. data/lib/hecks/hecks/hecks.world +42 -0
  220. data/lib/hecks/kernel_capabilities.rb +194 -0
  221. data/lib/hecks/language/bluebook/bluebook.bluebook +11 -0
  222. data/lib/hecks/language/bluebook/query.bluebook +7 -0
  223. data/lib/hecks/language/bluebook/vocabulary.bluebook +4 -4
  224. data/lib/hecks/language/hecksagon/domain_port.bluebook +10 -0
  225. data/lib/hecks/language/hecksagon/hecksagon.bluebook +20 -0
  226. data/lib/hecks/mcp_stdio_guard.rb +2 -2
  227. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
  228. data/lib/hecks/persistence_legacy_fixture.rb +113 -0
  229. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +8 -5
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +4 -1
  231. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +15 -2
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +1 -1
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +1 -1
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +32 -15
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +6 -3
  237. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
  238. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +1 -1
  239. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +43 -24
  240. data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
  241. data/lib/hecks/ports/persistence/state_codec.rb +24 -5
  242. data/lib/hecks/projection_files.rb +285 -0
  243. data/lib/hecks/projections/bootstrap_table.rb +1 -1
  244. data/lib/hecks/projections/deploy/fargate.rb +5 -5
  245. data/lib/hecks/projections/deploy/lambda.rb +43 -32
  246. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  247. data/lib/hecks/projections/deploy/preview/template.rb +1 -1
  248. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
  249. data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
  250. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
  251. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
  252. data/lib/hecks/projections/deploy/shared.rb +1 -1
  253. data/lib/hecks/projections/diagrams.rb +3 -3
  254. data/lib/hecks/projections/model/deviations.rb +1 -1
  255. data/lib/hecks/projections/parser_table.rb +4 -4
  256. data/lib/hecks/projections/reference.rb +2 -3
  257. data/lib/hecks/projections/rust_vocabulary.rb +1 -1
  258. data/lib/hecks/projector/cli_projector.rb +106 -37
  259. data/lib/hecks/projector.rb +1 -1
  260. data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
  261. data/lib/hecks/quality_control/adapters/agent.rb +58 -0
  262. data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
  263. data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
  264. data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
  265. data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
  266. data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
  267. data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
  268. data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
  269. data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
  270. data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
  271. data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
  272. data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
  273. data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
  274. data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
  275. data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
  276. data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
  277. data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
  278. data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
  279. data/lib/hecks/quality_control/cli/child.rb +45 -0
  280. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
  281. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
  282. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
  283. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
  284. data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
  285. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
  286. data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
  287. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
  288. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
  289. data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
  290. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
  291. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
  292. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
  293. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
  294. data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
  295. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
  296. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
  297. data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
  298. data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
  299. data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
  300. data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
  301. data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
  302. data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
  303. data/lib/hecks/query_ir.rb +346 -0
  304. data/lib/hecks/query_ir_mcp.rb +198 -0
  305. data/lib/hecks/release/runner/ci_publisher.rb +2 -2
  306. data/lib/hecks/release/runner/clean_tree.rb +42 -0
  307. data/lib/hecks/release/runner/console.rb +6 -0
  308. data/lib/hecks/release/runner/gem_publisher.rb +14 -7
  309. data/lib/hecks/release/runner/npm_publisher.rb +6 -36
  310. data/lib/hecks/release/runner/preflight.rb +26 -2
  311. data/lib/hecks/release/runner/published.rb +8 -26
  312. data/lib/hecks/release/runner/tagger.rb +30 -15
  313. data/lib/hecks/release/runner.rb +57 -6
  314. data/lib/hecks/router/namespace_installer.rb +3 -3
  315. data/lib/hecks/runtime/adapter_lookup.rb +124 -0
  316. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +4 -0
  317. data/lib/hecks/runtime/dependency_planning.rb +20 -2
  318. data/lib/hecks/runtime/dispatcher.rb +53 -3
  319. data/lib/hecks/runtime/interpreting.rb +3 -0
  320. data/lib/hecks/runtime/loader.rb +65 -12
  321. data/lib/hecks/runtime/outbox.rb +1 -1
  322. data/lib/hecks/runtime/policy_interpreter.rb +5 -2
  323. data/lib/hecks/runtime/port_operation_interpreter.rb +2 -10
  324. data/lib/hecks/runtime/query_interpreter.rb +77 -0
  325. data/lib/hecks/runtime/reaction_outcome.rb +89 -0
  326. data/lib/hecks/runtime/refusal_wording.rb +23 -1
  327. data/lib/hecks/runtime/registry/verification.rb +163 -4
  328. data/lib/hecks/runtime/registry.rb +57 -3
  329. data/lib/hecks/runtime/remote_dispatcher.rb +31 -2
  330. data/lib/hecks/runtime/value/coercion.rb +23 -135
  331. data/lib/hecks/runtime/value/field_checks.rb +172 -0
  332. data/lib/hecks/runtime/value.rb +4 -1
  333. data/lib/hecks/runtime.rb +20 -6
  334. data/lib/hecks/rust_build/conformance.rb +158 -0
  335. data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
  336. data/lib/hecks/rust_build/coverage.rb +218 -0
  337. data/lib/hecks/rust_build/native_build.rb +124 -0
  338. data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
  339. data/lib/hecks/rust_build/project_rust.rb +332 -0
  340. data/lib/hecks/rust_build/wasm.rb +105 -0
  341. data/lib/hecks/rust_build/wasm_browser.rb +113 -0
  342. data/lib/hecks/rust_build.rb +144 -0
  343. data/lib/hecks/storehouse.rb +16 -16
  344. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +2 -2
  345. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +1 -1
  346. data/lib/hecks/three_zero/forms.yml +127 -0
  347. data/lib/hecks/three_zero.rb +12 -0
  348. data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
  349. data/lib/hecks/tools/comment_style.rb +1070 -0
  350. data/lib/hecks/tools/corpus_report.rb +87 -0
  351. data/lib/hecks/tools/deploy_recipe.rb +255 -0
  352. data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
  353. data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
  354. data/lib/hecks/tools/era_projection_backfill.rb +94 -0
  355. data/lib/hecks/tools/era_reattest.rb +146 -0
  356. data/lib/hecks/tools/evolve_run.rb +189 -0
  357. data/lib/hecks/tools/fuzz_sweep.rb +373 -0
  358. data/lib/hecks/tools/heki_compaction.rb +100 -0
  359. data/lib/hecks/tools/hoist_local_givens.rb +246 -0
  360. data/lib/hecks/tools/journal_compaction.rb +89 -0
  361. data/lib/hecks/tools/oidc_manifests.rb +70 -0
  362. data/lib/hecks/tools/query_ir_run.rb +81 -0
  363. data/lib/hecks/tools/regeneration_run.rb +166 -0
  364. data/lib/hecks/tools/rust_comment_style.rb +642 -0
  365. data/lib/hecks/tools/sequence_script.rb +62 -0
  366. data/lib/hecks/tools/tail_merge.rb +117 -0
  367. data/lib/hecks/tools/tenant_provisioning.rb +142 -0
  368. data/lib/hecks/tools/translation_audit.rb +208 -0
  369. data/lib/hecks/tools/translation_scaffold.rb +131 -0
  370. data/lib/hecks/tools.rb +86 -0
  371. data/lib/hecks/version.rb +1 -1
  372. data/lib/hecks.rb +32 -15
  373. data/qa/settings.yml +66 -0
  374. data/rust/.cargo/config.toml +6 -0
  375. data/rust/.gitignore +2 -0
  376. data/rust/Cargo.lock +54 -0
  377. data/rust/Cargo.toml +61 -0
  378. data/rust/build/.cargo/config.toml +3 -0
  379. data/rust/build/.gitignore +1 -0
  380. data/rust/build/Cargo.lock +7 -0
  381. data/rust/build/Cargo.toml +19 -0
  382. data/rust/build/src/build_artifact.rs +47 -0
  383. data/rust/build/src/cargo_sync.rs +248 -0
  384. data/rust/build/src/json.rs +421 -0
  385. data/rust/build/src/lineage_pass.rs +285 -0
  386. data/rust/build/src/main.rs +51 -0
  387. data/rust/build/src/optional_pass.rs +150 -0
  388. data/rust/build/src/pipeline.rs +264 -0
  389. data/rust/build/src/reserved_names.rs +76 -0
  390. data/rust/build/src/resolve.rs +124 -0
  391. data/rust/build/src/root.rs +25 -0
  392. data/rust/build/src/sidecars.rs +73 -0
  393. data/rust/build/src/subprocess.rs +51 -0
  394. data/rust/build/src/tmp.rs +31 -0
  395. data/rust/codegen/.cargo/config.toml +3 -0
  396. data/rust/codegen/.gitignore +1 -0
  397. data/rust/codegen/Cargo.lock +7 -0
  398. data/rust/codegen/Cargo.toml +20 -0
  399. data/rust/codegen/src/attr.rs +32 -0
  400. data/rust/codegen/src/bridging.rs +481 -0
  401. data/rust/codegen/src/commands.rs +1226 -0
  402. data/rust/codegen/src/constraints.rs +109 -0
  403. data/rust/codegen/src/dependency_planning.rs +209 -0
  404. data/rust/codegen/src/domain_generator.rs +1288 -0
  405. data/rust/codegen/src/exemplar.rs +230 -0
  406. data/rust/codegen/src/expr_emitter.rs +87 -0
  407. data/rust/codegen/src/fielded.rs +192 -0
  408. data/rust/codegen/src/hecks_naming.rs +33 -0
  409. data/rust/codegen/src/json.rs +335 -0
  410. data/rust/codegen/src/json_codec.rs +697 -0
  411. data/rust/codegen/src/literal.rs +207 -0
  412. data/rust/codegen/src/main.rs +290 -0
  413. data/rust/codegen/src/manifest.rs +227 -0
  414. data/rust/codegen/src/mutations.rs +784 -0
  415. data/rust/codegen/src/naming.rs +286 -0
  416. data/rust/codegen/src/ports.rs +208 -0
  417. data/rust/codegen/src/prelude.rs +116 -0
  418. data/rust/codegen/src/queries.rs +786 -0
  419. data/rust/codegen/src/reactions.rs +532 -0
  420. data/rust/codegen/src/read_models.rs +697 -0
  421. data/rust/codegen/src/reference_specs.rs +40 -0
  422. data/rust/codegen/src/registry.rs +966 -0
  423. data/rust/codegen/src/reserved_names.rs +76 -0
  424. data/rust/codegen/src/shared.rs +93 -0
  425. data/rust/codegen/src/skip_reason.rs +25 -0
  426. data/rust/codegen/src/types.rs +362 -0
  427. data/rust/host/.cargo/config.toml +3 -0
  428. data/rust/host/.gitignore +1 -0
  429. data/rust/host/Cargo.lock +4297 -0
  430. data/rust/host/Cargo.toml +61 -0
  431. data/rust/host/HECKS_RELEASE +1 -0
  432. data/rust/host/rds-ca-bundle.pem +2736 -0
  433. data/rust/host/src/api.rs +1596 -0
  434. data/rust/host/src/approval.rs +652 -0
  435. data/rust/host/src/auth.rs +1287 -0
  436. data/rust/host/src/bin/expr_harness.rs +46 -0
  437. data/rust/host/src/bin/lineage_harness.rs +119 -0
  438. data/rust/host/src/bin/mint_harness.rs +135 -0
  439. data/rust/host/src/checkout.rs +265 -0
  440. data/rust/host/src/dispatch.rs +1443 -0
  441. data/rust/host/src/expr_json.rs +1117 -0
  442. data/rust/host/src/field_hints.rs +52 -0
  443. data/rust/host/src/ir.rs +733 -0
  444. data/rust/host/src/journal.rs +874 -0
  445. data/rust/host/src/lambda_client.rs +378 -0
  446. data/rust/host/src/log.rs +147 -0
  447. data/rust/host/src/main.rs +474 -0
  448. data/rust/host/src/mint.rs +1865 -0
  449. data/rust/host/src/payments/keystore.rs +330 -0
  450. data/rust/host/src/payments/tests.rs +1400 -0
  451. data/rust/host/src/payments.rs +767 -0
  452. data/rust/host/src/presentation.rs +698 -0
  453. data/rust/host/src/presentation_write.rs +1431 -0
  454. data/rust/host/src/query_step.rs +149 -0
  455. data/rust/host/src/rate_limit.rs +904 -0
  456. data/rust/host/src/reference_transform.rs +241 -0
  457. data/rust/host/src/reference_validate.rs +338 -0
  458. data/rust/host/src/resend.rs +296 -0
  459. data/rust/host/src/secrets.rs +106 -0
  460. data/rust/host/src/server.rs +663 -0
  461. data/rust/host/src/storage_shape.rs +175 -0
  462. data/rust/host/src/test_pg.rs +40 -0
  463. data/rust/host/src/ui_schema.rs +1351 -0
  464. data/rust/host/src/wasm_runner.rs +191 -0
  465. data/rust/host/src/web/newsletter/cooldown.rs +126 -0
  466. data/rust/host/src/web/newsletter/tests.rs +104 -0
  467. data/rust/host/src/web/newsletter.rs +310 -0
  468. data/rust/host/src/web/newsletter_send.rs +357 -0
  469. data/rust/host/src/web/registrations.rs +1505 -0
  470. data/rust/host/src/web.rs +2937 -0
  471. data/rust/lsp/.gitignore +1 -0
  472. data/rust/lsp/Cargo.lock +7 -0
  473. data/rust/lsp/Cargo.toml +16 -0
  474. data/rust/lsp/README.md +133 -0
  475. data/rust/lsp/src/diagnostics.rs +165 -0
  476. data/rust/lsp/src/json.rs +385 -0
  477. data/rust/lsp/src/main.rs +399 -0
  478. data/rust/lsp/src/outline.rs +177 -0
  479. data/rust/lsp/src/rpc.rs +67 -0
  480. data/rust/parser/.cargo/config.toml +5 -0
  481. data/rust/parser/.gitignore +1 -0
  482. data/rust/parser/Cargo.lock +7 -0
  483. data/rust/parser/Cargo.toml +21 -0
  484. data/rust/parser/src/build/closed_sets.rs +27 -0
  485. data/rust/parser/src/build/identity.rs +276 -0
  486. data/rust/parser/src/build/mod.rs +12 -0
  487. data/rust/parser/src/build/naming.rs +141 -0
  488. data/rust/parser/src/build/pattern_subset.rs +161 -0
  489. data/rust/parser/src/build/query_derive.rs +72 -0
  490. data/rust/parser/src/build/query_inference.rs +269 -0
  491. data/rust/parser/src/build/query_options.rs +100 -0
  492. data/rust/parser/src/build/read_model.rs +94 -0
  493. data/rust/parser/src/build/references.rs +32 -0
  494. data/rust/parser/src/canonical.rs +114 -0
  495. data/rust/parser/src/diag.rs +55 -0
  496. data/rust/parser/src/emit.rs +953 -0
  497. data/rust/parser/src/expr/ast_json.rs +147 -0
  498. data/rust/parser/src/expr/evaluator.rs +81 -0
  499. data/rust/parser/src/expr/mod.rs +84 -0
  500. data/rust/parser/src/expr/resolver.rs +497 -0
  501. data/rust/parser/src/ir.rs +387 -0
  502. data/rust/parser/src/keywords.rs +585 -0
  503. data/rust/parser/src/lex.rs +662 -0
  504. data/rust/parser/src/main.rs +252 -0
  505. data/rust/parser/src/parse/aggregate.rs +612 -0
  506. data/rust/parser/src/parse/chapter.rs +517 -0
  507. data/rust/parser/src/parse/command.rs +637 -0
  508. data/rust/parser/src/parse/domain_port.rs +127 -0
  509. data/rust/parser/src/parse/entity.rs +527 -0
  510. data/rust/parser/src/parse/file.rs +67 -0
  511. data/rust/parser/src/parse/hecksagon.rs +231 -0
  512. data/rust/parser/src/parse/lifecycle.rs +162 -0
  513. data/rust/parser/src/parse/mod.rs +1262 -0
  514. data/rust/parser/src/parse/policy.rs +87 -0
  515. data/rust/parser/src/parse/process_manager.rs +305 -0
  516. data/rust/parser/src/parse/query.rs +100 -0
  517. data/rust/parser/src/parse/read_model.rs +214 -0
  518. data/rust/parser/src/parse/value_object.rs +250 -0
  519. data/rust/parser/src/ruby_value.rs +422 -0
  520. data/rust/project/bridging.rb +257 -0
  521. data/rust/project/commands.rb +877 -0
  522. data/rust/project/constraints.rb +86 -0
  523. data/rust/project/dependency_planning.rb +178 -0
  524. data/rust/project/domain_generator.rb +980 -0
  525. data/rust/project/exemplar.rb +148 -0
  526. data/rust/project/expr_emitter.rb +72 -0
  527. data/rust/project/fielded.rb +141 -0
  528. data/rust/project/json_codec.rb +641 -0
  529. data/rust/project/mutations.rb +721 -0
  530. data/rust/project/naming.rb +205 -0
  531. data/rust/project/ports.rb +65 -0
  532. data/rust/project/queries.rb +493 -0
  533. data/rust/project/reactions.rb +317 -0
  534. data/rust/project/read_models.rb +690 -0
  535. data/rust/project/reference_specs.rb +27 -0
  536. data/rust/project/registry.rb +396 -0
  537. data/rust/project/skip_reason.rb +29 -0
  538. data/rust/project/types.rb +225 -0
  539. data/rust/project/write_if_changed.rb +83 -0
  540. data/rust/project.rb +30 -0
  541. data/rust/project_rust_pipeline.rb +383 -0
  542. data/rust/src/exemplar/commands.rs +279 -0
  543. data/rust/src/exemplar/constraints.rs +18 -0
  544. data/rust/src/exemplar/fielded.rs +280 -0
  545. data/rust/src/exemplar/json.rs +277 -0
  546. data/rust/src/exemplar/mod.rs +17 -0
  547. data/rust/src/exemplar/mutations.rs +80 -0
  548. data/rust/src/exemplar/queries.rs +33 -0
  549. data/rust/src/exemplar/reactions.rs +88 -0
  550. data/rust/src/exemplar/read_models.rs +48 -0
  551. data/rust/src/exemplar/registry.rs +166 -0
  552. data/rust/src/exemplar/types.rs +44 -0
  553. data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
  554. data/rust/src/kernel/attribute_shapes/list.rs +18 -0
  555. data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
  556. data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
  557. data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
  558. data/rust/src/kernel/cli.rs +585 -0
  559. data/rust/src/kernel/dispatch.rs +1090 -0
  560. data/rust/src/kernel/expr.rs +458 -0
  561. data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
  562. data/rust/src/kernel/expression_operators/comparison.rs +48 -0
  563. data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
  564. data/rust/src/kernel/expression_operators/logical.rs +13 -0
  565. data/rust/src/kernel/expression_operators/membership.rs +18 -0
  566. data/rust/src/kernel/expression_operators/mod.rs +68 -0
  567. data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
  568. data/rust/src/kernel/expression_operators/positional.rs +126 -0
  569. data/rust/src/kernel/expression_operators/presence.rs +101 -0
  570. data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
  571. data/rust/src/kernel/expression_operators/sized.rs +35 -0
  572. data/rust/src/kernel/expression_operators/text.rs +79 -0
  573. data/rust/src/kernel/expression_operators/to_string.rs +18 -0
  574. data/rust/src/kernel/json.rs +779 -0
  575. data/rust/src/kernel/mod.rs +133 -0
  576. data/rust/src/kernel/named_query.rs +219 -0
  577. data/rust/src/kernel/orchestrate.rs +1810 -0
  578. data/rust/src/kernel/pattern.rs +307 -0
  579. data/rust/src/kernel/query_comparators.rs +206 -0
  580. data/rust/src/kernel/query_ordering.rs +103 -0
  581. data/rust/src/kernel/read_model.rs +914 -0
  582. data/rust/src/kernel/reference_lookup.rs +149 -0
  583. data/rust/src/kernel/repository.rs +468 -0
  584. data/rust/src/kernel/routing.rs +612 -0
  585. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
  586. data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
  587. data/rust/src/kernel/vocab/field_hint.rs +70 -0
  588. data/rust/src/kernel/vocab/mod.rs +20 -0
  589. data/rust/src/kernel/vocab/query_comparator.rs +65 -0
  590. data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
  591. data/rust/src/lib.rs +8 -0
  592. data/rust/src/main.rs +16 -0
  593. data/rust/web/.gitignore +1 -0
  594. data/rust/web/Cargo.lock +119 -0
  595. data/rust/web/Cargo.toml +18 -0
  596. data/rust/web/src/lib.rs +8 -0
  597. metadata +477 -12
  598. data/lib/hecks/facade/cli_door.rb +0 -96
  599. data/lib/hecks/facade/cli_runner.rb +0 -127
  600. data/lib/hecks/mcp_door_scope.rb +0 -95
@@ -0,0 +1,474 @@
1
+ //! Lambda custom-runtime entry point (or a long-lived server under
2
+ //! HECKS_SERVE_MODE=1): boots Postgres/wasm state, then dispatches events.
3
+
4
+ mod api;
5
+ mod approval;
6
+ mod auth;
7
+ mod checkout;
8
+ mod dispatch;
9
+ mod expr_json;
10
+ mod field_hints;
11
+ mod ir;
12
+ mod journal;
13
+ mod lambda_client;
14
+ mod log;
15
+ mod mint;
16
+ mod payments;
17
+ mod presentation;
18
+ mod presentation_write;
19
+ mod query_step;
20
+ mod rate_limit;
21
+ mod reference_transform;
22
+ mod reference_validate;
23
+ mod resend;
24
+ mod secrets;
25
+ mod server;
26
+ mod storage_shape;
27
+ #[cfg(test)]
28
+ mod test_pg;
29
+ mod ui_schema;
30
+ mod wasm_runner;
31
+ mod web;
32
+
33
+ use lambda_runtime::{service_fn, Error, LambdaEvent};
34
+ use std::path::PathBuf;
35
+ use std::sync::Arc;
36
+ use tokio::sync::Mutex;
37
+
38
+ #[tokio::main]
39
+ async fn main() -> Result<(), Error> {
40
+ // HECKS_SERVE_MODE=1 switches the boot into the long-lived server
41
+ // loop below instead of the Lambda custom-runtime loop; every step
42
+ // from here through the era-minting sequence is shared, unchanged,
43
+ // by both modes.
44
+ let boot_started = std::time::Instant::now();
45
+ let serve_mode = std::env::var("HECKS_SERVE_MODE").as_deref() == Ok("1");
46
+
47
+ // HECKS_SESSION_COOKIE names the account cookie; refuse the boot
48
+ // here if it can't be written into a Set-Cookie header, rather
49
+ // than falling back silently at request time.
50
+ auth::resolve_account_cookie(std::env::var("HECKS_SESSION_COOKIE").ok().as_deref())?;
51
+
52
+ // DB_SECRET_ARN: the password is fetched from Secrets Manager here,
53
+ // at cold start, rather than trusted from a plaintext
54
+ // Environment.Variables value, which any principal with
55
+ // lambda:GetFunctionConfiguration could read regardless of the
56
+ // secret-at-rest protection its name suggests. Falls back to
57
+ // DATABASE_URL directly when DB_SECRET_ARN is absent (manual
58
+ // debugging over an SSM tunnel).
59
+ //
60
+ // The fetcher built here is reused below for GOOGLE_OAUTH_SECRET_ID
61
+ // and SESSION_SECRET_ARN too.
62
+ let secret_fetcher = secrets::AwsSecretFetcher::from_env().await;
63
+ let database_url = match std::env::var("DB_SECRET_ARN") {
64
+ Ok(secret_arn) => {
65
+ let db_host = std::env::var("DB_HOST").map_err(|_| "DB_HOST is required when DB_SECRET_ARN is set")?;
66
+ let db_name = std::env::var("DB_NAME").map_err(|_| "DB_NAME is required when DB_SECRET_ARN is set")?;
67
+ let secret_json = secret_fetcher
68
+ .fetch_secret_string(&secret_arn)
69
+ .await
70
+ .map_err(|e| format!("fetching DB_SECRET_ARN from Secrets Manager: {e:#}"))?;
71
+ let password = secrets::extract_field(&secret_json, "password")?;
72
+ // Literal, unescaped, no percent-encoding -- parse_database_url
73
+ // below never percent-decodes the password segment either way.
74
+ format!("postgres://postgres:{password}@{db_host}:5432/{db_name}")
75
+ }
76
+ Err(_) => std::env::var("DATABASE_URL")
77
+ .map_err(|_| "either DB_SECRET_ARN (+ DB_HOST/DB_NAME) or DATABASE_URL is required")?,
78
+ };
79
+
80
+ // GOOGLE_OAUTH_SECRET_ID/SESSION_SECRET_ARN: the same
81
+ // Secrets-Manager-at-cold-start approach as DB_SECRET_ARN above,
82
+ // applied to auth.rs's GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET and
83
+ // web.rs's SESSION_SECRET.
84
+ //
85
+ // Safety: this runs before any request is dispatched, so nothing
86
+ // else reads or writes the environment concurrently with these
87
+ // `set_var` calls.
88
+ if let Ok(oauth_secret_id) = std::env::var("GOOGLE_OAUTH_SECRET_ID") {
89
+ let secret_json = secret_fetcher
90
+ .fetch_secret_string(&oauth_secret_id)
91
+ .await
92
+ .map_err(|e| format!("fetching GOOGLE_OAUTH_SECRET_ID from Secrets Manager: {e:#}"))?;
93
+ let client_id = secrets::extract_field(&secret_json, "client_id")?;
94
+ let client_secret = secrets::extract_field(&secret_json, "client_secret")?;
95
+ unsafe {
96
+ std::env::set_var("GOOGLE_CLIENT_ID", client_id);
97
+ std::env::set_var("GOOGLE_CLIENT_SECRET", client_secret);
98
+ }
99
+ }
100
+ if let Ok(session_secret_arn) = std::env::var("SESSION_SECRET_ARN") {
101
+ let secret_json = secret_fetcher
102
+ .fetch_secret_string(&session_secret_arn)
103
+ .await
104
+ .map_err(|e| format!("fetching SESSION_SECRET_ARN from Secrets Manager: {e:#}"))?;
105
+ let session_secret = secrets::extract_field(&secret_json, "session_secret")?;
106
+ unsafe {
107
+ std::env::set_var("SESSION_SECRET", session_secret);
108
+ }
109
+ }
110
+ // RESEND_SECRET_ID: fetched the same way, but a failed fetch only
111
+ // logs -- mail is optional, and a missing secret must not stop the
112
+ // host from serving everything else (resend.rs answers 503 without
113
+ // a key).
114
+ if let Ok(resend_secret_id) = std::env::var("RESEND_SECRET_ID") {
115
+ let fetched = secret_fetcher.fetch_secret_string(&resend_secret_id).await.map_err(|e| format!("{e:#}"));
116
+ match fetched.and_then(|json| secrets::extract_field(&json, "api_key")) {
117
+ Ok(api_key) => unsafe { std::env::set_var("RESEND_API_KEY", api_key) },
118
+ Err(e) => eprintln!("RESEND_SECRET_ID could not be read, so email sending stays off: {e}"),
119
+ }
120
+ }
121
+ let wasm_path = PathBuf::from(
122
+ std::env::var("HECKS_WASM_PATH").unwrap_or_else(|_| "banking.wasm".to_string()),
123
+ );
124
+
125
+ // Must run before the Postgres connection opens -- `SET search_path`
126
+ // below has to run before `journal::ensure_schema` or anything else
127
+ // touches the connection, or unqualified names resolve against
128
+ // Postgres's default search_path instead of this domain's.
129
+ // HECKS_SCHEMA is optional.
130
+ let domain = std::env::var("HECKS_DOMAIN").map_err(|_| "HECKS_DOMAIN is required")?;
131
+ let schema = std::env::var("HECKS_SCHEMA").ok().filter(|s| !s.is_empty());
132
+ // Checkout on AWS keeps the business's payment keys in a named Secrets
133
+ // Manager secret; refuse before touching the database when none is named.
134
+ payments::check_boot(web::checkout_enabled(std::env::var("HECKS_CHECKOUT_DOMAIN").ok().as_deref(), &domain))?;
135
+
136
+ // RDS refuses a plain NoTls connection by default and needs AWS's
137
+ // own RDS CA specifically, not a generic public bundle (see
138
+ // Cargo.toml).
139
+ let mut roots = rustls::RootCertStore::empty();
140
+ for cert in rustls_pemfile::certs(&mut include_bytes!("../rds-ca-bundle.pem").as_slice()) {
141
+ roots.add(cert.map_err(|e| format!("parsing rds-ca-bundle.pem: {e}"))?)?;
142
+ }
143
+ let tls_config = rustls::ClientConfig::builder()
144
+ .with_root_certificates(roots)
145
+ .with_no_client_auth();
146
+ let tls = tokio_postgres_rustls::MakeRustlsConnect::new(tls_config);
147
+ // Not tokio_postgres::connect(url, tls) -- that treats DATABASE_URL
148
+ // as a strict URI, and RDS-generated passwords can contain
149
+ // URI-reserved `?`/`#` that CloudFormation never percent-encodes.
150
+ let mut config = parse_database_url(&database_url)?;
151
+ // RDS requires TLS (rds-ca-bundle.pem); local Postgres over loopback
152
+ // can't be verified against the RDS CA, so localhost uses NoTls.
153
+ let local_postgres = database_url_is_local(&config);
154
+ if !local_postgres {
155
+ config.ssl_mode(tokio_postgres::config::SslMode::Require);
156
+ }
157
+ // NoTlsStream and RustlsStream can't unify in one branch, so the
158
+ // two paths are spawned separately.
159
+ let connect_phase = log::phase_with("db_connect", serde_json::json!({ "tls": !local_postgres }));
160
+ let client = if local_postgres {
161
+ let (client, connection) = config
162
+ .connect(tokio_postgres::NoTls)
163
+ .await
164
+ .map_err(|e| format!("connecting to Postgres via DATABASE_URL: {e:?}"))?;
165
+ tokio::spawn(async move {
166
+ if let Err(e) = connection.await {
167
+ eprintln!("postgres connection error: {e:#}");
168
+ }
169
+ });
170
+ client
171
+ } else {
172
+ let (client, connection) = config
173
+ .connect(tls)
174
+ .await
175
+ .map_err(|e| format!("connecting to Postgres via DATABASE_URL: {e:?}"))?;
176
+ tokio::spawn(async move {
177
+ if let Err(e) = connection.await {
178
+ eprintln!("postgres connection error: {e:#}");
179
+ }
180
+ });
181
+ client
182
+ };
183
+
184
+ connect_phase.end();
185
+
186
+ // Every unqualified table/view reference this binary issues
187
+ // resolves through search_path, so this is what makes a shared
188
+ // instance's per-domain schemas transparent to the rest of the
189
+ // binary.
190
+ //
191
+ // CREATE SCHEMA before SET search_path: setting search_path to a
192
+ // schema that doesn't exist yet succeeds in Postgres, so a missing
193
+ // schema only surfaces later as a confusing "no schema has been
194
+ // selected to create in" error from `ensure_schema`. Idempotent: an
195
+ // existing schema is the ordinary case on every boot after the first.
196
+ if let Some(schema) = &schema {
197
+ client
198
+ .batch_execute(&format!("CREATE SCHEMA IF NOT EXISTS {}", journal::quote_ident(schema)))
199
+ .await
200
+ .map_err(|e| format!("creating schema {schema:?}: {e:#}"))?;
201
+ client
202
+ .batch_execute(&format!("SET search_path TO {}", journal::quote_ident(schema)))
203
+ .await
204
+ .map_err(|e| format!("setting search_path to {schema:?}: {e:#}"))?;
205
+ }
206
+
207
+ let phase = log::phase("ensure_schema");
208
+ journal::ensure_schema(&client)
209
+ .await
210
+ .map_err(|e| format!("provisioning hecks_lambda_journal: {e:#}"))?;
211
+ phase.end();
212
+
213
+ let ir = ir::ir().ok_or("HECKS_IR_PATH is not set or unreadable — this binary needs its own domain's ir.json sidecar")?;
214
+ ir::refuse_unsupported_persistence_adapters(ir)?;
215
+ let my_hash = storage_shape::mint_hash(ir);
216
+ let my_label = storage_shape::mint_label(ir);
217
+
218
+ let mut aggregates: Vec<mint::Aggregate> = ir::lineage_capable_aggregates(ir)
219
+ .into_iter()
220
+ .map(|(qualified, storage_name)| {
221
+ let name = qualified.rsplit("::").next().unwrap_or(&qualified).to_string();
222
+ mint::Aggregate { name, storage_name }
223
+ })
224
+ .collect();
225
+ // A chapter that `provides "membership"` names who may sign in; that
226
+ // aggregate can be vendored, so it may be missing from this domain's
227
+ // lineage-capable list. Mint its head anyway for Google-auth to read.
228
+ if let Some(membership) = ir::membership_provider(ir) {
229
+ let name = membership.aggregate.rsplit("::").next().unwrap_or(&membership.aggregate).to_string();
230
+ let storage_name = journal::snake(&name);
231
+ if !aggregates.iter().any(|a| a.storage_name == storage_name) {
232
+ aggregates.push(mint::Aggregate { name, storage_name });
233
+ }
234
+ }
235
+
236
+ // The era-partitioned lineage subsystem is provisioned only when
237
+ // something needs it: a lineage-capable aggregate, or Google auth
238
+ // configured (ADR 0034 requires it for Member sessions either way).
239
+ let needs_lineage = !aggregates.is_empty() || std::env::var("GOOGLE_CLIENT_ID").is_ok();
240
+
241
+ let era: Option<i32> = if needs_lineage {
242
+ // Idempotent (gated by `provisioner?`); must run before the
243
+ // first `journal::held_eras` call, since `hecks_eras` doesn't
244
+ // exist yet on a truly fresh Postgres instance.
245
+ let phase = log::phase("ensure_base");
246
+ mint::ensure_base(&client, &domain).await.map_err(|e| format!("provisioning hecks_eras for {domain}: {e:#}"))?;
247
+ phase.end();
248
+
249
+ // Compares this binary's own shape hash (`storage_shape`, over
250
+ // `ir::ir()`) against held eras to decide the boot action: boot
251
+ // at a matching era, mint era 1 if the domain is new, or mint
252
+ // the next era if it has drifted -- refusing toward
253
+ // `hecks scaffold_translation` (no edge covers it) or
254
+ // `hecks audit_translation --approve` (needs review) otherwise.
255
+ let era_phase = log::phase("era_resolve");
256
+ let held = journal::held_eras(&client, &domain).await.map_err(|e| format!("checking hecks_eras for {domain}: {e:#}"))?;
257
+
258
+ // The decision itself is a pure, unit-tested function
259
+ // (`mint::decide_boot_action`); everything below just carries
260
+ // out whichever of its four outcomes came back.
261
+ let decision = mint::decide_boot_action(&held, &my_label);
262
+ era_phase.end_with(serde_json::json!({ "held_eras": held.len(), "decision": decision.name() }));
263
+ let ordinal: i32 = match decision {
264
+ // Adopting an existing era still provisions what this binary
265
+ // is about to write into, the same as minting does --
266
+ // skipping it would let a matched-era host write into
267
+ // head-snapshot tables that were never created.
268
+ mint::BootDecision::UseExisting { ordinal } => {
269
+ let phase = log::phase_with("adopt_head_snapshots", serde_json::json!({ "era": ordinal }));
270
+ mint::adopt_head_snapshots(&client, &domain, &aggregates, ordinal)
271
+ .await
272
+ .map_err(|e| format!("adopting era {ordinal} of {domain}: {e:#}"))?;
273
+ phase.end();
274
+ ordinal
275
+ }
276
+ mint::BootDecision::HoldFirst => {
277
+ let source_text =
278
+ ir.get("source_text").and_then(serde_json::Value::as_str).ok_or("ir.json is missing source_text — regenerate with hecks project_rust")?;
279
+ let phase = log::phase_with("hold_first", serde_json::json!({ "era": 1 }));
280
+ mint::hold_first(&client, &domain, source_text, ir, &aggregates, None).await.map_err(|e| format!("minting era 1 of {domain}: {e:#}"))?;
281
+ phase.end();
282
+ 1
283
+ }
284
+ mint::BootDecision::LatestUnnamed { ordinal } => {
285
+ return Err(format!(
286
+ "cannot boot: {domain}'s latest held era (ordinal {ordinal}) has no name yet -- this crate can't \
287
+ name it itself (no bluebook parser, ADR 0012's own boundary) -- boot Ruby once against this \
288
+ database to name it (Runtime::EraCheck's own lazy-naming path), then redeploy",
289
+ )
290
+ .into());
291
+ }
292
+ mint::BootDecision::Mint { ordinal, from_ordinal, from_label } => {
293
+ let mut labels: Vec<String> = held.iter().map(|held_era| held_era.label.clone().unwrap_or_default()).collect();
294
+ labels.push(my_label.clone());
295
+ let edges = mint::parse_edges(ir, &domain);
296
+ let chain = mint::edge_chain(&edges, &labels).map_err(|e| {
297
+ format!(
298
+ "cannot boot: {domain} has drifted from era {from_ordinal} ({from_label}) to a shape \
299
+ ({my_label}) no translation edge covers -- {e} -- run hecks scaffold_translation, review it \
300
+ with hecks audit_translation, then redeploy",
301
+ )
302
+ })?;
303
+
304
+ // Checks every edge in the chain with a compute/rekey
305
+ // rule, not just the newest -- a chain can carry more
306
+ // than one, matched against the raw ir.json edge
307
+ // (digest-relevant shape) by the same from/to pair.
308
+ let raw_edges = ir.get("translations").and_then(serde_json::Value::as_array).cloned().unwrap_or_default();
309
+ let committed_approvals = approval::committed(ir);
310
+ let approval_phase = log::phase_with("approval_check", serde_json::json!({ "edges": chain.len(), "committed": committed_approvals.len() }));
311
+ for edge in &chain {
312
+ let Some(raw_edge) = raw_edges.iter().find(|candidate| {
313
+ candidate.get("domain").and_then(serde_json::Value::as_str) == Some(domain.as_str())
314
+ && candidate.get("from").and_then(serde_json::Value::as_str) == Some(edge.from.as_str())
315
+ && candidate.get("to").and_then(serde_json::Value::as_str) == Some(edge.to.as_str())
316
+ }) else {
317
+ return Err(format!("cannot boot: {domain}'s edge {} -> {} vanished between parsing and approval-checking it", edge.from, edge.to).into());
318
+ };
319
+ approval::check(&client, &domain, raw_edge, ordinal, &committed_approvals).await.map_err(|e| format!("{e:#}"))?;
320
+ }
321
+ approval_phase.end();
322
+
323
+ // Runs before anything is minted, over the live compiled
324
+ // chain (plain SELECTs, never a persisted matview), so a
325
+ // refusal leaves no half-born era.
326
+ let watermarks: std::collections::HashMap<i32, Option<i64>> = held.iter().map(|held_era| (held_era.ordinal, held_era.watermark)).collect();
327
+ let audit_phase = log::phase_with("audit_before_mint", serde_json::json!({ "era": ordinal, "edges": chain.len() }));
328
+ mint::audit_before_mint(&client, &domain, ir, &aggregates, ordinal, &chain, &raw_edges, &watermarks)
329
+ .await
330
+ .map_err(|e| format!("{e:#}"))?;
331
+ audit_phase.end();
332
+
333
+ let held_text = ir.get("source_text").and_then(serde_json::Value::as_str).ok_or("ir.json is missing source_text — regenerate with hecks project_rust")?;
334
+ let mint_phase = log::phase_with("mint_era", serde_json::json!({ "era": ordinal, "edges": chain.len() }));
335
+ mint::mint_era(&client, &domain, ordinal, &my_hash, &my_label, held_text, &aggregates, &chain, None, &mint::lifecycle_defaults(ir))
336
+ .await
337
+ .map_err(|e| format!("minting era {ordinal} of {domain}: {e:#}"))?;
338
+ mint_phase.end();
339
+ ordinal
340
+ }
341
+ };
342
+ Some(ordinal)
343
+ } else {
344
+ None
345
+ };
346
+
347
+ // The mirrored set is the IR's own capable list, by qualified name
348
+ // -- never "everything this kernel can mutate". A domain that binds
349
+ // everything to plain `Postgres` mirrors nothing.
350
+ let mirrored = ir::mirrored_aggregates(ir);
351
+ let lineage_config = Arc::new(journal::LineageConfig { domain, era, mirrored: Some(mirrored) });
352
+
353
+ // Mutex, not a bare Arc<Client> -- dispatch::handle needs
354
+ // Client::transaction (&mut Client) to hold the advisory lock
355
+ // across the rehydrate-then-append sequence (see dispatch.rs).
356
+ let client = Arc::new(Mutex::new(client));
357
+ let wasm_path = Arc::new(wasm_path);
358
+ // One invoker for this process's lifetime -- credentials/region
359
+ // resolve once at boot, not per invocation. See lambda_client.rs:
360
+ // the compiled `.wasm` module can't dispatch this itself (no
361
+ // network inside that sandbox).
362
+ let invoker = Arc::new(lambda_client::AwsLambdaInvoker::from_env().await);
363
+
364
+ // `role` is optional, mirroring `args`: an absent key means no
365
+ // caller asserted a role, and `dispatch::handle`'s `check_role`
366
+ // stays on its unchecked path. Read by `server::dispatch_body` below.
367
+ if serve_mode {
368
+ log::info(
369
+ "boot",
370
+ serde_json::json!({ "mode": "serve", "domain": &lineage_config.domain, "era": &my_label, "boot_ms": boot_started.elapsed().as_millis() as u64 }),
371
+ );
372
+ let version = server::version_body(&my_label, &my_hash, std::env::var("HECKS_BUILD").ok().as_deref());
373
+ let limits = Arc::new(rate_limit::RateLimits::from_env());
374
+ let state = server::ServerState { client, wasm_path, lineage_config, invoker, limits };
375
+ return server::serve(state, version).await;
376
+ }
377
+
378
+ lambda_runtime::run(service_fn(move |event: LambdaEvent<serde_json::Value>| {
379
+ let client = Arc::clone(&client);
380
+ let wasm_path = Arc::clone(&wasm_path);
381
+ let lineage_config = Arc::clone(&lineage_config);
382
+ let invoker = Arc::clone(&invoker);
383
+ async move {
384
+ let (body, _context) = event.into_parts();
385
+ server::dispatch_body(body, &client, &wasm_path, &lineage_config, invoker.as_ref()).await
386
+ }
387
+ }))
388
+ .await
389
+ }
390
+
391
+ // Parses `postgres://user:password@host:port/db` without treating it
392
+ // as a URI -- an RDS/Aurora auto-generated password can contain
393
+ // `?`/`#`, which a strict URI parser would treat as delimiters.
394
+ fn parse_database_url(url: &str) -> Result<tokio_postgres::Config, String> {
395
+ let rest = url
396
+ .strip_prefix("postgres://")
397
+ .or_else(|| url.strip_prefix("postgresql://"))
398
+ .ok_or_else(|| format!("DATABASE_URL doesn't start with postgres:// or postgresql://"))?;
399
+
400
+ let (authority, db_and_query) = rest
401
+ .split_once('/')
402
+ .ok_or_else(|| "DATABASE_URL has no '/' separating host from database name".to_string())?;
403
+ let dbname = db_and_query.split(['?', '#']).next().unwrap_or(db_and_query);
404
+
405
+ let (credentials, host_and_port) = match authority.rsplit_once('@') {
406
+ Some((credentials, host_and_port)) => (Some(credentials), host_and_port),
407
+ None => (None, authority),
408
+ };
409
+ let (host, port) = match host_and_port.rsplit_once(':') {
410
+ Some((host, port)) => {
411
+ let port: u16 = port
412
+ .parse()
413
+ .map_err(|e| format!("DATABASE_URL's port {port:?} isn't a valid number: {e}"))?;
414
+ (host, port)
415
+ }
416
+ None => (host_and_port, 5432),
417
+ };
418
+
419
+ let mut config = tokio_postgres::Config::new();
420
+ config.host(host).port(port).dbname(dbname);
421
+ if let Some(credentials) = credentials {
422
+ match credentials.split_once(':') {
423
+ Some((user, password)) => {
424
+ config.user(user).password(password);
425
+ }
426
+ None => {
427
+ config.user(credentials);
428
+ }
429
+ }
430
+ }
431
+ Ok(config)
432
+ }
433
+
434
+ fn database_url_is_local(config: &tokio_postgres::Config) -> bool {
435
+ use tokio_postgres::config::Host;
436
+ config.get_hosts().iter().any(|host| match host {
437
+ Host::Tcp(name) => name == "localhost" || name == "127.0.0.1" || name == "::1",
438
+ _ => false,
439
+ })
440
+ }
441
+
442
+ #[cfg(test)]
443
+ mod tests {
444
+ use super::*;
445
+
446
+ #[test]
447
+ fn parses_a_password_with_uri_reserved_characters() {
448
+ let config = parse_database_url(
449
+ "postgres://postgres:T3st$Pa?#ss*>Word@db.example.com:5432/pizzas",
450
+ )
451
+ .expect("should parse despite ?/# in the password");
452
+ assert_eq!(config.get_hosts().len(), 1);
453
+ assert_eq!(config.get_ports(), &[5432]);
454
+ assert_eq!(config.get_user(), Some("postgres"));
455
+ assert_eq!(config.get_dbname(), Some("pizzas"));
456
+ }
457
+
458
+ #[test]
459
+ fn parses_a_plain_password_too() {
460
+ let config = parse_database_url("postgres://postgres:plainpassword@localhost:5432/pizzas")
461
+ .expect("should parse a password with no special characters");
462
+ assert_eq!(config.get_user(), Some("postgres"));
463
+ assert_eq!(config.get_dbname(), Some("pizzas"));
464
+ }
465
+
466
+ #[test]
467
+ fn parses_a_local_url_with_no_user_or_port() {
468
+ let config = parse_database_url("postgres://localhost/app_development")
469
+ .expect("should parse a peer-auth local URL");
470
+ assert_eq!(config.get_dbname(), Some("app_development"));
471
+ assert_eq!(config.get_ports(), &[5432]);
472
+ assert!(database_url_is_local(&config));
473
+ }
474
+ }