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
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Projections
5
+ module Site
6
+ module CmsEditor
7
+ # Colour arithmetic for the editor's theme: parsing and writing six-digit hex, moving
8
+ # between red-green-blue and hue-saturation-lightness, mixing, and the contrast ratio the
9
+ # web accessibility guidelines define. Pure functions of their inputs, so the theme the
10
+ # editor is generated with is the same on every run.
11
+ module Color
12
+ # A six-digit hex colour with its leading hash.
13
+ HEX = /\A#[0-9a-fA-F]{6}\z/
14
+
15
+ # The step by which `reach` moves a colour's lightness while it searches.
16
+ STEP = 0.01
17
+
18
+ module_function
19
+
20
+ # @param value [String] a colour such as `"#1f7a6d"`
21
+ # @return [Array<Float>] its red, green and blue, each from 0.0 to 1.0
22
+ def rgb(value) = value.delete_prefix("#").scan(/../).map { |pair| pair.hex / 255.0 }
23
+
24
+ # @param channels [Array<Float>] red, green and blue, each from 0.0 to 1.0
25
+ # @return [String] the colour as lower-case hex
26
+ def hex(channels)
27
+ channels.each_with_object(+"#") { |c, text| text << format("%02x", (c.clamp(0.0, 1.0) * 255).round) }
28
+ end
29
+
30
+ # @param value [String] a colour
31
+ # @return [Array<Float>] hue in degrees, then saturation and lightness from 0.0 to 1.0
32
+ def hsl(value)
33
+ red, green, blue = rgb(value)
34
+ high = [red, green, blue].max
35
+ low = [red, green, blue].min
36
+ light = (high + low) / 2.0
37
+ return [0.0, 0.0, light] if high == low
38
+
39
+ spread = high - low
40
+ sat = light > 0.5 ? spread / (2.0 - high - low) : spread / (high + low)
41
+ [angle(red, green, blue, high, spread), sat, light]
42
+ end
43
+
44
+ # @param hue [Float] degrees
45
+ # @param sat [Float] saturation from 0.0 to 1.0
46
+ # @param light [Float] lightness from 0.0 to 1.0
47
+ # @return [String] the colour as hex
48
+ def from_hsl(hue, sat, light)
49
+ chroma = (1 - ((2 * light) - 1).abs) * sat
50
+ shift = light - (chroma / 2)
51
+ hex(sector(hue % 360, chroma, second(hue % 360, chroma)).map { |channel| channel + shift })
52
+ end
53
+
54
+ # @return [Float] the second-largest channel of a colour of the given hue and chroma
55
+ def second(hue, chroma) = chroma * (1 - (((hue / 60.0) % 2) - 1).abs)
56
+
57
+ # @param one [String] a colour
58
+ # @param other [String] another colour
59
+ # @param share [Float] how much of `other` goes in, from 0.0 to 1.0
60
+ # @return [String] the colours mixed channel by channel
61
+ def mix(one, other, share) = hex(rgb(one).zip(rgb(other)).map { |a, b| (a * (1 - share)) + (b * share) })
62
+
63
+ # @param value [String] a colour
64
+ # @return [Float] its relative luminance as the accessibility guidelines define it
65
+ def luminance(value)
66
+ red, green, blue = rgb(value).map { |c| c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055)**2.4 }
67
+ (0.2126 * red) + (0.7152 * green) + (0.0722 * blue)
68
+ end
69
+
70
+ # @param one [String] a colour
71
+ # @param other [String] another colour
72
+ # @return [Float] the contrast ratio, from 1.0 to 21.0
73
+ def contrast(one, other)
74
+ high = [luminance(one), luminance(other)].max
75
+ low = [luminance(one), luminance(other)].min
76
+ (high + 0.05) / (low + 0.05)
77
+ end
78
+
79
+ # Moves a colour's lightness, a little at a time, until it reads against every background.
80
+ #
81
+ # @param value [String] where to start
82
+ # @param against [Array<String>] backgrounds the colour must read on
83
+ # @param ratio [Float] the least contrast to reach
84
+ # @param toward [Symbol] `:darker` or `:lighter`
85
+ # @return [String] `value` itself when it already reads, else the nearest such colour
86
+ def reach(value, against, ratio, toward)
87
+ hue, sat, light = hsl(value)
88
+ sign = toward == :darker ? -STEP : STEP
89
+ light += sign until light.round(2).clamp(0.0,
90
+ 1.0) != light.round(2) || reads?(from_hsl(hue, sat, light), against, ratio)
91
+ from_hsl(hue, sat, light.clamp(0.0, 1.0))
92
+ end
93
+
94
+ # @param value [String] a colour
95
+ # @param against [Array<String>] backgrounds
96
+ # @param ratio [Float] the least contrast
97
+ # @return [Boolean] whether `value` reaches `ratio` on every background
98
+ def reads?(value, against, ratio) = against.all? { |back| contrast(value, back) >= ratio }
99
+
100
+ # @param background [String] a fill
101
+ # @param light [String] a light foreground to try
102
+ # @param dark [String] a dark foreground to try
103
+ # @return [String] whichever reads better on `background`
104
+ def foreground(background, light, dark) = contrast(light, background) >= contrast(dark, background) ? light : dark
105
+
106
+ # @return [Float] the hue, in degrees, of a colour given by its channels
107
+ def angle(red, green, blue, high, spread)
108
+ degrees = if high == red then ((green - blue) / spread) % 6
109
+ elsif high == green then ((blue - red) / spread) + 2
110
+ else ((red - green) / spread) + 4
111
+ end
112
+ degrees * 60.0
113
+ end
114
+
115
+ # @return [Array<Float>] the red, green and blue before lightness is added back
116
+ def sector(hue, chroma, mid)
117
+ [[chroma, mid, 0], [mid, chroma, 0], [0, chroma, mid], [0, mid, chroma], [mid, 0, chroma],
118
+ [chroma, 0, mid]][(hue / 60).floor % 6]
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Projections
5
+ module Site
6
+ module CmsEditor
7
+ # Several chapters as one editor: each aggregate keeps the name its chapter gave it and is
8
+ # stamped with the chapter that declares it, so two aggregates of one name in different
9
+ # chapters are two aggregates. The stamps exist only when the editor spans more than one
10
+ # chapter (`chapter`), or when a chapter's roles are limited (`roles`), so an editor of one
11
+ # open chapter reads exactly as a chapter alone does.
12
+ module Composition
13
+ module_function
14
+
15
+ # @param chapters [Array<Bluebook::Chapter>] the chapters, in the order the row names them
16
+ # @param skip [Array<String>] `Name` (in every chapter) or `Chapter::Name` to leave out
17
+ # @param roles [Hash{String => Array<String>}] the roles each chapter is limited to
18
+ # @yieldparam aggregate [Bluebook::Aggregate] one aggregate to shape
19
+ # @yieldreturn [Hash{String => Object}] the aggregate as the editor reads it
20
+ # @return [Array<Hash{String => Object}>] the shaped aggregates, chapter by chapter
21
+ def aggregates(chapters, skip:, roles:)
22
+ many = chapters.size > 1
23
+ chapters.flat_map do |chapter|
24
+ kept(chapter, skip).map { |agg| stamp(yield(agg), many ? chapter.name : nil, roles[chapter.name]) }
25
+ end
26
+ end
27
+
28
+ # @return [Array<Bluebook::Aggregate>] the chapter's aggregates that are not skipped
29
+ def kept(chapter, skip)
30
+ chapter.aggregates.reject { |agg| skip.intersect?([agg.hecks_name, "#{chapter.name}::#{agg.hecks_name}"]) }
31
+ end
32
+
33
+ # @return [Hash{String => Object}] `shaped` with its chapter and roles after its name
34
+ def stamp(shaped, named, roles)
35
+ extra = { "chapter" => named, "roles" => roles }.compact
36
+ { "name" => shaped["name"], **extra, **shaped.except("name") }
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Projections
5
+ module Site
6
+ module CmsEditor
7
+ # Which commands the editor asks a person to confirm before it runs them, and what colour
8
+ # each lifecycle state's badge has.
9
+ #
10
+ # A command is destructive when its first word is one of `WORDS` (`WithdrawOffer`,
11
+ # `DiscardDraft`), or when it is a lifecycle move into a state named for one of `STATES`.
12
+ # The test reads the bluebook's own words and structure, never a list of aggregates, so a
13
+ # domain with a different vocabulary confirms nothing it did not name that way. A move that
14
+ # can be undone, such as an archive with a restore, is not destructive by that alone.
15
+ #
16
+ # A state's tone is structural: the starting state is `neutral`, a state named for a
17
+ # destructive word is `danger`, and the others alternate `ok` and `info` in the order the
18
+ # moves reach them. The badge always carries the state's name; tone is never the only cue.
19
+ module Destructive
20
+ # The first words of a command that undoes or ends something.
21
+ WORDS = %w[withdraw retire discard delete cancel remove].freeze
22
+
23
+ # The states those commands move into.
24
+ STATES = %w[withdrawn retired discarded deleted cancelled canceled removed].freeze
25
+
26
+ module_function
27
+
28
+ # @param name [String] the command's name, such as `"DiscardDraft"`
29
+ # @param lifecycle [Hash{String => Object}, nil] the lifecycle as `Schema` reads it
30
+ # @return [Boolean] whether the editor confirms the command first
31
+ def command?(name, lifecycle)
32
+ return true if WORDS.include?(words(name).first)
33
+
34
+ move = lifecycle && lifecycle["transitions"].find { |transition| transition["verb"] == name }
35
+ !move.nil? && STATES.include?(move["to"].downcase)
36
+ end
37
+
38
+ # @param lifecycle [Hash{String => Object}, nil] the lifecycle as `Schema` reads it
39
+ # @return [Hash{String => String}] each state's tone, empty without a lifecycle
40
+ def tones(lifecycle)
41
+ return {} unless lifecycle
42
+
43
+ states = [lifecycle["default"], *lifecycle["transitions"].flat_map { |move| [*move["from"], move["to"]] }].uniq
44
+ cycle = %w[ok info].cycle
45
+ states.to_h { |state| [state, tone(state, lifecycle["default"], cycle)] }
46
+ end
47
+
48
+ # @return [String] the tone of one state; `cycle` hands out `ok` and `info` in turn
49
+ def tone(state, default, cycle)
50
+ return "neutral" if state == default
51
+
52
+ STATES.include?(state.downcase) ? "danger" : cycle.next
53
+ end
54
+
55
+ # @return [Array<String>] the lower-case words of a CamelCase or snake_case name
56
+ def words(name) = name.to_s.scan(/[A-Z][a-z0-9]*|[a-z0-9]+/).map(&:downcase)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "clearing"
4
+
5
+ module Hecks
6
+ module Projections
7
+ module Site
8
+ module CmsEditor
9
+ # An aggregate that keeps a draft of its content beside the live one: the editor writes the
10
+ # draft as the person types and publishes it only when asked.
11
+ #
12
+ # A command on the aggregate whose every argument is `draft_<x>` (the aggregate holds
13
+ # both `draft_<x>` and `<x>`, of one type, and some `<x>` is a rich-text body, a block list
14
+ # or a value object, alone or in a list) saves the draft. A command with a
15
+ # `sets <x>, to: state(:draft_<x>)` mutation publishes it; one that only empties
16
+ # `draft_<x>` (see `Clearing`) discards it. A missing publishing or discarding command is
17
+ # left out.
18
+ module Drafts
19
+ # What a draft attribute's name starts with.
20
+ PREFIX = "draft_"
21
+
22
+ module_function
23
+
24
+ # @param agg [Bluebook::Aggregate] the aggregate
25
+ # @param attributes [Array<Hash{String => Object}>] its attributes as `Schema` shapes
26
+ # them
27
+ # @return [Hash{String => String}, nil] `attribute` (the draft), `live`, `save`, and
28
+ # `publish` and `discard` when the aggregate has them; nil when it keeps no drafts
29
+ def of(agg, attributes)
30
+ found = agg.commands.lazy.filter_map { |command| saving(command, agg, attributes) }.first
31
+ return nil unless found
32
+
33
+ found.merge("publish" => publishing(agg, found), "discard" => discarding(agg, found)).compact
34
+ end
35
+
36
+ # @return [Hash{String => String}, nil] the draft and live attributes `command` saves
37
+ def saving(command, agg, attributes)
38
+ return nil unless acts_on?(command, agg) && !command.attributes.empty?
39
+
40
+ lives = command.attributes.map { |argument| live_of(argument, agg) }
41
+ live = lives.all? ? lives.find { |name| draftable?(name, attributes) } : nil
42
+ live && { "attribute" => "#{PREFIX}#{live}", "live" => live, "save" => command.hecks_name }
43
+ end
44
+
45
+ # @return [Boolean] whether the command acts on an existing instance of the aggregate
46
+ def acts_on?(command, agg) = !command.creates? && command.references.to_s == agg.hecks_name
47
+
48
+ # @return [String, nil] the live attribute `argument` is the draft of, when it is one
49
+ def live_of(argument, agg)
50
+ name = argument.name.to_s
51
+ live = name.delete_prefix(PREFIX)
52
+ return nil unless name.start_with?(PREFIX) && same_type?(agg, name, live) && agg.attributes.any? do |held|
53
+ held.name.to_s == name
54
+ end
55
+
56
+ live
57
+ end
58
+
59
+ # @return [Boolean] whether the aggregate holds both attributes, of one type
60
+ def same_type?(agg, draft, live)
61
+ types = [draft, live].map { |name| agg.attributes.find { |held| held.name.to_s == name }&.type.to_s }
62
+ types.none?(&:empty?) && types.uniq.size == 1
63
+ end
64
+
65
+ # @return [Boolean] whether the named attribute is a rich-text body, a block list or a
66
+ # value object (a page's fixed slots), alone or in a list
67
+ def draftable?(name, attributes)
68
+ attributes.any? do |attr|
69
+ attr["name"] == name && (attr["widget"] == "body" || attr.key?("blockList") || attr["kind"] == "object")
70
+ end
71
+ end
72
+
73
+ # @return [String, nil] the command that moves the draft into the live attribute
74
+ def publishing(agg, found)
75
+ agg.commands.find { |command| acts_on?(command, agg) && promotes?(command, found) }&.hecks_name
76
+ end
77
+
78
+ # @return [String, nil] the command that empties the draft and changes nothing live
79
+ def discarding(agg, found)
80
+ agg.commands.find { |command| acts_on?(command, agg) && empties?(command, agg, found) }&.hecks_name
81
+ end
82
+
83
+ def promotes?(command, found)
84
+ command.mutations.any? do |mutation|
85
+ mutation.target.to_s == found["live"] && mutation.source.is_a?(Hecks::StateRef) &&
86
+ mutation.source.name.to_s == found["attribute"]
87
+ end
88
+ end
89
+
90
+ def empties?(command, agg, found)
91
+ return false if command.hecks_name == found["save"] || targets_of(command) != [found["attribute"]]
92
+
93
+ clears = Clearing.of(command, agg).map { |argument| argument.name.to_s }
94
+ command.mutations.all? { |mutation| clears.include?(mutation.source.to_s) }
95
+ end
96
+
97
+ def targets_of(command) = command.mutations.map { |mutation| mutation.target.to_s }
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "key_kinds"
4
+ require_relative "moment"
5
+
6
+ module Hecks
7
+ module Projections
8
+ module Site
9
+ module CmsEditor
10
+ # What one attribute's names, pattern and closed set say about how a value of it is
11
+ # entered: a moment (see `Moment`), a `<kind>:<slug>` key (see `KeyKinds`) or one of a
12
+ # closed set's members. A value object with a single plain part is entered as that part,
13
+ # so the part's pattern and the value object's names count; a value object of several
14
+ # parts has parts of its own, each read the same way.
15
+ class Hints
16
+ # @param objects [Hash{String => Bluebook::ValueObject}] the value objects by name
17
+ def initialize(objects)
18
+ @objects = objects
19
+ end
20
+
21
+ # @param attribute [Bluebook::Attribute] a declared attribute
22
+ # @return [Hash{String => Object}] `widget`, `keys` and `options` when the attribute has
23
+ # them
24
+ def of(attribute)
25
+ leaf, names = leaf(attribute)
26
+ return {} unless leaf
27
+
28
+ { **moment(leaf, names), **keys(leaf), **options(attribute, leaf) }
29
+ end
30
+
31
+ private
32
+
33
+ # @return [Array(Bluebook::Attribute, Array<String>), nil] the attribute that holds the
34
+ # value and the names it goes by, or nil for a reference or a value object of parts
35
+ def leaf(attribute)
36
+ return nil if attribute.type.is_a?(Bluebook::Reference)
37
+
38
+ object = @objects[attribute.type.to_s]
39
+ return [attribute, [attribute.name.to_s]] unless object
40
+
41
+ part = plain_part(object)
42
+ part && [part, [attribute.name.to_s, attribute.type.to_s, part.name.to_s]]
43
+ end
44
+
45
+ # @return [Bluebook::Attribute, nil] the one part of a value object that is a plain value
46
+ def plain_part(object)
47
+ part = object.attributes.first
48
+ part if object.attributes.size == 1 && !part.list? && !@objects.key?(part.type.to_s)
49
+ end
50
+
51
+ def moment(leaf, names)
52
+ widget = Moment.widget(names) if leaf.type.to_s == "Integer"
53
+ widget ? { "widget" => widget } : {}
54
+ end
55
+
56
+ # A value object declared a closed set (`one_of`) is entered by choosing one of its
57
+ # members.
58
+ def options(attribute, leaf)
59
+ object = @objects[attribute.type.to_s]
60
+ return {} unless object&.closed_set?
61
+
62
+ members = object.members.filter_map { |member| member[leaf.name] }.uniq
63
+ members.empty? ? {} : { "options" => members.map(&:to_s) }
64
+ end
65
+
66
+ def keys(leaf)
67
+ kinds = KeyKinds.of(leaf.pattern)
68
+ kinds.empty? ? {} : { "keys" => kinds }
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Projections
5
+ module Site
6
+ module CmsEditor
7
+ # The editor's icons, drawn once here and emitted as one vector sprite
8
+ # (`src/ui/icons.ts`). Pages and the browser code refer to an icon by name with
9
+ # `<svg><use href="#i-name"/></svg>`, so there is no icon font, no request and no external
10
+ # file. Each is a 24 by 24 outline drawn with the current text colour.
11
+ module Icons
12
+ # Each icon's name and the drawing elements inside its symbol.
13
+ DRAWINGS = {
14
+ "menu" => '<path d="M4 7h16M4 12h16M4 17h16"/>',
15
+ "close" => '<path d="M6 6l12 12M18 6L6 18"/>',
16
+ "sun" => '<circle cx="12" cy="12" r="4"/>' \
17
+ '<path d="M12 3v2M12 19v2M3 12h2M19 12h2M5.6 5.6L7 7M17 17l1.4 1.4M5.6 18.4L7 17M17 7l1.4-1.4"/>',
18
+ "moon" => '<path d="M20 14.5A8 8 0 019.5 4 8 8 0 1020 14.5z"/>',
19
+ "sign-out" => '<path d="M10 4H5v16h5M15 8l4 4-4 4M19 12H9"/>',
20
+ "search" => '<circle cx="11" cy="11" r="6"/><path d="M16 16l4 4"/>',
21
+ "plus" => '<path d="M12 5v14M5 12h14"/>',
22
+ "copy" => '<rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V6a2 2 0 012-2h8"/>',
23
+ "check" => '<path d="M5 12.5l4.5 4.5L19 7.5"/>',
24
+ "chevron-up" => '<path d="M6 14l6-6 6 6"/>',
25
+ "chevron-down" => '<path d="M6 10l6 6 6-6"/>',
26
+ "chevron-right" => '<path d="M10 6l6 6-6 6"/>',
27
+ "arrow-up" => '<path d="M12 19V5M6 11l6-6 6 6"/>',
28
+ "arrow-down" => '<path d="M12 5v14M6 13l6 6 6-6"/>',
29
+ "sort" => '<path d="M8 9l4-4 4 4M8 15l4 4 4-4"/>',
30
+ "trash" => '<path d="M5 7h14M10 7V4h4v3M7 7l1 13h8l1-13M10 11v6M14 11v6"/>',
31
+ "pencil" => '<path d="M5 19l1-4L16 5l3 3L9 18l-4 1zM14 7l3 3"/>',
32
+ "bold" => '<path d="M7 5h6a3.5 3.5 0 010 7H7zM7 12h7a3.5 3.5 0 010 7H7z"/>',
33
+ "italic" => '<path d="M10 5h8M6 19h8M14 5l-4 14"/>',
34
+ "underline" => '<path d="M7 5v7a5 5 0 0010 0V5M5 20h14"/>',
35
+ "strike" => '<path d="M4 12h16M16 7a4 3 0 00-4-2c-2.5 0-4 1.2-4 3s1.5 2.5 4 4 4 2 4 4-1.7 3-4.2 3' \
36
+ 'a5 4 0 01-4.3-2"/>',
37
+ "code" => '<path d="M9 7l-5 5 5 5M15 7l5 5-5 5"/>',
38
+ "link" => '<path d="M10 14a4 4 0 005.7 0l3-3a4 4 0 00-5.7-5.7l-1 1' \
39
+ 'M14 10a4 4 0 00-5.7 0l-3 3A4 4 0 0011 18.7l1-1"/>',
40
+ "unlink" => '<path d="M10 14a4 4 0 005.7 0l3-3a4 4 0 00-5.7-5.7l-1 1' \
41
+ 'M14 10a4 4 0 00-5.7 0l-3 3A4 4 0 0011 18.7l1-1M4 4l16 16"/>',
42
+ "list-bullets" => '<path d="M9 6h11M9 12h11M9 18h11"/><circle cx="4.5" cy="6" r="1"/>' \
43
+ '<circle cx="4.5" cy="12" r="1"/><circle cx="4.5" cy="18" r="1"/>',
44
+ "list-numbers" => '<path d="M10 6h10M10 12h10M10 18h10M4 5l1.5-1v4M4 11.5h3l-3 3h3M4 17.5h3v1.5H5M7 19v1.5H4"/>',
45
+ "indent" => '<path d="M4 5h16M12 10h8M12 15h8M4 20h16M4 9l3 3-3 3"/>',
46
+ "outdent" => '<path d="M4 5h16M12 10h8M12 15h8M4 20h16M7 9l-3 3 3 3"/>',
47
+ "align-left" => '<path d="M4 6h16M4 10h10M4 14h16M4 18h10"/>',
48
+ "align-center" => '<path d="M4 6h16M7 10h10M4 14h16M7 18h10"/>',
49
+ "align-right" => '<path d="M4 6h16M10 10h10M4 14h16M10 18h10"/>',
50
+ "align-justify" => '<path d="M4 6h16M4 10h16M4 14h16M4 18h16"/>',
51
+ "image" => '<rect x="4" y="5" width="16" height="14" rx="2"/><circle cx="9" cy="10" r="1.5"/>' \
52
+ '<path d="M5 17l5-5 4 4 2-2 3 3"/>',
53
+ "divider" => '<path d="M4 12h16M8 7h8M8 17h8" stroke-dasharray="0"/>',
54
+ "quote" => '<path d="M5 17v-4a4 4 0 014-4M13 17v-4a4 4 0 014-4M5 17h4v-4H5M13 17h4v-4h-4"/>',
55
+ "line-break" => '<path d="M5 6v6a3 3 0 003 3h10M14 11l4 4-4 4"/>',
56
+ "undo" => '<path d="M9 7L4 12l5 5M4 12h10a5 5 0 010 10"/>',
57
+ "redo" => '<path d="M15 7l5 5-5 5M20 12H10a5 5 0 000 10"/>',
58
+ "upload" => '<path d="M12 16V5M7 10l5-5 5 5M5 19h14"/>',
59
+ "alert" => '<path d="M12 4l9 16H3zM12 10v4M12 17v.5"/>',
60
+ "info" => '<circle cx="12" cy="12" r="9"/><path d="M12 11v5M12 8v.5"/>',
61
+ "success" => '<circle cx="12" cy="12" r="9"/><path d="M8 12.5l3 3 5-6"/>',
62
+ "clock" => '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>',
63
+ "dot" => '<circle cx="12" cy="12" r="4"/>',
64
+ "inbox" => '<path d="M4 13l2-8h12l2 8M4 13v6h16v-6M4 13h5l1 2h4l1-2h5"/>',
65
+ "eye" => '<path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12z"/>' \
66
+ '<circle cx="12" cy="12" r="3"/>',
67
+ "refresh" => '<path d="M19 6v5h-5M5 18v-5h5M18.2 10A7 7 0 006 7.5M5.8 14A7 7 0 0018 16.5"/>',
68
+ "external" => '<path d="M14 4h6v6M20 4l-9 9M18 14v5H5V6h5"/>',
69
+ "phone" => '<rect x="7" y="3" width="10" height="18" rx="2"/><path d="M11 18h2"/>',
70
+ "tablet" => '<rect x="5" y="3" width="14" height="18" rx="2"/><path d="M11 18h2"/>',
71
+ "desktop" => '<rect x="3" y="4" width="18" height="12" rx="2"/><path d="M9 20h6M12 16v4"/>',
72
+ "calendar" => '<rect x="4" y="5" width="16" height="15" rx="2"/><path d="M4 10h16M9 3v4M15 3v4"/>',
73
+ "arrow-left" => '<path d="M19 12H5M11 6l-6 6 6 6"/>'
74
+ }.freeze
75
+
76
+ module_function
77
+
78
+ # @return [String] the symbols as one hidden sprite's body, each with an id `i-<name>`
79
+ def sprite
80
+ DRAWINGS.map { |name, drawing| %(<symbol id="i-#{name}" viewBox="0 0 24 24">#{drawing}</symbol>) }.join("\n")
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Projections
5
+ module Site
6
+ module CmsEditor
7
+ # The `<kind>:<slug>` key convention, read from an attribute's own `pattern:`.
8
+ #
9
+ # A key such as `post:hello` says what it names in its first part. The bluebook states the
10
+ # convention only when the attribute's pattern does: it must start `^kind:` or
11
+ # `^(kind|kind):`, with lower-case words of letters, digits and underscores. Anything
12
+ # else is not a convention the editor assumes.
13
+ module KeyKinds
14
+ # `^` then one word, or a group of words separated by `|`, then the `:`.
15
+ LEAD = /\A\^(?:\((?:\?:)?(?<kinds>\w+(?:\|\w+)*)\)|(?<kinds>\w+)):/
16
+
17
+ module_function
18
+
19
+ # @param pattern [String, nil] the attribute's declared pattern
20
+ # @return [Array<String>] the kinds the pattern allows before the colon; empty when none
21
+ def of(pattern)
22
+ found = LEAD.match(pattern.to_s)
23
+ return [] unless found
24
+
25
+ found[:kinds].split("|").grep(/\A[a-z][a-z0-9_]*\z/).uniq
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Projections
5
+ module Site
6
+ module CmsEditor
7
+ # What a holder's rules say about how much an attribute may hold: a list's least and most
8
+ # rows (`chapters.size >= 1`, `parts.size <= 12`) and a text's most characters
9
+ # (`summary.to_s.size <= 600`). The rules are the domain's own, so the editor shows the
10
+ # limit it will be held to, and a text that may be long is entered in a writing box.
11
+ #
12
+ # The holder is a value object (its invariants), an aggregate (its invariants) or a command
13
+ # (its `given`s). A rule is read only when it is the whole of the invariant, or the whole
14
+ # of it after an `x.unset? ||` guard on the same part.
15
+ module Limits
16
+ # A list's size compared with a number.
17
+ LIST = /\A(\w+)\.size (<=|>=) (\d+)\z/
18
+
19
+ # A text's length bounded above (`to_s.size`, `size` or `length`), maybe after a guard.
20
+ TEXT = /\A(?:(\w+)\.unset\? \|\| )?(\w+)\.(?:to_s\.)?(?:size|length) <= (\d+)\z/
21
+
22
+ # A text that may be longer than this many characters is entered in a writing box.
23
+ MULTILINE_FROM = 200
24
+
25
+ module_function
26
+
27
+ # @param attributes [Array<Hash{String => Object}>] the holder's attributes as `Schema`
28
+ # shapes them
29
+ # @param rules [Array<#canonical>] the holder's invariants or `given`s
30
+ # @return [Array<Hash{String => Object}>] the attributes, each with `min` and `max` when
31
+ # it is a list a rule bounds, and `maxLength` (with `multiline` above `MULTILINE_FROM`)
32
+ # when it is a text a rule bounds
33
+ def apply(attributes, rules)
34
+ texts = rules.map { |rule| rule.canonical.to_s }
35
+ lists = sizes(texts)
36
+ bound = lengths(texts)
37
+ attributes.map { |attr| attr.merge(listed(attr, lists), texted(attr, bound)) }
38
+ end
39
+
40
+ # @return [Hash{String => Hash{String => Integer}}] each list's `min` and `max`; where a
41
+ # holder says several, the tightest wins
42
+ def sizes(texts)
43
+ texts.filter_map { |text| LIST.match(text) }.each_with_object({}) do |found, bounds|
44
+ held = (bounds[found[1]] ||= {})
45
+ kind = found[2] == "<=" ? "max" : "min"
46
+ held[kind] = [held[kind], found[3].to_i].compact.public_send(kind == "max" ? :min : :max)
47
+ end
48
+ end
49
+
50
+ # @return [Hash{String => Integer}] each text's most characters
51
+ def lengths(texts)
52
+ texts.filter_map { |text| TEXT.match(text) }
53
+ .select { |found| found[1].nil? || found[1] == found[2] }
54
+ .to_h { |found| [found[2], found[3].to_i] }
55
+ end
56
+
57
+ def listed(attr, lists)
58
+ found = attr["list"] ? lists[attr["name"]] : nil
59
+ found ? found.reject { |kind, size| kind == "min" && size < 1 } : {}
60
+ end
61
+
62
+ def texted(attr, bound)
63
+ most = attr["type"] == "String" && !attr["list"] ? bound[attr["name"]] : nil
64
+ return {} unless most
65
+
66
+ { "maxLength" => most, **(most > MULTILINE_FROM ? { "multiline" => true } : {}) }
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end