hecks 3.9.1 → 3.11.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 (504) 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 +155 -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 +69 -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/ruby/aggregate_module.rb +171 -0
  40. data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
  41. data/lib/hecks/adapters/driving/ruby.rb +83 -0
  42. data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
  43. data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
  44. data/lib/hecks/behaviors/expectations/boot.rb +1 -1
  45. data/lib/hecks/bench/ruby_runner.rb +1 -1
  46. data/lib/hecks/bluebook/ask_resolution.rb +130 -0
  47. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
  48. data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
  49. data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
  50. data/lib/hecks/bluebook/assembly/marks.rb +1 -0
  51. data/lib/hecks/bluebook/attribute.rb +1 -1
  52. data/lib/hecks/bluebook/behaviour/command.rb +13 -8
  53. data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
  54. data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
  55. data/lib/hecks/bluebook/capabilities.rb +91 -9
  56. data/lib/hecks/bluebook/domain_port.rb +16 -4
  57. data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
  58. data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
  59. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
  60. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
  61. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
  62. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
  63. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
  64. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
  65. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
  66. data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
  67. data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
  68. data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
  69. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
  70. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
  71. data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
  72. data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
  73. data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
  74. data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
  75. data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
  76. data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
  77. data/lib/hecks/bluebook/expression/projection.json +36 -0
  78. data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
  79. data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
  80. data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
  81. data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
  82. data/lib/hecks/bluebook/expression/resolver.rb +20 -1
  83. data/lib/hecks/bluebook/hecksagon.rb +23 -3
  84. data/lib/hecks/bluebook/lifecycle.rb +22 -3
  85. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
  86. data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
  87. data/lib/hecks/bluebook/meta_validator/readings.rb +18 -1
  88. data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
  89. data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
  90. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
  91. data/lib/hecks/bluebook/meta_validator.rb +12 -12
  92. data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
  93. data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
  94. data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
  95. data/lib/hecks/bluebook/model_check.rb +8 -2
  96. data/lib/hecks/bluebook/policy.rb +4 -2
  97. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  98. data/lib/hecks/bluebook/synthesizer.rb +2 -0
  99. data/lib/hecks/bluebook.rb +1 -0
  100. data/lib/hecks/cli/command_table.rb +1 -1
  101. data/lib/hecks/cli/console.rb +2 -1
  102. data/lib/hecks/cli/document.rb +1 -1
  103. data/lib/hecks/cli/launcher_words.rb +4 -4
  104. data/lib/hecks/cli/mcp.rb +4 -4
  105. data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
  106. data/lib/hecks/cli/project_cli/templates.rb +5 -5
  107. data/lib/hecks/cli/project_cli.rb +9 -6
  108. data/lib/hecks/cli/run.rb +2 -2
  109. data/lib/hecks/cli/smoke_http/checks.rb +4 -1
  110. data/lib/hecks/cli/smoke_http/transport.rb +3 -3
  111. data/lib/hecks/cli/smoke_http.rb +3 -0
  112. data/lib/hecks/codemod.rb +5 -2
  113. data/lib/hecks/corpus/routes.rb +7 -0
  114. data/lib/hecks/corpus/rust.rb +3 -2
  115. data/lib/hecks/deploy/bluebook/deploy.bluebook +124 -2
  116. data/lib/hecks/deploy/bluebook/deploy.hecksagon +8 -0
  117. data/lib/hecks/deploy/oidc.json +16 -3
  118. data/lib/hecks/doc/reference/rendering.rb +6 -3
  119. data/lib/hecks/forms/app/commands.rb +8 -7
  120. data/lib/hecks/forms/app/queries.rb +2 -1
  121. data/lib/hecks/forms/app.rb +1 -1
  122. data/lib/hecks/forms/record_renderer.rb +1 -1
  123. data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
  124. data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
  125. data/lib/hecks/fuzzing/failure_triage.rb +2 -1
  126. data/lib/hecks/fuzzing/mutation/run.rb +11 -4
  127. data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
  128. data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
  129. data/lib/hecks/fuzzing/properties/guards.rb +1 -1
  130. data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
  131. data/lib/hecks/fuzzing/self_consistency.rb +6 -2
  132. data/lib/hecks/fuzzing/shrinker.rb +3 -1
  133. data/lib/hecks/fuzzing/value_generator.rb +19 -5
  134. data/lib/hecks/gate/stages.yml +4 -10
  135. data/lib/hecks/grammar/evolve.rb +1 -1
  136. data/lib/hecks/grammar/expression_operators.json +372 -0
  137. data/lib/hecks/grammar/operators.rb +3 -1
  138. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
  139. data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
  140. data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
  141. data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
  142. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
  143. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
  144. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
  145. data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
  146. data/lib/hecks/hecks/adapters/deploy_toolchain/clearing.rb +51 -0
  147. data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
  148. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +2 -0
  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 +129 -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/site_toolchain/role_prober.rb +78 -0
  173. data/lib/hecks/hecks/adapters/site_toolchain.rb +29 -4
  174. data/lib/hecks/hecks/adapters/terminal.rb +13 -11
  175. data/lib/hecks/hecks/adapters/world_clearing.rb +65 -0
  176. data/lib/hecks/hecks/codebase.behaviors +13 -1
  177. data/lib/hecks/hecks/codebase.bluebook +239 -5
  178. data/lib/hecks/hecks/custodian.behaviors +24 -6
  179. data/lib/hecks/hecks/custodian.bluebook +6 -1227
  180. data/lib/hecks/hecks/deploy.behaviors +1 -1
  181. data/lib/hecks/hecks/deploy.world +1 -3
  182. data/lib/hecks/hecks/hecks.behaviors +1 -1
  183. data/lib/hecks/hecks/hecks.hecksagon +61 -18
  184. data/lib/hecks/hecks/hecks.world +11 -10
  185. data/lib/hecks/hecks/rehearsal.bluebook +477 -0
  186. data/lib/hecks/hecks/site.behaviors +1 -1
  187. data/lib/hecks/hecks/site.world +0 -1
  188. data/lib/hecks/hecks/tooling.bluebook +1238 -0
  189. data/lib/hecks/indifferent_key.rb +20 -0
  190. data/lib/hecks/language/adapter.bluebook +13 -3
  191. data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
  192. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
  193. data/lib/hecks/language/bluebook/entity.bluebook +18 -0
  194. data/lib/hecks/language/bluebook/policy.bluebook +9 -1
  195. data/lib/hecks/language/bluebook/query.bluebook +1 -1
  196. data/lib/hecks/language/bluebook/vocabulary.bluebook +11 -4
  197. data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
  198. data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
  199. data/lib/hecks/language/oidc.json +10 -0
  200. data/lib/hecks/language/port.bluebook +4 -4
  201. data/lib/hecks/literal.rb +12 -3
  202. data/lib/hecks/ports/key_vault.rb +7 -2
  203. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
  204. data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
  205. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
  206. data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
  207. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +29 -0
  208. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
  209. data/lib/hecks/projections/deploy/fargate/settings/tuning.rb +15 -0
  210. data/lib/hecks/projections/deploy/fargate/settings.rb +3 -1
  211. data/lib/hecks/projections/deploy/fargate/stack/readers.rb +1 -0
  212. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  213. data/lib/hecks/projections/deploy/shared.rb +5 -2
  214. data/lib/hecks/projections/deploy/templates/fargate/Makefile.tmpl +7 -4
  215. data/lib/hecks/projections/model/deviations.rb +2 -2
  216. data/lib/hecks/projections/model.rb +2 -2
  217. data/lib/hecks/projections/oidc.rb +13 -1
  218. data/lib/hecks/projections/shape.rb +5 -2
  219. data/lib/hecks/projections/site/cms_editor/attributes.rb +95 -0
  220. data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
  221. data/lib/hecks/projections/site/cms_editor/checks.rb +77 -0
  222. data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
  223. data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
  224. data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
  225. data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
  226. data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
  227. data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
  228. data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
  229. data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
  230. data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
  231. data/lib/hecks/projections/site/cms_editor/media.rb +85 -0
  232. data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
  233. data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
  234. data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
  235. data/lib/hecks/projections/site/cms_editor/pretty.rb +40 -0
  236. data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
  237. data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
  238. data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
  239. data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
  240. data/lib/hecks/projections/site/cms_editor/schema.rb +161 -0
  241. data/lib/hecks/projections/site/cms_editor/setting.rb +130 -0
  242. data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
  243. data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +36 -0
  244. data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +370 -0
  245. data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
  246. data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +87 -0
  247. data/lib/hecks/projections/site/cms_editor/templates/src/auth/session.ts.tmpl +36 -0
  248. data/lib/hecks/projections/site/cms_editor/templates/src/auth/sso.ts.tmpl +20 -0
  249. data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
  250. data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
  251. data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
  252. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
  253. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
  254. data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
  255. data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
  256. data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
  257. data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
  258. data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
  259. data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
  260. data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
  261. data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
  262. data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
  263. data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
  264. data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
  265. data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +133 -0
  266. data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +29 -0
  267. data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
  268. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +43 -0
  269. data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +177 -0
  270. data/lib/hecks/projections/site/cms_editor/templates/src/media/multipart.ts.tmpl +53 -0
  271. data/lib/hecks/projections/site/cms_editor/templates/src/media/sniff.ts.tmpl +94 -0
  272. data/lib/hecks/projections/site/cms_editor/templates/src/media/storage.ts.tmpl +50 -0
  273. data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
  274. data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +214 -0
  275. data/lib/hecks/projections/site/cms_editor/templates/src/server.ts.tmpl +65 -0
  276. data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
  277. data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
  278. data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
  279. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +123 -0
  280. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_parse.js.tmpl +320 -0
  281. data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
  282. data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
  283. data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +275 -0
  284. data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +61 -0
  285. data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
  286. data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +92 -0
  287. data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
  288. data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
  289. data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +63 -0
  290. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +213 -0
  291. data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
  292. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
  293. data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
  294. data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
  295. data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
  296. data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
  297. data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
  298. data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
  299. data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
  300. data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
  301. data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
  302. data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +17 -0
  303. data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
  304. data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
  305. data/lib/hecks/projections/site/cms_editor.rb +133 -0
  306. data/lib/hecks/projections/site/role_probe.rb +74 -0
  307. data/lib/hecks/projector/cli_audience.rb +2 -1
  308. data/lib/hecks/projector/cli_projector/listing.rb +1 -1
  309. data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
  310. data/lib/hecks/projector/docs_projector/closing.rb +1 -1
  311. data/lib/hecks/projector/docs_projector.rb +1 -1
  312. data/lib/hecks/projector/exporter/bindings.rb +40 -10
  313. data/lib/hecks/projector/exporter/capability_values.rb +55 -0
  314. data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
  315. data/lib/hecks/projector/target.rb +11 -0
  316. data/lib/hecks/projector.rb +32 -5
  317. data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
  318. data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
  319. data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
  320. data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
  321. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
  322. data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
  323. data/lib/hecks/quality_control/quality_control.bluebook +6 -2
  324. data/lib/hecks/release/approval.rb +64 -0
  325. data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
  326. data/lib/hecks/router/namespace_installer.rb +16 -16
  327. data/lib/hecks/router.rb +1 -1
  328. data/lib/hecks/runtime/best_effort.rb +22 -0
  329. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
  330. data/lib/hecks/runtime/command_interpreter.rb +3 -2
  331. data/lib/hecks/runtime/command_rules/emission.rb +1 -1
  332. data/lib/hecks/runtime/dispatcher.rb +4 -4
  333. data/lib/hecks/runtime/entity_element/location.rb +1 -1
  334. data/lib/hecks/runtime/event.rb +5 -0
  335. data/lib/hecks/runtime/identity.rb +7 -6
  336. data/lib/hecks/runtime/interpreting.rb +2 -1
  337. data/lib/hecks/runtime/invocation_builder.rb +1 -1
  338. data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
  339. data/lib/hecks/runtime/loader.rb +10 -10
  340. data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
  341. data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
  342. data/lib/hecks/runtime/policy_interpreter.rb +6 -6
  343. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
  344. data/lib/hecks/runtime/reaction_invocation.rb +1 -1
  345. data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
  346. data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
  347. data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
  348. data/lib/hecks/runtime/registry/merging.rb +1 -1
  349. data/lib/hecks/runtime/registry/providers.rb +16 -0
  350. data/lib/hecks/runtime/registry/read_repository.rb +6 -4
  351. data/lib/hecks/runtime/registry/verification.rb +4 -1
  352. data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
  353. data/lib/hecks/runtime/registry.rb +1 -1
  354. data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
  355. data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
  356. data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
  357. data/lib/hecks/runtime/saga_interpreter.rb +3 -3
  358. data/lib/hecks/runtime/tenant_check.rb +5 -4
  359. data/lib/hecks/runtime/value/coercion.rb +2 -2
  360. data/lib/hecks/runtime/value/validation.rb +16 -4
  361. data/lib/hecks/runtime.rb +9 -9
  362. data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
  363. data/lib/hecks/rust_build/wasm.rb +1 -1
  364. data/lib/hecks/rust_build/wasm_browser.rb +1 -1
  365. data/lib/hecks/rust_build.rb +11 -0
  366. data/lib/hecks/served_options.rb +53 -0
  367. data/lib/hecks/site/bluebook/site.bluebook +29 -1
  368. data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
  369. data/lib/hecks/storehouse/audit.rb +1 -1
  370. data/lib/hecks/storehouse/dispatching.rb +8 -6
  371. data/lib/hecks/storehouse/introspection.rb +2 -2
  372. data/lib/hecks/storehouse/querying.rb +6 -4
  373. data/lib/hecks/storehouse.rb +4 -4
  374. data/lib/hecks/tools/comment_style/source_file.rb +4 -1
  375. data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
  376. data/lib/hecks/tools/lanes/rows.rb +6 -0
  377. data/lib/hecks/tools/oidc_manifests.rb +4 -5
  378. data/lib/hecks/tools/regeneration_run.rb +11 -4
  379. data/lib/hecks/tools/site_routes/cli.rb +1 -1
  380. data/lib/hecks/tools/site_routes/editor_files.rb +45 -0
  381. data/lib/hecks/tools/site_routes.rb +14 -7
  382. data/lib/hecks/tools/tenant_provisioning.rb +2 -2
  383. data/lib/hecks/tools/tools_doc.rb +4 -4
  384. data/lib/hecks/version.rb +1 -1
  385. data/lib/hecks/vocabulary.rb +4 -4
  386. data/lib/hecks.rb +15 -15
  387. data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
  388. data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
  389. data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
  390. data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
  391. data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
  392. data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
  393. data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
  394. data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
  395. data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
  396. data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
  397. data/prebuilt/verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json +1 -0
  398. data/rust/.gitignore +6 -0
  399. data/rust/Cargo.toml +9 -1
  400. data/rust/build/src/binding_pass.rs +103 -0
  401. data/rust/build/src/cargo_sync.rs +6 -0
  402. data/rust/build/src/lineage_pass.rs +3 -3
  403. data/rust/build/src/main.rs +17 -5
  404. data/rust/build/src/pipeline.rs +48 -5
  405. data/rust/build/src/seams_pass.rs +123 -0
  406. data/rust/build/src/translation_pass.rs +167 -0
  407. data/rust/codegen/src/asks.rs +103 -0
  408. data/rust/codegen/src/bridging.rs +1 -1
  409. data/rust/codegen/src/commands.rs +13 -8
  410. data/rust/codegen/src/constraints.rs +25 -0
  411. data/rust/codegen/src/domain_generator.rs +11 -9
  412. data/rust/codegen/src/expr_emitter.rs +13 -0
  413. data/rust/codegen/src/json_codec.rs +54 -15
  414. data/rust/codegen/src/main.rs +2 -1
  415. data/rust/codegen/src/mutations.rs +174 -4
  416. data/rust/codegen/src/naming.rs +30 -0
  417. data/rust/codegen/src/queries.rs +11 -2
  418. data/rust/codegen/src/reactions.rs +1 -1
  419. data/rust/codegen/src/types.rs +166 -8
  420. data/rust/host/HECKS_RELEASE +1 -1
  421. data/rust/host/src/api.rs +4 -4
  422. data/rust/host/src/bin/validate_harness.rs +43 -0
  423. data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
  424. data/rust/host/src/boundary_fuzz/ir.rs +1 -0
  425. data/rust/host/src/boundary_fuzz/server.rs +1 -1
  426. data/rust/host/src/checkout.rs +35 -19
  427. data/rust/host/src/commerce_ir.rs +408 -2
  428. data/rust/host/src/expr_json.rs +118 -0
  429. data/rust/host/src/mint.rs +1 -1
  430. data/rust/host/src/payments/tests.rs +27 -1
  431. data/rust/host/src/payments.rs +12 -4
  432. data/rust/host/src/query_step.rs +22 -4
  433. data/rust/host/src/reference_validate.rs +653 -108
  434. data/rust/host/src/resend.rs +2 -0
  435. data/rust/host/src/server.rs +19 -6
  436. data/rust/host/src/ui_schema.rs +132 -5
  437. data/rust/host/src/web/newsletter.rs +7 -8
  438. data/rust/host/src/web/newsletter_send.rs +38 -13
  439. data/rust/host/src/web/registrations.rs +229 -26
  440. data/rust/host/src/web.rs +3 -3
  441. data/rust/parser/src/emit.rs +38 -6
  442. data/rust/parser/src/expr/ast_json.rs +7 -1
  443. data/rust/parser/src/expr/resolver.rs +171 -6
  444. data/rust/parser/src/ir.rs +7 -0
  445. data/rust/parser/src/keywords.rs +24 -0
  446. data/rust/parser/src/main.rs +3 -0
  447. data/rust/parser/src/parse/aggregate.rs +2 -1
  448. data/rust/parser/src/parse/chapter.rs +2 -2
  449. data/rust/parser/src/parse/command.rs +2 -0
  450. data/rust/parser/src/parse/hecksagon.rs +5 -2
  451. data/rust/parser/src/parse/lifecycle.rs +81 -0
  452. data/rust/parser/src/parse/mod.rs +15 -1
  453. data/rust/parser/src/parse/policy.rs +13 -2
  454. data/rust/parser/src/parse/value_object.rs +13 -8
  455. data/rust/parser/src/ruby_value.rs +1 -1
  456. data/rust/src/exemplar/commands.rs +4 -4
  457. data/rust/src/exemplar/constraints.rs +1 -1
  458. data/rust/src/exemplar/json.rs +43 -1
  459. data/rust/src/kernel/cli.rs +7 -5
  460. data/rust/src/kernel/dispatch.rs +2 -2
  461. data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
  462. data/rust/src/kernel/expr.rs +73 -4
  463. data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
  464. data/rust/src/kernel/expression_operators/presence.rs +41 -3
  465. data/rust/src/kernel/expression_operators/text.rs +26 -2
  466. data/rust/src/kernel/json.rs +15 -0
  467. data/rust/src/kernel/mod.rs +1 -1
  468. data/rust/src/kernel/orchestrate.rs +54 -5
  469. data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
  470. data/rust/src/kernel/pattern.rs +60 -0
  471. data/rust/src/kernel/reference_lookup.rs +90 -7
  472. metadata +174 -36
  473. data/lib/hecks/doors/cli_door/nesting.rb +0 -34
  474. data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
  475. data/lib/hecks/doors/cli_door.rb +0 -124
  476. data/lib/hecks/doors/cli_runner/answers.rb +0 -138
  477. data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
  478. data/lib/hecks/doors/cli_runner/settling.rb +0 -115
  479. data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
  480. data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
  481. data/lib/hecks/doors/cli_runner.rb +0 -66
  482. data/lib/hecks/doors/command_request.rb +0 -119
  483. data/lib/hecks/doors/handle.rb +0 -208
  484. data/lib/hecks/doors/json_door.rb +0 -132
  485. data/lib/hecks/doors/launcher_options.rb +0 -247
  486. data/lib/hecks/doors/mcp_door/properties.rb +0 -47
  487. data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
  488. data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
  489. data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
  490. data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
  491. data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
  492. data/lib/hecks/doors/mcp_door.rb +0 -156
  493. data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
  494. data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
  495. data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
  496. data/lib/hecks/doors/mcp_door_scope.rb +0 -201
  497. data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
  498. data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
  499. data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
  500. data/lib/hecks/doors/ruby_door.rb +0 -80
  501. data/lib/hecks/doors/usage_cache.rb +0 -139
  502. data/prebuilt/0ee7f83cba59ce7d1c45c65ba166b071779f70143449f584f33ff7989f64eccb.marshal +0 -0
  503. data/prebuilt/6e4a6bcf0aa880e2e6b056e1846b70e5a768090b9dc998bbadeaf5d6bcfd3db8.marshal +0 -0
  504. data/prebuilt/verdicts-91aaf93ce8981bd9a754948f075d95650dc2ec6435015376f5c60992b36a24b1.json +0 -1
@@ -8,6 +8,14 @@
8
8
  # 40 the box stack has no instance
9
9
  # 41 the roll did not succeed on the box
10
10
  # 42 the roll succeeded but the box is not healthy after it
11
+ # 43 no restore anchor: the database's backups cannot be read, or are kept fewer days than
12
+ # MIN_BACKUP_RETENTION_DAYS (default 3)
13
+ #
14
+ # Before anything changes, the roll names the point the database can be restored to: the instance, the
15
+ # moment this roll starts, and how long its continuous backups are kept. A roll that mints a new era is
16
+ # a one-way door, so this is the rollback anchor, and it costs nothing (a point-in-time restore uses the
17
+ # backups the instance already keeps, where a manual snapshot would be stored until someone deleted it).
18
+ # SKIP_RESTORE_ANCHOR=1 skips it.
11
19
  #
12
20
  # Before the roll replaces a container, the box saves that container's log to
13
21
  # /var/log/hecks-captures/<container>-<UTC timestamp>.log and keeps the newest 14 per container
@@ -30,6 +38,27 @@ DB_HOST=$(out "$RDS_STACK" DbEndpoint)
30
38
  [ -n "$BOX" ] && [ "$BOX" != None ] || { echo "no instance in stack $BOX_STACK" >&2; exit 40; }
31
39
  ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
32
40
 
41
+ if [ "${SKIP_RESTORE_ANCHOR:-}" = 1 ]; then
42
+ echo "==> skipping the restore anchor (SKIP_RESTORE_ANCHOR=1)"
43
+ else
44
+ DB_ID=${DB_HOST%%.*}
45
+ BACKUPS=$(aws rds describe-db-instances --db-instance-identifier "$DB_ID" \
46
+ --query 'DBInstances[0].[BackupRetentionPeriod,LatestRestorableTime]' --output text) || BACKUPS=
47
+ read -r KEPT_DAYS RESTORABLE <<< "$BACKUPS"
48
+ case "${KEPT_DAYS:-}" in ''|*[!0-9]*)
49
+ echo "==> no restore anchor: could not read the backups of $DB_ID (SKIP_RESTORE_ANCHOR=1 rolls without one)" >&2
50
+ exit 43 ;;
51
+ esac
52
+ if [ "$KEPT_DAYS" -lt "${MIN_BACKUP_RETENTION_DAYS:-3}" ]; then
53
+ echo "==> no restore anchor: $DB_ID keeps backups $KEPT_DAYS day(s), fewer than ${MIN_BACKUP_RETENTION_DAYS:-3}" >&2
54
+ exit 43
55
+ fi
56
+ ANCHOR_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
57
+ echo "==> restore anchor: $DB_ID restorable to $ANCHOR_AT (latest $RESTORABLE, backups kept $KEPT_DAYS days)"
58
+ echo " to return: aws rds restore-db-instance-to-point-in-time --source-db-instance-identifier $DB_ID \\"
59
+ echo " --target-db-instance-identifier $DB_ID-restore --restore-time $ANCHOR_AT"
60
+ fi
61
+
33
62
  WORK=$(mktemp -d); trap 'rm -rf "$WORK"' EXIT
34
63
 
35
64
  # SMOKE_LISTENER=1 (a rehearsal box only) mounts a loopback HTTPS site on 127.0.0.1:8443 that adds the
@@ -24,7 +24,7 @@
24
24
  # 32 the box's Compose file is unreadable 33 the box stack has no instance
25
25
  # 34 the stack has no parameter for the container 35 the stack update failed or did not settle
26
26
  # 36 a parameter other than the container's changed 37 the task definition lacks the pushed image
27
- # 38 the task definition has no such container 40-42 deploy-box.sh's own
27
+ # 38 the task definition has no such container 40-43 deploy-box.sh's own
28
28
  #
29
29
  # Environment:
30
30
  # LOCAL_IMAGE local image to push, default <ecr repository>:latest
@@ -38,6 +38,7 @@ module Hecks
38
38
  plan.domain_env = Check.map!(settings.fetch(:domain_env, {}), "domain_env", nil_ok: true)
39
39
  plan.install_dir = install_dir(settings.fetch(:install_dir, DEFAULT_INSTALL_DIR))
40
40
  plan.build_context_dir = build_context_dir(settings.fetch(:build_context_dir, ""))
41
+ plan.host_dir = host_dir(settings.fetch(:host_crate, DEFAULT_HOST_DIR))
41
42
  end
42
43
 
43
44
  def check_execution_grant!(plan)
@@ -62,6 +63,20 @@ module Hecks
62
63
  raise ArgumentError, "install_dir must be an absolute path without a trailing slash, got #{value.inspect}"
63
64
  end
64
65
 
66
+ # The directory of the Cargo package the Makefile builds `bootstrap` from: an absolute
67
+ # path, or one relative to the Makefile's `ROOT`. Only path characters are accepted,
68
+ # since the value is written into a Makefile that shell lines expand.
69
+ def host_dir(value)
70
+ text = value.to_s
71
+ return text if text == DEFAULT_HOST_DIR
72
+ return text if text.match?(%r{\A/[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+)*\z})
73
+ return "$(ROOT)/#{text}" if text.match?(%r{\A[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+)*\z})
74
+
75
+ raise ArgumentError,
76
+ "host_crate must be a directory path of letters, digits, . _ - and /, absolute or relative to the " \
77
+ "Makefile's ROOT, got #{value.inspect}"
78
+ end
79
+
65
80
  def build_context_dir(value)
66
81
  text = value.to_s
67
82
  return text if text.empty? || text.match?(%r{\A[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+)*/\z})
@@ -19,7 +19,7 @@ module Hecks
19
19
  Plan = Struct.new(
20
20
  :ids, :names, :layout, :cdn, :alerts, :extras, :execute_command, :health_check_grace_period,
21
21
  :deregistration_delay, :desired_count_parameter, :domain_env, :install_dir, :build_context_dir,
22
- :db_name_parameter, :execution_database_grant,
22
+ :db_name_parameter, :execution_database_grant, :host_dir,
23
23
  keyword_init: true
24
24
  )
25
25
 
@@ -37,6 +37,8 @@ module Hecks
37
37
  }.freeze
38
38
  NAME_ROLES = [:cluster, :log_group, :service, :alb, :family, :alb_security_group_description, :db_secret_policy].freeze
39
39
  DEFAULT_INSTALL_DIR = "/usr/local/bin".freeze
40
+ # Where the generated Makefile builds the host binary unless `host_crate` names another.
41
+ DEFAULT_HOST_DIR = "$(ROOT)/rust/host".freeze
40
42
 
41
43
  extend Ids
42
44
  extend Tuning
@@ -65,6 +65,7 @@ module Hecks
65
65
  def vpc_ref = shared ? "!Ref OwningVpcId" : "!Ref #{db_id}Vpc"
66
66
  def sidecar_dir = plan.install_dir
67
67
  def build_context_dir = plan.build_context_dir
68
+ def host_dir = plan.host_dir
68
69
  def cmd_binary = sidecar_dir == Settings::DEFAULT_INSTALL_DIR ? domain_name : "#{sidecar_dir}/#{domain_name}"
69
70
  def desired_count_value = plan.desired_count_parameter ? "!Ref #{plan.desired_count_parameter}" : desired_count
70
71
  def declared_domain_literal = declared_domain_name.inspect
@@ -365,7 +365,7 @@ signup_admin() {
365
365
  # Prints what to paste into the browser console to sign in as the preview's first
366
366
  # admin: a session for that email signed with THIS preview's session secret (the
367
367
  # secret itself is never printed). Anyone with AWS access to the account could
368
- # mint the same, which is the point: it is the deployer's door, not a public one.
368
+ # mint the same, which is the point: it is the deployer's entry, not a public one.
369
369
  cmd_login() {
370
370
  [ -n "$(stack_status)" ] || die "no preview for branch '$BRANCH' - run 'preview.sh deploy'"
371
371
  admin_identity
@@ -7,6 +7,9 @@ module Hecks
7
7
  # VPC, RDS/Aurora, bastion and cross-domain invoke plumbing shared by the
8
8
  # `Lambda` and `Fargate` deploy targets. Plain module functions, not a Target.
9
9
  module Shared
10
+ # Raised when a bastion parameter names a stack output that is not declared.
11
+ class Error < RuntimeError; end
12
+
10
13
  # The names a domain's own VPC and database are rendered by.
11
14
  #
12
15
  # @!attribute [r] shared [Boolean] whether this domain borrows another domain's RDS instance
@@ -66,11 +69,11 @@ module Hecks
66
69
 
67
70
  # Generation-time check that every bastion parameter maps to a stack output.
68
71
  #
69
- # @raise [RuntimeError] if a parameter names a `from_output` absent from `stack_outputs`
72
+ # @raise [Error] if a parameter names a `from_output` absent from `stack_outputs`
70
73
  def check_bastion_parameters!(bastion_parameters, stack_outputs)
71
74
  bastion_parameters.each do |param|
72
75
  stack_outputs.any? { |o| o[:key] == param[:from_output] } or
73
- raise "bastion parameter #{param[:name]} references undeclared stack output #{param[:from_output]}"
76
+ raise Error, "bastion parameter #{param[:name]} references undeclared stack output #{param[:from_output]}"
74
77
  end
75
78
  end
76
79
 
@@ -1,8 +1,9 @@
1
1
  # GENERATED by hecks deploy project @@domain@@ — re-run it to refresh
2
2
  # this file rather than hand-editing.
3
3
  #
4
- # `make deploy` — builds rust/host for this domain, pushes it to this
5
- # stack's own ECR repository, and deploys the CloudFormation stack.
4
+ # `make deploy` — builds the host for this domain (rust/host, or the
5
+ # directory `host_crate` names), pushes it to this stack's own ECR
6
+ # repository, and deploys the CloudFormation stack.
6
7
  # No SAM anywhere in this path.
7
8
 
8
9
  ROOT := @@root@@
@@ -11,6 +12,8 @@ STACK := @@stack_name@@
11
12
  BASTION_STACK := @@stack_name@@-bastion
12
13
  REGION := @@region@@
13
14
  IMAGE_TAG := latest
15
+ # The Cargo package `bootstrap` is built from; `make HOST_DIR=<dir>` builds another.
16
+ HOST_DIR ?= @@host_dir@@
14
17
 
15
18
  build:
16
19
  # aarch64, not x86_64 — matches template.yaml's own
@@ -33,8 +36,8 @@ build:
33
36
  # rust/dist/@@domain_name@@.wasm/.ir.json from this domain's own
34
37
  # .bluebook.
35
38
  cd $(ROOT) && HECKS_ENVIRONMENT=memory ruby exe/hecks build.build_wasm domain=$(DOMAIN) --wait
36
- cd $(ROOT)/rust/host && CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc rustup run stable cargo build --release --target aarch64-unknown-linux-gnu --bin bootstrap
37
- cp $(ROOT)/rust/host/target/aarch64-unknown-linux-gnu/release/bootstrap @@domain_name@@-host
39
+ cd $(HOST_DIR) && CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc rustup run stable cargo build --release --target aarch64-unknown-linux-gnu --bin bootstrap
40
+ cp $(HOST_DIR)/target/aarch64-unknown-linux-gnu/release/bootstrap @@domain_name@@-host
38
41
  cp $(ROOT)/rust/dist/@@domain_name@@.wasm @@domain_name@@.wasm
39
42
  cp $(ROOT)/rust/dist/@@domain_name@@.ir.json @@domain_name@@.ir.json
40
43
 
@@ -17,8 +17,8 @@ module Hecks
17
17
 
18
18
  # One model field gathered from several declared ones.
19
19
  FOLDED = {
20
- "Aggregate" => { lifecycle: %i[state_field state_start transitions] },
21
- "Entity" => { lifecycle: %i[state_field state_start transitions] },
20
+ "Aggregate" => { lifecycle: %i[state_field state_start transitions marks] },
21
+ "Entity" => { lifecycle: %i[state_field state_start transitions marks] },
22
22
  "Query" => { order_by: %i[order_field order_way] }
23
23
  }.freeze
24
24
 
@@ -16,10 +16,10 @@ module Hecks
16
16
  construct: "Policy",
17
17
  file: "policy.rb",
18
18
  behaviour: "Behaviour::Policy",
19
- readers: %i[name on_event trigger_command target_domain expect_undelivered where for_each with_spec],
19
+ readers: %i[name on_event trigger_command ask target_domain expect_undelivered where for_each with_spec],
20
20
  accessors: %i[aggregate],
21
21
  defaults: { name: nil, on_event: "nil", trigger_command: "nil",
22
- target_domain: "nil", expect_undelivered: "false", where: "nil", for_each: "nil",
22
+ ask: "nil", target_domain: "nil", expect_undelivered: "false", where: "nil", for_each: "nil",
23
23
  with_spec: "[]", aggregate: "nil" },
24
24
  # The language holds a flag as "true" and the builder as `true`; both become a boolean.
25
25
  coerce: { name: ".to_s", aggregate: "&.to_s", expect_undelivered: ".to_s == \"true\"" },
@@ -1,3 +1,4 @@
1
+ require "json"
1
2
  require_relative "../naming"
2
3
  require_relative "../projector"
3
4
 
@@ -35,6 +36,17 @@ module Hecks
35
36
  }
36
37
  end
37
38
 
39
+ # The manifest as the text `oidc.json` holds: indented JSON ending in a newline, with an empty
40
+ # list as `[]`. `JSON.pretty_generate` spells an empty list `[\n\n]` in some json releases
41
+ # (2.7.2 among them) and `[]` in others, so the file is normalised here and a committed
42
+ # manifest compares equal whichever json a machine has.
43
+ #
44
+ # @param manifest [Hash{String => Object}] what `call` returns
45
+ # @return [String] the text of the manifest file
46
+ def render(manifest)
47
+ "#{JSON.pretty_generate(manifest).gsub(/\[\n\s*\]/, "[]")}\n"
48
+ end
49
+
38
50
  # Every command's scope entry, entity commands included (they dispatch as `Entity.Command`).
39
51
  # Sorted by scope so two manifest versions diff cleanly.
40
52
  #
@@ -79,7 +91,7 @@ module Hecks
79
91
  end
80
92
 
81
93
  # One scope entry per command, spelled like `banking:account.open`; snake-cased through
82
- # `Naming.snake`, as the facade names its door methods.
94
+ # `Naming.snake`, as the facade names its module methods.
83
95
  #
84
96
  # @param commands [Array<Bluebook::Command>] the commands to project
85
97
  # @param verb_prefix [String] the owning aggregate or entity's fully-qualified
@@ -6,6 +6,9 @@ module Hecks
6
6
  # The storage shape of one bluebook, the form `StorageShape.mint_hash` hashes to name an era.
7
7
  # Diffing two of these answers whether a change bumps the era.
8
8
  module Shape
9
+ # Raised when the shape projection is asked for without the era plugin loaded.
10
+ class Error < RuntimeError; end
11
+
9
12
  extend Projector::Target
10
13
 
11
14
  projects_as :shape
@@ -15,8 +18,8 @@ module Hecks
15
18
  # Registered even when the era plugin is unloaded; the call then refuses clearly.
16
19
  def call(bluebook:, options: {})
17
20
  unless Ports::Persistence.plugin?(:era)
18
- raise "the :shape projection needs the era persistence plugin loaded " \
19
- "(require \"hecks/ports/persistence/plugins/era\")"
21
+ raise Error, "the :shape projection needs the era persistence plugin loaded " \
22
+ "(require \"hecks/ports/persistence/plugins/era\")"
20
23
  end
21
24
 
22
25
  Runtime::StorageShape.project(bluebook)
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "blocks"
4
+ require_relative "hints"
5
+
6
+ module Hecks
7
+ module Projections
8
+ module Site
9
+ module CmsEditor
10
+ # One attribute as the editor reads it: its name, the type as the bluebook spells it, how a
11
+ # value of it is entered (`kind`), and whether it is optional or a list.
12
+ #
13
+ # A type that names one of the aggregate's value objects is an `object`; a reference is a
14
+ # `reference`; the primitives are `integer`, `number`, `boolean` and `text`, and any other
15
+ # is text. The shape decides what more it is, never a name: a body (`widget: "body"`), a
16
+ # block list (see `Blocks`), a moment, a `<kind>:` key (see `Hints`) or a picture.
17
+ class Attributes
18
+ # The parts a value object needs for its list of blocks to be a document body's blocks.
19
+ BLOCK_PARTS = %w[kind spans].freeze
20
+
21
+ # The kind a primitive's name enters as; any other name is text.
22
+ KINDS = { "Integer" => "integer", "Float" => "number", "Boolean" => "boolean", "TrueClass" => "boolean",
23
+ "FalseClass" => "boolean" }.freeze
24
+
25
+ # @param objects [Array<Bluebook::ValueObject>] the aggregate's value objects
26
+ def initialize(objects)
27
+ @by_name = objects.to_h { |object| [object.hecks_name, object] }
28
+ @objects = @by_name.keys
29
+ @hints = Hints.new(@by_name)
30
+ end
31
+
32
+ # @param attribute [Bluebook::Attribute] a declared attribute
33
+ # @return [Hash{String => Object}] the attribute as the editor reads it
34
+ def of(attribute)
35
+ type = attribute.type
36
+ return reference(attribute, type.target_name) if type.is_a?(Bluebook::Reference)
37
+
38
+ entry = base(attribute, type.to_s, kind(type.to_s))
39
+ return entry.merge("widget" => "body") if body?(attribute)
40
+
41
+ entry.merge(@hints.of(attribute), listing(attribute), pictured(attribute))
42
+ end
43
+
44
+ private
45
+
46
+ # @return [Hash{String => Object}] `blockList` when the attribute is a list of blocks
47
+ def listing(attribute)
48
+ found = Blocks.of(attribute, @by_name)
49
+ found ? { "blockList" => found } : {}
50
+ end
51
+
52
+ # @return [Hash{String => Object}] `picture` (the parts that name the file, its
53
+ # description and its caption) when the value object is a picture: it has a media key
54
+ # and an `alt` and is not a block of a body or a list of mixed blocks
55
+ def pictured(attribute)
56
+ object = @by_name[attribute.type.to_s]
57
+ return {} unless object && !Blocks.body_block?(object) && !listing(attribute).key?("blockList")
58
+
59
+ found = Blocks.picture(object)
60
+ found&.key?("key") && found.key?("alt") ? { "picture" => found } : {}
61
+ end
62
+
63
+ # A single (not listed) attribute whose value object holds a list of document blocks.
64
+ def body?(attribute)
65
+ return false if attribute.list?
66
+
67
+ block = block_object(@by_name[attribute.type.to_s])
68
+ !block.nil? && (BLOCK_PARTS - block.attributes.map { |part| part.name.to_s }).empty?
69
+ end
70
+
71
+ # @return [Bluebook::ValueObject, nil] the value object `object`'s `blocks` list holds
72
+ def block_object(object)
73
+ blocks = object&.attributes&.find { |part| part.name.to_s == "blocks" && part.list? }
74
+ blocks && @by_name[blocks.type.to_s]
75
+ end
76
+
77
+ def reference(attribute, target)
78
+ base(attribute, target, "reference").merge("target" => target)
79
+ end
80
+
81
+ def base(attribute, type, kind)
82
+ { "name" => attribute.name.to_s, "type" => type, "kind" => kind,
83
+ "optional" => attribute.optional?, "list" => attribute.list? }
84
+ end
85
+
86
+ def kind(type)
87
+ return "object" if @objects.include?(type)
88
+
89
+ KINDS.fetch(type, "text")
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Projections
5
+ module Site
6
+ module CmsEditor
7
+ # A list of mixed blocks, found by shape: a `list_of` value object that has a discriminator,
8
+ # a part that is one of a closed set of kinds. The editor draws it as ordered cards.
9
+ #
10
+ # The discriminator is a part whose type is a closed set (`one_of`) of one plain value, or,
11
+ # failing that, a text part that an invariant restricts to a literal list
12
+ # (`["a", "b"].include?(kind)`). A value object that holds `spans` is a body's block, which
13
+ # the rich-text widget edits, and is never a block list.
14
+ #
15
+ # Which slots each kind uses is read from a declared table: a closed set of rows with the
16
+ # parts `kind`, `requires` and `uses` whose kinds are among the discriminator's. With no
17
+ # such table every slot is shown, and the invariants that name a kind only hint which slots
18
+ # it reads. A limit is read from an invariant or given of the form `<list>.size <= <n>`.
19
+ module Blocks
20
+ # The parts a table of rules must have.
21
+ RULE_PARTS = %w[kind requires uses].freeze
22
+
23
+ # The parts that make a value object's picture, in the vocabulary of a body's image block.
24
+ PICTURE = { "key" => "media_ref", "alt" => "alt", "caption" => "caption" }.freeze
25
+
26
+ # An invariant that restricts a text part to a literal list of kinds.
27
+ LITERALS = /\A\[((?:"[^"]+"(?:, )?)+)\]\.include\?\((\w+)\)\z/
28
+
29
+ # An invariant that holds unless the kind is the named one: what the rest of it reads.
30
+ GUARD = /\A(\w+)(?:\.value)? != "([^"]+)" \|\| (.*)\z/m
31
+
32
+ # A limit written as a size comparison.
33
+ LIMIT = /\A(\w+)\.size <= (\d+)\z/
34
+
35
+ module_function
36
+
37
+ # @param attribute [Bluebook::Attribute] a declared attribute
38
+ # @param objects [Hash{String => Bluebook::ValueObject}] the aggregate's value objects
39
+ # @return [Hash{String => Object}, nil] the block list the attribute is, or nil
40
+ def of(attribute, objects)
41
+ item = objects[attribute.type.to_s]
42
+ return nil unless attribute.list? && item && !body_block?(item)
43
+
44
+ found = discriminator(item, objects)
45
+ found && describe(item, objects, *found)
46
+ end
47
+
48
+ # @return [Boolean] whether the value object holds `spans`, as a body's block does
49
+ def body_block?(item) = item.attributes.any? { |part| part.name.to_s == "spans" }
50
+
51
+ # @return [Array(String, Array<String>), nil] the part and the kinds it is one of
52
+ def discriminator(item, objects)
53
+ closed(item, objects) || literal(item)
54
+ end
55
+
56
+ # @return [Array(String, Array<String>), nil] a part typed as a closed set of plain values
57
+ def closed(item, objects)
58
+ item.attributes.each do |part|
59
+ kinds = members_of(objects[part.type.to_s]) unless part.list?
60
+ return [part.name.to_s, kinds] if kinds && !kinds.empty?
61
+ end
62
+ nil
63
+ end
64
+
65
+ # @return [Array<String>, nil] the values of a closed set of one plain part
66
+ def members_of(set)
67
+ return nil unless set&.closed_set? && set.attributes.size == 1
68
+
69
+ set.members.filter_map { |member| member[set.attributes.first.name]&.to_s }
70
+ end
71
+
72
+ # @return [Array(String, Array<String>), nil] a text part restricted by a literal list
73
+ def literal(item)
74
+ found = literals(item).find { |match| item.attributes.any? { |part| part.name.to_s == match[2] && !part.list? } }
75
+ found && [found[2], found[1].scan(/"([^"]+)"/).flatten]
76
+ end
77
+
78
+ # @return [Array<MatchData>] the invariants that restrict a part to a literal list
79
+ def literals(item) = item.invariants.filter_map { |rule| LITERALS.match(rule.canonical.to_s) }
80
+
81
+ def describe(item, objects, name, kinds)
82
+ table = rules(objects, kinds)
83
+ found = { "discriminator" => name, "kinds" => kinds, "limits" => limits(item), "pictures" => pictures(item, objects) }
84
+ found["rules"] = table if table
85
+ found["mentions"] = mentions(item, name, kinds) unless table
86
+ found.reject { |_, value| value.respond_to?(:empty?) && value.empty? }
87
+ end
88
+
89
+ # @return [Hash{String => Hash}, nil] each kind's `requires` and `uses`, from the table
90
+ def rules(objects, kinds)
91
+ table = objects.values.find { |object| ruled?(object, kinds) }
92
+ return nil unless table
93
+
94
+ table.members.to_h { |row| [row[:kind].to_s, { "requires" => words(row[:requires]), "uses" => words(row[:uses]) }] }
95
+ end
96
+
97
+ def ruled?(object, kinds)
98
+ object.closed_set? && (RULE_PARTS - object.attributes.map { |part| part.name.to_s }).empty? &&
99
+ object.members.all? { |row| kinds.include?(row[:kind].to_s) }
100
+ end
101
+
102
+ def words(text) = text.to_s.split(",").map(&:strip).reject(&:empty?)
103
+
104
+ # @return [Hash{String => Array<String>}] the slots each kind's guarded invariants read
105
+ def mentions(item, name, kinds)
106
+ parts = item.attributes.map { |part| part.name.to_s } - [name]
107
+ guarded(item, name).each_with_object({}) do |(kind, rest), found|
108
+ next unless kinds.include?(kind)
109
+
110
+ found[kind] = ((found[kind] || []) | parts.select { |part| rest.match?(/\b#{Regexp.escape(part)}\b/) })
111
+ end
112
+ end
113
+
114
+ def guarded(item, name)
115
+ item.invariants.filter_map do |rule|
116
+ match = GUARD.match(rule.canonical.to_s)
117
+ [match[2], match[3]] if match && match[1] == name
118
+ end
119
+ end
120
+
121
+ # @return [Hash{String => Integer}] each list part of the item and its declared maximum
122
+ def limits(item)
123
+ item.invariants.filter_map do |rule|
124
+ match = LIMIT.match(rule.canonical.to_s)
125
+ [match[1], match[2].to_i] if match
126
+ end.to_h
127
+ end
128
+
129
+ # @param commands [Array<Bluebook::Command>] the aggregate's commands
130
+ # @param name [String] a list attribute's name
131
+ # @return [Integer, nil] the most the list may hold, from a command's `given`
132
+ def maximum(commands, name)
133
+ sizes = commands.flat_map(&:givens).filter_map do |rule|
134
+ match = LIMIT.match(rule.canonical.to_s)
135
+ match[2].to_i if match && match[1] == name
136
+ end
137
+ sizes.min
138
+ end
139
+
140
+ # @return [Hash{String => Hash}] the value object and each value object its lists hold,
141
+ # to the parts that name a picture, for those that have the parts
142
+ def pictures(item, objects)
143
+ held = item.attributes.select(&:list?).filter_map { |part| objects[part.type.to_s] }
144
+ [item, *held].filter_map { |object| (found = picture(object)) && [object.hecks_name, found] }.to_h
145
+ end
146
+
147
+ def picture(object)
148
+ names = object.attributes.reject(&:list?).map { |part| part.name.to_s }
149
+ return nil unless names.include?(PICTURE["key"]) && names.include?(PICTURE["alt"])
150
+
151
+ PICTURE.select { |_, part| names.include?(part) }
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "preview"
4
+ require_relative "scale_checks"
5
+ require_relative "theme_checks"
6
+
7
+ module Hecks
8
+ module Projections
9
+ module Site
10
+ module CmsEditor
11
+ # What the `Editor` row must satisfy beyond the class of each field: paths that start with
12
+ # a slash, a sign-in path the editor serves itself, a role to admit, and a login page a
13
+ # visitor with no session can reach.
14
+ module Checks
15
+ # A base path: one or more segments, starting with a slash and not ending with one.
16
+ BASE_PATH = %r{\A(/[^/\s]+)+\z}
17
+
18
+ # The largest upload size cap a row may name: 100 MiB.
19
+ MAX_UPLOAD = 104_857_600
20
+
21
+ module_function
22
+
23
+ # @param row [Hash{Symbol => String}] the row, defaults filled
24
+ # @param roles [Array<String>] the roles the row names
25
+ # @param table [Table, nil] the checked route table, to check the login page against
26
+ # @return [Array<String>] every problem, in the order the fields are declared
27
+ def problems(row, roles, table)
28
+ base = row[:base_path]
29
+ [*path_problems(row, base), *("Editor roles name no role" if roles.empty?), *login_problems(row[:login], table),
30
+ *media_problems(row), *media_chapter_problems(row), *ThemeChecks.problems(row), *ScaleChecks.problems(row),
31
+ *Preview.problems(row[:preview].strip)]
32
+ end
33
+
34
+ # @return [Array<String>] the problems with the base path and the sign-in path
35
+ def path_problems(row, base)
36
+ found = []
37
+ found << "base_path #{base.inspect} must start with a slash and not end with one" unless BASE_PATH.match?(base)
38
+ found << "sso_path #{row[:sso_path]} must be under #{base}/" unless row[:sso_path].start_with?("#{base}/")
39
+ found
40
+ end
41
+
42
+ # @return [Array<String>] the problems with the picture directory and the upload size cap
43
+ def media_problems(row)
44
+ found = []
45
+ found << "media_dir must not be empty" if row[:media_dir].strip.empty?
46
+ cap = row[:media_max_bytes]
47
+ unless cap.match?(/\A[1-9]\d{0,9}\z/) && cap.to_i <= MAX_UPLOAD
48
+ found << "media_max_bytes #{cap.inspect} must be a whole number of bytes from 1 to #{MAX_UPLOAD}"
49
+ end
50
+ found
51
+ end
52
+
53
+ # @return [Array<String>] the problem with a `media` chapter that is one the editor edits
54
+ def media_chapter_problems(row)
55
+ named = row[:media].strip
56
+ return [] unless row[:chapter].split(",").map(&:strip).include?(named)
57
+
58
+ ["media #{named.inspect} is this editor's own chapter; name another chapter"]
59
+ end
60
+
61
+ # The login page is a public row: a visitor with no session must reach it.
62
+ #
63
+ # @return [Array<String>] the problems with the login page
64
+ def login_problems(login, table)
65
+ return ["login #{login} must start with a slash"] unless login.start_with?("/")
66
+ return [] if table.nil?
67
+
68
+ found = table.rows.find { |candidate| candidate.path == login }
69
+ return ["login #{login} is not a route of the table"] if found.nil?
70
+
71
+ found.auth == "public" ? [] : ["login #{login} is #{found.auth}; the login page must be public"]
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Projections
5
+ module Site
6
+ module CmsEditor
7
+ # A command argument that only clears: the bluebook's way of setting an optional field back
8
+ # to nothing is an optional argument the command never reads, named as the source of a
9
+ # `sets <field>, to: :<argument>` mutation (`attribute :nothing, Body, optional: true`,
10
+ # `sets :draft_body, to: :nothing`). Nobody types into it, so the editor offers no field
11
+ # for it and the server sends its empty value itself: an empty list, or nothing at all.
12
+ #
13
+ # The shape decides it, never a name. The argument is optional, is no attribute of the
14
+ # aggregate, feeds only plain `sets` mutations of other fields that can be empty (optional
15
+ # or a list), and no `given` or `ensures` of the command reads it.
16
+ module Clearing
17
+ module_function
18
+
19
+ # @param command [Bluebook::Command] the command
20
+ # @param aggregate [Bluebook::Aggregate] the aggregate it belongs to
21
+ # @return [Array<Bluebook::Attribute>] the command's arguments that only clear
22
+ def of(command, aggregate)
23
+ command.attributes.select { |argument| only_clears?(argument, command, aggregate) }
24
+ end
25
+
26
+ # @param shaped [Hash{String => Object}] the command as the editor reads it
27
+ # @param only [Array<Bluebook::Attribute>] its arguments that only clear
28
+ # @return [Hash{String => Object}] `shaped` without those arguments, and naming the ones
29
+ # that are lists as `empty`, for the editor to send as empty lists
30
+ def apply(shaped, only)
31
+ return shaped if only.empty?
32
+
33
+ names = only.map { |argument| argument.name.to_s }
34
+ lists = only.select(&:list?).map { |argument| argument.name.to_s }
35
+ kept = shaped["attributes"].reject { |attr| names.include?(attr["name"]) }
36
+ shaped.merge("attributes" => kept, **(lists.empty? ? {} : { "empty" => lists }))
37
+ end
38
+
39
+ # @return [Boolean] whether `argument` is a clearing argument of `command`
40
+ def only_clears?(argument, command, aggregate)
41
+ return false if !argument.optional? || named?(aggregate.attributes, argument.name)
42
+
43
+ fed = fed_by(argument, command)
44
+ !fed.empty? && fed.all? { |mutation| clears?(mutation, aggregate) } && !read?(argument, command)
45
+ end
46
+
47
+ # @return [Array<Bluebook::Mutation>] the mutations that take their value from `argument`
48
+ def fed_by(argument, command)
49
+ command.mutations.select { |mutation| mutation.source.is_a?(Symbol) && named?([argument], mutation.source) }
50
+ end
51
+
52
+ # @return [Boolean] whether any of `attributes` is called `name`
53
+ def named?(attributes, name) = attributes.any? { |held| held.name.to_s == name.to_s }
54
+
55
+ # @return [Boolean] whether the mutation is a plain set of a field that can be empty
56
+ def clears?(mutation, aggregate)
57
+ target = aggregate.attributes.find { |held| held.name.to_s == mutation.target.to_s }
58
+ mutation.op == :set && !target.nil? && (target.optional? || target.list?)
59
+ end
60
+
61
+ # @return [Boolean] whether a `given` or `ensures` of the command names the argument
62
+ def read?(argument, command)
63
+ word = /\b#{Regexp.escape(argument.name.to_s)}\b/
64
+ [*command.givens, *command.ensures].any? { |rule| word.match?(rule.canonical.to_s) }
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end