hecks 2.8.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 (886) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +50 -7
  3. data/lib/hecks/adapters/driven/claude_code.rb +4 -104
  4. data/lib/hecks/adapters/driven/d1.rb +44 -237
  5. data/lib/hecks/adapters/driven/folder.rb +17 -76
  6. data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
  7. data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
  8. data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
  9. data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
  10. data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
  11. data/lib/hecks/adapters/driven/heki.rb +13 -42
  12. data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
  15. data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
  16. data/lib/hecks/adapters/driven/lambda.rb +10 -84
  17. data/lib/hecks/adapters/driven/local_storage.rb +5 -40
  18. data/lib/hecks/adapters/driven/memory.rb +32 -100
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +10 -32
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
  24. data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
  25. data/lib/hecks/adapters/driven/postgres.rb +149 -136
  26. data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
  27. data/lib/hecks/adapters/driven/prism.rb +48 -38
  28. data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
  29. data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
  30. data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
  31. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
  32. data/lib/hecks/adapters/driven/sqlite.rb +128 -65
  33. data/lib/hecks/adapters/driven/tenant_provisioner.rb +76 -62
  34. data/lib/hecks/adapters/driven.rb +5 -36
  35. data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
  36. data/lib/hecks/behaviors/dsl.rb +7 -25
  37. data/lib/hecks/behaviors/expectations.rb +32 -142
  38. data/lib/hecks/behaviors/ir.rb +7 -20
  39. data/lib/hecks/behaviors/rspec.rb +6 -17
  40. data/lib/hecks/behaviors/runner.rb +10 -34
  41. data/lib/hecks/behaviors.rb +4 -8
  42. data/lib/hecks/bench/cli.rb +62 -0
  43. data/lib/hecks/bench/environment.rb +71 -0
  44. data/lib/hecks/bench/postgres_probe.rb +30 -0
  45. data/lib/hecks/bench/report.rb +76 -0
  46. data/lib/hecks/bench/ruby_runner.rb +93 -0
  47. data/lib/hecks/bench/run.rb +55 -0
  48. data/lib/hecks/bench/rust_runner.rb +101 -0
  49. data/lib/hecks/bench/stats.rb +46 -0
  50. data/lib/hecks/bench/suite.rb +98 -0
  51. data/lib/hecks/bench/workload.rb +89 -0
  52. data/lib/hecks/bench.rb +17 -0
  53. data/lib/hecks/bluebook/aggregate.rb +9 -75
  54. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
  55. data/lib/hecks/bluebook/assembly/build.rb +4 -15
  56. data/lib/hecks/bluebook/assembly/contract.rb +15 -136
  57. data/lib/hecks/bluebook/assembly/contracts.rb +56 -174
  58. data/lib/hecks/bluebook/assembly/marks.rb +31 -223
  59. data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
  60. data/lib/hecks/bluebook/assembly.rb +6 -45
  61. data/lib/hecks/bluebook/attribute.rb +12 -61
  62. data/lib/hecks/bluebook/behaviour/aggregate.rb +39 -39
  63. data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
  64. data/lib/hecks/bluebook/behaviour/chapter.rb +14 -42
  65. data/lib/hecks/bluebook/behaviour/command.rb +14 -66
  66. data/lib/hecks/bluebook/behaviour/domain_port.rb +11 -20
  67. data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
  68. data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
  69. data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
  70. data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
  71. data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
  72. data/lib/hecks/bluebook/behaviour/read_model.rb +25 -31
  73. data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
  74. data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
  75. data/lib/hecks/bluebook/capabilities.rb +17 -49
  76. data/lib/hecks/bluebook/chapter.rb +16 -50
  77. data/lib/hecks/bluebook/command.rb +23 -81
  78. data/lib/hecks/bluebook/domain_port.rb +49 -67
  79. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
  80. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
  81. data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
  82. data/lib/hecks/bluebook/dsl/binding_proxy.rb +16 -69
  83. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +96 -708
  84. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +63 -169
  85. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +3 -1
  86. data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
  87. data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
  88. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +42 -89
  89. data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
  90. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
  91. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +148 -197
  92. data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
  93. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
  94. data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
  95. data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
  96. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
  97. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
  98. data/lib/hecks/bluebook/dsl/query_builder.rb +38 -43
  99. data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
  100. data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
  101. data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
  102. data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
  103. data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
  104. data/lib/hecks/bluebook/dsl/world_builder.rb +46 -111
  105. data/lib/hecks/bluebook/entity.rb +13 -71
  106. data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
  107. data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
  108. data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
  109. data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
  110. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
  111. data/lib/hecks/bluebook/expression/resolver.rb +39 -661
  112. data/lib/hecks/bluebook/hexagon.rb +18 -33
  113. data/lib/hecks/bluebook/lifecycle.rb +5 -16
  114. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
  115. data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
  116. data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
  117. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
  118. data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
  119. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +25 -176
  120. data/lib/hecks/bluebook/meta_validator/shapes.rb +53 -267
  121. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -306
  122. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
  123. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
  124. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
  125. data/lib/hecks/bluebook/meta_validator.rb +87 -352
  126. data/lib/hecks/bluebook/model_check/client_profile.rb +6 -21
  127. data/lib/hecks/bluebook/model_check.rb +122 -596
  128. data/lib/hecks/bluebook/pattern_subset.rb +49 -69
  129. data/lib/hecks/bluebook/process_manager.rb +13 -83
  130. data/lib/hecks/bluebook/project_loader.rb +3 -39
  131. data/lib/hecks/bluebook/project_register.rb +12 -60
  132. data/lib/hecks/bluebook/query.rb +27 -51
  133. data/lib/hecks/bluebook/read_model.rb +57 -81
  134. data/lib/hecks/bluebook/reference.rb +7 -41
  135. data/lib/hecks/bluebook/smoke_test.rb +9 -99
  136. data/lib/hecks/bluebook/synthesizer.rb +13 -62
  137. data/lib/hecks/bluebook/translation.rb +26 -62
  138. data/lib/hecks/bluebook/value_object.rb +11 -36
  139. data/lib/hecks/bluebook.rb +5 -22
  140. data/lib/hecks/cache_dir.rb +4 -23
  141. data/lib/hecks/canonical_json.rb +30 -0
  142. data/lib/hecks/chapters.rb +102 -0
  143. data/lib/hecks/cli/behaviors.rb +61 -0
  144. data/lib/hecks/cli/canonicalise.rb +25 -0
  145. data/lib/hecks/cli/check_engine_agreement.rb +32 -0
  146. data/lib/hecks/cli/check_era.rb +88 -0
  147. data/lib/hecks/cli/console.rb +66 -0
  148. data/lib/hecks/cli/deploy_template_diff.rb +40 -0
  149. data/lib/hecks/cli/doc_coverage.rb +32 -0
  150. data/lib/hecks/cli/document.rb +5 -23
  151. data/lib/hecks/cli/expression_projection.rb +41 -0
  152. data/lib/hecks/cli/follow.rb +88 -0
  153. data/lib/hecks/cli/history.rb +44 -0
  154. data/lib/hecks/cli/ir.rb +4 -6
  155. data/lib/hecks/cli/mcp.rb +5 -6
  156. data/lib/hecks/cli/model_check.rb +24 -88
  157. data/lib/hecks/cli/pattern_cases.rb +47 -0
  158. data/lib/hecks/cli/present.rb +39 -0
  159. data/lib/hecks/cli/project_cli.rb +223 -64
  160. data/lib/hecks/cli/project_diagrams.rb +4 -10
  161. data/lib/hecks/cli/project_glossary.rb +44 -0
  162. data/lib/hecks/cli/reference.rb +46 -0
  163. data/lib/hecks/cli/refresh_projections.rb +44 -0
  164. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
  165. data/lib/hecks/cli/release.rb +70 -0
  166. data/lib/hecks/cli/release_gem.rb +59 -0
  167. data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
  168. data/lib/hecks/cli/rspec_shard_files.rb +66 -0
  169. data/lib/hecks/cli/run.rb +15 -61
  170. data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
  171. data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
  172. data/lib/hecks/cli/shape.rb +70 -0
  173. data/lib/hecks/cli/smoke_http.rb +230 -0
  174. data/lib/hecks/cli/smoke_test.rb +7 -19
  175. data/lib/hecks/cli/spec_example.rb +27 -0
  176. data/lib/hecks/cli/statements.rb +32 -0
  177. data/lib/hecks/cli/stores.rb +4 -6
  178. data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
  179. data/lib/hecks/cli.rb +216 -73
  180. data/lib/hecks/codemod.rb +324 -0
  181. data/lib/hecks/construct.rb +8 -34
  182. data/lib/hecks/corpus.rb +423 -0
  183. data/lib/hecks/deploy/bluebook/deploy.bluebook +617 -158
  184. data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -11
  185. data/lib/hecks/deploy/oidc.json +82 -2
  186. data/lib/hecks/doc/reference.rb +351 -0
  187. data/lib/hecks/doors/cli_door.rb +196 -0
  188. data/lib/hecks/doors/cli_runner.rb +297 -0
  189. data/lib/hecks/{facade → doors}/command_request.rb +14 -34
  190. data/lib/hecks/doors/handle.rb +206 -0
  191. data/lib/hecks/doors/json_door.rb +132 -0
  192. data/lib/hecks/doors/launcher_options.rb +129 -0
  193. data/lib/hecks/{cli → doors}/mcp_door.rb +25 -120
  194. data/lib/hecks/doors/mcp_door_scope.rb +97 -0
  195. data/lib/hecks/doors/ruby_door/aggregate_door.rb +162 -0
  196. data/lib/hecks/doors/ruby_door/chapter.rb +72 -0
  197. data/lib/hecks/doors/ruby_door.rb +77 -0
  198. data/lib/hecks/doors.rb +61 -0
  199. data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
  200. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
  201. data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
  202. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
  203. data/lib/hecks/embryonaut_bluebook.rb +5 -72
  204. data/lib/hecks/engine_agreement.rb +156 -0
  205. data/lib/hecks/facade.rb +23 -51
  206. data/lib/hecks/forms/app.rb +21 -76
  207. data/lib/hecks/forms/banking_presentation.rb +50 -0
  208. data/lib/hecks/forms/command_form_renderer.rb +10 -29
  209. data/lib/hecks/forms/field_renderer.rb +17 -156
  210. data/lib/hecks/forms/field_shape.rb +31 -222
  211. data/lib/hecks/forms/html.rb +12 -47
  212. data/lib/hecks/forms/index_renderer.rb +1 -4
  213. data/lib/hecks/forms/page.rb +3 -9
  214. data/lib/hecks/forms/params.rb +14 -60
  215. data/lib/hecks/forms/port_argument.rb +2 -19
  216. data/lib/hecks/forms/query_form_renderer.rb +4 -79
  217. data/lib/hecks/forms/record_renderer.rb +8 -75
  218. data/lib/hecks/forms/record_table.rb +7 -50
  219. data/lib/hecks/forms/reference_options.rb +3 -30
  220. data/lib/hecks/forms/value_object_shape.rb +8 -35
  221. data/lib/hecks/forms.rb +5 -33
  222. data/lib/hecks/fqn.rb +5 -12
  223. data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
  224. data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
  225. data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
  226. data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
  227. data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
  228. data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
  229. data/lib/hecks/framework.rb +11 -80
  230. data/lib/hecks/freezer.rb +5 -39
  231. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
  232. data/lib/hecks/fuzzing/combination_miner.rb +86 -0
  233. data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
  234. data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
  235. data/lib/hecks/fuzzing/differential.rb +156 -0
  236. data/lib/hecks/fuzzing/domain_generator.rb +615 -0
  237. data/lib/hecks/fuzzing/era_boundary.rb +71 -0
  238. data/lib/hecks/fuzzing/form_census.rb +136 -0
  239. data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
  240. data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
  241. data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
  242. data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
  243. data/lib/hecks/fuzzing/outside_world.rb +45 -0
  244. data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
  245. data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
  246. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
  247. data/lib/hecks/fuzzing/properties/guards.rb +190 -0
  248. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
  249. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
  250. data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
  251. data/lib/hecks/fuzzing/properties/querying.rb +175 -0
  252. data/lib/hecks/fuzzing/properties.rb +162 -0
  253. data/lib/hecks/fuzzing/qa_settings.rb +123 -0
  254. data/lib/hecks/fuzzing/replay.rb +505 -0
  255. data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
  256. data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
  257. data/lib/hecks/fuzzing/self_consistency.rb +339 -0
  258. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
  259. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
  260. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
  261. data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
  262. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
  263. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
  264. data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
  265. data/lib/hecks/fuzzing/shrinker.rb +166 -0
  266. data/lib/hecks/fuzzing/structural_skips.rb +33 -0
  267. data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
  268. data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
  269. data/lib/hecks/fuzzing/value_generator.rb +216 -0
  270. data/lib/hecks/fuzzing.rb +23 -0
  271. data/lib/hecks/grammar/evolve.rb +336 -0
  272. data/lib/hecks/grammar/expression.bluebook +15 -40
  273. data/lib/hecks/grammar/translation.bluebook +3 -6
  274. data/lib/hecks/grammar.rb +16 -41
  275. data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
  276. data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
  277. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
  278. data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
  279. data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
  280. data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
  281. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
  282. data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
  283. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
  284. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
  285. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
  286. data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
  287. data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
  288. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
  289. data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
  290. data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
  291. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
  292. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
  293. data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
  294. data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
  295. data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
  296. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
  297. data/lib/hecks/hecks/adapters/git.adapter +7 -0
  298. data/lib/hecks/hecks/adapters/git.rb +102 -0
  299. data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
  300. data/lib/hecks/hecks/adapters/host_http.rb +65 -0
  301. data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
  302. data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
  303. data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
  304. data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
  305. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
  306. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
  307. data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
  308. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
  309. data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
  310. data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
  311. data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
  312. data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
  313. data/lib/hecks/hecks/adapters/local_files.rb +46 -0
  314. data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
  315. data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
  316. data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
  317. data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
  318. data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
  319. data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
  320. data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
  321. data/lib/hecks/hecks/adapters/shell.adapter +7 -0
  322. data/lib/hecks/hecks/adapters/shell.rb +49 -0
  323. data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
  324. data/lib/hecks/hecks/adapters/terminal.rb +74 -0
  325. data/lib/hecks/hecks/codebase.behaviors +172 -0
  326. data/lib/hecks/hecks/codebase.bluebook +2453 -0
  327. data/lib/hecks/hecks/context_map.hecksagon +13 -0
  328. data/lib/hecks/hecks/custodian.behaviors +179 -0
  329. data/lib/hecks/hecks/custodian.bluebook +1847 -0
  330. data/lib/hecks/hecks/deploy.behaviors +74 -0
  331. data/lib/hecks/hecks/deploy.world +12 -0
  332. data/lib/hecks/hecks/environments/memory.world +10 -0
  333. data/lib/hecks/hecks/governance.world +7 -0
  334. data/lib/hecks/hecks/hecks.behaviors +62 -0
  335. data/lib/hecks/hecks/hecks.bluebook +124 -0
  336. data/lib/hecks/hecks/hecks.hecksagon +390 -0
  337. data/lib/hecks/hecks/hecks.world +42 -0
  338. data/lib/hecks/ir.rb +14 -83
  339. data/lib/hecks/kernel_capabilities.rb +194 -0
  340. data/lib/hecks/language/adapter.bluebook +2 -7
  341. data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
  342. data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
  343. data/lib/hecks/language/bluebook/bluebook.bluebook +26 -83
  344. data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
  345. data/lib/hecks/language/bluebook/command.bluebook +27 -144
  346. data/lib/hecks/language/bluebook/entity.bluebook +24 -97
  347. data/lib/hecks/language/bluebook/policy.bluebook +16 -67
  348. data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
  349. data/lib/hecks/language/bluebook/projection.bluebook +126 -60
  350. data/lib/hecks/language/bluebook/query.bluebook +19 -43
  351. data/lib/hecks/language/bluebook/shape.bluebook +23 -73
  352. data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
  353. data/lib/hecks/language/bluebook/vocabulary.bluebook +83 -198
  354. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
  355. data/lib/hecks/language/hecksagon/domain_port.bluebook +14 -19
  356. data/lib/hecks/language/hecksagon/hecksagon.bluebook +26 -25
  357. data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
  358. data/lib/hecks/language/oidc.json +35 -0
  359. data/lib/hecks/language/port.bluebook +4 -17
  360. data/lib/hecks/language/translation/translation.bluebook +3 -9
  361. data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
  362. data/lib/hecks/language/world/wiring.bluebook +1 -3
  363. data/lib/hecks/language/world/world.bluebook +5 -7
  364. data/lib/hecks/literal.rb +8 -50
  365. data/lib/hecks/mcp_stdio_guard.rb +4 -41
  366. data/lib/hecks/naming.rb +29 -137
  367. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
  368. data/lib/hecks/persistence_legacy_fixture.rb +113 -0
  369. data/lib/hecks/ports/access_control.rb +7 -23
  370. data/lib/hecks/ports/agent/answers.rb +5 -23
  371. data/lib/hecks/ports/agent.rb +47 -172
  372. data/lib/hecks/ports/authentication.rb +12 -39
  373. data/lib/hecks/ports/authorization.rb +16 -43
  374. data/lib/hecks/ports/clock.rb +5 -45
  375. data/lib/hecks/ports/extraction.rb +7 -12
  376. data/lib/hecks/ports/identity_assignment.rb +8 -29
  377. data/lib/hecks/ports/identity_generation.rb +6 -29
  378. data/lib/hecks/ports/identity_resolution.rb +2 -10
  379. data/lib/hecks/ports/key_vault.rb +9 -45
  380. data/lib/hecks/ports/persistence/append_only.rb +61 -64
  381. data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
  382. data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
  383. data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
  384. data/lib/hecks/ports/persistence/plugin.rb +13 -42
  385. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +44 -144
  386. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
  387. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +20 -85
  388. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +6 -25
  389. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +18 -24
  390. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
  391. data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
  392. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
  393. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
  394. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
  395. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
  396. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
  397. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
  398. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +19 -76
  399. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
  400. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
  401. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
  402. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
  403. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
  404. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +43 -127
  405. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
  406. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +131 -555
  407. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
  408. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
  409. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
  410. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
  411. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
  412. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
  413. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
  414. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +50 -47
  415. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -124
  416. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
  417. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
  418. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
  419. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
  420. data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
  421. data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
  422. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
  423. data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
  424. data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
  425. data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
  426. data/lib/hecks/ports/persistence/state_codec.rb +47 -108
  427. data/lib/hecks/ports/persistence.rb +7 -14
  428. data/lib/hecks/ports/projection.rb +42 -84
  429. data/lib/hecks/ports/query/in_memory.rb +10 -58
  430. data/lib/hecks/ports/query/ordering.rb +11 -32
  431. data/lib/hecks/ports/query.rb +14 -37
  432. data/lib/hecks/ports.rb +2 -8
  433. data/lib/hecks/projection_files.rb +285 -0
  434. data/lib/hecks/projections/bootstrap_table.rb +12 -44
  435. data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
  436. data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
  437. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
  438. data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
  439. data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
  440. data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
  441. data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
  442. data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
  443. data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
  444. data/lib/hecks/projections/deploy/fargate.rb +40 -207
  445. data/lib/hecks/projections/deploy/lambda.rb +191 -708
  446. data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
  447. data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
  448. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  449. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
  450. data/lib/hecks/projections/deploy/preview/script.rb +2 -14
  451. data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
  452. data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
  453. data/lib/hecks/projections/deploy/preview/template.rb +3 -59
  454. data/lib/hecks/projections/deploy/preview.rb +2 -67
  455. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
  456. data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
  457. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
  458. data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
  459. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
  460. data/lib/hecks/projections/deploy/scripts.rb +4 -57
  461. data/lib/hecks/projections/deploy/shared.rb +48 -147
  462. data/lib/hecks/projections/deploy/smoke.rb +4 -61
  463. data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
  464. data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
  465. data/lib/hecks/projections/deploy/template_diff.rb +5 -57
  466. data/lib/hecks/projections/diagrams.rb +22 -584
  467. data/lib/hecks/projections/glossary/html.rb +14 -130
  468. data/lib/hecks/projections/glossary/markdown.rb +8 -69
  469. data/lib/hecks/projections/glossary/mermaid.rb +9 -66
  470. data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
  471. data/lib/hecks/projections/glossary/sentences.rb +14 -159
  472. data/lib/hecks/projections/glossary.rb +30 -222
  473. data/lib/hecks/projections/ir.rb +3 -10
  474. data/lib/hecks/projections/model/deviations.rb +16 -43
  475. data/lib/hecks/projections/model.rb +12 -51
  476. data/lib/hecks/projections/oidc.rb +12 -56
  477. data/lib/hecks/projections/parser_table.rb +13 -32
  478. data/lib/hecks/projections/reference.rb +4 -18
  479. data/lib/hecks/projections/rust_vocabulary.rb +13 -251
  480. data/lib/hecks/projections/shape.rb +3 -29
  481. data/lib/hecks/projections/statements.rb +7 -86
  482. data/lib/hecks/projections/vocabulary.rb +6 -22
  483. data/lib/hecks/projections.rb +2 -16
  484. data/lib/hecks/projector/cli_projector.rb +154 -400
  485. data/lib/hecks/projector/docs_projector.rb +10 -201
  486. data/lib/hecks/projector/exporter.rb +42 -134
  487. data/lib/hecks/projector/ir_projector.rb +2 -9
  488. data/lib/hecks/projector/narrate_projector.rb +15 -189
  489. data/lib/hecks/projector/target.rb +13 -76
  490. data/lib/hecks/projector.rb +30 -121
  491. data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
  492. data/lib/hecks/quality_control/adapters/agent.rb +58 -0
  493. data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
  494. data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
  495. data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
  496. data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
  497. data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
  498. data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
  499. data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
  500. data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
  501. data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
  502. data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
  503. data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
  504. data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
  505. data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
  506. data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
  507. data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
  508. data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
  509. data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
  510. data/lib/hecks/quality_control/cli/child.rb +45 -0
  511. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
  512. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
  513. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
  514. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
  515. data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
  516. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
  517. data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
  518. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
  519. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
  520. data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
  521. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
  522. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
  523. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
  524. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
  525. data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
  526. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
  527. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
  528. data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
  529. data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
  530. data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
  531. data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
  532. data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
  533. data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
  534. data/lib/hecks/query_ir.rb +346 -0
  535. data/lib/hecks/query_ir_mcp.rb +198 -0
  536. data/lib/hecks/query_specification/common/comparators.rb +5 -26
  537. data/lib/hecks/query_specification/common/comparison.rb +35 -143
  538. data/lib/hecks/query_specification/common/dsl.rb +8 -24
  539. data/lib/hecks/query_specification/common/null_policy.rb +20 -70
  540. data/lib/hecks/query_specification/common/options.rb +2 -6
  541. data/lib/hecks/query_specification/common/where_clause.rb +2 -10
  542. data/lib/hecks/query_specification/field_path.rb +37 -77
  543. data/lib/hecks/query_specification/hop_path.rb +12 -76
  544. data/lib/hecks/release/gem_pin.rb +2 -26
  545. data/lib/hecks/release/runner/ci_publisher.rb +5 -8
  546. data/lib/hecks/release/runner/clean_tree.rb +42 -0
  547. data/lib/hecks/release/runner/commands.rb +2 -4
  548. data/lib/hecks/release/runner/console.rb +6 -0
  549. data/lib/hecks/release/runner/gem_publisher.rb +15 -9
  550. data/lib/hecks/release/runner/npm_publisher.rb +9 -53
  551. data/lib/hecks/release/runner/preflight.rb +29 -20
  552. data/lib/hecks/release/runner/published.rb +8 -26
  553. data/lib/hecks/release/runner/tagger.rb +31 -23
  554. data/lib/hecks/release/runner.rb +60 -38
  555. data/lib/hecks/rendering.rb +4 -26
  556. data/lib/hecks/router/namespace_installer.rb +15 -39
  557. data/lib/hecks/router.rb +4 -8
  558. data/lib/hecks/runtime/adapter_lookup.rb +124 -0
  559. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  560. data/lib/hecks/runtime/boot_gates.rb +3 -18
  561. data/lib/hecks/runtime/caller.rb +19 -59
  562. data/lib/hecks/runtime/capability_graph.rb +5 -21
  563. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  564. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +27 -160
  565. data/lib/hecks/runtime/command_interpreter.rb +124 -306
  566. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  567. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  568. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  569. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  570. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  571. data/lib/hecks/runtime/dependency_planning.rb +50 -104
  572. data/lib/hecks/runtime/dispatcher.rb +134 -243
  573. data/lib/hecks/runtime/entity_element.rb +40 -462
  574. data/lib/hecks/runtime/entity_interpreter.rb +64 -219
  575. data/lib/hecks/runtime/errors.rb +32 -115
  576. data/lib/hecks/runtime/event.rb +3 -25
  577. data/lib/hecks/runtime/identity.rb +18 -154
  578. data/lib/hecks/runtime/instance.rb +18 -150
  579. data/lib/hecks/runtime/interpreting.rb +14 -70
  580. data/lib/hecks/runtime/invocation.rb +28 -164
  581. data/lib/hecks/runtime/loader.rb +109 -194
  582. data/lib/hecks/runtime/outbox.rb +39 -262
  583. data/lib/hecks/runtime/policy_interpreter.rb +43 -202
  584. data/lib/hecks/runtime/port_operation_interpreter.rb +28 -97
  585. data/lib/hecks/runtime/query_interpreter.rb +122 -187
  586. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  587. data/lib/hecks/runtime/reaction_outcome.rb +89 -0
  588. data/lib/hecks/runtime/read_model_interpreter.rb +117 -208
  589. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  590. data/lib/hecks/runtime/reference_hop.rb +8 -81
  591. data/lib/hecks/runtime/refusal_wording.rb +28 -87
  592. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  593. data/lib/hecks/runtime/registry/verification.rb +213 -227
  594. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  595. data/lib/hecks/runtime/registry.rb +156 -247
  596. data/lib/hecks/runtime/remote_dispatcher.rb +68 -154
  597. data/lib/hecks/runtime/routing.rb +4 -15
  598. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  599. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  600. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  601. data/lib/hecks/runtime/tenant_check.rb +6 -68
  602. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  603. data/lib/hecks/runtime/value/admission.rb +16 -67
  604. data/lib/hecks/runtime/value/coercion.rb +162 -760
  605. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  606. data/lib/hecks/runtime/value/field_checks.rb +172 -0
  607. data/lib/hecks/runtime/value.rb +15 -84
  608. data/lib/hecks/runtime.rb +35 -69
  609. data/lib/hecks/rust_build/conformance.rb +158 -0
  610. data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
  611. data/lib/hecks/rust_build/coverage.rb +218 -0
  612. data/lib/hecks/rust_build/native_build.rb +124 -0
  613. data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
  614. data/lib/hecks/rust_build/project_rust.rb +332 -0
  615. data/lib/hecks/rust_build/wasm.rb +105 -0
  616. data/lib/hecks/rust_build/wasm_browser.rb +113 -0
  617. data/lib/hecks/rust_build.rb +144 -0
  618. data/lib/hecks/storehouse.rb +155 -582
  619. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +10 -34
  620. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  621. data/lib/hecks/three_zero/forms.yml +127 -0
  622. data/lib/hecks/three_zero.rb +12 -0
  623. data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
  624. data/lib/hecks/tools/comment_style.rb +1070 -0
  625. data/lib/hecks/tools/corpus_report.rb +87 -0
  626. data/lib/hecks/tools/deploy_recipe.rb +255 -0
  627. data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
  628. data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
  629. data/lib/hecks/tools/era_projection_backfill.rb +94 -0
  630. data/lib/hecks/tools/era_reattest.rb +146 -0
  631. data/lib/hecks/tools/evolve_run.rb +189 -0
  632. data/lib/hecks/tools/fuzz_sweep.rb +373 -0
  633. data/lib/hecks/tools/heki_compaction.rb +100 -0
  634. data/lib/hecks/tools/hoist_local_givens.rb +246 -0
  635. data/lib/hecks/tools/journal_compaction.rb +89 -0
  636. data/lib/hecks/tools/oidc_manifests.rb +70 -0
  637. data/lib/hecks/tools/query_ir_run.rb +81 -0
  638. data/lib/hecks/tools/regeneration_run.rb +166 -0
  639. data/lib/hecks/tools/rust_comment_style.rb +642 -0
  640. data/lib/hecks/tools/sequence_script.rb +62 -0
  641. data/lib/hecks/tools/tail_merge.rb +117 -0
  642. data/lib/hecks/tools/tenant_provisioning.rb +142 -0
  643. data/lib/hecks/tools/translation_audit.rb +208 -0
  644. data/lib/hecks/tools/translation_scaffold.rb +131 -0
  645. data/lib/hecks/tools.rb +86 -0
  646. data/lib/hecks/vendoring/git_environment.rb +2 -8
  647. data/lib/hecks/vendoring/git_source.rb +4 -10
  648. data/lib/hecks/vendoring.rb +7 -46
  649. data/lib/hecks/version.rb +3 -14
  650. data/lib/hecks.rb +52 -90
  651. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  652. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  653. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  654. data/qa/settings.yml +66 -0
  655. data/rust/.cargo/config.toml +6 -0
  656. data/rust/.gitignore +2 -0
  657. data/rust/Cargo.lock +54 -0
  658. data/rust/Cargo.toml +61 -0
  659. data/rust/build/.cargo/config.toml +3 -0
  660. data/rust/build/.gitignore +1 -0
  661. data/rust/build/Cargo.lock +7 -0
  662. data/rust/build/Cargo.toml +19 -0
  663. data/rust/build/src/build_artifact.rs +47 -0
  664. data/rust/build/src/cargo_sync.rs +248 -0
  665. data/rust/build/src/json.rs +421 -0
  666. data/rust/build/src/lineage_pass.rs +285 -0
  667. data/rust/build/src/main.rs +51 -0
  668. data/rust/build/src/optional_pass.rs +150 -0
  669. data/rust/build/src/pipeline.rs +264 -0
  670. data/rust/build/src/reserved_names.rs +76 -0
  671. data/rust/build/src/resolve.rs +124 -0
  672. data/rust/build/src/root.rs +25 -0
  673. data/rust/build/src/sidecars.rs +73 -0
  674. data/rust/build/src/subprocess.rs +51 -0
  675. data/rust/build/src/tmp.rs +31 -0
  676. data/rust/codegen/.cargo/config.toml +3 -0
  677. data/rust/codegen/.gitignore +1 -0
  678. data/rust/codegen/Cargo.lock +7 -0
  679. data/rust/codegen/Cargo.toml +20 -0
  680. data/rust/codegen/src/attr.rs +32 -0
  681. data/rust/codegen/src/bridging.rs +481 -0
  682. data/rust/codegen/src/commands.rs +1226 -0
  683. data/rust/codegen/src/constraints.rs +109 -0
  684. data/rust/codegen/src/dependency_planning.rs +209 -0
  685. data/rust/codegen/src/domain_generator.rs +1288 -0
  686. data/rust/codegen/src/exemplar.rs +230 -0
  687. data/rust/codegen/src/expr_emitter.rs +87 -0
  688. data/rust/codegen/src/fielded.rs +192 -0
  689. data/rust/codegen/src/hecks_naming.rs +33 -0
  690. data/rust/codegen/src/json.rs +335 -0
  691. data/rust/codegen/src/json_codec.rs +697 -0
  692. data/rust/codegen/src/literal.rs +207 -0
  693. data/rust/codegen/src/main.rs +290 -0
  694. data/rust/codegen/src/manifest.rs +227 -0
  695. data/rust/codegen/src/mutations.rs +784 -0
  696. data/rust/codegen/src/naming.rs +286 -0
  697. data/rust/codegen/src/ports.rs +208 -0
  698. data/rust/codegen/src/prelude.rs +116 -0
  699. data/rust/codegen/src/queries.rs +786 -0
  700. data/rust/codegen/src/reactions.rs +532 -0
  701. data/rust/codegen/src/read_models.rs +697 -0
  702. data/rust/codegen/src/reference_specs.rs +40 -0
  703. data/rust/codegen/src/registry.rs +966 -0
  704. data/rust/codegen/src/reserved_names.rs +76 -0
  705. data/rust/codegen/src/shared.rs +93 -0
  706. data/rust/codegen/src/skip_reason.rs +25 -0
  707. data/rust/codegen/src/types.rs +362 -0
  708. data/rust/host/.cargo/config.toml +3 -0
  709. data/rust/host/.gitignore +1 -0
  710. data/rust/host/Cargo.lock +4297 -0
  711. data/rust/host/Cargo.toml +61 -0
  712. data/rust/host/HECKS_RELEASE +1 -0
  713. data/rust/host/rds-ca-bundle.pem +2736 -0
  714. data/rust/host/src/api.rs +1596 -0
  715. data/rust/host/src/approval.rs +652 -0
  716. data/rust/host/src/auth.rs +1287 -0
  717. data/rust/host/src/bin/expr_harness.rs +46 -0
  718. data/rust/host/src/bin/lineage_harness.rs +119 -0
  719. data/rust/host/src/bin/mint_harness.rs +135 -0
  720. data/rust/host/src/checkout.rs +265 -0
  721. data/rust/host/src/dispatch.rs +1443 -0
  722. data/rust/host/src/expr_json.rs +1117 -0
  723. data/rust/host/src/field_hints.rs +52 -0
  724. data/rust/host/src/ir.rs +733 -0
  725. data/rust/host/src/journal.rs +874 -0
  726. data/rust/host/src/lambda_client.rs +378 -0
  727. data/rust/host/src/log.rs +147 -0
  728. data/rust/host/src/main.rs +474 -0
  729. data/rust/host/src/mint.rs +1865 -0
  730. data/rust/host/src/payments/keystore.rs +330 -0
  731. data/rust/host/src/payments/tests.rs +1400 -0
  732. data/rust/host/src/payments.rs +767 -0
  733. data/rust/host/src/presentation.rs +698 -0
  734. data/rust/host/src/presentation_write.rs +1431 -0
  735. data/rust/host/src/query_step.rs +149 -0
  736. data/rust/host/src/rate_limit.rs +904 -0
  737. data/rust/host/src/reference_transform.rs +241 -0
  738. data/rust/host/src/reference_validate.rs +338 -0
  739. data/rust/host/src/resend.rs +296 -0
  740. data/rust/host/src/secrets.rs +106 -0
  741. data/rust/host/src/server.rs +663 -0
  742. data/rust/host/src/storage_shape.rs +175 -0
  743. data/rust/host/src/test_pg.rs +40 -0
  744. data/rust/host/src/ui_schema.rs +1351 -0
  745. data/rust/host/src/wasm_runner.rs +191 -0
  746. data/rust/host/src/web/newsletter/cooldown.rs +126 -0
  747. data/rust/host/src/web/newsletter/tests.rs +104 -0
  748. data/rust/host/src/web/newsletter.rs +310 -0
  749. data/rust/host/src/web/newsletter_send.rs +357 -0
  750. data/rust/host/src/web/registrations.rs +1505 -0
  751. data/rust/host/src/web.rs +2937 -0
  752. data/rust/lsp/.gitignore +1 -0
  753. data/rust/lsp/Cargo.lock +7 -0
  754. data/rust/lsp/Cargo.toml +16 -0
  755. data/rust/lsp/README.md +133 -0
  756. data/rust/lsp/src/diagnostics.rs +165 -0
  757. data/rust/lsp/src/json.rs +385 -0
  758. data/rust/lsp/src/main.rs +399 -0
  759. data/rust/lsp/src/outline.rs +177 -0
  760. data/rust/lsp/src/rpc.rs +67 -0
  761. data/rust/parser/.cargo/config.toml +5 -0
  762. data/rust/parser/.gitignore +1 -0
  763. data/rust/parser/Cargo.lock +7 -0
  764. data/rust/parser/Cargo.toml +21 -0
  765. data/rust/parser/src/build/closed_sets.rs +27 -0
  766. data/rust/parser/src/build/identity.rs +276 -0
  767. data/rust/parser/src/build/mod.rs +12 -0
  768. data/rust/parser/src/build/naming.rs +141 -0
  769. data/rust/parser/src/build/pattern_subset.rs +161 -0
  770. data/rust/parser/src/build/query_derive.rs +72 -0
  771. data/rust/parser/src/build/query_inference.rs +269 -0
  772. data/rust/parser/src/build/query_options.rs +100 -0
  773. data/rust/parser/src/build/read_model.rs +94 -0
  774. data/rust/parser/src/build/references.rs +32 -0
  775. data/rust/parser/src/canonical.rs +114 -0
  776. data/rust/parser/src/diag.rs +55 -0
  777. data/rust/parser/src/emit.rs +953 -0
  778. data/rust/parser/src/expr/ast_json.rs +147 -0
  779. data/rust/parser/src/expr/evaluator.rs +81 -0
  780. data/rust/parser/src/expr/mod.rs +84 -0
  781. data/rust/parser/src/expr/resolver.rs +497 -0
  782. data/rust/parser/src/ir.rs +387 -0
  783. data/rust/parser/src/keywords.rs +585 -0
  784. data/rust/parser/src/lex.rs +662 -0
  785. data/rust/parser/src/main.rs +252 -0
  786. data/rust/parser/src/parse/aggregate.rs +612 -0
  787. data/rust/parser/src/parse/chapter.rs +517 -0
  788. data/rust/parser/src/parse/command.rs +637 -0
  789. data/rust/parser/src/parse/domain_port.rs +127 -0
  790. data/rust/parser/src/parse/entity.rs +527 -0
  791. data/rust/parser/src/parse/file.rs +67 -0
  792. data/rust/parser/src/parse/hecksagon.rs +231 -0
  793. data/rust/parser/src/parse/lifecycle.rs +162 -0
  794. data/rust/parser/src/parse/mod.rs +1262 -0
  795. data/rust/parser/src/parse/policy.rs +87 -0
  796. data/rust/parser/src/parse/process_manager.rs +305 -0
  797. data/rust/parser/src/parse/query.rs +100 -0
  798. data/rust/parser/src/parse/read_model.rs +214 -0
  799. data/rust/parser/src/parse/value_object.rs +250 -0
  800. data/rust/parser/src/ruby_value.rs +422 -0
  801. data/rust/project/bridging.rb +257 -0
  802. data/rust/project/commands.rb +877 -0
  803. data/rust/project/constraints.rb +86 -0
  804. data/rust/project/dependency_planning.rb +178 -0
  805. data/rust/project/domain_generator.rb +980 -0
  806. data/rust/project/exemplar.rb +148 -0
  807. data/rust/project/expr_emitter.rb +72 -0
  808. data/rust/project/fielded.rb +141 -0
  809. data/rust/project/json_codec.rb +641 -0
  810. data/rust/project/mutations.rb +721 -0
  811. data/rust/project/naming.rb +205 -0
  812. data/rust/project/ports.rb +65 -0
  813. data/rust/project/queries.rb +493 -0
  814. data/rust/project/reactions.rb +317 -0
  815. data/rust/project/read_models.rb +690 -0
  816. data/rust/project/reference_specs.rb +27 -0
  817. data/rust/project/registry.rb +396 -0
  818. data/rust/project/skip_reason.rb +29 -0
  819. data/rust/project/types.rb +225 -0
  820. data/rust/project/write_if_changed.rb +83 -0
  821. data/rust/project.rb +30 -0
  822. data/rust/project_rust_pipeline.rb +383 -0
  823. data/rust/src/exemplar/commands.rs +279 -0
  824. data/rust/src/exemplar/constraints.rs +18 -0
  825. data/rust/src/exemplar/fielded.rs +280 -0
  826. data/rust/src/exemplar/json.rs +277 -0
  827. data/rust/src/exemplar/mod.rs +17 -0
  828. data/rust/src/exemplar/mutations.rs +80 -0
  829. data/rust/src/exemplar/queries.rs +33 -0
  830. data/rust/src/exemplar/reactions.rs +88 -0
  831. data/rust/src/exemplar/read_models.rs +48 -0
  832. data/rust/src/exemplar/registry.rs +166 -0
  833. data/rust/src/exemplar/types.rs +44 -0
  834. data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
  835. data/rust/src/kernel/attribute_shapes/list.rs +18 -0
  836. data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
  837. data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
  838. data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
  839. data/rust/src/kernel/cli.rs +585 -0
  840. data/rust/src/kernel/dispatch.rs +1090 -0
  841. data/rust/src/kernel/expr.rs +458 -0
  842. data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
  843. data/rust/src/kernel/expression_operators/comparison.rs +48 -0
  844. data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
  845. data/rust/src/kernel/expression_operators/logical.rs +13 -0
  846. data/rust/src/kernel/expression_operators/membership.rs +18 -0
  847. data/rust/src/kernel/expression_operators/mod.rs +68 -0
  848. data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
  849. data/rust/src/kernel/expression_operators/positional.rs +126 -0
  850. data/rust/src/kernel/expression_operators/presence.rs +101 -0
  851. data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
  852. data/rust/src/kernel/expression_operators/sized.rs +35 -0
  853. data/rust/src/kernel/expression_operators/text.rs +79 -0
  854. data/rust/src/kernel/expression_operators/to_string.rs +18 -0
  855. data/rust/src/kernel/json.rs +779 -0
  856. data/rust/src/kernel/mod.rs +133 -0
  857. data/rust/src/kernel/named_query.rs +219 -0
  858. data/rust/src/kernel/orchestrate.rs +1810 -0
  859. data/rust/src/kernel/pattern.rs +307 -0
  860. data/rust/src/kernel/query_comparators.rs +206 -0
  861. data/rust/src/kernel/query_ordering.rs +103 -0
  862. data/rust/src/kernel/read_model.rs +914 -0
  863. data/rust/src/kernel/reference_lookup.rs +149 -0
  864. data/rust/src/kernel/repository.rs +468 -0
  865. data/rust/src/kernel/routing.rs +612 -0
  866. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
  867. data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
  868. data/rust/src/kernel/vocab/field_hint.rs +70 -0
  869. data/rust/src/kernel/vocab/mod.rs +20 -0
  870. data/rust/src/kernel/vocab/query_comparator.rs +65 -0
  871. data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
  872. data/rust/src/lib.rs +8 -0
  873. data/rust/src/main.rs +16 -0
  874. data/rust/web/.gitignore +1 -0
  875. data/rust/web/Cargo.lock +119 -0
  876. data/rust/web/Cargo.toml +18 -0
  877. data/rust/web/src/lib.rs +8 -0
  878. metadata +478 -12
  879. data/lib/hecks/facade/cli_door.rb +0 -178
  880. data/lib/hecks/facade/cli_runner.rb +0 -271
  881. data/lib/hecks/facade/handle.rb +0 -293
  882. data/lib/hecks/facade/json_door.rb +0 -247
  883. data/lib/hecks/facade/surface/aggregate_door.rb +0 -222
  884. data/lib/hecks/facade/surface/chapter.rb +0 -116
  885. data/lib/hecks/facade/surface.rb +0 -61
  886. data/lib/hecks/mcp_door_scope.rb +0 -176
@@ -4,46 +4,8 @@ require_relative "shared"
4
4
  module Hecks
5
5
  module Projections
6
6
  module Deploy
7
- # The AWS Lambda deploy target — docs/decisions/0018-rehydrate-replay-lambda-host.md.
8
- # An export (`Projector::Target#projects_as`'s own `needs_world: true`),
9
- # not an ordinary projection: it reads a domain's own
10
- # `deployed_to("AwsLambda")` `.world` settings, not only its
11
- # declaration, because a running system has to know how it is wired.
12
- #
13
- # `bin/project_deploy` finds and boots the domain's own chapter and
14
- # its `.world`/`.hecksagon` bindings, calls this through
15
- # `Projector.call(:aws_lambda, bluebook:, options:, world:)`, and
16
- # writes the returned tree — see that script for the CLI-facing parts
17
- # (ARGV parsing, `--tenant`/`--schema`, finding the `.world` file) this
18
- # target never needs to know about.
19
- #
20
- # ## What this generates
21
- #
22
- # The SAM template and build Makefile for `rust/host` (the
23
- # wasmtime+Postgres Lambda entry point) from a domain's own `.world`
24
- # file, the same way `bin/project_wasm` generates the `.wasm` artifact
25
- # from the domain's own `.bluebook` — no hand-authored deployment
26
- # config for any given domain, only a generator run against whatever it
27
- # declares:
28
- #
29
- # deployed_to("AwsLambda") { region "..."; memory 512; timeout 10 }
30
- #
31
- # is a verb in a domain's `.world` file, read through `Hecks.world`'s
32
- # own generic settings bag
33
- # (`lib/hecks/bluebook/dsl/world_builder.rb#method_missing`).
34
- #
35
- # Self-contained: the returned `template.yaml` owns its own private
36
- # VPC, subnets, and RDS Postgres instance, not just the Lambda — no
37
- # externally-supplied database, no secret anyone has to type (RDS's
38
- # own `ManageMasterUserPassword` plus a CloudFormation dynamic
39
- # reference compose `DATABASE_URL` at deploy time). `PackageType:
40
- # Zip`, `Runtime: provided.al2023` — no container, no Docker, no ECR.
41
- #
42
- # `Shared` (`lib/hecks/projections/deploy/shared.rb`) carries the parts
43
- # of this generator `Fargate` needs too — VPC/subnet/security-group
44
- # resources, RDS/Aurora, `bastion.yaml`, and the era-minting/
45
- # translation Make recipes — plain functions, not a registered target
46
- # of their own.
7
+ # The AWS Lambda deploy target (ADR 0018): renders `template.yaml`,
8
+ # `Makefile`, `samconfig.toml`, and `bastion.yaml` from a domain's `.world`.
47
9
  module Lambda
48
10
  extend Projector::Target
49
11
 
@@ -51,37 +13,14 @@ module Hecks
51
13
 
52
14
  module_function
53
15
 
54
- # Generates `template.yaml`, `Makefile`, `samconfig.toml`, and
55
- # (unless this domain borrows another domain's RDS instance)
56
- # `bastion.yaml` for one domain's `deployed_to("AwsLambda")` deploy
57
- # target.
16
+ # Renders `template.yaml`, `Makefile`, `samconfig.toml`, and (unless
17
+ # this domain shares another domain's RDS instance) `bastion.yaml`.
58
18
  #
59
- # `bluebook:` establishes admission — a real chapter, not merely a
60
- # directory — but generation itself reads from `options`: every
61
- # loaded chapter (`options[:cross_domain_registry]`), not only this
62
- # one, is what a cross-domain policy's own invoke grant needs.
63
- #
64
- # @param bluebook [Bluebook::Behaviour::Chapter] the domain's own booted chapter
65
- # @param options [Hash] generation options
66
- # @option options [Bluebook::World] :world the domain's `.world` settings,
67
- # required — `Projector.call`'s own `world:` keyword merges this in
68
- # @option options [String] :domain_dir the domain directory's path
69
- # @option options [String] :root the hecks project root, for `rust/host`,
70
- # `rust/dist`, and this project's own `Gemfile.lock`
71
- # @option options [String] :world_file the `.world` file's own path, quoted in
72
- # refusal messages
73
- # @option options [Runtime::Registry] :cross_domain_registry every chapter this
74
- # domain loads (its own, plus any framework attachments), for resolving
75
- # cross-domain policy invoke targets
76
- # @option options [Hash] :tenant `--tenant`/`--schema` overrides
77
- # (`{tenant: "acme", schema: "acme"}`), or omitted for an ordinary deploy
78
- # @return [Hash{String => String}] `"template.yaml"`, `"Makefile"`,
79
- # `"samconfig.toml"`, and — unless this domain declares `database "Shared"` —
80
- # `"bastion.yaml"`, each mapped to its rendered content
81
- # @raise [ArgumentError] if the domain's own deploy settings conflict (both
82
- # `web "Rust"` and a `lambda_handler.rb`, `database "Shared"` with no
83
- # `owner`, and the other refusals `deploy.bluebook`'s own
84
- # `LambdaTarget.Declare` and this generator raise)
19
+ # @param bluebook [Bluebook::Behaviour::Chapter] the domain's booted chapter
20
+ # @param options [Hash] :world, :domain_dir, :root, :world_file, and
21
+ # :cross_domain_registry are required; :tenant is optional
22
+ # @return [Hash{String => String}] generated file contents by filename
23
+ # @raise [ArgumentError] if `deployed_to("AwsLambda")` is invalid
85
24
  def call(bluebook:, options: {})
86
25
  world = options.fetch(:world)
87
26
  domain = options.fetch(:domain_dir)
@@ -95,19 +34,9 @@ module Hecks
95
34
 
96
35
  deploy_settings = world.for_verb("deployed_to")
97
36
 
98
- # **`PII` detection** — structural only, no live boot: `load_bluebooks`/a
99
- # bare `.hecksagon` `Kernel.load` populate `registry.pending_privacy_
100
- # markings` (`AggregateDoor#mark_sensitive`/`BindingProxy#mark_sensitive`,
101
- # lib/hecks/runtime/registry.rb) the same way `Runtime::Loader.boot`'s
102
- # own first phase does, without that method's later `run_boot_gates!`/
103
- # `dispatcher_for` steps — those need a live persistence adapter (a real
104
- # Postgres connection for a PostgresEra-bound domain), which a static
105
- # template generator must never require. A registry of its own, not
106
- # `cross_domain_registry` — that one boots this domain's bluebook too,
107
- # but through a different, hand-rolled port/adapter load (persistence
108
- # + extraction + memory + prism only) than the one `pending_privacy_
109
- # markings` was verified against; kept separate rather than assumed
110
- # equivalent.
37
+ # Structural-only load (no `run_boot_gates!`/live persistence adapter)
38
+ # populates `registry.pending_privacy_markings` for pii detection below
39
+ # without requiring a real Postgres connection to generate a template.
111
40
  pii_registry = Hecks::Runtime::Registry.new(root: File.expand_path(domain))
112
41
  Hecks.with_registry(pii_registry) do
113
42
  bootstrap = Hecks::Ports::Loading.bootstrap
@@ -118,21 +47,12 @@ module Hecks
118
47
  Dir.glob(File.join(bluebook_dir, "*.hecksagon")).each { |file| Kernel.load(file) }
119
48
  end
120
49
 
121
- # Only `category: "pii"` counts — `phi` or any other vocabulary a
122
- # consuming domain's own `mark_sensitive` calls use stays a Governance/
123
- # redaction concern (Privacy::Marking's own mechanism) without also
124
- # provisioning CloudFront/WAF, which this generator has no way to know
125
- # is warranted for, say, health data under a different compliance
126
- # regime entirely. A domain wanting the same protection for a
127
- # non-"pii" category marks it "pii" too — the category string is
128
- # open-ended by design (privacy.bluebook's own header), not a closed
129
- # enum this generator could instead enumerate.
50
+ # Only `category: "pii"` triggers CloudFront/WAF provisioning; other
51
+ # marking categories (e.g. "phi") stay a Governance/redaction concern.
130
52
  pii_detected = pii_registry.pending_privacy_markings.any? { |marking| marking[:category].to_s == "pii" }
131
53
 
132
- # **The tenant override itself** — see this file's own header comment on
133
- # `--tenant`/`--schema` for the full reasoning. Applied here, before
134
- # `infra_name`/`hecks_schema` are computed below, so both read it the
135
- # same way they already read any other `deployed_to` setting.
54
+ # Applied before infra_name/hecks_schema below, so both read the
55
+ # override the same way they read any other `deployed_to` setting.
136
56
  if tenant_options[:tenant]
137
57
  base_stack_name = deploy_settings[:stack_name] || domain_name
138
58
  deploy_settings = deploy_settings.merge(
@@ -141,57 +61,20 @@ module Hecks
141
61
  )
142
62
  end
143
63
 
144
- # **Every AWS-facing name below** — stack, Lambda logical id (and so the
145
- # RDS logical id derived from it, `#{logical_id}Db`), S3 prefixes,
146
- # bastion/secret names — reads this, not `domain_name`, from here on.
147
- # Ordinarily they're the same string, and most domains never set
148
- # `stack_name` at all. They diverge on purpose for a domain whose
149
- # declared identity (`Hecks.bluebook`, hence `world.domain`, hence
150
- # `.world`'s own filename — `domain_name` above, needed just to find
151
- # that file) does not match its live AWS stack's own name: a
152
- # CloudFormation stack cannot be renamed in place, and this domain's
153
- # logical ids are already load-bearing on a real RDS instance other
154
- # domains borrow from in Shared mode (see `owner_stack_name` below) —
155
- # regenerating the template under a new stack/logical-id would not
156
- # rename anything, it would stand up a second, empty, disconnected
157
- # set of AWS resources next to the real one. `stack_name
158
- # "embryonaut"` in `deployed_to("AwsLambda")` pins the original name
159
- # forever, independent of any later `formerly_known_as`-style rename
160
- # to the domain's own declared identity.
64
+ # Every AWS-facing name (stack, logical ids, S3 prefixes, secrets)
65
+ # reads `infra_name`, not `domain_name`, so a `stack_name` override
66
+ # survives a later `formerly_known_as` rename of the declared identity
67
+ # without renaming (and disconnecting from) the live AWS stack.
161
68
  infra_name = deploy_settings[:stack_name] || domain_name
162
69
 
163
- # **The actual Postgres/RDS identifier** — every domain before this one
164
- # ever picked a single-word `stack_name` (or none at all, falling back
165
- # to a single-word directory `domain_name`), so `infra_name` itself was
166
- # already a valid database name and nothing ever needed a second
167
- # variable. `stack_name "quality-control-webhook"` (a real, live,
168
- # multi-word choice — the first one) broke that assumption for real:
169
- # RDS's own DBName/DatabaseName parameter refuses it outright
170
- # ("DBName must begin with a letter and contain only alphanumeric
171
- # characters" — confirmed live, a genuine CREATE_FAILED before this
172
- # existed), and `infra_name` unsanitized is also what every downstream
173
- # consumer (WebFunction/#{logical_id}'s own DB_NAME Environment
174
- # variable, every Makefile shell command that connects `-d
175
- # #{infra_name}`) reads as the database to actually connect to — so
176
- # every one of those has to agree on the same sanitized spelling, not
177
- # just the one CloudFormation property that happened to refuse loudly.
178
- # Stack names, logical ids, and S3 prefixes are unchanged (still read
179
- # `infra_name` directly) — CloudFormation/S3 both allow hyphens fine;
180
- # only the actual database identifier needed this at all.
70
+ # RDS's own DBName/DatabaseName parameter refuses non-alphanumeric
71
+ # characters; every downstream consumer (Environment vars, Makefile
72
+ # shell commands) has to read the same sanitized spelling.
181
73
  db_name = infra_name.gsub(/[^a-zA-Z0-9]/, "")
182
74
 
183
- # Validated, not hand-checked — dispatches into lib/hecks/deploy's
184
- # own LambdaTarget.Declare (the same given/invariant machinery every
185
- # other kind of bluebook mistake in this codebase is caught by),
186
- # replacing a `fetch(:region) { abort ... }` chain with
187
- # real, named, corpus-testable refusals: memory/timeout are actual
188
- # Integers here (never silently stringified) and checked against
189
- # Lambda's own real 128-10240 MB / 900s ceilings, not just "present or
190
- # not." Only the default values stay Ruby's own concern —
191
- # deploy.bluebook's own header explains why: a command-level `default:`
192
- # is honored only at the JSON/Rust-codegen boundary, never by Ruby's
193
- # own direct dispatch — so this validates a fully-resolved target,
194
- # after defaulting, not instead of it.
75
+ # Dispatches into LambdaTarget.Declare's given/invariant machinery for
76
+ # named, corpus-testable refusals (memory/timeout checked as real
77
+ # Integers against Lambda's 128-10240 MB / 900s ceilings).
195
78
  deploy_dispatcher = Hecks.boot(File.expand_path("../../deploy", __dir__))
196
79
  begin
197
80
  target = deploy_dispatcher.dispatch(
@@ -201,14 +84,7 @@ module Hecks
201
84
  region: { value: deploy_settings[:region] },
202
85
  memory: { value: deploy_settings.fetch(:memory, 512) },
203
86
  timeout: { value: deploy_settings.fetch(:timeout, 10) },
204
- # "Postgres" — Embryonaut's own live choice, and every domain
205
- # declared before this setting existed — unless a domain's own
206
- # deployed_to("AwsLambda") names "Aurora" explicitly.
207
87
  database: { value: deploy_settings.fetch(:database, "Postgres") },
208
- # "None" — every domain declared before this setting existed,
209
- # Embryonaut included (its own public web UI is the separate,
210
- # pre-existing lambda_handler.rb-file-presence mechanism, not this
211
- # attribute) — unless a domain names "Rust" explicitly.
212
88
  web: { value: deploy_settings.fetch(:web, "None") }
213
89
  }
214
90
  ).instance
@@ -216,68 +92,26 @@ module Hecks
216
92
  raise ArgumentError, "#{world_file}'s deployed_to(\"AwsLambda\") is invalid: #{e.message}"
217
93
  end
218
94
 
219
- # `dispatch "None"` / `secret_env "..."` — deploy-time wiring facts, not
220
- # business invariants `Deploy::LambdaTarget.Declare` itself needs to
221
- # hold, read straight off `deploy_settings` the same way `owner`/
222
- # `owner_stack`/`schema`/`stack_name` already are (this file's own
223
- # comment on `infra_name`, above, has the full reasoning for why those
224
- # stay Ruby-level rather than validated attributes).
225
- #
226
- # `dispatch "None"` decides whether the primary rust/host dispatch
227
- # Lambda (`#{logical_id}`, computed further below) gets generated at
228
- # all. Default (unset, every domain declared
229
- # before this setting existed) keeps generating the rust/host wasmtime
230
- # dispatch Lambda exactly as always — "None" is the new, opt-in case: no
231
- # rust/host dispatch Lambda for this domain at all, because a domain
232
- # with no `.bluebook`-shaped command surface of its own to dispatch
233
- # through (QualityControl's own real first user: a driving GitHub
234
- # webhook adapter, `lib/hecks/adapters/driving/github_webhook.rb`,
235
- # calling straight into `QualityControl::Clearance` through Ruby, never
236
- # through rust/host's wasmtime dispatch at all) has nothing for that
237
- # Lambda to ever serve. Reuses the existing `lambda_handler.rb`-file-
238
- # presence WebFunction mechanism wholesale (Runtime: ruby3.2, Function
239
- # URL AuthType `NONE`, container build + patch-pg-native for `pg`'s native
240
- # extension) rather than inventing a second Ruby-Lambda shape — the
241
- # WebFunction path already proved itself for real (Embryonaut's own live
242
- # stack) before this ever needed a second consumer; deploy.bluebook's
243
- # own generator, "one generator reads what a domain declares," extends
244
- # rather than forks.
95
+ # `dispatch "None"` skips generating a rust/host dispatch Lambda for a
96
+ # domain with no `.bluebook` command surface to dispatch through (e.g.
97
+ # QualityControl's GitHub webhook, dispatching straight into Ruby);
98
+ # such a domain's WebFunction becomes its only Lambda.
245
99
  dispatch_none = deploy_settings[:dispatch].to_s == "None"
246
100
 
247
- # `secret_env "GITHUB_WEBHOOK_SECRET"` — names the env var
248
- # WebFunction's own `lambda_handler.rb` should find the fetched secret's
249
- # real plaintext under, once it resolves `#{secret_env}_ARN` (below,
250
- # WebFunction's own Environment) via the AWS SDK the same cold-start
251
- # pattern DATABASE_URL/SESSION_SECRET/GOOGLE_CLIENT_ID already use
252
- # (WebFunction's own header comment: "fetch at runtime, over the SDK,
253
- # never let CloudFormation/Lambda configuration see it at all"). Wired
254
- # generically — any WebFunction-shaped domain declaring one gets its own
255
- # auto-generated, never-typed-or-seen SecretsManager secret + least-
256
- # privilege grant, not something specific to GitHub webhooks; this
257
- # domain's own `qa/lambda_handler.rb` is simply the first real consumer.
101
+ # Names the env var WebFunction's `lambda_handler.rb` finds the fetched
102
+ # secret's plaintext under, once it resolves `#{secret_env}_ARN`.
258
103
  webhook_secret_env = deploy_settings[:secret_env]
259
104
 
260
- # `handler_module "QaWebhookLambdaHandler"` — the same name
261
- # `lambda_handler.rb`'s own module actually defines, named here rather
262
- # than assumed, since `dispatch "None"` is a generic mechanism (any
263
- # future domain could adopt it, each with its own wrapper module name)
264
- # — "WebLambdaHandler" stays the fixed, historical default for every
265
- # domain that has never set this (Embryonaut's own, unchanged).
105
+ # The module name `lambda_handler.rb` actually defines; named here
106
+ # since `dispatch "None"` domains may each pick their own.
266
107
  webhook_handler_module = deploy_settings.fetch(:handler_module, "WebLambdaHandler")
267
108
  if dispatch_none && !deploy_settings.key?(:handler_module)
268
109
  raise ArgumentError, "#{world_file}'s deployed_to(\"AwsLambda\") declares dispatch \"None\" but no handler_module — add handler_module \"YourModuleName\" naming the module #{domain}/lambda_handler.rb defines."
269
110
  end
270
111
 
271
- # Every policy in every loaded chapter, not just the target domain's
272
- # own top-level list — `uses_framework` (Compliance's own header:
273
- # "genuinely supports either mode") could in principle attach a chapter
274
- # that itself declares a cross-domain policy, and a policy nested inside
275
- # `aggregate "X" do ... end` is exactly as real a cross-domain trigger as
276
- # one declared at the bluebook's own top level (docs/implemented/guides/policies-
277
- # and-process-managers.md: "A policy does not care where it was
278
- # written"). `to_h`'s own `policies` already flattens aggregate-scoped
279
- # and top-level policies into one list per bluebook — read the same way
280
- # `rust/project/reactions.rb`'s `emit_cross_domain_policy_table` does.
112
+ # Scans every loaded chapter, not just this domain's own — a
113
+ # `uses_framework`-attached chapter can itself declare a cross-domain
114
+ # policy, including one nested inside `aggregate "X" do ... end`.
281
115
  cross_domain_lambda_targets = cross_domain_registry.bluebooks.flat_map { |chapter_name, bluebook|
282
116
  bluebook.policies.select(&:target_domain).map(&:target_domain)
283
117
  }.uniq.sort
@@ -291,15 +125,8 @@ module Hecks
291
125
  shared = database == "Shared"
292
126
  rust_web = target.state[:web].value == "Rust"
293
127
 
294
- # **`WAFv2` for CloudFront needs us-east-1** — not a preference, a hard
295
- # AWS API constraint (`AWS::WAFv2::WebACL` with `Scope: CLOUDFRONT` is
296
- # refused by CloudFormation outside us-east-1, independent of which
297
- # region the distribution itself, being global, would otherwise
298
- # suggest). Refused here, before writing a single file, the same
299
- # discipline this generator already holds every other one of its own
300
- # refusals to — a generated template that would only fail at deploy
301
- # time, in a region a caller may not think to suspect, is a worse
302
- # failure mode than refusing now with the fix named.
128
+ # AWS::WAFv2::WebACL with Scope: CLOUDFRONT is refused by
129
+ # CloudFormation outside us-east-1; refused here before writing a file.
303
130
  if pii_detected && region != "us-east-1"
304
131
  raise ArgumentError, "#{world_file} marks a field \"pii\" but deployed_to(\"AwsLambda\") sets region " \
305
132
  "#{region.inspect} — a CloudFront-scoped WAFv2 WebACL can only be created in " \
@@ -307,25 +134,13 @@ module Hecks
307
134
  "genuinely holds none."
308
135
  end
309
136
 
310
- # Read straight off `deploy_settings` (the raw `WorldBuilder` bag), not
311
- # through `target` — these two are optional and pii-only, unlike every
312
- # `target.state[...]` field above, which `LambdaTarget` validates as
313
- # always-present for every `AwsLambda` deployment regardless of pii.
314
- # "none" (`RestrictionType: none`, `Locations: []`) is CloudFront's own
315
- # default shape for "no restriction configured" — declaring one later
316
- # is a `deployed_to` edit, not a template rewrite.
137
+ # Read off `deploy_settings` directly, not `target` — these two are
138
+ # optional and pii-only, unlike every always-validated `target.state`.
317
139
  geo_restriction_type = deploy_settings[:geo_restriction] || "none"
318
140
  geo_restriction_countries = deploy_settings[:geo_restriction_countries] || []
319
141
 
320
- # **The storehouse** — this domain provisions no RDS/VPC of its own at all;
321
- # it borrows another already-deployed domain's instance instead,
322
- # isolated by a native Postgres schema (this domain's own lowercase
323
- # name) rather than a separate database. `owner` is read the same way
324
- # `pg_version`/`google_oauth_present` below are — a Ruby-level fact off
325
- # `deploy_settings` directly, not one of `Deploy::LambdaTarget.Declare`'s
326
- # own validated attributes (deploy.bluebook's own comment on why: which
327
- # domain owns the shared instance is a deploy-time wiring fact, not a
328
- # business invariant).
142
+ # Shared mode provisions no RDS/VPC of its own; it borrows another
143
+ # already-deployed domain's instance, isolated by Postgres schema.
329
144
  if shared
330
145
  owner_domain_name = deploy_settings[:owner] or raise ArgumentError, <<~MSG
331
146
  #{world_file}'s deployed_to("AwsLambda") declares database "Shared" but no owner. Add one, e.g.:
@@ -338,182 +153,83 @@ module Hecks
338
153
 
339
154
  naming the already-deployed domain whose Postgres instance this one borrows.
340
155
  MSG
341
- # `hecks-<lowercase name>` / lowercase name-as-dbname — the same
342
- # two conventions `stack_name`/`DBName: #{infra_name}` below already
343
- # commit to for this domain, and the default for the owner too. Not
344
- # always the owner's real stack name, though — a domain generated
345
- # before this convention existed keeps whatever it was actually
346
- # deployed as (real, live example: Embryonaut's own stack is
347
- # "hecksagain-embryonaut", a legacy prefix, not "hecks-embryonaut" --
348
- # `deploy:`'s own owner-outputs lookup below would look for a stack
349
- # that has never existed and fail before this domain's own `sam
350
- # deploy` ever ran). `owner_stack "hecksagain-embryonaut"` is the
351
- # escape hatch, read the same deploy_settings-direct way `owner`
352
- # itself is (deploy.bluebook's own comment on why neither lives in
353
- # the validated LambdaTarget aggregate) — optional, defaults to the
354
- # ordinary convention for every domain that doesn't need it.
156
+ # `owner_stack` is the escape hatch for an owner whose live stack
157
+ # name predates the "hecks-<lowercase name>" convention below.
355
158
  owner_stack_name = deploy_settings[:owner_stack] || "hecks-#{owner_domain_name.downcase}"
356
159
  owner_db_name = owner_domain_name.downcase
357
160
  end
358
161
 
359
- # `HECKS_SCHEMA` — rust/host's own optional counterpart to Ruby's
360
- # `settings[:schema]` (postgres.rb's own `connect_for`). Automatic for a
361
- # Shared-mode domain (this domain's own lowercase name — matches the
362
- # schema the real migration creates for it, `CREATE SCHEMA
363
- # #{infra_name}`, and needs no separate declaration since Shared mode
364
- # already implies it). Optional and explicit otherwise
365
- # (`deployed_to("AwsLambda") { schema "embryonaut" }`) — for an owning
366
- # domain that later migrates itself onto a shared instance's own schema
367
- # too (Embryonaut, in the real migration this exists for), without
368
- # switching its own `database` setting away from where its real RDS
369
- # instance already lives.
162
+ # rust/host's optional counterpart to Ruby's `settings[:schema]`.
163
+ # Automatic (this domain's lowercase name) in Shared mode; otherwise
164
+ # explicit via `deployed_to("AwsLambda") { schema "..." }`.
370
165
  hecks_schema = shared ? infra_name : deploy_settings[:schema]
371
166
 
372
167
  logical_id = "#{infra_name.split(/[_-]/).map(&:capitalize).join}Function"
373
- # `stack_prefix` — the "hecks-" half of this domain's own stack name, a
374
- # setting only for a domain whose live stack predates the convention
375
- # (real, live example: Embryonaut's own stack, both its Lambda function
376
- # names, and its Google OAuth secret are all "hecksagain-embryonaut*",
377
- # generated by the pre-rename hecksagain fork). Same shape and
378
- # reasoning as `owner_stack` above — read `deploy_settings`-direct, not
379
- # validated by LambdaTarget, optional — but for this domain rather than
380
- # the owner it borrows from. Everything downstream (stack, FunctionName
381
- # for both Lambdas, the OAuth secret name and its IAM policy, the
382
- # bastion stack, samconfig's stack_name/s3_prefix) reads `stack_name`,
383
- # so one setting moves them all together; `infra_name` (logical ids,
384
- # deploy/<dir>, DB name) is untouched — that is `stack_name`'s job.
385
- # Defaults to "hecks", so every domain that doesn't set it regenerates
386
- # byte-identically.
168
+ # The "hecks-" half of this domain's own stack name; overridable for a
169
+ # live stack that predates the convention. Everything downstream
170
+ # (FunctionNames, OAuth secret, bastion stack, samconfig) reads
171
+ # `stack_name`; `infra_name` (logical ids, DB name) is untouched.
387
172
  stack_prefix = deploy_settings[:stack_prefix] || "hecks"
388
173
  stack_name = "#{stack_prefix}-#{infra_name}"
389
174
 
390
175
  db_id = "#{logical_id.sub(/Function\z/, '')}Db"
391
- # The Aurora shape splits the database in two (a DBCluster carrying
392
- # the managed password/endpoint, plus at least one DBInstance inside
393
- # it) — every `.Endpoint.Address`/`.MasterUserSecret.SecretArn`
394
- # reference below has to point at the cluster, not `db_id` itself,
395
- # when Aurora is chosen. The plain-RDS path is unaffected: `db_ref_id`
396
- # equals `db_id` exactly, so every one of those references renders
397
- # byte-identical to before this existed.
176
+ # Aurora splits the database into a DBCluster plus DBInstance(s), so
177
+ # every `.Endpoint.Address`/`.MasterUserSecret` reference below has to
178
+ # point at the cluster, not `db_id`, when Aurora is chosen.
398
179
  db_ref_id = aurora ? "#{db_id}Cluster" : db_id
399
180
 
400
- # The Aurora path uses a self-managed secret (`#{db_id}Secret`, below —
401
- # see its own comment for why: ManageMasterUserPassword's rotation
402
- # breaks a Lambda's static Environment), not the plain-RDS path's own
403
- # `db_ref_id.MasterUserSecret` attribute (which only exists for the
404
- # managed-password feature at all). `secret_sub` is the identifier for
405
- # use inside a `!Sub` "${...}" interpolation (dot-free for a plain
406
- # `!Ref`, dotted for a `!GetAtt` attribute — CloudFormation's `${}`
407
- # auto-detects either shape); `secret_intrinsic` is the same fact
408
- # spelled as a bare YAML value, for the one site (stack_outputs) that
409
- # isn't inside a `!Sub` string at all.
181
+ # Aurora uses a self-managed secret (ManageMasterUserPassword rotation
182
+ # would break a Lambda's static Environment); plain RDS uses its own
183
+ # MasterUserSecret attribute. `secret_sub` is for `!Sub "${...}"`
184
+ # interpolation; `secret_intrinsic` is the same fact as a bare value.
410
185
  secret_sub = aurora ? "#{db_id}Secret" : "#{db_ref_id}.MasterUserSecret.SecretArn"
411
186
  secret_intrinsic = aurora ? "!Ref #{db_id}Secret" : "!GetAtt #{db_ref_id}.MasterUserSecret.SecretArn"
412
- # The same `${...}`-inside-`!Sub` identifier `secret_sub` above already
413
- # is -- used here for #{logical_id}'s own runtime IAM grant instead of a
414
- # deploy-time `{{resolve:secretsmanager:...}}` dynamic reference (see
415
- # that Environment.Variables comment below for why DATABASE_URL moved
416
- # off that mechanism entirely). Shared mode has no `secret_sub` of its
417
- # own to reach for -- `OwningDatabaseSecretArn` (this template's own
418
- # Parameter, filled from the owning stack's Output) already is the bare
419
- # ARN string, not a Ref/GetAtt target inside this stack.
187
+ # Shared mode has no `secret_sub` of its own; `OwningDatabaseSecretArn`
188
+ # (this template's Parameter) is already a bare ARN string.
420
189
  db_secret_ref = shared ? "OwningDatabaseSecretArn" : secret_sub
421
190
 
422
- # **Opt-in, by file presence** — a domain that wants its own web app on
423
- # Lambda too (not every domain has one; Banking doesn't) drops a
424
- # `lambda_handler.rb` at its own root, the Rack-to-Function-URL-event
425
- # adapter Sinatra runs through. No new .world verb for this: the file
426
- # itself is the declaration, the same way a `.hecksagon` file's mere
427
- # existence is what makes a domain framework-attached.
191
+ # Opt-in by file presence: a domain wanting its own web app on Lambda
192
+ # drops a `lambda_handler.rb` (the Rack-to-Function-URL-event adapter
193
+ # Sinatra runs through) at its own root. No separate .world verb.
428
194
  web_handler_present = File.exist?(File.join(domain, "lambda_handler.rb"))
429
195
 
430
- # A domain declares one web story, not both — `web "Rust"` (rust/host
431
- # serves its own web UI in-process) and a `lambda_handler.rb` (a
432
- # separate Ruby WebFunction) are two different mechanisms for the same
433
- # job; picking one silently when both are present would be exactly the
434
- # kind of ambiguity this generator's own `deploy.bluebook` gate exists
435
- # to refuse instead of guess at.
196
+ # A domain declares one web story, not both.
436
197
  if rust_web && web_handler_present
437
198
  raise ArgumentError, "#{domain} declares both web \"Rust\" (#{world_file}) and a lambda_handler.rb (#{File.join(domain, 'lambda_handler.rb')}) — pick one."
438
199
  end
439
200
 
440
- # Read straight out of the domain's own Gemfile.lock, not hardcoded —
441
- # `patch-pg-native`'s build recipe below has to fetch the same pg
442
- # version Bundler resolved, or its from-source extension and the
443
- # gem's own Ruby wrapper (lib/pg.rb, autoloads, etc.) can drift apart.
444
- # The plain, platform-less lock line ("pg (1.6.3)") is what this
445
- # matches — platform-suffixed siblings ("pg (1.6.3-aarch64-linux)")
446
- # fail the all-digits-and-dots capture on purpose.
447
- #
448
- # Optional, not required whenever a web app exists — Embryonaut's own
449
- # Sinatra app talks to Postgres directly (hence `pg` in its
450
- # Gemfile.lock) but that's that app's own choice, not a rule every
451
- # WebFunction follows. A web app that only ever dispatches through a
452
- # Lambda-routed domain (RemoteDispatcher/Adapters::Lambda, never a
453
- # local Postgres connection — hecks_on_web's own apps, e.g.) has no
454
- # `pg` gem and needs none of the native-extension patching below; the
455
- # generated Makefile's own `deploy:` target branches on whether this
456
- # is present, same convention `google_oauth_present` already uses for
457
- # its own opt-in machinery.
201
+ # Read from the domain's own Gemfile.lock, not hardcoded — the
202
+ # `patch-pg-native` build recipe below must fetch the same pg version
203
+ # Bundler resolved, or the from-source extension can drift from it.
458
204
  pg_version = nil
459
205
  if web_handler_present
460
206
  # `dispatch "None"` reads this project's own root Gemfile.lock, not
461
- # #{domain}'s — WebFunction's own CodeUri is `root` in that case
462
- # (`web_code_uri`, below), not #{domain} itself, precisely because
463
- # #{domain} (a bluebook directory inside this very repo, e.g. "qa")
464
- # has no Gemfile of its own to bundle at all; it needs `lib/hecks`
465
- # and this project's own Gemfile.lock, sitting one level up. Every
466
- # other WebFunction (Embryonaut's own self-contained app checkout,
467
- # vendoring its own copy of hecks) keeps reading its own, unchanged.
207
+ # #{domain}'s: #{domain} has no Gemfile of its own to bundle (it
208
+ # needs `lib/hecks` from one level up), so CodeUri is `root` there.
468
209
  pg_version_path = File.join(dispatch_none ? root : domain, "Gemfile.lock")
469
210
  pg_version = File.read(pg_version_path)[/^\s+pg \(([\d.]+)\)/, 1]
470
211
  end
471
212
  web_logical_id = "WebFunction"
472
213
 
473
- # `dispatch "None"` — same reason `pg_version_path` reaches one
474
- # directory further up, above: #{domain} alone has no Gemfile/lib/hecks
475
- # of its own to zip, so WebFunction's CodeUri has to be this whole
476
- # project's own root instead (the identical absolute-path shape
477
- # Embryonaut's own real, live deploy already uses for a checkout that
478
- # isn't colocated with `deploy/<stack>/` either — bin/project_deploy's
479
- # own header: "Self-contained... no hand-authored deployment config").
480
- # `web_handler_relpath` follows the same split: #{domain}/lambda_handler
481
- # is only the right relative path once CodeUri stops being #{domain}
482
- # itself — every existing WebFunction (CodeUri: #{domain}) still finds
483
- # its own lambda_handler.rb at that directory's own root, unchanged.
214
+ # Same `dispatch "None"` split as `pg_version_path` above: #{domain}
215
+ # alone has no Gemfile/lib/hecks to zip, so CodeUri is `root` instead.
484
216
  web_code_uri = dispatch_none ? root : domain
485
217
  web_handler_relpath = dispatch_none ? "#{domain}/lambda_handler" : "lambda_handler"
486
218
 
487
- # Opt-in, by file presence + content — the same convention
488
- # `web_handler_present` itself uses. `.env.local` is #{domain}'s own
489
- # gitignored local-dev secrets file (never committed, never read by
490
- # this tool for anything but detecting the key is there); the real
491
- # value is synced into Secrets Manager by `make sync-google-oauth`
492
- # below, straight from that same file, at deploy time — never baked
493
- # into this generated, git-tracked template.yaml as plaintext.
494
- # Also gates the NAT Gateway/public subnet template.yaml's own "no NAT
495
- # gateway" comment describes — computed here, once, before
496
- # stack_outputs/bastion_parameters below need to know whether
497
- # #{db_id}PublicSubnet exists at all to pass along to bastion.yaml.
219
+ # `.env.local` is #{domain}'s gitignored local-dev secrets file, read
220
+ # only to detect the key is present; `make sync-google-oauth` syncs
221
+ # the real value into Secrets Manager at deploy time.
498
222
  google_oauth_present = (web_handler_present || rust_web) &&
499
223
  File.exist?(File.join(domain, ".env.local")) &&
500
224
  File.read(File.join(domain, ".env.local")).match?(/^GOOGLE_CLIENT_ID=\S/)
501
225
 
502
- # **The Ruby case stays refused** — a shared-instance Ruby WebFunction
503
- # needs its own cross-stack DATABASE_URL wiring this generator doesn't
504
- # build yet (web_handler_present's own Member-style direct-Postgres
505
- # binding), real, unbuilt design, not a small addition. This check
506
- # alone is why the google_oauth_present check below can assume
507
- # web_handler_present is already false by the time it runs — it always
508
- # aborts first when both are true.
226
+ # A shared-instance Ruby WebFunction needs cross-stack DATABASE_URL
227
+ # wiring this generator doesn't build yet.
509
228
  if shared && web_handler_present
510
229
  raise ArgumentError, "#{domain} declares both database \"Shared\" and a lambda_handler.rb — a shared-instance Ruby WebFunction isn't supported yet."
511
230
  end
512
231
 
513
- # `dispatch "None"` means WebFunction becomes the domain's only Lambda —
514
- # there is nothing left to reach through a Function URL at all without a
515
- # `lambda_handler.rb` to serve it, and (below) no rust_web dispatch
516
- # Lambda whose own Function URL that role could fall back to either.
232
+ # `dispatch "None"` means WebFunction is the domain's only Lambda.
517
233
  if dispatch_none && !web_handler_present
518
234
  raise ArgumentError, "#{domain} declares dispatch \"None\" but has no lambda_handler.rb — dispatch \"None\" means no rust/host dispatch Lambda at all, so a WebFunction (lambda_handler.rb) has to exist to be the domain's only Lambda."
519
235
  end
@@ -521,33 +237,12 @@ module Hecks
521
237
  raise ArgumentError, "#{domain} declares both dispatch \"None\" and web \"Rust\" — dispatch \"None\" already means there is no rust/host Lambda for rust_web's own in-process web UI to run inside."
522
238
  end
523
239
 
524
- # **The Rust case is not refused** — rust_web's own OAuth wiring (the main
525
- # dispatch function's own Environment/VpcConfig, generated below) needs
526
- # no NAT Gateway of its own in Shared mode: it already runs inside the
527
- # owner's borrowed private subnets (OwningSubnetAId/OwningSubnetBId)
528
- # and borrowed security group (OwningSecurityGroupId) — the same ones
529
- # this domain's own dispatch traffic already uses — and the owner's own
530
- # template already routes those subnets through its NAT Gateway and
531
- # already permits 443-to-internet egress on that security group (added
532
- # there for the owner's own WebFunction's real, live OAuth
533
- # token-exchange bug — see that stack's own
534
- # `#{owner_domain_name}FunctionEgressToInternet` resource). Nothing new
535
- # to provision for this combination; the Parameters section below just
536
- # has to declare both the Owning* set (Shared mode) and
537
- # WebRedirectBaseUrl (OAuth) together, not as alternatives — see its
538
- # own comment on why an if/elsif there would be wrong.
539
- #
540
- # Still a real gap, left refused: rust_web isn't checked here
541
- # explicitly because shared && web_handler_present already aborted
542
- # above whenever web_handler_present is true — so by construction, if
543
- # `shared && google_oauth_present` is ever true at this point,
544
- # web_handler_present must be false, meaning google_oauth_present's own
545
- # `(web_handler_present || rust_web)` can only have been satisfied by
546
- # rust_web. Nothing left to refuse here.
547
-
548
- # The stack↔bastion contract, and the least-privilege cross-domain
549
- # invoke grant — `Shared`'s own header explains why both are
550
- # `Fargate`'s problem too, not only this target's.
240
+ # A Shared-mode rust_web domain with OAuth needs no new NAT Gateway:
241
+ # it already runs inside the owner's borrowed private subnets and
242
+ # security group, which already route to the internet for OAuth.
243
+
244
+ # The stack-bastion contract and the least-privilege cross-domain
245
+ # invoke grant are shared with `Fargate` — see `Shared`'s header.
551
246
  stack_outputs = Shared.stack_outputs(
552
247
  shared: shared, db_id: db_id, db_ref_id: db_ref_id, secret_intrinsic: secret_intrinsic,
553
248
  compute_security_group_ref: "!Ref #{logical_id}SecurityGroup", google_oauth_present: google_oauth_present
@@ -555,14 +250,9 @@ module Hecks
555
250
  bastion_parameters = Shared.bastion_parameters(shared: shared, google_oauth_present: google_oauth_present)
556
251
  Shared.check_bastion_parameters!(bastion_parameters, stack_outputs)
557
252
 
558
- # Computed here, as its own local, rather than as two heredocs
559
- # opened side by side inside the big template heredoc's own
560
- # interpolation below — a heredoc's body is read starting from
561
- # the line after it opens, so two on one line only stay
562
- # unambiguous as long as neither branch is ever edited to span
563
- # the other's own territory. An ordinary `if`/`else`, each
564
- # heredoc entirely inside its own branch, cannot develop that
565
- # failure mode at all.
253
+ # A plain local, not two heredocs opened side by side inside the
254
+ # template heredoc's own interpolation — a heredoc body starts on the
255
+ # line after it opens, so an if/else here keeps each one unambiguous.
566
256
  web_google_oauth_env_yaml =
567
257
  if google_oauth_present
568
258
  <<~GOOGLE.each_line.with_index.map { |l, i| i.zero? ? l : " " + l }.join.rstrip
@@ -592,7 +282,7 @@ module Hecks
592
282
  end
593
283
 
594
284
  template_yaml = <<~YAML
595
- # GENERATED by bin/project_deploy #{domain} — re-run it to refresh
285
+ # GENERATED by hecks deploy project #{domain} — re-run it to refresh
596
286
  # this file rather than hand-editing. Source: #{world_file}'s own
597
287
  # deployed_to("AwsLambda") block.
598
288
  #
@@ -619,22 +309,10 @@ module Hecks
619
309
  journal (docs/decisions/0018), backed by its own private RDS
620
310
  Postgres instance. PackageType Zip, no container.
621
311
  #{
622
- # Two independent reasons a Parameters section might be needed --
623
- # google_oauth_present (any web mode) and shared (any domain
624
- # borrowing an owner's VPC) -- are collected into one array and
625
- # joined under a single `Parameters:` header (YAML permits exactly
626
- # one), rather than treated as mutually exclusive alternatives via
627
- # an if/elsif/else here. That shape breaks for the one combination
628
- # both can be true at once (a Shared-mode rust_web domain with
629
- # real Google OAuth): the elsif branch would never run, so
630
- # OwningSubnetAId/OwningSecurityGroupId/etc. would never get declared as
631
- # Parameters at all, even though VpcConfig below (`shared ?
632
- # "SubnetIds: [!Ref OwningSubnetAId, ...]" : ...`) already
633
- # references them unconditionally whenever `shared` is true --
634
- # exactly the CloudFormation-references-an-undeclared-Parameter
635
- # break bastion_parameters' own generation-time assertion (above)
636
- # exists to catch for a different table. This fixes it for both
637
- # the "either" and the "both" case.
312
+ # google_oauth_present and shared are independent and collected
313
+ # into one array under a single `Parameters:` header, not an
314
+ # if/elsif/else — both can be true at once (a Shared-mode
315
+ # rust_web domain with real Google OAuth).
638
316
  param_blocks = []
639
317
  if google_oauth_present
640
318
  param_blocks << <<~OAUTHPARAMS.rstrip
@@ -660,8 +338,8 @@ module Hecks
660
338
  # targets, below) via the SAME `aws cloudformation describe-stacks`
661
339
  # pattern `mint-era` already used for a bastion, one stack over
662
340
  # instead of one sibling stack over. Never a CloudFormation
663
- # Export/ImportValue -- this codebase uses none anywhere (bin/
664
- # project_deploy's own header on why: a live shell lookup keeps
341
+ # Export/ImportValue -- this codebase uses none anywhere (hecks
342
+ # deploy project's own header on why: a live shell lookup keeps
665
343
  # this stack independently deployable/deletable, never coupled to
666
344
  # #{owner_domain_name}'s own stack through CloudFormation itself).
667
345
  OwningVpcId:
@@ -693,17 +371,8 @@ module Hecks
693
371
  if param_blocks.empty?
694
372
  ""
695
373
  else
696
- # Every line indented 2, not just appended flush-left — each
697
- # heredoc above squiggly-dedents to column 0 (its own
698
- # least-indented line, matching every other heredoc's own
699
- # convention in this file), but these are Parameters: children,
700
- # which YAML requires indented under it. Confirmed the hard way:
701
- # without this, `ruby -ryaml` parses the file without raising
702
- # (it's still syntactically valid YAML) but
703
- # `doc["Parameters"]["OwningVpcId"]` is nil — WebRedirectBaseUrl/
704
- # OwningVpcId/etc. land as their own top-level document keys,
705
- # siblings of Parameters/Resources, not children of Parameters
706
- # at all.
374
+ # Indented 2: the heredocs above dedent to column 0, but these
375
+ # are Parameters: children, which YAML requires indented under it.
707
376
  "Parameters:\n" + param_blocks.join("\n").each_line.map { |l| " #{l}" }.join
708
377
  end
709
378
  }
@@ -805,13 +474,8 @@ module Hecks
805
474
  # sibling of VpcConfig) is the one runtime IAM permission
806
475
  # that fetch needs, scoped to this one secret ARN.
807
476
  #{if shared
808
- # **The storehouse** — #{owner_domain_name}'s own live
809
- # Endpoint/Secret, looked up at deploy time into
810
- # OwningDatabaseEndpoint/OwningDatabaseSecretArn (this
811
- # template's own Parameters, above), not a resource in
812
- # this stack. Same dbname as #{owner_domain_name}'s own
813
- # DBName -- one database, isolated by schema
814
- # (HECKS_SCHEMA below), not a second database.
477
+ # Owner's live Endpoint/Secret, looked up into
478
+ # OwningDatabaseEndpoint/OwningDatabaseSecretArn.
815
479
  "DB_HOST: !Ref OwningDatabaseEndpoint\n" \
816
480
  " DB_NAME: #{owner_db_name}\n" \
817
481
  " DB_SECRET_ARN: !Sub \"${OwningDatabaseSecretArn}\""
@@ -821,13 +485,13 @@ module Hecks
821
485
  " DB_SECRET_ARN: !Sub \"${#{secret_sub}}\""
822
486
  end}
823
487
  # `domain_name`, NOT `infra_name` — this names the actual
824
- # .wasm FILE bin/project_wasm writes (rust/dist/#{domain_name}.wasm,
488
+ # .wasm FILE `hecks build_wasm` writes (rust/dist/#{domain_name}.wasm,
825
489
  # its own target_mod_name convention, a Rust-build-pipeline
826
490
  # concern unrelated to AWS resource identity) and what the
827
491
  # Makefile below actually bundles into the Lambda package
828
492
  # under. Confirmed the hard way: pinning this to infra_name
829
493
  # alongside the real AWS-identity fields left it looking for
830
- # a file bin/project_wasm never produces.
494
+ # a file `hecks build_wasm` never produces.
831
495
  HECKS_WASM_PATH: !Sub "/var/task/#{domain_name}.wasm"
832
496
  # STATIC, not a deploy-time parameter — HECKS_DOMAIN is this
833
497
  # domain's own declared name, known at generation time the
@@ -972,7 +636,7 @@ module Hecks
972
636
  PasswordLength: 64
973
637
  ExcludePunctuation: true
974
638
 
975
- #{webhook_secret_env ? <<-WEBHOOKSECRET : ""}
639
+ #{webhook_secret_env ? <<~WEBHOOKSECRET.rstrip : ""}
976
640
  # Auto-generated, never typed or seen — the SAME
977
641
  # ManageMasterUserPassword/SessionSecret pattern above, applied to
978
642
  # `secret_env`'s own webhook secret instead (deploy_settings' own
@@ -1158,7 +822,7 @@ module Hecks
1158
822
  Outputs:
1159
823
  FunctionUrl:
1160
824
  Value: !GetAtt #{logical_id}Url.FunctionUrl#{web_handler_present ? "\n WebFunctionUrl:\n Value: !GetAtt #{web_logical_id}Url.FunctionUrl" : ""}
1161
- # stack_outputs (bin/project_deploy, above the heredocs) is the ONE
825
+ # stack_outputs (hecks deploy project, above the heredocs) is the ONE
1162
826
  # place these four names live — DatabaseEndpoint/DatabaseSecretArn
1163
827
  # feed `make mint-era`'s own boot step directly; VpcId/
1164
828
  # DbSecurityGroupId ALSO feed bastion.yaml's own Parameters (a
@@ -1167,7 +831,7 @@ module Hecks
1167
831
  # network without this main template ever knowing a bastion
1168
832
  # exists) and the Makefile's own --parameter-overrides. Rename a
1169
833
  # key in that table and every consumer follows; reference one that
1170
- # doesn't exist there and bin/project_deploy refuses before
834
+ # doesn't exist there and hecks deploy project refuses before
1171
835
  # writing a single file, instead of a bastion.yaml whose Parameter
1172
836
  # nothing could ever fill. (`PrivateSubnetId` used to sit here too
1173
837
  # — dropped, nothing ever consumed it; bastion.yaml mints its own
@@ -1176,31 +840,13 @@ module Hecks
1176
840
  YAML
1177
841
 
1178
842
  # Spliced in after the heredoc renders, not interpolated inside it —
1179
- # found live, the hard way: `<<~`'s own dedent strips whatever the
1180
- # shallowest leading-whitespace line in the raw source text has, computed
1181
- # before any `#{...}` interpolation runs. `cross_domain_lambda_policies`
1182
- # is "" for the overwhelmingly common case (no cross-domain policies at
1183
- # all), and a `#{...}` marker written at column 0 in the source (so it
1184
- # renders flush when empty) is itself a zero-indent line by that same
1185
- # raw-text measure — dragging the whole heredoc's computed dedent down to
1186
- # zero and leaving every other line's original leading whitespace
1187
- # un-stripped, a real, generated-then-caught-by-spec bug (`bin/
1188
- # project_deploy`'s own contract spec: "Outputs only declares []" — not
1189
- # actually empty, just re-indented into structural nonsense by this).
1190
- # A plain, unconditional `String#sub` after the fact has no such
1191
- # interaction with the text it's replacing into.
843
+ # a `#{...}` marker at column 0 would drag the heredoc's own dedent
844
+ # computation to zero and leave every other line un-stripped.
1192
845
  template_yaml = template_yaml.sub(/^([ \t]*)# TMPL:cross_domain_lambda_policies\n/) { Shared.cross_domain_invoke_policy_yaml(cross_domain_lambda_targets, $1) }
1193
846
 
1194
- # **The `PII` → CloudFront splice** — operates on the already-fully-rendered
1195
- # string, the same reason the `dispatch_none` splice (below) does rather
1196
- # than threading a third reindentation layer through the main heredoc
1197
- # above: `pii_cloudfront_yaml` builds its own already-correctly-indented
1198
- # text from scratch (its own `reindent` lambda), so there is nothing here
1199
- # for a heredoc dedent computation to interact with badly.
1200
- # `fronted_logical_id`/`use_oac` are resolved here, not earlier, because
1201
- # both need `web_handler_present`/`web_logical_id`/`rust_web`/`logical_id`
1202
- # — every one of which is computed after this generator's own pii
1203
- # detection, above.
847
+ # Operates on the already-rendered string — `pii_cloudfront_yaml`
848
+ # builds its own correctly-indented text, so nothing here interacts
849
+ # with a heredoc's own dedent computation.
1204
850
  if pii_detected
1205
851
  fronted_logical_id = web_handler_present ? web_logical_id : logical_id
1206
852
  use_oac = !web_handler_present && !rust_web
@@ -1212,46 +858,22 @@ module Hecks
1212
858
  template_yaml = template_yaml.sub(/^Outputs:\n/) { "#{pii_resources}Outputs:\n PiiDistributionDomainName:\n Value: !GetAtt PiiDistribution.DomainName\n" }
1213
859
  end
1214
860
 
1215
- # `dispatch "None"` — surgical removal, POST-render, rather than a
1216
- # fourth reindentation layer threaded through the heredoc above.
1217
- # Tried that first: wrapping `#{logical_id}`'s own resource block in a
1218
- # conditional nested heredoc (matching `RUSTSECRET`/`WEB`/`OWNDB`'s own
1219
- # established `<<~TAG.each_line.with_index.map { ... }` convention)
1220
- # looked right, but broke several pre-existing embedded multi-line
1221
- # `#{shared ? "a\nb" : "c"}`-shaped strings already living inside that
1222
- # block (the DB_HOST/DB_NAME/DB_SECRET_ARN Environment lines among
1223
- # them) — those assume they sit at exactly one reindentation layer deep
1224
- # (the outer template heredoc's own margin), and adding a second layer
1225
- # on top shifted every line their own hardcoded embedded-newline
1226
- # indentation didn't already account for. Confirmed live, diffing a
1227
- # real regenerated examples/banking (`dispatch` unset — the ordinary,
1228
- # far more common path) against its own git-tracked output: real
1229
- # indentation corruption, not a false alarm. Operating on the already-
1230
- # fully-rendered, already-correctly-indented string instead sidesteps
1231
- # that whole class of interaction — no new heredoc layer, so nothing
1232
- # already living inside the old one has to change its own assumptions.
861
+ # Surgical removal, post-render, not a nested heredoc — a conditional
862
+ # nested heredoc broke embedded multi-line `#{shared ? ... : ...}`
863
+ # strings inside the resource block, which assume exactly one
864
+ # reindentation layer (confirmed regenerating examples/banking).
1233
865
  if dispatch_none
1234
- # The #{logical_id} resource itself — non-greedy through its own
1235
- # `FunctionUrlConfig`/`AuthType` closing pair (the actual last
1236
- # property this resource ever renders, confirmed above), so a second
1237
- # occurrence of "AuthType:" later in the document (WebFunction's own,
1238
- # always AuthType: `NONE`) can never be matched instead.
866
+ # Non-greedy through the resource's own FunctionUrlConfig/AuthType
867
+ # closing pair, so WebFunction's own later "AuthType:" can't match.
1239
868
  template_yaml = template_yaml.sub(
1240
869
  /^ #{Regexp.escape(logical_id)}:\n.*?\n AuthType: (?:NONE|AWS_IAM)\n/m, ""
1241
870
  )
1242
- # WebFunction's own Policies — `LambdaInvokePolicy: FunctionName: !Ref
1243
- # #{logical_id}` grants invoking a function that, above, was just
1244
- # removed from this template entirely; SAM refuses a `!Ref` to an
1245
- # undeclared resource at package time, not silently.
871
+ # Removes the now-dangling `!Ref` to the resource just deleted above
872
+ # — SAM refuses an unresolved Ref at package time, not silently.
1246
873
  template_yaml = template_yaml.sub(
1247
874
  /^ - LambdaInvokePolicy:\n FunctionName: !Ref #{Regexp.escape(logical_id)}\n/, ""
1248
875
  )
1249
- # Outputs — no `#{logical_id}Url` exists for `FunctionUrl` to
1250
- # `!GetAtt`; WebFunction's own URL becomes the stack's one and only
1251
- # "FunctionUrl" (never "WebFunctionUrl" — that name is reserved for
1252
- # the two-URL case, a domain that also has a #{logical_id} of its
1253
- # own to disambiguate from, which this one, by construction, never
1254
- # does).
876
+ # WebFunction's own URL becomes the stack's only "FunctionUrl".
1255
877
  template_yaml = template_yaml.sub(
1256
878
  /^ FunctionUrl:\n Value: !GetAtt #{Regexp.escape(logical_id)}Url\.FunctionUrl\n WebFunctionUrl:\n Value: !GetAtt #{Regexp.escape(web_logical_id)}Url\.FunctionUrl\n/,
1257
879
  " FunctionUrl:\n Value: !GetAtt #{web_logical_id}Url.FunctionUrl\n"
@@ -1260,48 +882,24 @@ module Hecks
1260
882
 
1261
883
 
1262
884
 
1263
- # **The ephemeral era-minting bastion** — a separate, sibling stack
1264
- # (`#{stack_name}-bastion`), never merged into template.yaml itself.
1265
- # The main stack stays exactly as minimal as its own header already
1266
- # commits to (no NAT gateway, no bastion sitting there costing money
1267
- # by default) — this template only ever exists for the few minutes
1268
- # `make mint-era` (below) needs it, then gets deleted. SSM Session
1269
- # Manager only, never SSH: no key pair, no inbound security group rule
1270
- # at all (the instance's own SG declares zero Ingress) — the only way
1271
- # in is `aws ssm start-session`, itself gated by the caller's own IAM
1272
- # permissions, nothing this template opens to the internet.
1273
- # Skipped entirely when `shared` — this domain provisions no RDS/VPC
1274
- # of its own for a bastion to reach (bastion_parameters is empty for
1275
- # the same reason, above); a bare `!Ref VpcId` with no declared
1276
- # Parameter would just fail at deploy time. Era-minting for a
1277
- # Shared-mode domain reuses its owner's own already-standing
1278
- # infrastructure instead — see the Makefile's own `mint-era` comment
1279
- # for the manual path this leaves until that's automated too.
885
+ # A separate, sibling stack for the few minutes `make mint-era`
886
+ # needs it, then deleted — SSM Session Manager only, never SSH, no
887
+ # inbound rule at all. Skipped when `shared`: era-minting reuses the
888
+ # owner's own standing infrastructure instead.
1280
889
  bastion_yaml = shared ? nil : Shared.bastion_yaml(
1281
890
  domain: domain, infra_name: infra_name, stack_name: stack_name, db_id: db_id,
1282
891
  google_oauth_present: google_oauth_present, bastion_parameters: bastion_parameters
1283
892
  )
1284
893
 
1285
- # `mint-era`'s own recipe body — a top-level variable, not inlined at
1286
- # its call site inside the Makefile heredoc below, on purpose: Make
1287
- # recipe lines need a literal tab as their true first character (no
1288
- # leading spaces at all), and nesting this heredoc a second level
1289
- # deeper inside another `#{...}` interpolation (the way `OWNDB`/params
1290
- # above handle conditional template.yaml content) would need its own
1291
- # per-line re-indent pass — one that adds spaces before every line
1292
- # including the already-tab-prefixed recipe ones, corrupting Make's
1293
- # own recipe-line detection. A single top-level heredoc, each line
1294
- # already at its own final indentation (recipe lines as " \t..." —
1295
- # the same two-space-before-the-tab convention every other recipe
1296
- # line in this Makefile already uses, stripped by the encolosing
1297
- # `<<~MAKE` heredoc's own squiggly stripping below, same as them),
1298
- # sidesteps that: nothing re-touches an already-correct line twice.
894
+ # A top-level variable, not inlined in the Makefile heredoc below —
895
+ # Make recipe lines need a literal leading tab, and a nested
896
+ # interpolation's re-indent pass would corrupt that detection.
1299
897
  mint_era_recipe =
1300
898
  if shared
1301
899
  <<~SHAREDMINT.rstrip
1302
900
  # NOT AUTOMATED YET for a Shared-mode domain — it has no RDS/VPC
1303
901
  # of its own to stand a temporary bastion next to (bastion.yaml
1304
- # itself isn't even generated here — bin/project_deploy's own
902
+ # itself isn't even generated here — hecks deploy project's own
1305
903
  # comment on why). Minting era 1 for #{infra_name}'s own schema
1306
904
  # needs a tunnel to #{owner_domain_name}'s ALREADY-EXISTING RDS
1307
905
  # instance instead -- reuse #{owner_domain_name}'s own deploy
@@ -1350,7 +948,7 @@ bastion_yaml = shared ? nil : Shared.bastion_yaml(
1350
948
  else
1351
949
  <<~OWNMINT.rstrip
1352
950
  \t@echo "Looking up $(STACK)'s VPC/security group..."
1353
- # stack_outputs/bastion_parameters (bin/project_deploy) are the ONE
951
+ # stack_outputs/bastion_parameters (hecks deploy project) are the ONE
1354
952
  # place these OutputKey strings and parameter names live — this eval
1355
953
  # chain and the --parameter-overrides line just below are both
1356
954
  # generated from the SAME table template.yaml's Outputs and
@@ -1469,48 +1067,22 @@ bastion_yaml = shared ? nil : Shared.bastion_yaml(
1469
1067
  \techo "Tearing down the temporary bastion stack..."; \\
1470
1068
  \taws cloudformation delete-stack --stack-name $(BASTION_STACK); \\
1471
1069
  \taws cloudformation wait stack-delete-complete --stack-name $(BASTION_STACK); \\
1472
- \techo "$(BASTION_STACK) deleted. Era 1 should now be held if BOOT_STATUS was 0 — verify with bin/console or a Postgres query against hecks_eras."; \\
1070
+ \techo "$(BASTION_STACK) deleted. Era 1 should now be held if BOOT_STATUS was 0 — verify with hecks console or a Postgres query against hecks_eras."; \\
1473
1071
  \texit $$BOOT_STATUS
1474
1072
  OWNMINT
1475
1073
  end
1476
1074
 
1477
- # `scaffold-translation`/`translation-audit` — the two-step fix
1478
- # minter.rb's own refusal names ("run bin/scaffold_translation to write
1479
- # the edge, check it with bin/translation_audit, then boot again") when
1480
- # a deploy's own pre-flight boot check refuses a shape change with no
1481
- # translation edge covering it. Same bastion/tunnel/retry/teardown
1482
- # chain mint_era_recipe already uses — only the one thing done over the
1483
- # tunnel differs: these run the scaffold/audit scripts (hecks's
1484
- # own bin/, not a Ruby -e one-liner) instead of a boot check, since
1485
- # both scripts already do their own registry-loading internally.
1075
+ # Shares mint_era_recipe's bastion/tunnel/retry/teardown chain but
1076
+ # runs `hecks ask scaffold_translation` or `hecks ask audit_translation` over it.
1486
1077
  #
1487
- # HECKS_SCHEMA/DATABASE_URL below are not actually consumed by those
1488
- # scripts, despite reading as if they were: grep-confirmed, neither
1489
- # `bin/scaffold_translation` nor `bin/translation_audit` nor
1490
- # `Hecks::Bluebook::Behaviour::World#for_binding` nor
1491
- # `PostgresEra.connect_for` ever reads `ENV["DATABASE_URL"]` or
1492
- # `ENV["HECKS_SCHEMA"]` — both scripts call
1493
- # `registry.binding_settings(...)`, a pure hash lookup
1494
- # against whatever literal `database "..."` string #{domain}'s own
1495
- # `.world` file declares. Without `db_env_blind:` below, this whole
1496
- # recipe would stand up a real bastion, punch a live 5432 ingress rule
1497
- # into production's security group, open a real SSM tunnel to
1498
- # #{stack_name}'s RDS instance, tear it all down again — and then
1499
- # silently scaffold/audit the developer's local dev database the
1500
- # entire time, reporting success. `db_env_blind:`
1501
- # (below) is exactly this: true for scaffold-translation/
1502
- # translation-audit (confirmed env-blind), left false for
1503
- # migrate-console-settings (an app-owned script this generator doesn't
1504
- # control the internals of — it may honor these vars; not asserting
1505
- # either way here). A `db_env_blind` recipe refuses before ever
1506
- # touching AWS unless `ALLOW_LOCAL_DB=1` is set, rather than silently
1507
- # doing the wrong (but locally successful-looking) thing.
1508
- # `cwd:`/`run_prefix:` — every existing caller runs a hecks-owned
1509
- # script from hecks's own $(root) with `-Ilib` (source, not the
1510
- # installed gem); `migrate_console_settings_recipe` below is the one
1511
- # exception, an app-owned script that needs the app's own Gemfile
1512
- # context instead — `cd $(DOMAIN) && bundle exec ruby`, not `-Ilib`.
1513
- translation_recipe = lambda do |verb, script, extra_args = "", cwd: "$(ROOT)", run_prefix: "ruby -Ilib", db_env_blind: false|
1078
+ # Neither script actually reads DATABASE_URL/HECKS_SCHEMA (both call
1079
+ # `registry.binding_settings`, a lookup against the literal `database
1080
+ # "..."` in #{domain}'s `.world`) — without `db_env_blind:`, this
1081
+ # recipe would open a real tunnel to production and silently
1082
+ # scaffold/audit the local dev database instead, reporting success.
1083
+ # `db_env_blind` refuses unless `ALLOW_LOCAL_DB=1` is set.
1084
+ translation_recipe = lambda do |verb, script, extra_args = "", cwd: "$(ROOT)", run_prefix: "ruby -Ilib", db_env_blind: false,
1085
+ domain_arg: "$(DOMAIN) "|
1514
1086
  if shared
1515
1087
  <<~SHAREDTRANSLATION.rstrip
1516
1088
  \t@echo "#{verb} isn't automated yet for a Shared-mode domain (database \\"Shared\\") -- see mint-era's own comment in the generated Makefile for the manual path through #{owner_domain_name}'s own tunnel."; \\
@@ -1559,7 +1131,7 @@ bastion_yaml = shared ? nil : Shared.bastion_yaml(
1559
1131
  \t\t\tnc -z localhost 15432 2>/dev/null && break; \\
1560
1132
  \t\t\tsleep 1; \\
1561
1133
  \t\tdone; \\
1562
- \t\tcd #{cwd} && DATABASE_URL="postgres://postgres:$$DB_PASS_URLENC@localhost:15432/#{db_name}" HECKS_SCHEMA="#{hecks_schema}" #{run_prefix} #{script} #{cwd == "$(ROOT)" ? "$(DOMAIN) " : ""}#{extra_args}&& { RUN_STATUS=0; break; }; \\
1134
+ \t\tcd #{cwd} && DATABASE_URL="postgres://postgres:$$DB_PASS_URLENC@localhost:15432/#{db_name}" HECKS_SCHEMA="#{hecks_schema}" #{run_prefix} #{script} #{cwd == "$(ROOT)" ? domain_arg : ""}#{extra_args}&& { RUN_STATUS=0; break; }; \\
1563
1135
  \t\tRUN_STATUS=$$?; \\
1564
1136
  \t\techo "#{verb} attempt $$attempt/5 failed (exit $$RUN_STATUS) -- restarting the tunnel and retrying in 3s..."; \\
1565
1137
  \t\tsleep 3; \\
@@ -1574,41 +1146,31 @@ bastion_yaml = shared ? nil : Shared.bastion_yaml(
1574
1146
  end
1575
1147
  end
1576
1148
 
1577
- scaffold_translation_recipe = translation_recipe.call("scaffold-translation", "bin/scaffold_translation", db_env_blind: true)
1578
- translation_audit_recipe = translation_recipe.call("translation-audit", "bin/translation_audit", db_env_blind: true)
1579
-
1580
- # `make migrate-console-settings` — the same bastion/tunnel/retry/
1581
- # teardown chain as scaffold-translation/translation-audit, running an
1582
- # app-owned one-time migration script instead of one of hecks's
1583
- # own bin/ tools (see translation_recipe's own `cwd:`/`run_prefix:`
1584
- # comment). Only meaningful for a domain that actually has one — most
1585
- # domains don't, so this target is generated unconditionally but simply
1586
- # has nothing to run for them; harmless (`bundle exec ruby` on a
1587
- # missing file just fails loudly, same as any other missing script
1588
- # would).
1149
+ # `hecks ask` answers the scaffold as text, and refuses (exit 1) where the script aborted.
1150
+ scaffold_translation_recipe = translation_recipe.call(
1151
+ "scaffold-translation", "exe/hecks ask scaffold_translation", db_env_blind: true,
1152
+ run_prefix: "HECKS_ENVIRONMENT=memory ruby", domain_arg: "domain=$(DOMAIN) "
1153
+ )
1154
+ # A refused audit is a refused query: `hecks ask` exits 1, so the target fails too.
1155
+ translation_audit_recipe = translation_recipe.call(
1156
+ "translation-audit", "exe/hecks ask audit_translation", db_env_blind: true,
1157
+ run_prefix: "HECKS_ENVIRONMENT=memory ruby", domain_arg: "domain=$(DOMAIN) "
1158
+ )
1159
+
1160
+ # Same chain, running an app-owned one-time migration script instead
1161
+ # of a hecks tool. `bin/migrate_console_settings` belongs to the domain's own
1162
+ # application, not to hecks, so it stays a `bin/` path when hecks drops its own.
1163
+ # Harmless when the domain has none: `bundle exec ruby` on a missing file just fails
1164
+ # loudly.
1589
1165
  migrate_console_settings_recipe = translation_recipe.call(
1590
1166
  "migrate-console-settings", "bin/migrate_console_settings", "",
1591
1167
  cwd: "$(DOMAIN)", run_prefix: "bundle exec ruby"
1592
1168
  )
1593
1169
 
1594
- # `rename-schema`'s own recipe body — same top-level-variable-not-inlined
1595
- # reasoning as `mint_era_recipe` just above (recipe lines need a literal
1596
- # leading tab; nesting this heredoc a level deeper would need its own
1597
- # re-indent pass). Reuses the same bastion.yaml, the same stack/
1598
- # BASTION_STACK Make variables mint-era already defines, and the same
1599
- # stand-up/tunnel/teardown-no-matter-what shell chain — only the one
1600
- # thing done over the tunnel differs: a schema rename instead of a
1601
- # Ruby boot. `OLD`/`NEW` are Make command-line variables
1602
- # (`make rename-schema OLD=old NEW=new`), not baked in here, so this
1603
- # is genuinely reusable — the domain whose own schema this renames is
1604
- # whichever one owns this stack's RDS instance (this generator's own
1605
- # `#{infra_name}` database on it), not necessarily this domain forever.
1606
- #
1607
- # Idempotent by inspection, not by catching a Postgres error: checks
1608
- # which of old/new actually exists as a schema before touching
1609
- # anything, so a second run (or a run after a partial failure) reads
1610
- # as a clear no-op message rather than a bare "schema already exists"
1611
- # error with no context.
1170
+ # `OLD`/`NEW` are Make command-line variables (`make rename-schema
1171
+ # OLD=old NEW=new`), reusable across whichever domain owns this
1172
+ # stack's RDS instance. Idempotent by inspection (checks which schema
1173
+ # exists first), not by catching a Postgres error.
1612
1174
  rename_schema_recipe =
1613
1175
  if shared
1614
1176
  <<~SHAREDRENAME.rstrip
@@ -1718,27 +1280,11 @@ bastion_yaml = shared ? nil : Shared.bastion_yaml(
1718
1280
  OWNRENAME
1719
1281
  end
1720
1282
 
1721
- # `deploy:`'s own `sam deploy` call — `google_oauth_present` and
1722
- # `shared` are collected together here rather than spelled as an
1723
- # if/elsif/else, the same shape `Parameters:` above holds to: the
1724
- # two are independent facts about a domain, not alternatives, and
1725
- # a client site can be both — an elsif shape would silently drop the
1726
- # Owning* overrides whenever OAuth is also present. Caught live
1727
- # the first time a Shared-mode domain with real Google OAuth
1728
- # actually ran `make deploy`: `sam deploy` refused
1729
- # with "Parameters: [OwningVpcId, ...] must have values" because
1730
- # nothing had ever passed them. WebRedirectBaseUrl stays a conditional
1731
- # override (empty on a genuine first deploy, before the Function Url
1732
- # exists — see that Parameter's own comment); Owning* is unconditional
1733
- # whenever `shared`, in every branch that reaches `sam deploy` at all.
1734
- # One continuous `\`-joined shell chain, however many pieces
1735
- # contribute to it — `@` (Make's own "don't echo this line" prefix)
1736
- # is only meaningful on the true first line of that chain; written on
1737
- # any later line it stops being a Make directive at all and becomes
1738
- # literal shell text (`@WEB_URL=...` parses as a bogus command, not
1739
- # an assignment) the moment two independently-`@`-prefixed pieces
1740
- # get concatenated. Built unprefixed here; `@` is added once, to
1741
- # whichever piece actually ends up first, right before joining.
1283
+ # `google_oauth_present` and `shared` are independent facts, not
1284
+ # alternatives — an elsif would silently drop Owning* overrides
1285
+ # whenever OAuth is also present. `@` (Make's "don't echo" prefix)
1286
+ # is only meaningful on the chain's true first line, so lines are
1287
+ # built unprefixed and `@` is added once, right before joining.
1742
1288
  owner_lookup_lines = shared ? [
1743
1289
  %(OWNER_VPC_ID=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query "Stacks[0].Outputs[?OutputKey=='VpcId'].OutputValue" --output text); \\),
1744
1290
  %(OWNER_SUBNET_A_ID=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query "Stacks[0].Outputs[?OutputKey=='PrivateSubnetAId'].OutputValue" --output text); \\),
@@ -1777,36 +1323,13 @@ bastion_yaml = shared ? nil : Shared.bastion_yaml(
1777
1323
  deploy_shell_chain = owner_lookup_lines + sam_deploy_lines
1778
1324
  deploy_shell_chain = ["@#{deploy_shell_chain.first}"] + deploy_shell_chain.drop(1) if deploy_shell_chain.first&.match?(/=\$\$\(/)
1779
1325
 
1780
- # `deploy:`'s own pre-deploy `mint-era` bridge (below, inlined into
1781
- # `PREDEPLOYBRIDGE`) — a plain Ruby string built here, not inlined
1782
- # directly in that heredoc, for the same reason as everywhere else
1783
- # comment: it needs its own `if/else` branch on `google_oauth_present`,
1784
- # and Ruby heredoc-in-string-interpolation only reads cleanly one level
1785
- # deep before it gets hard to follow.
1786
- #
1787
- # Google OAuth newly added to an existing stack deadlocks this bridge —
1788
- # confirmed live: stack_outputs (above) only gains PublicSubnetId/
1789
- # BastionSubnetId entries once `google_oauth_present` is true, but those
1790
- # two CloudFormation resources (#{db_id}PublicSubnet/
1791
- # #{db_id}BastionPublicSubnet) don't exist on a stack that predates
1792
- # turning OAuth on — only the upcoming `sam deploy` (further below,
1793
- # still ahead of us here) creates them. This pre-check's own `mint-era`
1794
- # call evaluates stack_outputs against the currently live stack (an
1795
- # `aws cloudformation describe-stacks` eval chain, same one mint-era's
1796
- # own recipe uses), gets an empty string back for both, and hands
1797
- # bastion.yaml an empty `AWS::EC2::Subnet::Id` — CloudFormation refuses
1798
- # that outright. The pre-check fails before `sam deploy` ever runs, so
1799
- # the one deploy that would actually create those outputs never gets
1800
- # the chance to: a hard deadlock, `make deploy` can never get OAuth
1801
- # provisioned on a stack that didn't have it already. Detected the same
1802
- # way a genuine first deploy is detected just below (describe-stacks
1803
- # succeeding or not) — here, describe-stacks succeeds (the stack
1804
- # itself exists) but the specific output this deploy is newly adding
1805
- # does not, yet. Skip the pre-deploy bridge in exactly that one case;
1806
- # the unconditional `mint-era` call at the very end of `deploy:` still
1807
- # covers it once `sam deploy` has actually created PublicSubnetId/
1808
- # BastionSubnetId — the exact same "runs once, after the stack exists"
1809
- # path a domain's true first deploy already takes below.
1326
+ # Google OAuth newly added to an existing stack would deadlock this
1327
+ # bridge: PublicSubnetId/BastionSubnetId don't exist on the live stack
1328
+ # until the upcoming `sam deploy` creates them, so the pre-check's own
1329
+ # `mint-era` call would hand bastion.yaml an empty Subnet::Id and
1330
+ # CloudFormation would refuse it before `sam deploy` ever runs. Skip
1331
+ # the bridge in exactly that case; the unconditional `mint-era` call
1332
+ # at the end of `deploy:` still covers it afterward.
1810
1333
  predeploy_bridge_shell =
1811
1334
  if google_oauth_present
1812
1335
  <<~OAUTHBRIDGE.rstrip
@@ -1842,7 +1365,7 @@ bastion_yaml = shared ? nil : Shared.bastion_yaml(
1842
1365
  ).map { |l| "\t#{l}" }.join("\n")
1843
1366
 
1844
1367
  makefile_content = <<~MAKE
1845
- # GENERATED by bin/project_deploy #{domain} — re-run it to refresh
1368
+ # GENERATED by hecks deploy project #{domain} — re-run it to refresh
1846
1369
  # this file rather than hand-editing.
1847
1370
  #
1848
1371
  # `sam build` invokes the build-<LogicalId> target below (SAM's own
@@ -1853,7 +1376,7 @@ bastion_yaml = shared ? nil : Shared.bastion_yaml(
1853
1376
  # `brew install cargo-lambda`.
1854
1377
 
1855
1378
  HOST_DIR := #{File.join(root, "rust", "host")}
1856
- # `domain_name`, matching bin/project_wasm's own target_mod_name
1379
+ # `domain_name`, matching `hecks build_wasm`'s own target_mod_name
1857
1380
  # (a Rust-build-pipeline naming convention, not AWS resource
1858
1381
  # identity — see HECKS_WASM_PATH's own comment above).
1859
1382
  WASM := #{File.join(root, "rust", "dist", "#{domain_name}.wasm")}
@@ -1868,13 +1391,13 @@ bastion_yaml = shared ? nil : Shared.bastion_yaml(
1868
1391
  # start of a work session silently kept getting deployed through
1869
1392
  # every later kernel change (a real, live bug: this cost a stale
1870
1393
  # deploy against a wasm that predated the kernel's own "mutations"
1871
- # field). `bin/project_wasm` recompiles fast enough that "always
1394
+ # field). `hecks build_wasm` recompiles fast enough that "always
1872
1395
  # rebuild" is the safe default, matching rust/host's own bootstrap
1873
1396
  # build just below, which was never guarded this way to begin with.
1874
- \tcd #{root} && bin/project_wasm #{domain}
1397
+ \tcd #{root} && HECKS_ENVIRONMENT=memory ruby exe/hecks build_wasm domain=#{domain} --wait
1875
1398
  \t@rustup target list --installed 2>/dev/null | grep -qx aarch64-unknown-linux-gnu || rustup target add aarch64-unknown-linux-gnu
1876
1399
  # `rustup run stable`, not a bare `cargo lambda` — same reasoning
1877
- # bin/project_wasm's own Makefile-equivalent line holds itself to: a
1400
+ # `hecks build_wasm`'s own Makefile-equivalent line holds itself to: a
1878
1401
  # `cargo`/`rustc` earlier on PATH than rustup's own shims (Homebrew
1879
1402
  # installs one; common on this kind of machine) is a DIFFERENT
1880
1403
  # toolchain that never saw `rustup target add`, and fails with
@@ -1940,26 +1463,26 @@ bastion_yaml = shared ? nil : Shared.bastion_yaml(
1940
1463
  # `make verify-parity-#{logical_id}` — closes the exact gap the
1941
1464
  # equivalence-gap plan's own Phase 8 named: parity was checked ONLY
1942
1465
  # against CI's fixed test corpus, completely decoupled from what a
1943
- # real `sam deploy` actually ships — `bin/rust_conformance` (the
1466
+ # real `sam deploy` actually ships — `hecks check_conformance` (the
1944
1467
  # differential harness, docs/decisions/0010-ruby-is-the-reference-
1945
1468
  # implementation.md) had never once been run against a specific
1946
1469
  # compiled deploy artifact. Runs it here, for real, against
1947
1470
  # $(WASM) -- the EXACT file `build-#{logical_id}` (above) just built
1948
1471
  # and `deploy:` (below) is about to ship -- not a corpus-wide `cargo
1949
- # build`'s own separate binary. `bin/rust_conformance` exits non-zero
1472
+ # build`'s own separate binary. `hecks check_conformance --wait` exits non-zero
1950
1473
  # on any mismatch, which this target lets propagate uncaught: a real
1951
1474
  # divergence between THIS artifact and Ruby's own reading of the
1952
1475
  # identical source blocks `make deploy` before `sam deploy` ever
1953
1476
  # runs, the same way a failing build already would.
1954
1477
  #
1955
1478
  # `spec/corpus/#{domain_name}.json` is this domain's OWN pinned
1956
- # fuzzer-replay script (the same shape `bin/fuzz`/`bin/run` already
1479
+ # fuzzer-replay script (the same shape `hecks fuzz`/`hecks run` already
1957
1480
  # use) -- if a domain doesn't have one yet, this warns LOUDLY and
1958
1481
  # continues rather than either silently skipping (this project's own
1959
1482
  # standing rule against a silent gap reading as full coverage) or
1960
1483
  # blocking every deploy of a domain nobody has written one for yet.
1961
1484
  #
1962
- # SECOND HALF, BELOW — `bin/rust_conformance_fuzz`, ADR 0037's own fuzz
1485
+ # SECOND HALF, BELOW — `hecks fuzz_conformance`, ADR 0037's own fuzz
1963
1486
  # bridge pointed at this exact $(WASM). A pinned script only proves
1964
1487
  # "matches Ruby on the cases we thought to write down" (ADR 0039's own
1965
1488
  # honest framing); the fuzz bridge is what actually FOUND Findings 1-6.
@@ -1973,12 +1496,12 @@ bastion_yaml = shared ? nil : Shared.bastion_yaml(
1973
1496
  .PHONY: verify-parity-#{logical_id}
1974
1497
  verify-parity-#{logical_id}:
1975
1498
  \t@if [ -f #{root}/spec/corpus/#{domain_name}.json ]; then \\
1976
- \t\tcd #{root} && bin/rust_conformance #{domain} spec/corpus/#{domain_name}.json $(WASM); \\
1499
+ \t\tcd #{root} && HECKS_ENVIRONMENT=memory ruby exe/hecks check_conformance domain=#{domain} script=spec/corpus/#{domain_name}.json artifact=$(WASM) --wait; \\
1977
1500
  \telse \\
1978
- \t\techo "verify-parity-#{logical_id}: no spec/corpus/#{domain_name}.json -- SKIPPING the pre-deploy Ruby/Rust parity check, nothing to compare $(WASM) against. Write one (bin/fuzz/bin/run's own script shape) before this domain's next deploy."; \\
1501
+ \t\techo "verify-parity-#{logical_id}: no spec/corpus/#{domain_name}.json -- SKIPPING the pre-deploy Ruby/Rust parity check, nothing to compare $(WASM) against. Write one (hecks fuzz/hecks run's own script shape) before this domain's next deploy."; \\
1979
1502
  \tfi
1980
1503
  \t@echo "verify-parity-#{logical_id}: fuzzing $(WASM) against generated sequences (ADR 0037's bridge, WARN-ONLY -- see this target's own comment)..."
1981
- \t-@cd #{root} && bin/rust_conformance_fuzz #{domain} $(WASM)
1504
+ \t-@cd #{root} && HECKS_ENVIRONMENT=memory ruby exe/hecks fuzz_conformance domain=#{domain} artifact=$(WASM) --wait
1982
1505
 
1983
1506
  # `make mint-era` — takes #{stack_name}'s RDS instance from freshly
1984
1507
  # created to "era 1 minted, ready for HECKS_DOMAIN/HECKS_ERA to
@@ -2201,7 +1724,7 @@ bastion_yaml = shared ? nil : Shared.bastion_yaml(
2201
1724
  # safe either way, since mint-era's own quiet-reboot path is a
2202
1725
  # no-op the moment nothing has actually changed.
2203
1726
  #
2204
- # See predeploy_bridge_shell's own comment (bin/project_deploy,
1727
+ # See predeploy_bridge_shell's own comment (hecks deploy project,
2205
1728
  # above, right before deploy_recipe_lines) for why this branches
2206
1729
  # on google_oauth_present at all -- the short version: adding
2207
1730
  # Google OAuth to an EXISTING stack would otherwise deadlock this
@@ -2214,15 +1737,10 @@ bastion_yaml = shared ? nil : Shared.bastion_yaml(
2214
1737
  \t$(MAKE) mint-era
2215
1738
  MAKE
2216
1739
 
2217
- # Everything `sam deploy --guided` asks interactively — stack name,
2218
- # region, capabilities, rollback behavior — is already known once the
2219
- # domain and its deployed_to("AwsLambda") block are: none of it needs
2220
- # a human typing answers on every deploy. There's no secret to carry
2221
- # here either now: DATABASE_URL is composed inside the template itself
2222
- # from RDS's own auto-generated Secrets Manager password, so nothing
2223
- # gets typed, saved, or passed as a --parameter-overrides flag at all.
1740
+ # Everything `sam deploy --guided` would ask interactively is already
1741
+ # known from the domain's own `deployed_to("AwsLambda")` block.
2224
1742
  samconfig_toml = <<~TOML
2225
- # GENERATED by bin/project_deploy #{domain} — re-run it to refresh
1743
+ # GENERATED by hecks deploy project #{domain} — re-run it to refresh
2226
1744
  # this file rather than hand-editing.
2227
1745
  version = 0.1
2228
1746
 
@@ -2243,46 +1761,11 @@ bastion_yaml = shared ? nil : Shared.bastion_yaml(
2243
1761
  files
2244
1762
  end
2245
1763
 
2246
- # Builds the CloudFront/WAFv2/logging resources a `PII`-marked domain gets fronted by.
2247
- #
2248
- # **`PII` → CloudFront** — once a domain marks a field "pii", its own public
2249
- # surface (`WebFunction` when one exists, `#{logical_id}` otherwise —
2250
- # `fronted_logical_id`, computed where both are known, in `call`) gets
2251
- # fronted by a distribution carrying a WAFv2 WebACL (AWS managed rule
2252
- # groups), security response headers, geo-restriction, and access
2253
- # logging — every other domain's own template is untouched
2254
- # (`pii_detected` false means `call` never invokes this at all).
2255
- #
2256
- # **`OAC` only for the AWS_IAM case** (`use_oac`) — the already-public
2257
- # `WebFunction`/`rust_web` shape (`AuthType: NONE`) is left exactly as
2258
- # reachable as it already was; CloudFront adds WAF/headers/geo/logging
2259
- # on top of that, it does not change who could already call the
2260
- # Function URL directly. `#{logical_id}` itself (the AWS_IAM,
2261
- # internal-dispatch default) is the opposite: OAC lets CloudFront sign
2262
- # requests to it via SigV4 while `PiiLambdaInvokePermission`'s own
2263
- # `SourceArn` admits only this one distribution — direct, unsigned
2264
- # access to the Function URL stays refused exactly as it was before
2265
- # this ran.
2266
- #
2267
- # **Managed cache/origin-request policy ids** — not custom resources.
2268
- # `4135ea2d-6df8-44a3-9df3-4b5a84be39ad`/`216adef6-5c7f-47e4-b989-
2269
- # 5492eafa07d3` are AWS's own permanent, account-independent
2270
- # `Managed-CachingDisabled`/`Managed-AllViewer` ids (the same ones the
2271
- # console's own dropdown offers) — this fronts a Lambda dispatch
2272
- # endpoint, not a static site; caching a response meant for exactly
2273
- # one caller would be a real correctness bug, not a performance choice
2274
- # made once here.
2275
- #
2276
- # @param fronted_logical_id [String] the Lambda resource this distribution fronts
2277
- # @param use_oac [Boolean] true only when `fronted_logical_id`'s own FunctionUrlConfig is
2278
- # AWS_IAM (never true for WebFunction/rust_web, both always `NONE`)
2279
- # @param geo_restriction_type ["none", "allowlist", "blocklist"] `deployed_to`'s own
2280
- # `geo_restriction` setting; "none" (no restriction, structurally present so a later
2281
- # change is a one-line `deployed_to` edit, not a template rewrite) when unset
2282
- # @param geo_restriction_countries [Array<String>] ISO 3166-1 alpha-2 codes; ignored when
2283
- # `geo_restriction_type` is "none"
2284
- # @return [String] the Resources entries to splice in before Outputs:, absolutely
2285
- # indented to 2 spaces (this stack's own top-level Resources entry column)
1764
+ # Builds the CloudFront/WAFv2/logging resources a pii-marked domain is
1765
+ # fronted by. `use_oac` signs requests only for the AWS_IAM case; the
1766
+ # already-public WebFunction/rust_web Function URL stays as reachable
1767
+ # as before. Managed cache/origin-request policy ids are AWS's own
1768
+ # permanent `Managed-CachingDisabled`/`Managed-AllViewer` ids.
2286
1769
  def pii_cloudfront_yaml(fronted_logical_id:, use_oac:, geo_restriction_type:, geo_restriction_countries:)
2287
1770
  reindent = ->(text) { text.each_line.map { |line| line.strip.empty? ? line : " #{line}" }.join }
2288
1771