hecks 3.10.0 → 3.12.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 (542) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/bluebook/pizzas.bluebook +29 -0
  3. data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
  4. data/exe/hecks +4 -4
  5. data/lib/hecks/adapters/driven/d1.rb +2 -10
  6. data/lib/hecks/adapters/driven/heki.adapter +1 -1
  7. data/lib/hecks/adapters/driven/heki.rb +2 -12
  8. data/lib/hecks/adapters/driven/lambda.adapter +1 -1
  9. data/lib/hecks/adapters/driven/lambda.rb +3 -18
  10. data/lib/hecks/adapters/driven/postgres.rb +4 -11
  11. data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
  12. data/lib/hecks/adapters/driven/sqlite.rb +2 -10
  13. data/lib/hecks/adapters/driving/admission.rb +46 -0
  14. data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
  15. data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
  16. data/lib/hecks/adapters/driving/cli.rb +127 -0
  17. data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
  18. data/lib/hecks/adapters/driving/cli_runner/resolution.rb +158 -0
  19. data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
  20. data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
  21. data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
  22. data/lib/hecks/adapters/driving/cli_runner.rb +70 -0
  23. data/lib/hecks/adapters/driving/command_request.rb +121 -0
  24. data/lib/hecks/adapters/driving/handle.rb +214 -0
  25. data/lib/hecks/adapters/driving/json.rb +136 -0
  26. data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
  27. data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
  28. data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
  29. data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
  30. data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
  31. data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
  32. data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
  33. data/lib/hecks/adapters/driving/mcp.rb +159 -0
  34. data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
  35. data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
  36. data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
  37. data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
  38. data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
  39. data/lib/hecks/adapters/driving/newcomer.rb +50 -0
  40. data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
  41. data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
  42. data/lib/hecks/adapters/driving/ruby.rb +83 -0
  43. data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
  44. data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
  45. data/lib/hecks/behaviors/expectations/boot.rb +1 -1
  46. data/lib/hecks/bench/ruby_runner.rb +1 -1
  47. data/lib/hecks/bluebook/ask_resolution.rb +130 -0
  48. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
  49. data/lib/hecks/bluebook/assembly/contracts/read_models.rb +1 -1
  50. data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
  51. data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
  52. data/lib/hecks/bluebook/assembly/marks.rb +4 -15
  53. data/lib/hecks/bluebook/assembly/scalars.rb +39 -0
  54. data/lib/hecks/bluebook/attribute.rb +1 -1
  55. data/lib/hecks/bluebook/behaviour/command.rb +13 -8
  56. data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
  57. data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
  58. data/lib/hecks/bluebook/capabilities.rb +91 -9
  59. data/lib/hecks/bluebook/domain_port.rb +16 -4
  60. data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
  61. data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
  62. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
  63. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
  64. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
  65. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
  66. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
  67. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
  68. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
  69. data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
  70. data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
  71. data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
  72. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
  73. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
  74. data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
  75. data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
  76. data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
  77. data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
  78. data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
  79. data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
  80. data/lib/hecks/bluebook/expression/projection.json +36 -0
  81. data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
  82. data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
  83. data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
  84. data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
  85. data/lib/hecks/bluebook/expression/resolver.rb +20 -1
  86. data/lib/hecks/bluebook/hecksagon.rb +23 -3
  87. data/lib/hecks/bluebook/lifecycle.rb +22 -3
  88. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
  89. data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
  90. data/lib/hecks/bluebook/meta_validator/readings.rb +24 -1
  91. data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
  92. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +6 -3
  93. data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
  94. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
  95. data/lib/hecks/bluebook/meta_validator.rb +12 -12
  96. data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
  97. data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
  98. data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
  99. data/lib/hecks/bluebook/model_check.rb +8 -2
  100. data/lib/hecks/bluebook/policy.rb +4 -2
  101. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  102. data/lib/hecks/bluebook/synthesizer.rb +2 -0
  103. data/lib/hecks/bluebook.rb +2 -0
  104. data/lib/hecks/cli/command_table.rb +1 -1
  105. data/lib/hecks/cli/console.rb +2 -1
  106. data/lib/hecks/cli/document.rb +1 -1
  107. data/lib/hecks/cli/launcher_words.rb +4 -4
  108. data/lib/hecks/cli/mcp.rb +4 -4
  109. data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
  110. data/lib/hecks/cli/project_cli/templates.rb +5 -5
  111. data/lib/hecks/cli/project_cli.rb +9 -6
  112. data/lib/hecks/cli/run.rb +2 -2
  113. data/lib/hecks/cli/smoke_http/checks.rb +4 -1
  114. data/lib/hecks/cli/smoke_http/transport.rb +3 -3
  115. data/lib/hecks/cli/smoke_http.rb +3 -0
  116. data/lib/hecks/codemod.rb +5 -2
  117. data/lib/hecks/corpus/routes.rb +7 -0
  118. data/lib/hecks/corpus/rust.rb +3 -2
  119. data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -4
  120. data/lib/hecks/doc/reference/rendering.rb +6 -3
  121. data/lib/hecks/forms/app/commands.rb +8 -7
  122. data/lib/hecks/forms/app/queries.rb +2 -1
  123. data/lib/hecks/forms/app.rb +1 -1
  124. data/lib/hecks/forms/record_renderer.rb +1 -1
  125. data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
  126. data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
  127. data/lib/hecks/fuzzing/failure_triage.rb +2 -1
  128. data/lib/hecks/fuzzing/mutation/run.rb +11 -4
  129. data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
  130. data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
  131. data/lib/hecks/fuzzing/properties/guards.rb +1 -1
  132. data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
  133. data/lib/hecks/fuzzing/self_consistency.rb +6 -2
  134. data/lib/hecks/fuzzing/shrinker.rb +3 -1
  135. data/lib/hecks/fuzzing/value_generator.rb +19 -5
  136. data/lib/hecks/gate/stages.yml +33 -7
  137. data/lib/hecks/grammar/evolve.rb +1 -1
  138. data/lib/hecks/grammar/expression_operators.json +372 -0
  139. data/lib/hecks/grammar/operators.rb +3 -1
  140. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
  141. data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
  142. data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
  143. data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
  144. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
  145. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
  146. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
  147. data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
  148. data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
  149. data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
  150. data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
  151. data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
  152. data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
  153. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
  154. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
  155. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
  156. data/lib/hecks/hecks/adapters/local_files.rb +2 -2
  157. data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
  158. data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
  159. data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
  160. data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
  161. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
  162. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
  163. data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
  164. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
  165. data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +133 -0
  166. data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
  167. data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
  168. data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
  169. data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
  170. data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
  171. data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
  172. data/lib/hecks/hecks/adapters/terminal.rb +13 -11
  173. data/lib/hecks/hecks/codebase.behaviors +13 -1
  174. data/lib/hecks/hecks/codebase.bluebook +239 -5
  175. data/lib/hecks/hecks/custodian.behaviors +24 -6
  176. data/lib/hecks/hecks/custodian.bluebook +6 -1227
  177. data/lib/hecks/hecks/deploy.behaviors +1 -1
  178. data/lib/hecks/hecks/deploy.world +1 -3
  179. data/lib/hecks/hecks/hecks.behaviors +1 -1
  180. data/lib/hecks/hecks/hecks.hecksagon +53 -18
  181. data/lib/hecks/hecks/hecks.world +13 -10
  182. data/lib/hecks/hecks/rehearsal.bluebook +477 -0
  183. data/lib/hecks/hecks/site.behaviors +1 -1
  184. data/lib/hecks/hecks/site.world +0 -1
  185. data/lib/hecks/hecks/tooling.bluebook +1238 -0
  186. data/lib/hecks/indifferent_key.rb +20 -0
  187. data/lib/hecks/language/adapter.bluebook +13 -3
  188. data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
  189. data/lib/hecks/language/bluebook/bluebook.behaviors +23 -0
  190. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
  191. data/lib/hecks/language/bluebook/entity.bluebook +18 -0
  192. data/lib/hecks/language/bluebook/policy.behaviors +19 -0
  193. data/lib/hecks/language/bluebook/policy.bluebook +9 -1
  194. data/lib/hecks/language/bluebook/query.bluebook +1 -1
  195. data/lib/hecks/language/bluebook/vocabulary.bluebook +63 -4
  196. data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
  197. data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
  198. data/lib/hecks/language/oidc.json +10 -0
  199. data/lib/hecks/language/port.bluebook +4 -4
  200. data/lib/hecks/literal.rb +12 -3
  201. data/lib/hecks/ports/key_vault.rb +7 -2
  202. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
  203. data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
  204. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
  205. data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
  206. data/lib/hecks/projection_files/expression_tables.rb +7 -2
  207. data/lib/hecks/projections/deploy/box/compose.rb +6 -4
  208. data/lib/hecks/projections/deploy/box/edge.rb +62 -0
  209. data/lib/hecks/projections/deploy/box/logging.rb +99 -0
  210. data/lib/hecks/projections/deploy/box/roll_scripts.rb +16 -8
  211. data/lib/hecks/projections/deploy/box/settings/cdn.rb +40 -0
  212. data/lib/hecks/projections/deploy/box/settings/logs.rb +40 -0
  213. data/lib/hecks/projections/deploy/box/settings/patterns.rb +11 -0
  214. data/lib/hecks/projections/deploy/box/settings/types.rb +16 -1
  215. data/lib/hecks/projections/deploy/box/settings.rb +6 -2
  216. data/lib/hecks/projections/deploy/box/stack_yaml.rb +5 -2
  217. data/lib/hecks/projections/deploy/box/templates/Makefile.tmpl +16 -0
  218. data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +8 -4
  219. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +32 -0
  220. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
  221. data/lib/hecks/projections/deploy/box/templates/edge.mk.tmpl +17 -0
  222. data/lib/hecks/projections/deploy/box/templates/edge.yaml.tmpl +65 -0
  223. data/lib/hecks/projections/deploy/box/templates/log-group.yaml.tmpl +12 -0
  224. data/lib/hecks/projections/deploy/box/templates/log-policy.yaml.tmpl +7 -0
  225. data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +5 -4
  226. data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +5 -4
  227. data/lib/hecks/projections/deploy/box.rb +15 -3
  228. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  229. data/lib/hecks/projections/deploy/shared.rb +5 -2
  230. data/lib/hecks/projections/model/deviations.rb +2 -2
  231. data/lib/hecks/projections/model.rb +2 -2
  232. data/lib/hecks/projections/oidc.rb +13 -1
  233. data/lib/hecks/projections/shape.rb +5 -2
  234. data/lib/hecks/projections/site/cms_editor/attributes.rb +26 -6
  235. data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
  236. data/lib/hecks/projections/site/cms_editor/checks.rb +14 -1
  237. data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
  238. data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
  239. data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
  240. data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
  241. data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
  242. data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
  243. data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
  244. data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
  245. data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
  246. data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
  247. data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
  248. data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
  249. data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
  250. data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
  251. data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
  252. data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
  253. data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
  254. data/lib/hecks/projections/site/cms_editor/schema.rb +96 -19
  255. data/lib/hecks/projections/site/cms_editor/setting.rb +52 -9
  256. data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
  257. data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +18 -1
  258. data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +251 -57
  259. data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
  260. data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +32 -24
  261. data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
  262. data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
  263. data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
  264. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
  265. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
  266. data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
  267. data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
  268. data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
  269. data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
  270. data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
  271. data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
  272. data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
  273. data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
  274. data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
  275. data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
  276. data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
  277. data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +78 -24
  278. data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +6 -0
  279. data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
  280. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +19 -2
  281. data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +31 -8
  282. data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
  283. data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +131 -2
  284. data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
  285. data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
  286. data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
  287. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +8 -5
  288. data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
  289. data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
  290. data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +229 -34
  291. data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +40 -81
  292. data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
  293. data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +29 -12
  294. data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
  295. data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
  296. data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +53 -17
  297. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +178 -88
  298. data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
  299. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
  300. data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
  301. data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
  302. data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
  303. data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
  304. data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
  305. data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
  306. data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
  307. data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
  308. data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
  309. data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +2 -1
  310. data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
  311. data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
  312. data/lib/hecks/projections/site/cms_editor.rb +49 -23
  313. data/lib/hecks/projections/site/role_probe.rb +2 -0
  314. data/lib/hecks/projections/site/site_root/workflow.rb +5 -0
  315. data/lib/hecks/projector/cli_audience.rb +22 -1
  316. data/lib/hecks/projector/cli_projector/listing.rb +1 -1
  317. data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
  318. data/lib/hecks/projector/cli_projector/usage.rb +15 -4
  319. data/lib/hecks/projector/docs_projector/closing.rb +1 -1
  320. data/lib/hecks/projector/docs_projector.rb +1 -1
  321. data/lib/hecks/projector/exporter/bindings.rb +40 -10
  322. data/lib/hecks/projector/exporter/capability_values.rb +55 -0
  323. data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
  324. data/lib/hecks/projector/target.rb +11 -0
  325. data/lib/hecks/projector.rb +32 -5
  326. data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
  327. data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
  328. data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
  329. data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
  330. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
  331. data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
  332. data/lib/hecks/quality_control/quality_control.bluebook +6 -2
  333. data/lib/hecks/release/approval.rb +64 -0
  334. data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
  335. data/lib/hecks/router/namespace_installer.rb +16 -16
  336. data/lib/hecks/router.rb +1 -1
  337. data/lib/hecks/runtime/best_effort.rb +22 -0
  338. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
  339. data/lib/hecks/runtime/command_interpreter.rb +3 -2
  340. data/lib/hecks/runtime/command_rules/emission.rb +1 -1
  341. data/lib/hecks/runtime/dispatcher.rb +4 -4
  342. data/lib/hecks/runtime/entity_element/location.rb +1 -1
  343. data/lib/hecks/runtime/event.rb +5 -0
  344. data/lib/hecks/runtime/identity.rb +7 -6
  345. data/lib/hecks/runtime/interpreting.rb +2 -1
  346. data/lib/hecks/runtime/invocation_builder.rb +1 -1
  347. data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
  348. data/lib/hecks/runtime/loader.rb +10 -10
  349. data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
  350. data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
  351. data/lib/hecks/runtime/policy_interpreter.rb +6 -6
  352. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
  353. data/lib/hecks/runtime/reaction_invocation.rb +1 -1
  354. data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
  355. data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
  356. data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
  357. data/lib/hecks/runtime/registry/merging.rb +1 -1
  358. data/lib/hecks/runtime/registry/providers.rb +16 -0
  359. data/lib/hecks/runtime/registry/read_repository.rb +6 -4
  360. data/lib/hecks/runtime/registry/verification.rb +4 -1
  361. data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
  362. data/lib/hecks/runtime/registry.rb +1 -1
  363. data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
  364. data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
  365. data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
  366. data/lib/hecks/runtime/saga_interpreter.rb +3 -3
  367. data/lib/hecks/runtime/tenant_check.rb +5 -4
  368. data/lib/hecks/runtime/value/coercion.rb +2 -2
  369. data/lib/hecks/runtime/value/validation.rb +16 -4
  370. data/lib/hecks/runtime.rb +9 -9
  371. data/lib/hecks/rust_build/host.rb +11 -4
  372. data/lib/hecks/rust_build/prebuilt_binary.rb +101 -0
  373. data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
  374. data/lib/hecks/rust_build/wasm.rb +1 -1
  375. data/lib/hecks/rust_build/wasm_browser.rb +1 -1
  376. data/lib/hecks/rust_build.rb +11 -0
  377. data/lib/hecks/served_options.rb +53 -0
  378. data/lib/hecks/site/bluebook/site.bluebook +1 -0
  379. data/lib/hecks/storehouse/audit.rb +1 -1
  380. data/lib/hecks/storehouse/dispatching.rb +8 -6
  381. data/lib/hecks/storehouse/introspection.rb +2 -2
  382. data/lib/hecks/storehouse/querying.rb +6 -4
  383. data/lib/hecks/storehouse.rb +4 -4
  384. data/lib/hecks/tools/ci_gate_decision.rb +35 -0
  385. data/lib/hecks/tools/ci_gates.rb +20 -1
  386. data/lib/hecks/tools/comment_style/source_file.rb +4 -1
  387. data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
  388. data/lib/hecks/tools/lanes/files.rb +8 -0
  389. data/lib/hecks/tools/lanes/prebuilt_workflow.rb +81 -0
  390. data/lib/hecks/tools/lanes/rows.rb +6 -0
  391. data/lib/hecks/tools/lanes.rb +1 -0
  392. data/lib/hecks/tools/oidc_manifests.rb +4 -5
  393. data/lib/hecks/tools/regeneration_run.rb +11 -4
  394. data/lib/hecks/tools/release_only.rb +111 -0
  395. data/lib/hecks/tools/site_routes/editor_files.rb +12 -5
  396. data/lib/hecks/tools/tenant_provisioning.rb +2 -2
  397. data/lib/hecks/tools/tools_doc.rb +4 -4
  398. data/lib/hecks/version.rb +1 -1
  399. data/lib/hecks/vocabulary.rb +19 -5
  400. data/lib/hecks.rb +15 -15
  401. data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
  402. data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
  403. data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
  404. data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
  405. data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
  406. data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
  407. data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
  408. data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
  409. data/prebuilt/9ee54e2f0d4d6d3fe046827779b221e02de665fba911a048ae5dd8b8fbfb7d90.marshal +0 -0
  410. data/prebuilt/d74a00e996222c1d8b676d5959092effc46b4dc59e0555f3bf9554ac58e3094a.marshal +0 -0
  411. data/prebuilt/verdicts-df19f35868d3284a70f55e42dcef87ef4e65e6a678e5135f72e1f6cff4c1357b.json +1 -0
  412. data/rust/.gitignore +6 -0
  413. data/rust/Cargo.toml +6 -0
  414. data/rust/build/src/binding_pass.rs +103 -0
  415. data/rust/build/src/cargo_sync.rs +6 -0
  416. data/rust/build/src/lineage_pass.rs +3 -3
  417. data/rust/build/src/main.rs +17 -5
  418. data/rust/build/src/pipeline.rs +48 -5
  419. data/rust/build/src/seams_pass.rs +123 -0
  420. data/rust/build/src/translation_pass.rs +167 -0
  421. data/rust/codegen/src/asks.rs +103 -0
  422. data/rust/codegen/src/commands.rs +13 -8
  423. data/rust/codegen/src/constraints.rs +25 -0
  424. data/rust/codegen/src/domain_generator.rs +2 -0
  425. data/rust/codegen/src/expr_emitter.rs +13 -0
  426. data/rust/codegen/src/json_codec.rs +54 -15
  427. data/rust/codegen/src/main.rs +1 -0
  428. data/rust/codegen/src/mutations.rs +128 -3
  429. data/rust/codegen/src/queries.rs +11 -2
  430. data/rust/codegen/src/reactions.rs +1 -1
  431. data/rust/codegen/src/types.rs +166 -8
  432. data/rust/host/HECKS_RELEASE +1 -1
  433. data/rust/host/src/api.rs +4 -4
  434. data/rust/host/src/bin/validate_harness.rs +43 -0
  435. data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
  436. data/rust/host/src/boundary_fuzz/ir.rs +1 -0
  437. data/rust/host/src/boundary_fuzz/server.rs +1 -1
  438. data/rust/host/src/checkout.rs +35 -19
  439. data/rust/host/src/commerce_ir.rs +408 -2
  440. data/rust/host/src/expr_json.rs +118 -0
  441. data/rust/host/src/extension.rs +13 -1
  442. data/rust/host/src/lib.rs +1 -0
  443. data/rust/host/src/mint.rs +1 -1
  444. data/rust/host/src/payments/tests.rs +27 -1
  445. data/rust/host/src/payments.rs +12 -4
  446. data/rust/host/src/platform_signin/attack_tests.rs +1100 -0
  447. data/rust/host/src/platform_signin/config.rs +512 -0
  448. data/rust/host/src/platform_signin/cookie.rs +237 -0
  449. data/rust/host/src/platform_signin/extension.rs +190 -0
  450. data/rust/host/src/platform_signin/fetcher.rs +322 -0
  451. data/rust/host/src/platform_signin/handler.rs +251 -0
  452. data/rust/host/src/platform_signin/handler_tests.rs +554 -0
  453. data/rust/host/src/platform_signin/jti.rs +196 -0
  454. data/rust/host/src/platform_signin/jwks.rs +542 -0
  455. data/rust/host/src/platform_signin/jws.rs +249 -0
  456. data/rust/host/src/platform_signin/known_answer_tests.rs +237 -0
  457. data/rust/host/src/platform_signin/mapping_tests.rs +254 -0
  458. data/rust/host/src/platform_signin/mod.rs +75 -0
  459. data/rust/host/src/platform_signin/mode_tests.rs +382 -0
  460. data/rust/host/src/platform_signin/pages.rs +204 -0
  461. data/rust/host/src/platform_signin/query.rs +233 -0
  462. data/rust/host/src/platform_signin/session.rs +151 -0
  463. data/rust/host/src/platform_signin/settings.rs +385 -0
  464. data/rust/host/src/platform_signin/strict_json.rs +351 -0
  465. data/rust/host/src/platform_signin/test_support.rs +235 -0
  466. data/rust/host/src/platform_signin/ttl_tests.rs +94 -0
  467. data/rust/host/src/platform_signin/verify.rs +604 -0
  468. data/rust/host/src/platform_signin/wiring_support.rs +319 -0
  469. data/rust/host/src/query_step.rs +22 -4
  470. data/rust/host/src/reference_validate.rs +653 -108
  471. data/rust/host/src/resend.rs +2 -0
  472. data/rust/host/src/server.rs +19 -6
  473. data/rust/host/src/ui_schema.rs +132 -5
  474. data/rust/host/src/web/newsletter.rs +7 -8
  475. data/rust/host/src/web/newsletter_send.rs +38 -13
  476. data/rust/host/src/web/registrations.rs +229 -26
  477. data/rust/host/src/web.rs +53 -9
  478. data/rust/parser/src/emit.rs +38 -6
  479. data/rust/parser/src/expr/ast_json.rs +7 -1
  480. data/rust/parser/src/expr/resolver.rs +171 -6
  481. data/rust/parser/src/ir.rs +7 -0
  482. data/rust/parser/src/keywords.rs +24 -0
  483. data/rust/parser/src/main.rs +3 -0
  484. data/rust/parser/src/parse/aggregate.rs +2 -1
  485. data/rust/parser/src/parse/chapter.rs +2 -2
  486. data/rust/parser/src/parse/command.rs +2 -0
  487. data/rust/parser/src/parse/hecksagon.rs +5 -2
  488. data/rust/parser/src/parse/lifecycle.rs +81 -0
  489. data/rust/parser/src/parse/mod.rs +15 -1
  490. data/rust/parser/src/parse/policy.rs +13 -2
  491. data/rust/parser/src/parse/value_object.rs +34 -17
  492. data/rust/parser/src/ruby_value.rs +1 -1
  493. data/rust/src/exemplar/commands.rs +4 -4
  494. data/rust/src/exemplar/constraints.rs +1 -1
  495. data/rust/src/exemplar/json.rs +43 -1
  496. data/rust/src/kernel/cli.rs +7 -5
  497. data/rust/src/kernel/dispatch.rs +2 -2
  498. data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
  499. data/rust/src/kernel/expr.rs +66 -3
  500. data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
  501. data/rust/src/kernel/expression_operators/text.rs +26 -2
  502. data/rust/src/kernel/json.rs +15 -0
  503. data/rust/src/kernel/mod.rs +1 -1
  504. data/rust/src/kernel/orchestrate.rs +54 -5
  505. data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
  506. data/rust/src/kernel/pattern.rs +60 -0
  507. data/rust/src/kernel/reference_lookup.rs +90 -7
  508. metadata +178 -38
  509. data/lib/hecks/doors/cli_door/nesting.rb +0 -34
  510. data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
  511. data/lib/hecks/doors/cli_door.rb +0 -124
  512. data/lib/hecks/doors/cli_runner/answers.rb +0 -138
  513. data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
  514. data/lib/hecks/doors/cli_runner/settling.rb +0 -115
  515. data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
  516. data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
  517. data/lib/hecks/doors/cli_runner.rb +0 -66
  518. data/lib/hecks/doors/command_request.rb +0 -119
  519. data/lib/hecks/doors/handle.rb +0 -208
  520. data/lib/hecks/doors/json_door.rb +0 -132
  521. data/lib/hecks/doors/launcher_options.rb +0 -247
  522. data/lib/hecks/doors/mcp_door/properties.rb +0 -47
  523. data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
  524. data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
  525. data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
  526. data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
  527. data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
  528. data/lib/hecks/doors/mcp_door.rb +0 -156
  529. data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
  530. data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
  531. data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
  532. data/lib/hecks/doors/mcp_door_scope.rb +0 -201
  533. data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
  534. data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
  535. data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
  536. data/lib/hecks/doors/ruby_door.rb +0 -80
  537. data/lib/hecks/doors/usage_cache.rb +0 -139
  538. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_widget.js.tmpl +0 -282
  539. data/lib/hecks/projections/site/cms_editor/templates/src/ui/media_picker.js.tmpl +0 -109
  540. data/prebuilt/c28d6d9db14eea3e17161e9d9c262efee30b256cf97693a7cfd552bce24b2f7f.marshal +0 -0
  541. data/prebuilt/cb375cba61ca0dbe8ddb9739c306510fe05e5f4b7a9b6955caff8224c63ea0e0.marshal +0 -0
  542. data/prebuilt/verdicts-d6d584ad43b5f3cebf88aafbbdbf024c38574acf18e06adffbc555f7786b1c2f.json +0 -1
@@ -126,7 +126,7 @@ module Hecks
126
126
  # skips the vendored lookup
127
127
  # @return [Array<String>] file paths to read, in load order; `[]` when none applies
128
128
  def fallback_source_files(bluebook, directory, domain_files, registry)
129
- vendored_name = registry && vendored_bluebook_name_for(registry, bluebook.name)
129
+ vendored_name = registry && vendored_bluebook_name_for(registry, bluebook.name, directory)
130
130
  framework_path = Framework.members[bluebook.name]
131
131
  return [framework_path] if framework_path
132
132
  return vendored_source_for(directory, vendored_name) if vendored_name
@@ -139,19 +139,30 @@ module Hecks
139
139
 
140
140
  # Recovers the vendored package name behind a bluebook, if some hecksagon vendored it.
141
141
  #
142
- # Matched by Pascal-casing: the vendored name Pascal-cases to this
143
- # bluebook's own declared name.
142
+ # Matched first by Pascal-casing (the package name Pascal-cases to the bluebook's declared
143
+ # name), then by the package's own files: a package whose chapter is named otherwise (the
144
+ # package `gallery` declaring `Galleries`) is the one whose files declare that name.
144
145
  #
145
146
  # @param registry [Runtime::Registry] the registry whose hecksagons are searched
146
147
  # @param bluebook_name [String] the bluebook's declared Pascal-case name
148
+ # @param directory [String] path of the domain's own bluebook directory, whose parent
149
+ # holds `vendor/embryonaut_bluebooks`
147
150
  # @return [String, nil] the package name as written in `attaches ... from: :vendor`; nil
148
- # when no hecksagon vendored a package whose name Pascal-cases to `bluebook_name`
149
- def vendored_bluebook_name_for(registry, bluebook_name)
150
- registry.hecksagons.each_value do |hecksagon|
151
- match = hecksagon.vendored_packages.find { |name| Naming.pascal(name) == bluebook_name }
152
- return match if match
153
- end
154
- nil
151
+ # when no vendored package matches `bluebook_name` by name or by declaration
152
+ def vendored_bluebook_name_for(registry, bluebook_name, directory)
153
+ names = registry.hecksagons.each_value.flat_map(&:vendored_packages).uniq
154
+ names.find { |name| Naming.pascal(name) == bluebook_name } ||
155
+ names.find { |name| declared_by_package?(directory, name, bluebook_name) }
156
+ end
157
+
158
+ # Asks whether any file of a vendored package opens the named bluebook.
159
+ #
160
+ # @param directory [String] path of the domain's own bluebook directory
161
+ # @param package [String] the vendored package name
162
+ # @param bluebook_name [String] the declared bluebook name looked for
163
+ # @return [Boolean] true when some `.bluebook` file of the package declares `bluebook_name`
164
+ def declared_by_package?(directory, package, bluebook_name)
165
+ vendored_source_for(directory, package).any? { |path| declares_bluebook?(path, bluebook_name) }
155
166
  end
156
167
 
157
168
  # Lists the `.bluebook` files a vendored embryonaut package ships.
@@ -1,3 +1,5 @@
1
+ require_relative "../../../../../indifferent_key"
2
+
1
3
  module Hecks
2
4
  module Adapters
3
5
  class PostgresEra
@@ -17,11 +19,11 @@ module Hecks
17
19
  # Coalesce instead of `setting` for the era: the factory always passes `era:`, nil until
18
20
  # the boot gate resolves it, and nil can never be a real override.
19
21
  def resolve_era!(settings)
20
- @era = settings.key?(:era) ? settings[:era] : settings["era"]
22
+ @era = IndifferentKey.read(settings, :era)
21
23
  @era ||= @lineage.current_era
22
24
  # Non-nil only for a held-but-superseded boot. `append` and `atomic_put` refuse on it
23
25
  # because a superuser walks through the RLS fence.
24
- @superseded_by = settings.key?(:superseded_by) ? settings[:superseded_by] : settings["superseded_by"]
26
+ @superseded_by = IndifferentKey.read(settings, :superseded_by)
25
27
  end
26
28
 
27
29
  def provision!
@@ -1,6 +1,7 @@
1
1
  require_relative "../lineage"
2
2
  require_relative "../../storage_shape"
3
3
  require_relative "context"
4
+ require_relative "../../../../../../indifferent_key"
4
5
 
5
6
  module Hecks
6
7
  module Adapters
@@ -22,7 +23,7 @@ module Hecks
22
23
  lineage.check_fence_applies!(allow_superuser: PostgresEra.setting(settings, :allow_superuser, default: false))
23
24
  lineage.ensure_base!
24
25
  # key? first, never `||`: a stored `false` must not read as an absent key.
25
- role = settings.key?(:role) ? settings[:role] : settings["role"]
26
+ role = IndifferentKey.read(settings, :role)
26
27
  resolve_era(Context.new(registry: registry, bluebook: bluebook, current_text: current_text,
27
28
  lineage: lineage, role: role, directory: directory))
28
29
  ensure
@@ -26,7 +26,7 @@ module Hecks
26
26
  # @raise [ArgumentError] on an unknown option
27
27
  def build(registry, domain, aggregate, bind, **)
28
28
  opts = BuildOptions.new(**)
29
- settings = adapter_settings(registry, domain, bind, opts.settings_verb)
29
+ settings = adapter_settings(registry, domain, aggregate, bind, opts.settings_verb)
30
30
  adapter = registry.adapter_class(bind.adapter).new(aggregate: aggregate, settings: settings, root: registry.root)
31
31
  repository = AppendOnly.new(CodecBoundary.guard!(adapter))
32
32
  # `:atomic_append` (PostgresEra) means `append` already committed the
@@ -39,14 +39,17 @@ module Hecks
39
39
  #
40
40
  # @param registry [Runtime::Registry] the booted registry
41
41
  # @param domain [String, Symbol] the domain the aggregate belongs to
42
+ # @param aggregate [Bluebook::Aggregate] the aggregate the adapter is built for
42
43
  # @param bind [Bluebook::Bind] the bind naming the adapter
43
44
  # @param settings_verb [String] the verb whose settings configure the adapter
44
- # @return [Hash] the declared settings plus the domain, resolved era and superseding era
45
- def adapter_settings(registry, domain, bind, settings_verb)
45
+ # @return [Hash] the declared settings, under the adapter's own declared defaults, plus
46
+ # the domain, resolved era and superseding era
47
+ def adapter_settings(registry, domain, aggregate, bind, settings_verb)
46
48
  registry.check_verb(bind)
47
49
  settings = registry.binding_settings(domain, settings_verb, bind.adapter)
48
50
  .reject { |key, _| key.to_sym == :role }
49
51
  registry.check_settings(bind, settings)
52
+ settings = registry.with_adapter_defaults(bind.adapter, aggregate, settings)
50
53
  # domain, resolved era and superseding era ride along outside the declared
51
54
  # settings: a lineage adapter needs them to journal per domain, per-era.
52
55
  settings.merge(domain: domain.to_s, era: registry.resolved_eras[domain.to_s],
@@ -47,10 +47,15 @@ module Hecks
47
47
  declared = algebra.fetch(operator[:symbol]) do
48
48
  raise Refused, "#{operator[:symbol]} is admitted but Vocabulary::Comparison declares no algebra for it"
49
49
  end
50
- row.merge(compares_less_than: declared[:compares_less_than],
51
- compares_equal: declared[:compares_equal], negated: declared[:negated])
50
+ row.merge(compares_less_than: spelled_true?(declared[:compares_less_than]),
51
+ compares_equal: spelled_true?(declared[:compares_equal]),
52
+ negated: spelled_true?(declared[:negated]))
52
53
  end
53
54
 
55
+ # @param cell [Object] a `Comparison` cell, declared as the text `"true"` or `"false"`
56
+ # @return [Boolean] whether the cell spells true
57
+ def spelled_true?(cell) = cell.to_s == "true"
58
+
54
59
  # @param dropped [Hash{String => Array<String>}] operator to the guards evaluating through it
55
60
  # @return [String] one line for each operator a projection would strand
56
61
  def dropped_message(dropped)
@@ -13,11 +13,13 @@ module Hecks
13
13
  # an ECS task definition when the world names one
14
14
  def render_compose_sh(plan, region)
15
15
  if plan.task_definition
16
- template("render-compose-taskdef.sh.tmpl", "STACK" => plan.infra_name, "FAMILY" => plan.task_definition,
17
- "PROXY_IMAGE" => plan.proxy_image)
16
+ template("render-compose-taskdef.sh.tmpl",
17
+ { "STACK" => plan.infra_name, "FAMILY" => plan.task_definition, "PROXY_IMAGE" => plan.proxy_image }
18
+ .merge(compose_log_values(plan, region, "$s.name")))
18
19
  else
19
- template("render-compose.sh.tmpl", "STACK" => plan.infra_name, "REGION" => region,
20
- "DB_NAME" => plan.database_name, "PROXY_IMAGE" => plan.proxy_image)
20
+ template("render-compose.sh.tmpl",
21
+ { "STACK" => plan.infra_name, "REGION" => region, "DB_NAME" => plan.database_name,
22
+ "PROXY_IMAGE" => plan.proxy_image }.merge(compose_log_values(plan, region, ".name")))
21
23
  end
22
24
  end
23
25
 
@@ -0,0 +1,62 @@
1
+ module Hecks
2
+ module Projections
3
+ module Deploy
4
+ module Box
5
+ # The CloudFront edge of a box deploy, for a world that declares `cdn`: the `edge.yaml`
6
+ # stack, the `edge.mk` that deploys it, and the two pieces `make stacks` gains so the box
7
+ # admits the CDN's addresses. Mixed into `Box`, which supplies `template`.
8
+ module Edge
9
+ # The AWS-managed prefix list of CloudFront's origin-facing addresses. Its id differs in
10
+ # every region, so it is looked up by this name rather than written down.
11
+ PREFIX_LIST_NAME = "com.amazonaws.global.cloudfront.origin-facing".freeze
12
+
13
+ # @param plan [Settings::Plan] the resolved settings
14
+ # @param region [String] the validated region
15
+ # @return [Hash{String => String}] `edge.yaml` and `edge.mk`, or nothing without a `cdn`
16
+ def edge_files(plan, region)
17
+ return {} unless plan.cdn
18
+
19
+ { "edge.yaml" => edge_yaml(plan),
20
+ "edge.mk" => template("edge.mk.tmpl", "EDGE_STACK" => plan.edge_stack, "REGION" => region) }
21
+ end
22
+
23
+ # @param plan [Settings::Plan] the resolved settings, with a `cdn`
24
+ # @return [String] the CloudFront stack
25
+ def edge_yaml(plan)
26
+ values = { "STACK" => plan.infra_name, "ALIASES" => plan.cdn.aliases.join(","),
27
+ "ORIGIN_HEADER" => plan.origin_header, "ORIGIN_SECRET" => plan.origin_secret }
28
+ template("edge.yaml.tmpl", values)
29
+ end
30
+
31
+ # @param plan [Settings::Plan] the resolved settings
32
+ # @param region [String] the validated region
33
+ # @return [Hash{String => String}] the Makefile markers `CDN_CHECK` and `CDN_PARAM`:
34
+ # empty without a `cdn`, so a world that does not opt in renders the same Makefile
35
+ def edge_makefile_values(plan, region)
36
+ return { "CDN_CHECK" => "", "CDN_PARAM" => "" } unless plan.cdn
37
+
38
+ { "CDN_CHECK" => prefix_list_check(region), "CDN_PARAM" => "\t\tCdnPrefixListId=#{prefix_list_lookup(region)} \\" }
39
+ end
40
+
41
+ private
42
+
43
+ def prefix_list_query(region)
44
+ "aws ec2 describe-managed-prefix-lists --region #{region} --filters " \
45
+ "Name=prefix-list-name,Values=#{PREFIX_LIST_NAME} --query \"PrefixLists[0].PrefixListId\" --output text"
46
+ end
47
+
48
+ def prefix_list_lookup(region)
49
+ "$$(#{prefix_list_query(region)})"
50
+ end
51
+
52
+ # Stops `make stacks` before it changes anything when the prefix list cannot be found,
53
+ # because a box deployed without it has no inbound rule and the CDN gets a 504.
54
+ def prefix_list_check(region)
55
+ "\t@#{prefix_list_query(region)} | grep -q '^pl-' || { echo \"no #{PREFIX_LIST_NAME} " \
56
+ "prefix list in #{region}: the box would admit no CDN and answer 504\" >&2; exit 1; }"
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,99 @@
1
+ module Hecks
2
+ module Projections
3
+ module Deploy
4
+ module Box
5
+ # Where the box's containers write their logs. With `logs "cloudwatch"` (the default) every
6
+ # service uses Docker's `awslogs` driver into one log group the box stack creates, one
7
+ # stream per service, and the box role may write to that group only. With `logs "local"`
8
+ # the services keep Docker's rotating `json-file` logs, which a roll deletes with the
9
+ # container.
10
+ #
11
+ # `docker logs` keeps working with `awslogs`: Docker's dual-logging cache (Docker 20.10 and
12
+ # later, which the Amazon Linux 2023 package installs) keeps a local copy for reads.
13
+ # Mixed into `Box`, which supplies `template`.
14
+ module Logging
15
+ LOG_EXIT_CODE = "# 44 the box stack has no log group yet: update the stack (make stacks) before rolling\n".freeze
16
+ LOG_NOTE = <<~NOTE.freeze
17
+ # Every container logs to the CloudWatch group the box stack creates. A container whose log
18
+ # stream cannot be written does not start, so the roll first checks that the stack has the
19
+ # group (and the role's right to write it) and exits 44 if not. SKIP_LOG_GROUP_CHECK=1 skips it.
20
+ # `docker logs` and the log capture below still read the box's local copy (Docker's dual-logging cache).
21
+ #
22
+ NOTE
23
+ LOG_GROUP_CHECK = <<~CHECK.freeze
24
+ if [ "${SKIP_LOG_GROUP_CHECK:-}" = 1 ]; then
25
+ echo "==> skipping the log group check (SKIP_LOG_GROUP_CHECK=1)"
26
+ elif ! aws cloudformation describe-stack-resource --stack-name "$BOX_STACK" --logical-resource-id BoxLogGroup >/dev/null 2>&1; then
27
+ echo "==> $BOX_STACK has no BoxLogGroup: its containers would fail to start on the awslogs driver." >&2
28
+ echo " Run make stacks to update box.yaml (log group and the role's logs policy) first, then roll again." >&2
29
+ exit 44
30
+ fi
31
+ CHECK
32
+ COMPOSE_LOG_NOTE = <<~NOTE.freeze
33
+ #
34
+ # Every service logs to the CloudWatch group the box stack creates (one stream per service). A
35
+ # container whose stream cannot be written does not start, so update the box stack (make stacks:
36
+ # the group and the role's logs policy) before the first roll that uses this file. `docker logs`
37
+ # still reads the box's local copy.
38
+ NOTE
39
+ JSON_FILE = '{driver: "json-file", options: {"max-size": "10m", "max-file": "3"}}'.freeze
40
+
41
+ # @param plan [Settings::Plan] the resolved settings
42
+ # @return [String] the log group resource spliced into `box.yaml`, or nothing for `local`
43
+ def log_group_yaml(plan)
44
+ return "" unless plan.cloudwatch_logs?
45
+
46
+ template("log-group.yaml.tmpl", "LOG_GROUP" => plan.log_group,
47
+ "RETENTION_DAYS" => plan.log_retention_days.to_s)
48
+ end
49
+
50
+ # @param plan [Settings::Plan] the resolved settings
51
+ # @return [String] the role policy that lets the box write to its log group only, or
52
+ # nothing for `local`
53
+ def log_policy_yaml(plan)
54
+ plan.cloudwatch_logs? ? template("log-policy.yaml.tmpl", {}) : ""
55
+ end
56
+
57
+ # The values `render-compose.sh` splices in: the jq definition of `log` and the call that
58
+ # names each service's stream. `local` renders the `json-file` definition and the bare
59
+ # `log`, so its output does not change.
60
+ #
61
+ # @param plan [Settings::Plan] the resolved settings
62
+ # @param region [String] the validated region
63
+ # @param service [String] the jq expression for a service's name in the template
64
+ # @return [Hash{String => String}] the template values
65
+ def compose_log_values(plan, region, service)
66
+ return { "COMPOSE_LOG_NOTE" => "", **local_log_values } unless plan.cloudwatch_logs?
67
+
68
+ { "COMPOSE_LOG_NOTE" => COMPOSE_LOG_NOTE, "LOG_DEF" => awslogs_definition(plan, region),
69
+ "LOG_SERVICE" => "log(#{service})", "LOG_CADDY" => 'log("caddy")', "LOG_TUNNEL" => 'log("cloudflared")' }
70
+ end
71
+
72
+ # The `deploy-box.sh` pieces that stop a roll onto a stack without the log group.
73
+ #
74
+ # @param plan [Settings::Plan] the resolved settings
75
+ # @return [Hash{String => String}] the exit-code line, the comment and the check, or empty
76
+ # strings for `local`
77
+ def log_roll_values(plan)
78
+ return { "LOG_EXIT_CODE" => "", "LOG_NOTE" => "", "LOG_GROUP_CHECK" => "" } unless plan.cloudwatch_logs?
79
+
80
+ { "LOG_EXIT_CODE" => LOG_EXIT_CODE, "LOG_NOTE" => LOG_NOTE, "LOG_GROUP_CHECK" => LOG_GROUP_CHECK }
81
+ end
82
+
83
+ private
84
+
85
+ def awslogs_definition(plan, region)
86
+ options = %({"awslogs-region": "#{region}", "awslogs-group": "#{plan.log_group}", \
87
+ "awslogs-create-group": "false", tag: $service})
88
+ %(def log($service): {driver: "awslogs", options: #{options}};)
89
+ end
90
+
91
+ def local_log_values
92
+ { "LOG_DEF" => "def log: #{JSON_FILE};", "LOG_SERVICE" => "log", "LOG_CADDY" => "log",
93
+ "LOG_TUNNEL" => "log" }
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -45,7 +45,8 @@ module Hecks
45
45
  "RDS_STACK" => plan.rds_stack, "REGION" => region,
46
46
  "DIR" => "/opt/#{plan.infra_name}", "HEALTH_CHECKS" => health_checks(plan),
47
47
  "SMOKE_HEADER" => smoke_header(plan), "USAGE" => deploy_usage(plan),
48
- "DB_SECRET_LINE" => database_secret_line(plan))
48
+ "DB_SECRET_LINE" => database_secret_line(plan),
49
+ **log_roll_values(plan))
49
50
  end
50
51
 
51
52
  # @param plan [Settings::Plan] the resolved settings
@@ -94,18 +95,25 @@ module Hecks
94
95
  end
95
96
 
96
97
  # @param plan [Settings::Plan] the resolved settings
98
+ # @param region [String] the validated region
97
99
  # @return [String] a Makefile whose targets create the two stacks and roll the box
98
- def makefile(plan)
99
- hint = plan.task_definition ? "[TASKDEF=family:revision]" : '[TAGS="web=20260101 worker=20260101"]'
100
- base = template("Makefile.tmpl", { "STACK" => plan.infra_name, "DEPLOY_HINT" => hint,
101
- "RDS_STACK" => plan.rds_stack, "BOX_STACK" => plan.box_stack,
102
- "DEPLOY_RECIPE" => RollRecipe.deploy(plan) }
103
- .merge(database_makefile_values(plan)))
104
- plan.hosting ? "#{base}\ninclude hosting.mk\n" : base
100
+ def makefile(plan, region)
101
+ values = makefile_values(plan, region)
102
+ base = template("Makefile.tmpl", values)
103
+ includes = [("hosting.mk" if plan.hosting), ("edge.mk" if plan.cdn)].compact
104
+ includes.empty? ? base : "#{base}\n#{includes.map { |file| "include #{file}\n" }.join}"
105
105
  end
106
106
 
107
107
  private
108
108
 
109
+ def makefile_values(plan, region)
110
+ hint = plan.task_definition ? "[TASKDEF=family:revision]" : '[TAGS="web=20260101 worker=20260101"]'
111
+ { "STACK" => plan.infra_name, "DEPLOY_HINT" => hint, "RDS_STACK" => plan.rds_stack,
112
+ "BOX_STACK" => plan.box_stack, "DEPLOY_RECIPE" => RollRecipe.deploy(plan),
113
+ "AMI_PARAMETER" => ami_parameter(plan.instance_type) }
114
+ .merge(database_makefile_values(plan), edge_makefile_values(plan, region))
115
+ end
116
+
109
117
  def schema_list(migration)
110
118
  migration.schemas.map { |name| "`#{name}`" }.join(", ")
111
119
  end
@@ -0,0 +1,40 @@
1
+ require_relative "patterns"
2
+
3
+ module Hecks
4
+ module Projections
5
+ module Deploy
6
+ module Box
7
+ module Settings
8
+ # Reads the `cdn` setting: the CloudFront edge in front of the box. Extended onto
9
+ # `Settings`. The alias names themselves are checked by the Deploy bluebook's
10
+ # `BoxTarget.Declare` before this runs; this checks the shape and the guard.
11
+ module CdnReaders
12
+ include Patterns
13
+
14
+ # @param value [Hash{Symbol => Object}, nil] the world's `cdn` setting
15
+ # @param origin_header [String, nil] the declared origin header
16
+ # @return [Cdn, nil] the edge to generate, or nil when the world declares none
17
+ # @raise [ArgumentError] when the shape is wrong or there is no origin guard
18
+ def read_cdn(value, origin_header)
19
+ return nil if value.nil?
20
+
21
+ aliases = cdn_aliases(value)
22
+ raise ArgumentError, CDN_NEEDS_ORIGIN if origin_header.nil?
23
+
24
+ Settings::Cdn.new(aliases: aliases.uniq)
25
+ end
26
+
27
+ private
28
+
29
+ def cdn_aliases(value)
30
+ listed = value.is_a?(Hash) && value.keys == [:aliases] ? value[:aliases] : nil
31
+ raise ArgumentError, CDN_SHAPE unless listed.is_a?(Array) && !listed.empty?
32
+
33
+ listed.map { |name| check(:cdn_aliases, name, CDN_ALIAS) }
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ require_relative "patterns"
2
+
3
+ module Hecks
4
+ module Projections
5
+ module Deploy
6
+ module Box
7
+ module Settings
8
+ # Reads the `logs` and `log_retention_days` settings: where the containers' logs go and
9
+ # how long CloudWatch keeps them. Extended onto `Settings`.
10
+ #
11
+ # Retention is one of CloudWatch's own values and never "never expire", so a box's log
12
+ # bill is bounded by a declaration.
13
+ module LogReaders
14
+ include Patterns
15
+
16
+ # @param settings [Hash{Symbol => Object}] the world's `AwsBox` settings
17
+ # @return [Hash{Symbol => Object}] the plan's `logs` and `log_retention_days`
18
+ # @raise [ArgumentError] when `logs` is not a known destination, or the retention is not
19
+ # a value CloudWatch accepts
20
+ def read_logs(settings)
21
+ destination = settings.fetch(:logs, "cloudwatch").to_s
22
+ raise ArgumentError, LOGS_SHAPE unless LOG_DESTINATIONS.include?(destination)
23
+
24
+ days = settings.fetch(:log_retention_days, DEFAULT_LOG_RETENTION_DAYS)
25
+ raise ArgumentError, retention_refusal(days) unless LOG_RETENTION_DAYS.include?(days)
26
+
27
+ { logs: destination, log_retention_days: days }
28
+ end
29
+
30
+ private
31
+
32
+ def retention_refusal(days)
33
+ "log_retention_days: #{days.inspect} is not a CloudWatch retention; use one of #{LOG_RETENTION_DAYS.join(", ")}"
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -23,6 +23,13 @@ module Hecks
23
23
  S3_BUCKET = /\A[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]\z/
24
24
  S3_SHAPE = "s3_access: a list of `{ bucket: \"name\", write: true }` hashes (write is optional)".freeze
25
25
  FROM_TASKDEF = %i[env secrets repository].freeze
26
+ LOG_DESTINATIONS = %w[cloudwatch local].freeze
27
+ DEFAULT_LOG_RETENTION_DAYS = 30
28
+ # The retention periods CloudWatch Logs accepts, in days, without "never expire".
29
+ LOG_RETENTION_DAYS = [1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1096, 1827, 2192, 2557,
30
+ 2922, 3288, 3653].freeze
31
+ LOGS_SHAPE = "logs: \"cloudwatch\" ships every container's log to CloudWatch Logs; " \
32
+ "\"local\" keeps Docker's rotating json-file logs on the box".freeze
26
33
 
27
34
  # The settings that size an RDS instance: with `shared_database` they are the shared
28
35
  # instance's.
@@ -37,6 +44,10 @@ module Hecks
37
44
  "(the secret holding the tunnel token)".freeze
38
45
  ORIGIN_ENV_SHAPE = "origin_env: the container environment variable names that hold the origin secret " \
39
46
  "need an origin_secret and a task_definition to compare against".freeze
47
+ CDN_ALIAS = /\A[a-z0-9.-]{4,253}\z/
48
+ CDN_SHAPE = "cdn: a hash with `aliases`, the non-empty list of host names the CloudFront edge answers on".freeze
49
+ CDN_NEEDS_ORIGIN = "cdn: the box admits CloudFront's addresses on port 80, so it needs the origin guard " \
50
+ "that refuses every other caller; declare origin_header and origin_secret".freeze
40
51
  ORIGIN_PAIR = "origin_header and origin_secret go together: the header a CDN sends, " \
41
52
  "and the secret that holds its value".freeze
42
53
  end
@@ -45,18 +45,33 @@ module Hecks
45
45
  # @!attribute [r] write [Boolean] whether a production box may also write and delete
46
46
  Bucket = Struct.new(:name, :write, keyword_init: true)
47
47
 
48
+ # The CloudFront edge a world asks for with `cdn`.
49
+ #
50
+ # @!attribute [r] aliases [Array<String>] the host names the distribution answers on
51
+ Cdn = Struct.new(:aliases, keyword_init: true)
52
+
48
53
  # Everything the generator reads, checked.
49
54
  Plan = Struct.new(
50
55
  :infra_name, :stack_prefix, :instance_type, :volume_gb, :swap_gb, :database_class,
51
56
  :storage_gb, :backup_days, :snapshots_keep, :database_name, :engine_version,
52
57
  :containers, :routes, :default_container, :origin_header, :origin_secret,
53
58
  :secret_prefixes, :writable_secrets, :origin_env, :tunnel, :tunnel_service, :proxy_image,
54
- :task_definition, :migration, :s3_buckets, :hosting, :shared_database, keyword_init: true
59
+ :task_definition, :migration, :s3_buckets, :hosting, :shared_database, :cdn,
60
+ :logs, :log_retention_days, keyword_init: true
55
61
  ) do
56
62
  # @return [String] the CloudFormation stack that holds the database: the shared
57
63
  # instance's when the world names one, else this site's own
58
64
  def rds_stack = shared_database || "#{stack_prefix}-#{infra_name}-rds"
59
65
 
66
+ # @return [String] the CloudFormation stack that holds the CloudFront edge
67
+ def edge_stack = "#{stack_prefix}-#{infra_name}-edge"
68
+
69
+ # @return [String] the CloudWatch log group the box's containers write to
70
+ def log_group = "/#{box_stack}"
71
+
72
+ # @return [Boolean] whether the containers' logs ship to CloudWatch Logs
73
+ def cloudwatch_logs? = logs == "cloudwatch"
74
+
60
75
  # @return [Boolean] whether the database lives on an instance shared with other sites
61
76
  def shared? = !shared_database.nil?
62
77
 
@@ -4,6 +4,8 @@ require_relative "settings/types"
4
4
  require_relative "settings/checks"
5
5
  require_relative "settings/containers"
6
6
  require_relative "settings/origin"
7
+ require_relative "settings/cdn"
8
+ require_relative "settings/logs"
7
9
 
8
10
  module Hecks
9
11
  module Projections
@@ -21,6 +23,8 @@ module Hecks
21
23
  extend Checks
22
24
  extend ContainerReaders
23
25
  extend OriginReaders
26
+ extend CdnReaders
27
+ extend LogReaders
24
28
 
25
29
  module_function
26
30
 
@@ -50,7 +54,7 @@ module Hecks
50
54
  shared_database: read_shared_database(settings),
51
55
  s3_buckets: read_s3_access(settings.fetch(:s3_access, [])),
52
56
  hosting: HostingSettings.read(settings, task_definition)
53
- }
57
+ }.merge(read_logs(settings))
54
58
  end
55
59
 
56
60
  # @param settings [Hash{Symbol => Object}] the world's `AwsBox` settings
@@ -62,7 +66,7 @@ module Hecks
62
66
  origin_env = read_origin_env(settings, secret, task_definition)
63
67
  tunnel, tunnel_service = read_tunnel(settings.fetch(:tunnel, false), containers)
64
68
  { origin_header: header, origin_secret: secret, origin_env: origin_env,
65
- tunnel: tunnel, tunnel_service: tunnel_service }
69
+ tunnel: tunnel, tunnel_service: tunnel_service, cdn: read_cdn(settings[:cdn], header) }
66
70
  end
67
71
 
68
72
  # @return [Hash{Symbol => Object}] the plan's names, sizes, routes and secret scopes
@@ -20,16 +20,19 @@ module Hecks
20
20
  template("box.yaml.tmpl",
21
21
  "STACK" => plan.infra_name, "INSTANCE_TYPE" => plan.instance_type,
22
22
  "VOLUME_GB" => plan.volume_gb.to_s, "SNAPSHOTS_KEEP" => plan.snapshots_keep.to_s,
23
- "AMI_PARAMETER" => ami_parameter(plan.instance_type),
24
23
  "COMPUTE_DOMAIN" => compute_domain(region),
25
24
  "SECRET_RESOURCES" => secret_resources(plan), "WRITABLE_SECRETS" => writable_secrets(plan),
26
25
  "TUNNEL_EGRESS" => tunnel_egress(plan),
27
26
  "SWAP_COMMANDS" => swap_commands(plan), "ECR_REPOSITORIES" => ecr_repositories(plan),
28
- "ECR_OUTPUTS" => ecr_outputs(plan), "S3_POLICY" => s3_policy(plan))
27
+ "ECR_OUTPUTS" => ecr_outputs(plan), "S3_POLICY" => s3_policy(plan),
28
+ "LOG_GROUP" => log_group_yaml(plan), "LOG_POLICY" => log_policy_yaml(plan))
29
29
  end
30
30
 
31
31
  # Graviton families end their generation digit with `g` (`t4g`, `m7gd`, `c6gn`).
32
32
  #
33
+ # Only a first create reads it, from `make stacks`: a live box keeps the image it runs,
34
+ # because a newer one would replace the instance.
35
+ #
33
36
  # @param instance_type [String] an EC2 instance type
34
37
  # @return [String] the SSM parameter of the matching Amazon Linux image
35
38
  def ami_parameter(instance_type)
@@ -1,21 +1,37 @@
1
1
  # @@STACK@@: @@WHAT@@.
2
2
  # make stacks VPC=vpc-... PRIVATE_SUBNETS=subnet-a,subnet-b PUBLIC_SUBNET=subnet-c [REHEARSAL=true]
3
3
  # make deploy @@DEPLOY_HINT@@
4
+ # `make stacks` keeps a live box on the image it runs, so an update never replaces the instance.
5
+ # To move to a newer image on purpose: make stacks ... AMI_ID=ami-...
4
6
  RDS_STACK = @@RDS_STACK@@
5
7
  BOX_STACK = @@BOX_STACK@@
6
8
  @@REHEARSAL_NOTE@@
7
9
  REHEARSAL ?= false
10
+ AMI_ID ?=
8
11
 
9
12
  HECKS ?= hecks
10
13
 
11
14
  @@PHONY@@
12
15
  stacks:
16
+ @@CDN_CHECK@@
13
17
  @@RDS_DEPLOY@@
18
+ @AMI="$(AMI_ID)"; SRC="given as AMI_ID"; \
19
+ if [ -z "$$AMI" ]; then \
20
+ BOX=$$(aws cloudformation describe-stack-resource --stack-name $(BOX_STACK) --logical-resource-id Box --query StackResourceDetail.PhysicalResourceId --output text 2>/dev/null); \
21
+ case "$$BOX" in \
22
+ i-*) AMI=$$(aws ec2 describe-instances --instance-ids "$$BOX" --query "Reservations[0].Instances[0].ImageId" --output text); SRC="pinned: the image $$BOX runs" ;; \
23
+ *) AMI=$$(aws ssm get-parameter --name @@AMI_PARAMETER@@ --query Parameter.Value --output text); SRC="new: no $(BOX_STACK) stack yet, so the latest image" ;; \
24
+ esac; \
25
+ fi; \
26
+ case "$$AMI" in ami-*) ;; *) echo "no ami- image found for $(BOX_STACK): pass one with AMI_ID=ami-..." >&2; exit 1 ;; esac; \
27
+ echo "==> AmiId $$AMI ($$SRC)"; \
14
28
  aws cloudformation deploy --template-file box.yaml --stack-name $(BOX_STACK) --capabilities CAPABILITY_IAM \
15
29
  --parameter-overrides VpcId=$(VPC) SubnetId=$(PUBLIC_SUBNET) \
16
30
  DbSecurityGroupId=$$(aws cloudformation describe-stacks --stack-name $(RDS_STACK) --query "Stacks[0].Outputs[?OutputKey=='DbSecurityGroupId'].OutputValue" --output text) \
17
31
  @@DB_SECRET_PARAM@@
32
+ AmiId=$$AMI \
18
33
  AlertTopicArn=$$(aws cloudformation describe-stacks --stack-name $(RDS_STACK) --query "Stacks[0].Outputs[?OutputKey=='AlertTopicArn'].OutputValue" --output text) \
34
+ @@CDN_PARAM@@
19
35
  Rehearsal=$(REHEARSAL)
20
36
 
21
37
  @@PROVISION@@
@@ -20,9 +20,12 @@ Parameters:
20
20
  Type: String
21
21
  Default: @@INSTANCE_TYPE@@
22
22
  AmiId:
23
- Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
24
- Default: @@AMI_PARAMETER@@
25
- Description: Never re-resolve this on a live box, because a newer image replaces the instance.
23
+ Type: AWS::EC2::Image::Id
24
+ Description: >-
25
+ The image the box runs, always given and never resolved here. Resolving the public image
26
+ key again on a live box finds the newest image, and a changed ImageId replaces the instance
27
+ and its Elastic IP association. make stacks passes the running box's own image, and takes a
28
+ newer one only when AMI_ID is given.
26
29
  RootVolumeGb:
27
30
  Type: Number
28
31
  Default: @@VOLUME_GB@@
@@ -82,7 +85,7 @@ Resources:
82
85
  SourceSecurityGroupId: !Ref BoxSecurityGroup
83
86
  Description: App box
84
87
 
85
- BoxRole:
88
+ @@LOG_GROUP@@ BoxRole:
86
89
  Type: AWS::IAM::Role
87
90
  Properties:
88
91
  AssumeRolePolicyDocument:
@@ -106,6 +109,7 @@ Resources:
106
109
  @@SECRET_RESOURCES@@
107
110
  @@WRITABLE_SECRETS@@
108
111
  @@S3_POLICY@@
112
+ @@LOG_POLICY@@
109
113
 
110
114
  BoxProfile:
111
115
  Type: AWS::IAM::InstanceProfile