hecks 3.10.0 → 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 (481) 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 +18 -2
  116. data/lib/hecks/doc/reference/rendering.rb +6 -3
  117. data/lib/hecks/forms/app/commands.rb +8 -7
  118. data/lib/hecks/forms/app/queries.rb +2 -1
  119. data/lib/hecks/forms/app.rb +1 -1
  120. data/lib/hecks/forms/record_renderer.rb +1 -1
  121. data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
  122. data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
  123. data/lib/hecks/fuzzing/failure_triage.rb +2 -1
  124. data/lib/hecks/fuzzing/mutation/run.rb +11 -4
  125. data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
  126. data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
  127. data/lib/hecks/fuzzing/properties/guards.rb +1 -1
  128. data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
  129. data/lib/hecks/fuzzing/self_consistency.rb +6 -2
  130. data/lib/hecks/fuzzing/shrinker.rb +3 -1
  131. data/lib/hecks/fuzzing/value_generator.rb +19 -5
  132. data/lib/hecks/gate/stages.yml +4 -10
  133. data/lib/hecks/grammar/evolve.rb +1 -1
  134. data/lib/hecks/grammar/expression_operators.json +372 -0
  135. data/lib/hecks/grammar/operators.rb +3 -1
  136. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
  137. data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
  138. data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
  139. data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
  140. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
  141. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
  142. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
  143. data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
  144. data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
  145. data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
  146. data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
  147. data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
  148. data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
  149. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
  150. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
  151. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
  152. data/lib/hecks/hecks/adapters/local_files.rb +2 -2
  153. data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
  154. data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
  155. data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
  156. data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
  157. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
  158. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
  159. data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
  160. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
  161. data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +129 -0
  162. data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
  163. data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
  164. data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
  165. data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
  166. data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
  167. data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
  168. data/lib/hecks/hecks/adapters/terminal.rb +13 -11
  169. data/lib/hecks/hecks/codebase.behaviors +13 -1
  170. data/lib/hecks/hecks/codebase.bluebook +239 -5
  171. data/lib/hecks/hecks/custodian.behaviors +24 -6
  172. data/lib/hecks/hecks/custodian.bluebook +6 -1227
  173. data/lib/hecks/hecks/deploy.behaviors +1 -1
  174. data/lib/hecks/hecks/deploy.world +1 -3
  175. data/lib/hecks/hecks/hecks.behaviors +1 -1
  176. data/lib/hecks/hecks/hecks.hecksagon +53 -18
  177. data/lib/hecks/hecks/hecks.world +10 -10
  178. data/lib/hecks/hecks/rehearsal.bluebook +477 -0
  179. data/lib/hecks/hecks/site.behaviors +1 -1
  180. data/lib/hecks/hecks/site.world +0 -1
  181. data/lib/hecks/hecks/tooling.bluebook +1238 -0
  182. data/lib/hecks/indifferent_key.rb +20 -0
  183. data/lib/hecks/language/adapter.bluebook +13 -3
  184. data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
  185. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
  186. data/lib/hecks/language/bluebook/entity.bluebook +18 -0
  187. data/lib/hecks/language/bluebook/policy.bluebook +9 -1
  188. data/lib/hecks/language/bluebook/query.bluebook +1 -1
  189. data/lib/hecks/language/bluebook/vocabulary.bluebook +11 -4
  190. data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
  191. data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
  192. data/lib/hecks/language/oidc.json +10 -0
  193. data/lib/hecks/language/port.bluebook +4 -4
  194. data/lib/hecks/literal.rb +12 -3
  195. data/lib/hecks/ports/key_vault.rb +7 -2
  196. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
  197. data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
  198. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
  199. data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
  200. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +29 -0
  201. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
  202. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  203. data/lib/hecks/projections/deploy/shared.rb +5 -2
  204. data/lib/hecks/projections/model/deviations.rb +2 -2
  205. data/lib/hecks/projections/model.rb +2 -2
  206. data/lib/hecks/projections/oidc.rb +13 -1
  207. data/lib/hecks/projections/shape.rb +5 -2
  208. data/lib/hecks/projections/site/cms_editor/attributes.rb +26 -6
  209. data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
  210. data/lib/hecks/projections/site/cms_editor/checks.rb +14 -1
  211. data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
  212. data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
  213. data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
  214. data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
  215. data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
  216. data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
  217. data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
  218. data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
  219. data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
  220. data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
  221. data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
  222. data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
  223. data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
  224. data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
  225. data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
  226. data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
  227. data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
  228. data/lib/hecks/projections/site/cms_editor/schema.rb +96 -19
  229. data/lib/hecks/projections/site/cms_editor/setting.rb +52 -9
  230. data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
  231. data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +18 -1
  232. data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +251 -57
  233. data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
  234. data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +32 -24
  235. data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
  236. data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
  237. data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
  238. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
  239. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
  240. data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
  241. data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
  242. data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
  243. data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
  244. data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
  245. data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
  246. data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
  247. data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
  248. data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
  249. data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
  250. data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
  251. data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +78 -24
  252. data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +6 -0
  253. data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
  254. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +19 -2
  255. data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +31 -8
  256. data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
  257. data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +131 -2
  258. data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
  259. data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
  260. data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
  261. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +8 -5
  262. data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
  263. data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
  264. data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +229 -34
  265. data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +40 -81
  266. data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
  267. data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +29 -12
  268. data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
  269. data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
  270. data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +53 -17
  271. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +178 -88
  272. data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
  273. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
  274. data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
  275. data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
  276. data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
  277. data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
  278. data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
  279. data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
  280. data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
  281. data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
  282. data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
  283. data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +2 -1
  284. data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
  285. data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
  286. data/lib/hecks/projections/site/cms_editor.rb +49 -23
  287. data/lib/hecks/projections/site/role_probe.rb +2 -0
  288. data/lib/hecks/projector/cli_audience.rb +2 -1
  289. data/lib/hecks/projector/cli_projector/listing.rb +1 -1
  290. data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
  291. data/lib/hecks/projector/docs_projector/closing.rb +1 -1
  292. data/lib/hecks/projector/docs_projector.rb +1 -1
  293. data/lib/hecks/projector/exporter/bindings.rb +40 -10
  294. data/lib/hecks/projector/exporter/capability_values.rb +55 -0
  295. data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
  296. data/lib/hecks/projector/target.rb +11 -0
  297. data/lib/hecks/projector.rb +32 -5
  298. data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
  299. data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
  300. data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
  301. data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
  302. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
  303. data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
  304. data/lib/hecks/quality_control/quality_control.bluebook +6 -2
  305. data/lib/hecks/release/approval.rb +64 -0
  306. data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
  307. data/lib/hecks/router/namespace_installer.rb +16 -16
  308. data/lib/hecks/router.rb +1 -1
  309. data/lib/hecks/runtime/best_effort.rb +22 -0
  310. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
  311. data/lib/hecks/runtime/command_interpreter.rb +3 -2
  312. data/lib/hecks/runtime/command_rules/emission.rb +1 -1
  313. data/lib/hecks/runtime/dispatcher.rb +4 -4
  314. data/lib/hecks/runtime/entity_element/location.rb +1 -1
  315. data/lib/hecks/runtime/event.rb +5 -0
  316. data/lib/hecks/runtime/identity.rb +7 -6
  317. data/lib/hecks/runtime/interpreting.rb +2 -1
  318. data/lib/hecks/runtime/invocation_builder.rb +1 -1
  319. data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
  320. data/lib/hecks/runtime/loader.rb +10 -10
  321. data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
  322. data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
  323. data/lib/hecks/runtime/policy_interpreter.rb +6 -6
  324. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
  325. data/lib/hecks/runtime/reaction_invocation.rb +1 -1
  326. data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
  327. data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
  328. data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
  329. data/lib/hecks/runtime/registry/merging.rb +1 -1
  330. data/lib/hecks/runtime/registry/providers.rb +16 -0
  331. data/lib/hecks/runtime/registry/read_repository.rb +6 -4
  332. data/lib/hecks/runtime/registry/verification.rb +4 -1
  333. data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
  334. data/lib/hecks/runtime/registry.rb +1 -1
  335. data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
  336. data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
  337. data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
  338. data/lib/hecks/runtime/saga_interpreter.rb +3 -3
  339. data/lib/hecks/runtime/tenant_check.rb +5 -4
  340. data/lib/hecks/runtime/value/coercion.rb +2 -2
  341. data/lib/hecks/runtime/value/validation.rb +16 -4
  342. data/lib/hecks/runtime.rb +9 -9
  343. data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
  344. data/lib/hecks/rust_build/wasm.rb +1 -1
  345. data/lib/hecks/rust_build/wasm_browser.rb +1 -1
  346. data/lib/hecks/rust_build.rb +11 -0
  347. data/lib/hecks/served_options.rb +53 -0
  348. data/lib/hecks/site/bluebook/site.bluebook +1 -0
  349. data/lib/hecks/storehouse/audit.rb +1 -1
  350. data/lib/hecks/storehouse/dispatching.rb +8 -6
  351. data/lib/hecks/storehouse/introspection.rb +2 -2
  352. data/lib/hecks/storehouse/querying.rb +6 -4
  353. data/lib/hecks/storehouse.rb +4 -4
  354. data/lib/hecks/tools/comment_style/source_file.rb +4 -1
  355. data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
  356. data/lib/hecks/tools/lanes/rows.rb +6 -0
  357. data/lib/hecks/tools/oidc_manifests.rb +4 -5
  358. data/lib/hecks/tools/regeneration_run.rb +11 -4
  359. data/lib/hecks/tools/site_routes/editor_files.rb +12 -5
  360. data/lib/hecks/tools/tenant_provisioning.rb +2 -2
  361. data/lib/hecks/tools/tools_doc.rb +4 -4
  362. data/lib/hecks/version.rb +1 -1
  363. data/lib/hecks/vocabulary.rb +4 -4
  364. data/lib/hecks.rb +15 -15
  365. data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
  366. data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
  367. data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
  368. data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
  369. data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
  370. data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
  371. data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
  372. data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
  373. data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
  374. data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
  375. data/prebuilt/verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json +1 -0
  376. data/rust/.gitignore +6 -0
  377. data/rust/Cargo.toml +6 -0
  378. data/rust/build/src/binding_pass.rs +103 -0
  379. data/rust/build/src/cargo_sync.rs +6 -0
  380. data/rust/build/src/lineage_pass.rs +3 -3
  381. data/rust/build/src/main.rs +17 -5
  382. data/rust/build/src/pipeline.rs +48 -5
  383. data/rust/build/src/seams_pass.rs +123 -0
  384. data/rust/build/src/translation_pass.rs +167 -0
  385. data/rust/codegen/src/asks.rs +103 -0
  386. data/rust/codegen/src/commands.rs +13 -8
  387. data/rust/codegen/src/constraints.rs +25 -0
  388. data/rust/codegen/src/domain_generator.rs +2 -0
  389. data/rust/codegen/src/expr_emitter.rs +13 -0
  390. data/rust/codegen/src/json_codec.rs +54 -15
  391. data/rust/codegen/src/main.rs +1 -0
  392. data/rust/codegen/src/mutations.rs +128 -3
  393. data/rust/codegen/src/queries.rs +11 -2
  394. data/rust/codegen/src/reactions.rs +1 -1
  395. data/rust/codegen/src/types.rs +166 -8
  396. data/rust/host/HECKS_RELEASE +1 -1
  397. data/rust/host/src/api.rs +4 -4
  398. data/rust/host/src/bin/validate_harness.rs +43 -0
  399. data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
  400. data/rust/host/src/boundary_fuzz/ir.rs +1 -0
  401. data/rust/host/src/boundary_fuzz/server.rs +1 -1
  402. data/rust/host/src/checkout.rs +35 -19
  403. data/rust/host/src/commerce_ir.rs +408 -2
  404. data/rust/host/src/expr_json.rs +118 -0
  405. data/rust/host/src/mint.rs +1 -1
  406. data/rust/host/src/payments/tests.rs +27 -1
  407. data/rust/host/src/payments.rs +12 -4
  408. data/rust/host/src/query_step.rs +22 -4
  409. data/rust/host/src/reference_validate.rs +653 -108
  410. data/rust/host/src/resend.rs +2 -0
  411. data/rust/host/src/server.rs +19 -6
  412. data/rust/host/src/ui_schema.rs +132 -5
  413. data/rust/host/src/web/newsletter.rs +7 -8
  414. data/rust/host/src/web/newsletter_send.rs +38 -13
  415. data/rust/host/src/web/registrations.rs +229 -26
  416. data/rust/host/src/web.rs +3 -3
  417. data/rust/parser/src/emit.rs +38 -6
  418. data/rust/parser/src/expr/ast_json.rs +7 -1
  419. data/rust/parser/src/expr/resolver.rs +171 -6
  420. data/rust/parser/src/ir.rs +7 -0
  421. data/rust/parser/src/keywords.rs +24 -0
  422. data/rust/parser/src/main.rs +3 -0
  423. data/rust/parser/src/parse/aggregate.rs +2 -1
  424. data/rust/parser/src/parse/chapter.rs +2 -2
  425. data/rust/parser/src/parse/command.rs +2 -0
  426. data/rust/parser/src/parse/hecksagon.rs +5 -2
  427. data/rust/parser/src/parse/lifecycle.rs +81 -0
  428. data/rust/parser/src/parse/mod.rs +15 -1
  429. data/rust/parser/src/parse/policy.rs +13 -2
  430. data/rust/parser/src/parse/value_object.rs +13 -8
  431. data/rust/parser/src/ruby_value.rs +1 -1
  432. data/rust/src/exemplar/commands.rs +4 -4
  433. data/rust/src/exemplar/constraints.rs +1 -1
  434. data/rust/src/exemplar/json.rs +43 -1
  435. data/rust/src/kernel/cli.rs +7 -5
  436. data/rust/src/kernel/dispatch.rs +2 -2
  437. data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
  438. data/rust/src/kernel/expr.rs +66 -3
  439. data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
  440. data/rust/src/kernel/expression_operators/text.rs +26 -2
  441. data/rust/src/kernel/json.rs +15 -0
  442. data/rust/src/kernel/mod.rs +1 -1
  443. data/rust/src/kernel/orchestrate.rs +54 -5
  444. data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
  445. data/rust/src/kernel/pattern.rs +60 -0
  446. data/rust/src/kernel/reference_lookup.rs +90 -7
  447. metadata +140 -38
  448. data/lib/hecks/doors/cli_door/nesting.rb +0 -34
  449. data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
  450. data/lib/hecks/doors/cli_door.rb +0 -124
  451. data/lib/hecks/doors/cli_runner/answers.rb +0 -138
  452. data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
  453. data/lib/hecks/doors/cli_runner/settling.rb +0 -115
  454. data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
  455. data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
  456. data/lib/hecks/doors/cli_runner.rb +0 -66
  457. data/lib/hecks/doors/command_request.rb +0 -119
  458. data/lib/hecks/doors/handle.rb +0 -208
  459. data/lib/hecks/doors/json_door.rb +0 -132
  460. data/lib/hecks/doors/launcher_options.rb +0 -247
  461. data/lib/hecks/doors/mcp_door/properties.rb +0 -47
  462. data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
  463. data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
  464. data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
  465. data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
  466. data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
  467. data/lib/hecks/doors/mcp_door.rb +0 -156
  468. data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
  469. data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
  470. data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
  471. data/lib/hecks/doors/mcp_door_scope.rb +0 -201
  472. data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
  473. data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
  474. data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
  475. data/lib/hecks/doors/ruby_door.rb +0 -80
  476. data/lib/hecks/doors/usage_cache.rb +0 -139
  477. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_widget.js.tmpl +0 -282
  478. data/lib/hecks/projections/site/cms_editor/templates/src/ui/media_picker.js.tmpl +0 -109
  479. data/prebuilt/c28d6d9db14eea3e17161e9d9c262efee30b256cf97693a7cfd552bce24b2f7f.marshal +0 -0
  480. data/prebuilt/cb375cba61ca0dbe8ddb9739c306510fe05e5f4b7a9b6955caff8224c63ea0e0.marshal +0 -0
  481. data/prebuilt/verdicts-d6d584ad43b5f3cebf88aafbbdbf024c38574acf18e06adffbc555f7786b1c2f.json +0 -1
@@ -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
@@ -75,7 +75,7 @@ module Hecks
75
75
  # @return [Hash{String => Object}] the picture aggregate as the editor's schema names it
76
76
  def describe(agg, command, found)
77
77
  listing = agg["queries"].find { |query| query["attributes"].empty? }
78
- { "aggregate" => agg["name"], "command" => command["name"], "fields" => found,
78
+ { "aggregate" => agg["name"], **agg.slice("chapter"), "command" => command["name"], "fields" => found,
79
79
  "listing" => listing && listing["name"] }
80
80
  end
81
81
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Projections
5
+ module Site
6
+ module CmsEditor
7
+ # Which integer attributes are moments in time, read from the names the bluebook gave them.
8
+ #
9
+ # An integer is a moment when one of the names it goes by (the attribute's, its value
10
+ # object's, the value object's part's) ends in `_at` or `At` (a date and a time), ends in
11
+ # `_on` or `On` (a date), or has `epoch` in it (seconds since 1970, so a date and a time).
12
+ # The value is always whole seconds since 1970 UTC; the editor shows it in the viewer's own
13
+ # time zone. A name is the only evidence the bluebook gives, so the rule is this narrow:
14
+ # an integer named `duration` or `Season` is never a date.
15
+ module Moment
16
+ # Names that end in `at` or `on` as a word, so `created_at` and `CreatedAt` match and
17
+ # `format`, `Season` and `Combat` do not.
18
+ AT = /(?:_at|[a-z0-9]At)\z/
19
+ ON = /(?:_on|[a-z0-9]On)\z/
20
+
21
+ module_function
22
+
23
+ # @param names [Array<String>] the names the integer goes by
24
+ # @return [String, nil] `"datetime"`, `"date"`, or nil when the integer is not a moment
25
+ def widget(names)
26
+ return "datetime" if names.any? { |name| AT.match?(name) || name.match?(/epoch/i) }
27
+
28
+ "date" if names.any? { |name| ON.match?(name) }
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "color"
4
+
5
+ module Hecks
6
+ module Projections
7
+ module Site
8
+ module CmsEditor
9
+ # The editor's colours, light and dark, derived from one accent colour, as the custom
10
+ # properties of a daisyUI theme (`--color-base-100`, `--color-primary`, ...) and the few
11
+ # extras the pages use (`--ed-muted`, `--ed-edge`). Every text and fill pair is moved
12
+ # until it reaches the accessibility standard's level for it (4.5:1 for text, 3:1 for the
13
+ # edge of a control), so a brand colour that would fail cannot make the editor unreadable.
14
+ #
15
+ # The neutrals (ground, page, ink, muted ink, rule) are fixed when the row names no
16
+ # accent, and take the accent's hue at low saturation when it does, so an accent retints
17
+ # the whole theme. Primary buttons are filled with ink (daisyUI's `neutral`); the accent
18
+ # (`primary`) marks selection, focus, links, the active navigation item and the saved or
19
+ # published state.
20
+ class Palette
21
+ # The accent a row that names none gets: a deep verdigris.
22
+ DEFAULT_ACCENT = "#1f7a6d"
23
+
24
+ # The neutrals a row that names no accent gets, per theme.
25
+ FIXED = {
26
+ light: { ground: "#f4f6f8", surface: "#ffffff", ink: "#1a222c", muted: "#5a6675", rule: "#dce1e7" },
27
+ dark: { ground: "#11161b", surface: "#181f26", ink: "#e7ebef", muted: "#9aa6b3", rule: "#28313b" }
28
+ }.freeze
29
+
30
+ # Hues of the status colours, in degrees.
31
+ TONES = { ok: 150.0, warn: 38.0, danger: 4.0 }.freeze
32
+
33
+ # The daisyUI variable each status tone fills.
34
+ STATUS = { ok: "success", warn: "warning", danger: "error" }.freeze
35
+
36
+ # @param accent [String, nil] a six-digit hex colour, or nil for the default
37
+ def initialize(accent = nil)
38
+ @custom = !accent.to_s.empty?
39
+ @accent = (accent.to_s.empty? ? DEFAULT_ACCENT : accent).downcase
40
+ end
41
+
42
+ # @return [Hash{Symbol => Hash{String => String}}] each theme's properties, name to value
43
+ def themes = { light: properties(:light), dark: properties(:dark) }
44
+
45
+ private
46
+
47
+ def properties(theme)
48
+ base = neutrals(theme)
49
+ found = base.merge(accents(theme, base)).merge(tones(theme, base))
50
+ { **daisy(found), **extras(found) }
51
+ end
52
+
53
+ def lighter(theme) = theme == :light ? :darker : :lighter
54
+
55
+ def neutrals(theme)
56
+ fixed = @custom ? tinted(FIXED.fetch(theme)) : FIXED.fetch(theme)
57
+ backs = [fixed[:surface], fixed[:ground]]
58
+ edge = Color.reach(Color.mix(fixed[:rule], fixed[:muted], 0.5), backs, 3.0, lighter(theme))
59
+ { **fixed, muted: Color.reach(fixed[:muted], backs, 4.5, lighter(theme)), edge: edge }
60
+ end
61
+
62
+ # The fixed neutrals with the accent's hue at the saturation each already has.
63
+ def tinted(fixed)
64
+ hue = Color.hsl(@accent).first
65
+ fixed.transform_values do |hex|
66
+ _, sat, light = Color.hsl(hex)
67
+ hex == "#ffffff" ? hex : Color.from_hsl(hue, sat, light)
68
+ end
69
+ end
70
+
71
+ def accents(theme, base)
72
+ backs = [base[:surface], base[:ground]]
73
+ fill = theme == :light ? @accent : Color.reach(@accent, backs, 4.5, :lighter)
74
+ soft = Color.mix(base[:surface], fill, theme == :light ? 0.1 : 0.18)
75
+ { primary: Color.reach(fill, [*backs, soft], 4.5, lighter(theme)), accent_soft: soft }
76
+ end
77
+
78
+ def tones(theme, base)
79
+ TONES.each_with_object({}) do |(name, hue), found|
80
+ back = theme == :light ? Color.from_hsl(hue, 0.5, 0.94) : Color.from_hsl(hue, 0.28, 0.18)
81
+ start = Color.from_hsl(hue, 0.55, theme == :light ? 0.3 : 0.72)
82
+ found[:"#{name}_bg"] = back
83
+ found[:"#{name}_fg"] = Color.reach(start, [back, base[:surface]], 4.5, lighter(theme))
84
+ end
85
+ end
86
+
87
+ # The first of white, a near-black and black that reads on `fill`.
88
+ def on(fill) = ["#ffffff", "#0f151a", "#000000"].find { |hex| Color.contrast(hex, fill) >= 4.5 }
89
+
90
+ def daisy(found)
91
+ colours = surfaces(found).merge(fills(found))
92
+ named = colours.slice("primary", "secondary", "accent", "info", *STATUS.values)
93
+ contents = named.to_h { |name, value| ["#{name}-content", on(value)] }
94
+ colours.merge(contents).transform_keys { |name| "--color-#{name}" }
95
+ end
96
+
97
+ def surfaces(found)
98
+ { "base-100" => found[:surface], "base-200" => found[:ground], "base-300" => found[:rule],
99
+ "base-content" => found[:ink], "neutral" => found[:ink], "neutral-content" => found[:surface] }
100
+ end
101
+
102
+ def fills(found)
103
+ { "primary" => found[:primary], "secondary" => found[:muted], "accent" => found[:primary], "info" => found[:primary],
104
+ **STATUS.to_h { |tone, name| [name, found[:"#{tone}_fg"]] } }
105
+ end
106
+
107
+ def extras(found)
108
+ { "--ed-muted" => found[:muted], "--ed-edge" => found[:edge], "--ed-soft" => found[:accent_soft],
109
+ "--ed-ok-bg" => found[:ok_bg], "--ed-warn-bg" => found[:warn_bg], "--ed-danger-bg" => found[:danger_bg] }
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end