hecks 3.10.0 → 3.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (542) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/bluebook/pizzas.bluebook +29 -0
  3. data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
  4. data/exe/hecks +4 -4
  5. data/lib/hecks/adapters/driven/d1.rb +2 -10
  6. data/lib/hecks/adapters/driven/heki.adapter +1 -1
  7. data/lib/hecks/adapters/driven/heki.rb +2 -12
  8. data/lib/hecks/adapters/driven/lambda.adapter +1 -1
  9. data/lib/hecks/adapters/driven/lambda.rb +3 -18
  10. data/lib/hecks/adapters/driven/postgres.rb +4 -11
  11. data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
  12. data/lib/hecks/adapters/driven/sqlite.rb +2 -10
  13. data/lib/hecks/adapters/driving/admission.rb +46 -0
  14. data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
  15. data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
  16. data/lib/hecks/adapters/driving/cli.rb +127 -0
  17. data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
  18. data/lib/hecks/adapters/driving/cli_runner/resolution.rb +158 -0
  19. data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
  20. data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
  21. data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
  22. data/lib/hecks/adapters/driving/cli_runner.rb +70 -0
  23. data/lib/hecks/adapters/driving/command_request.rb +121 -0
  24. data/lib/hecks/adapters/driving/handle.rb +214 -0
  25. data/lib/hecks/adapters/driving/json.rb +136 -0
  26. data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
  27. data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
  28. data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
  29. data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
  30. data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
  31. data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
  32. data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
  33. data/lib/hecks/adapters/driving/mcp.rb +159 -0
  34. data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
  35. data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
  36. data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
  37. data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
  38. data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
  39. data/lib/hecks/adapters/driving/newcomer.rb +50 -0
  40. data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
  41. data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
  42. data/lib/hecks/adapters/driving/ruby.rb +83 -0
  43. data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
  44. data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
  45. data/lib/hecks/behaviors/expectations/boot.rb +1 -1
  46. data/lib/hecks/bench/ruby_runner.rb +1 -1
  47. data/lib/hecks/bluebook/ask_resolution.rb +130 -0
  48. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
  49. data/lib/hecks/bluebook/assembly/contracts/read_models.rb +1 -1
  50. data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
  51. data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
  52. data/lib/hecks/bluebook/assembly/marks.rb +4 -15
  53. data/lib/hecks/bluebook/assembly/scalars.rb +39 -0
  54. data/lib/hecks/bluebook/attribute.rb +1 -1
  55. data/lib/hecks/bluebook/behaviour/command.rb +13 -8
  56. data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
  57. data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
  58. data/lib/hecks/bluebook/capabilities.rb +91 -9
  59. data/lib/hecks/bluebook/domain_port.rb +16 -4
  60. data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
  61. data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
  62. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
  63. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
  64. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
  65. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
  66. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
  67. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
  68. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
  69. data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
  70. data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
  71. data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
  72. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
  73. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
  74. data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
  75. data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
  76. data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
  77. data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
  78. data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
  79. data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
  80. data/lib/hecks/bluebook/expression/projection.json +36 -0
  81. data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
  82. data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
  83. data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
  84. data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
  85. data/lib/hecks/bluebook/expression/resolver.rb +20 -1
  86. data/lib/hecks/bluebook/hecksagon.rb +23 -3
  87. data/lib/hecks/bluebook/lifecycle.rb +22 -3
  88. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
  89. data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
  90. data/lib/hecks/bluebook/meta_validator/readings.rb +24 -1
  91. data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
  92. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +6 -3
  93. data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
  94. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
  95. data/lib/hecks/bluebook/meta_validator.rb +12 -12
  96. data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
  97. data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
  98. data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
  99. data/lib/hecks/bluebook/model_check.rb +8 -2
  100. data/lib/hecks/bluebook/policy.rb +4 -2
  101. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  102. data/lib/hecks/bluebook/synthesizer.rb +2 -0
  103. data/lib/hecks/bluebook.rb +2 -0
  104. data/lib/hecks/cli/command_table.rb +1 -1
  105. data/lib/hecks/cli/console.rb +2 -1
  106. data/lib/hecks/cli/document.rb +1 -1
  107. data/lib/hecks/cli/launcher_words.rb +4 -4
  108. data/lib/hecks/cli/mcp.rb +4 -4
  109. data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
  110. data/lib/hecks/cli/project_cli/templates.rb +5 -5
  111. data/lib/hecks/cli/project_cli.rb +9 -6
  112. data/lib/hecks/cli/run.rb +2 -2
  113. data/lib/hecks/cli/smoke_http/checks.rb +4 -1
  114. data/lib/hecks/cli/smoke_http/transport.rb +3 -3
  115. data/lib/hecks/cli/smoke_http.rb +3 -0
  116. data/lib/hecks/codemod.rb +5 -2
  117. data/lib/hecks/corpus/routes.rb +7 -0
  118. data/lib/hecks/corpus/rust.rb +3 -2
  119. data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -4
  120. data/lib/hecks/doc/reference/rendering.rb +6 -3
  121. data/lib/hecks/forms/app/commands.rb +8 -7
  122. data/lib/hecks/forms/app/queries.rb +2 -1
  123. data/lib/hecks/forms/app.rb +1 -1
  124. data/lib/hecks/forms/record_renderer.rb +1 -1
  125. data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
  126. data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
  127. data/lib/hecks/fuzzing/failure_triage.rb +2 -1
  128. data/lib/hecks/fuzzing/mutation/run.rb +11 -4
  129. data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
  130. data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
  131. data/lib/hecks/fuzzing/properties/guards.rb +1 -1
  132. data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
  133. data/lib/hecks/fuzzing/self_consistency.rb +6 -2
  134. data/lib/hecks/fuzzing/shrinker.rb +3 -1
  135. data/lib/hecks/fuzzing/value_generator.rb +19 -5
  136. data/lib/hecks/gate/stages.yml +33 -7
  137. data/lib/hecks/grammar/evolve.rb +1 -1
  138. data/lib/hecks/grammar/expression_operators.json +372 -0
  139. data/lib/hecks/grammar/operators.rb +3 -1
  140. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
  141. data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
  142. data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
  143. data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
  144. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
  145. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
  146. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
  147. data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
  148. data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
  149. data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
  150. data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
  151. data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
  152. data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
  153. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
  154. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
  155. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
  156. data/lib/hecks/hecks/adapters/local_files.rb +2 -2
  157. data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
  158. data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
  159. data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
  160. data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
  161. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
  162. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
  163. data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
  164. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
  165. data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +133 -0
  166. data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
  167. data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
  168. data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
  169. data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
  170. data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
  171. data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
  172. data/lib/hecks/hecks/adapters/terminal.rb +13 -11
  173. data/lib/hecks/hecks/codebase.behaviors +13 -1
  174. data/lib/hecks/hecks/codebase.bluebook +239 -5
  175. data/lib/hecks/hecks/custodian.behaviors +24 -6
  176. data/lib/hecks/hecks/custodian.bluebook +6 -1227
  177. data/lib/hecks/hecks/deploy.behaviors +1 -1
  178. data/lib/hecks/hecks/deploy.world +1 -3
  179. data/lib/hecks/hecks/hecks.behaviors +1 -1
  180. data/lib/hecks/hecks/hecks.hecksagon +53 -18
  181. data/lib/hecks/hecks/hecks.world +13 -10
  182. data/lib/hecks/hecks/rehearsal.bluebook +477 -0
  183. data/lib/hecks/hecks/site.behaviors +1 -1
  184. data/lib/hecks/hecks/site.world +0 -1
  185. data/lib/hecks/hecks/tooling.bluebook +1238 -0
  186. data/lib/hecks/indifferent_key.rb +20 -0
  187. data/lib/hecks/language/adapter.bluebook +13 -3
  188. data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
  189. data/lib/hecks/language/bluebook/bluebook.behaviors +23 -0
  190. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
  191. data/lib/hecks/language/bluebook/entity.bluebook +18 -0
  192. data/lib/hecks/language/bluebook/policy.behaviors +19 -0
  193. data/lib/hecks/language/bluebook/policy.bluebook +9 -1
  194. data/lib/hecks/language/bluebook/query.bluebook +1 -1
  195. data/lib/hecks/language/bluebook/vocabulary.bluebook +63 -4
  196. data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
  197. data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
  198. data/lib/hecks/language/oidc.json +10 -0
  199. data/lib/hecks/language/port.bluebook +4 -4
  200. data/lib/hecks/literal.rb +12 -3
  201. data/lib/hecks/ports/key_vault.rb +7 -2
  202. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
  203. data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
  204. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
  205. data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
  206. data/lib/hecks/projection_files/expression_tables.rb +7 -2
  207. data/lib/hecks/projections/deploy/box/compose.rb +6 -4
  208. data/lib/hecks/projections/deploy/box/edge.rb +62 -0
  209. data/lib/hecks/projections/deploy/box/logging.rb +99 -0
  210. data/lib/hecks/projections/deploy/box/roll_scripts.rb +16 -8
  211. data/lib/hecks/projections/deploy/box/settings/cdn.rb +40 -0
  212. data/lib/hecks/projections/deploy/box/settings/logs.rb +40 -0
  213. data/lib/hecks/projections/deploy/box/settings/patterns.rb +11 -0
  214. data/lib/hecks/projections/deploy/box/settings/types.rb +16 -1
  215. data/lib/hecks/projections/deploy/box/settings.rb +6 -2
  216. data/lib/hecks/projections/deploy/box/stack_yaml.rb +5 -2
  217. data/lib/hecks/projections/deploy/box/templates/Makefile.tmpl +16 -0
  218. data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +8 -4
  219. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +32 -0
  220. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
  221. data/lib/hecks/projections/deploy/box/templates/edge.mk.tmpl +17 -0
  222. data/lib/hecks/projections/deploy/box/templates/edge.yaml.tmpl +65 -0
  223. data/lib/hecks/projections/deploy/box/templates/log-group.yaml.tmpl +12 -0
  224. data/lib/hecks/projections/deploy/box/templates/log-policy.yaml.tmpl +7 -0
  225. data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +5 -4
  226. data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +5 -4
  227. data/lib/hecks/projections/deploy/box.rb +15 -3
  228. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  229. data/lib/hecks/projections/deploy/shared.rb +5 -2
  230. data/lib/hecks/projections/model/deviations.rb +2 -2
  231. data/lib/hecks/projections/model.rb +2 -2
  232. data/lib/hecks/projections/oidc.rb +13 -1
  233. data/lib/hecks/projections/shape.rb +5 -2
  234. data/lib/hecks/projections/site/cms_editor/attributes.rb +26 -6
  235. data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
  236. data/lib/hecks/projections/site/cms_editor/checks.rb +14 -1
  237. data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
  238. data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
  239. data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
  240. data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
  241. data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
  242. data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
  243. data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
  244. data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
  245. data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
  246. data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
  247. data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
  248. data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
  249. data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
  250. data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
  251. data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
  252. data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
  253. data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
  254. data/lib/hecks/projections/site/cms_editor/schema.rb +96 -19
  255. data/lib/hecks/projections/site/cms_editor/setting.rb +52 -9
  256. data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
  257. data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +18 -1
  258. data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +251 -57
  259. data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
  260. data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +32 -24
  261. data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
  262. data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
  263. data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
  264. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
  265. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
  266. data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
  267. data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
  268. data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
  269. data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
  270. data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
  271. data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
  272. data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
  273. data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
  274. data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
  275. data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
  276. data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
  277. data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +78 -24
  278. data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +6 -0
  279. data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
  280. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +19 -2
  281. data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +31 -8
  282. data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
  283. data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +131 -2
  284. data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
  285. data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
  286. data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
  287. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +8 -5
  288. data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
  289. data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
  290. data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +229 -34
  291. data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +40 -81
  292. data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
  293. data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +29 -12
  294. data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
  295. data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
  296. data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +53 -17
  297. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +178 -88
  298. data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
  299. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
  300. data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
  301. data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
  302. data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
  303. data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
  304. data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
  305. data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
  306. data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
  307. data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
  308. data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
  309. data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +2 -1
  310. data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
  311. data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
  312. data/lib/hecks/projections/site/cms_editor.rb +49 -23
  313. data/lib/hecks/projections/site/role_probe.rb +2 -0
  314. data/lib/hecks/projections/site/site_root/workflow.rb +5 -0
  315. data/lib/hecks/projector/cli_audience.rb +22 -1
  316. data/lib/hecks/projector/cli_projector/listing.rb +1 -1
  317. data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
  318. data/lib/hecks/projector/cli_projector/usage.rb +15 -4
  319. data/lib/hecks/projector/docs_projector/closing.rb +1 -1
  320. data/lib/hecks/projector/docs_projector.rb +1 -1
  321. data/lib/hecks/projector/exporter/bindings.rb +40 -10
  322. data/lib/hecks/projector/exporter/capability_values.rb +55 -0
  323. data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
  324. data/lib/hecks/projector/target.rb +11 -0
  325. data/lib/hecks/projector.rb +32 -5
  326. data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
  327. data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
  328. data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
  329. data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
  330. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
  331. data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
  332. data/lib/hecks/quality_control/quality_control.bluebook +6 -2
  333. data/lib/hecks/release/approval.rb +64 -0
  334. data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
  335. data/lib/hecks/router/namespace_installer.rb +16 -16
  336. data/lib/hecks/router.rb +1 -1
  337. data/lib/hecks/runtime/best_effort.rb +22 -0
  338. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
  339. data/lib/hecks/runtime/command_interpreter.rb +3 -2
  340. data/lib/hecks/runtime/command_rules/emission.rb +1 -1
  341. data/lib/hecks/runtime/dispatcher.rb +4 -4
  342. data/lib/hecks/runtime/entity_element/location.rb +1 -1
  343. data/lib/hecks/runtime/event.rb +5 -0
  344. data/lib/hecks/runtime/identity.rb +7 -6
  345. data/lib/hecks/runtime/interpreting.rb +2 -1
  346. data/lib/hecks/runtime/invocation_builder.rb +1 -1
  347. data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
  348. data/lib/hecks/runtime/loader.rb +10 -10
  349. data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
  350. data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
  351. data/lib/hecks/runtime/policy_interpreter.rb +6 -6
  352. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
  353. data/lib/hecks/runtime/reaction_invocation.rb +1 -1
  354. data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
  355. data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
  356. data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
  357. data/lib/hecks/runtime/registry/merging.rb +1 -1
  358. data/lib/hecks/runtime/registry/providers.rb +16 -0
  359. data/lib/hecks/runtime/registry/read_repository.rb +6 -4
  360. data/lib/hecks/runtime/registry/verification.rb +4 -1
  361. data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
  362. data/lib/hecks/runtime/registry.rb +1 -1
  363. data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
  364. data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
  365. data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
  366. data/lib/hecks/runtime/saga_interpreter.rb +3 -3
  367. data/lib/hecks/runtime/tenant_check.rb +5 -4
  368. data/lib/hecks/runtime/value/coercion.rb +2 -2
  369. data/lib/hecks/runtime/value/validation.rb +16 -4
  370. data/lib/hecks/runtime.rb +9 -9
  371. data/lib/hecks/rust_build/host.rb +11 -4
  372. data/lib/hecks/rust_build/prebuilt_binary.rb +101 -0
  373. data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
  374. data/lib/hecks/rust_build/wasm.rb +1 -1
  375. data/lib/hecks/rust_build/wasm_browser.rb +1 -1
  376. data/lib/hecks/rust_build.rb +11 -0
  377. data/lib/hecks/served_options.rb +53 -0
  378. data/lib/hecks/site/bluebook/site.bluebook +1 -0
  379. data/lib/hecks/storehouse/audit.rb +1 -1
  380. data/lib/hecks/storehouse/dispatching.rb +8 -6
  381. data/lib/hecks/storehouse/introspection.rb +2 -2
  382. data/lib/hecks/storehouse/querying.rb +6 -4
  383. data/lib/hecks/storehouse.rb +4 -4
  384. data/lib/hecks/tools/ci_gate_decision.rb +35 -0
  385. data/lib/hecks/tools/ci_gates.rb +20 -1
  386. data/lib/hecks/tools/comment_style/source_file.rb +4 -1
  387. data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
  388. data/lib/hecks/tools/lanes/files.rb +8 -0
  389. data/lib/hecks/tools/lanes/prebuilt_workflow.rb +81 -0
  390. data/lib/hecks/tools/lanes/rows.rb +6 -0
  391. data/lib/hecks/tools/lanes.rb +1 -0
  392. data/lib/hecks/tools/oidc_manifests.rb +4 -5
  393. data/lib/hecks/tools/regeneration_run.rb +11 -4
  394. data/lib/hecks/tools/release_only.rb +111 -0
  395. data/lib/hecks/tools/site_routes/editor_files.rb +12 -5
  396. data/lib/hecks/tools/tenant_provisioning.rb +2 -2
  397. data/lib/hecks/tools/tools_doc.rb +4 -4
  398. data/lib/hecks/version.rb +1 -1
  399. data/lib/hecks/vocabulary.rb +19 -5
  400. data/lib/hecks.rb +15 -15
  401. data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
  402. data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
  403. data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
  404. data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
  405. data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
  406. data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
  407. data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
  408. data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
  409. data/prebuilt/9ee54e2f0d4d6d3fe046827779b221e02de665fba911a048ae5dd8b8fbfb7d90.marshal +0 -0
  410. data/prebuilt/d74a00e996222c1d8b676d5959092effc46b4dc59e0555f3bf9554ac58e3094a.marshal +0 -0
  411. data/prebuilt/verdicts-df19f35868d3284a70f55e42dcef87ef4e65e6a678e5135f72e1f6cff4c1357b.json +1 -0
  412. data/rust/.gitignore +6 -0
  413. data/rust/Cargo.toml +6 -0
  414. data/rust/build/src/binding_pass.rs +103 -0
  415. data/rust/build/src/cargo_sync.rs +6 -0
  416. data/rust/build/src/lineage_pass.rs +3 -3
  417. data/rust/build/src/main.rs +17 -5
  418. data/rust/build/src/pipeline.rs +48 -5
  419. data/rust/build/src/seams_pass.rs +123 -0
  420. data/rust/build/src/translation_pass.rs +167 -0
  421. data/rust/codegen/src/asks.rs +103 -0
  422. data/rust/codegen/src/commands.rs +13 -8
  423. data/rust/codegen/src/constraints.rs +25 -0
  424. data/rust/codegen/src/domain_generator.rs +2 -0
  425. data/rust/codegen/src/expr_emitter.rs +13 -0
  426. data/rust/codegen/src/json_codec.rs +54 -15
  427. data/rust/codegen/src/main.rs +1 -0
  428. data/rust/codegen/src/mutations.rs +128 -3
  429. data/rust/codegen/src/queries.rs +11 -2
  430. data/rust/codegen/src/reactions.rs +1 -1
  431. data/rust/codegen/src/types.rs +166 -8
  432. data/rust/host/HECKS_RELEASE +1 -1
  433. data/rust/host/src/api.rs +4 -4
  434. data/rust/host/src/bin/validate_harness.rs +43 -0
  435. data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
  436. data/rust/host/src/boundary_fuzz/ir.rs +1 -0
  437. data/rust/host/src/boundary_fuzz/server.rs +1 -1
  438. data/rust/host/src/checkout.rs +35 -19
  439. data/rust/host/src/commerce_ir.rs +408 -2
  440. data/rust/host/src/expr_json.rs +118 -0
  441. data/rust/host/src/extension.rs +13 -1
  442. data/rust/host/src/lib.rs +1 -0
  443. data/rust/host/src/mint.rs +1 -1
  444. data/rust/host/src/payments/tests.rs +27 -1
  445. data/rust/host/src/payments.rs +12 -4
  446. data/rust/host/src/platform_signin/attack_tests.rs +1100 -0
  447. data/rust/host/src/platform_signin/config.rs +512 -0
  448. data/rust/host/src/platform_signin/cookie.rs +237 -0
  449. data/rust/host/src/platform_signin/extension.rs +190 -0
  450. data/rust/host/src/platform_signin/fetcher.rs +322 -0
  451. data/rust/host/src/platform_signin/handler.rs +251 -0
  452. data/rust/host/src/platform_signin/handler_tests.rs +554 -0
  453. data/rust/host/src/platform_signin/jti.rs +196 -0
  454. data/rust/host/src/platform_signin/jwks.rs +542 -0
  455. data/rust/host/src/platform_signin/jws.rs +249 -0
  456. data/rust/host/src/platform_signin/known_answer_tests.rs +237 -0
  457. data/rust/host/src/platform_signin/mapping_tests.rs +254 -0
  458. data/rust/host/src/platform_signin/mod.rs +75 -0
  459. data/rust/host/src/platform_signin/mode_tests.rs +382 -0
  460. data/rust/host/src/platform_signin/pages.rs +204 -0
  461. data/rust/host/src/platform_signin/query.rs +233 -0
  462. data/rust/host/src/platform_signin/session.rs +151 -0
  463. data/rust/host/src/platform_signin/settings.rs +385 -0
  464. data/rust/host/src/platform_signin/strict_json.rs +351 -0
  465. data/rust/host/src/platform_signin/test_support.rs +235 -0
  466. data/rust/host/src/platform_signin/ttl_tests.rs +94 -0
  467. data/rust/host/src/platform_signin/verify.rs +604 -0
  468. data/rust/host/src/platform_signin/wiring_support.rs +319 -0
  469. data/rust/host/src/query_step.rs +22 -4
  470. data/rust/host/src/reference_validate.rs +653 -108
  471. data/rust/host/src/resend.rs +2 -0
  472. data/rust/host/src/server.rs +19 -6
  473. data/rust/host/src/ui_schema.rs +132 -5
  474. data/rust/host/src/web/newsletter.rs +7 -8
  475. data/rust/host/src/web/newsletter_send.rs +38 -13
  476. data/rust/host/src/web/registrations.rs +229 -26
  477. data/rust/host/src/web.rs +53 -9
  478. data/rust/parser/src/emit.rs +38 -6
  479. data/rust/parser/src/expr/ast_json.rs +7 -1
  480. data/rust/parser/src/expr/resolver.rs +171 -6
  481. data/rust/parser/src/ir.rs +7 -0
  482. data/rust/parser/src/keywords.rs +24 -0
  483. data/rust/parser/src/main.rs +3 -0
  484. data/rust/parser/src/parse/aggregate.rs +2 -1
  485. data/rust/parser/src/parse/chapter.rs +2 -2
  486. data/rust/parser/src/parse/command.rs +2 -0
  487. data/rust/parser/src/parse/hecksagon.rs +5 -2
  488. data/rust/parser/src/parse/lifecycle.rs +81 -0
  489. data/rust/parser/src/parse/mod.rs +15 -1
  490. data/rust/parser/src/parse/policy.rs +13 -2
  491. data/rust/parser/src/parse/value_object.rs +34 -17
  492. data/rust/parser/src/ruby_value.rs +1 -1
  493. data/rust/src/exemplar/commands.rs +4 -4
  494. data/rust/src/exemplar/constraints.rs +1 -1
  495. data/rust/src/exemplar/json.rs +43 -1
  496. data/rust/src/kernel/cli.rs +7 -5
  497. data/rust/src/kernel/dispatch.rs +2 -2
  498. data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
  499. data/rust/src/kernel/expr.rs +66 -3
  500. data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
  501. data/rust/src/kernel/expression_operators/text.rs +26 -2
  502. data/rust/src/kernel/json.rs +15 -0
  503. data/rust/src/kernel/mod.rs +1 -1
  504. data/rust/src/kernel/orchestrate.rs +54 -5
  505. data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
  506. data/rust/src/kernel/pattern.rs +60 -0
  507. data/rust/src/kernel/reference_lookup.rs +90 -7
  508. metadata +178 -38
  509. data/lib/hecks/doors/cli_door/nesting.rb +0 -34
  510. data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
  511. data/lib/hecks/doors/cli_door.rb +0 -124
  512. data/lib/hecks/doors/cli_runner/answers.rb +0 -138
  513. data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
  514. data/lib/hecks/doors/cli_runner/settling.rb +0 -115
  515. data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
  516. data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
  517. data/lib/hecks/doors/cli_runner.rb +0 -66
  518. data/lib/hecks/doors/command_request.rb +0 -119
  519. data/lib/hecks/doors/handle.rb +0 -208
  520. data/lib/hecks/doors/json_door.rb +0 -132
  521. data/lib/hecks/doors/launcher_options.rb +0 -247
  522. data/lib/hecks/doors/mcp_door/properties.rb +0 -47
  523. data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
  524. data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
  525. data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
  526. data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
  527. data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
  528. data/lib/hecks/doors/mcp_door.rb +0 -156
  529. data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
  530. data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
  531. data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
  532. data/lib/hecks/doors/mcp_door_scope.rb +0 -201
  533. data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
  534. data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
  535. data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
  536. data/lib/hecks/doors/ruby_door.rb +0 -80
  537. data/lib/hecks/doors/usage_cache.rb +0 -139
  538. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_widget.js.tmpl +0 -282
  539. data/lib/hecks/projections/site/cms_editor/templates/src/ui/media_picker.js.tmpl +0 -109
  540. data/prebuilt/c28d6d9db14eea3e17161e9d9c262efee30b256cf97693a7cfd552bce24b2f7f.marshal +0 -0
  541. data/prebuilt/cb375cba61ca0dbe8ddb9739c306510fe05e5f4b7a9b6955caff8224c63ea0e0.marshal +0 -0
  542. data/prebuilt/verdicts-d6d584ad43b5f3cebf88aafbbdbf024c38574acf18e06adffbc555f7786b1c2f.json +0 -1
@@ -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
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "targets"
4
+
5
+ module Hecks
6
+ module Projections
7
+ module Site
8
+ module CmsEditor
9
+ # Which attributes name another aggregate's instance, found by the bluebook's own
10
+ # declarations, so the editor offers the instances in place of a text box. In this order:
11
+ # a `<kind>:<slug>` key by its pattern (see `KeyKinds`), each kind that is an aggregate's
12
+ # snake-case name offering that aggregate; a `reference_to` attribute whose target is an
13
+ # editor aggregate; a value object of one text part named `<Stem>Ref`, where `<Stem>` is
14
+ # the type of an editor aggregate's identity or its name; and `<Image|Picture|Photo|Media>
15
+ # Ref` with a picture aggregate, which suggests pictures and accepts any other text. A
16
+ # target is found once: the same chapter's, else the only one of its name; else none.
17
+ module Pickers
18
+ # The stems that name a picture, for the editor's picture aggregate.
19
+ PICTURE_STEMS = %w[Image Picture Photo Media].freeze
20
+
21
+ module_function
22
+
23
+ # @param aggregates [Array<Hash{String => Object}>] the aggregates as `Schema` shapes them
24
+ # @param media [Hash{String => Object}, nil] the picture aggregate, as `Media` says
25
+ # @return [Array<Hash{String => Object}>] the aggregates, each attribute that names an
26
+ # instance carrying its `picker`
27
+ def apply(aggregates, media = nil)
28
+ targets = Targets.new(aggregates, media)
29
+ aggregates.map { |agg| annotate(agg, targets) }
30
+ end
31
+
32
+ # @return [Hash{String => Object}] `agg` with a picker on each attribute that has one
33
+ def annotate(agg, targets)
34
+ mark = ->(list) { list.map { |attr| with_picker(attr, agg, targets) } }
35
+ agg.merge("attributes" => mark.call(agg["attributes"]), "valueObjects" => objects(agg, mark),
36
+ "commands" => marked(agg["commands"], mark), "queries" => marked(agg["queries"], mark))
37
+ end
38
+
39
+ # @return [Array<Hash>] the commands or queries, the attributes of each marked
40
+ def marked(items, mark) = items.map { |item| item.merge("attributes" => mark.call(item["attributes"])) }
41
+
42
+ # @return [Hash{String => Array}] the value objects, the parts of one of several parts
43
+ # marked; a value object of one part is entered as the attribute that names it
44
+ def objects(agg, mark)
45
+ agg["valueObjects"].transform_values do |parts|
46
+ parts.size == 1 ? parts.map { |part| part.except("keys") } : mark.call(parts)
47
+ end
48
+ end
49
+
50
+ # @return [Hash{String => Object}] `attr` with its `picker`, or as it was
51
+ def with_picker(attr, agg, targets)
52
+ picker = picker_for(attr, agg, targets)
53
+ picker ? attr.except("keys").merge("picker" => picker) : attr
54
+ end
55
+
56
+ def picker_for(attr, agg, targets)
57
+ return kinds(attr["keys"], agg, targets) if attr["keys"]
58
+ return targets.named(attr["target"], agg) if attr["kind"] == "reference"
59
+
60
+ stem = reference_stem(attr, agg)
61
+ stem && (targets.keyed_by(stem, agg) || (targets.pictures if PICTURE_STEMS.include?(stem)))
62
+ end
63
+
64
+ # @return [Hash{String => Array}] one entry per kind, with the aggregate it names if any
65
+ def kinds(words, agg, targets)
66
+ { "kinds" => words.map { |word| { "kind" => word, **(targets.kind(word, agg) || {}) } } }
67
+ end
68
+
69
+ # @return [String, nil] `Stem` of a `StemRef` value object that has one plain text part
70
+ def reference_stem(attr, agg)
71
+ return nil unless plain_text_object?(attr, agg["valueObjects"][attr["type"]])
72
+
73
+ stem = attr["type"].delete_suffix("Ref")
74
+ stem unless stem.empty? || stem == attr["type"]
75
+ end
76
+
77
+ # @return [Boolean] whether `attr` is a value object whose one part is a single text
78
+ def plain_text_object?(attr, parts)
79
+ attr["kind"] == "object" && parts&.size == 1 && parts.first["kind"] == "text" && !parts.first["list"]
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module Hecks
6
+ module Projections
7
+ module Site
8
+ module CmsEditor
9
+ # The `Editor` row's `preview`: where a record is shown on the site, as a URL template the
10
+ # editor frames in a side drawer.
11
+ #
12
+ # A template is an `http://` or `https://` address, or a path that starts with one slash,
13
+ # with the placeholders `{key}`, `{kind}`, `{slug}` and `{id}` filled from a record's
14
+ # identity when the page is built. Anything else is refused, so a template cannot name a
15
+ # scheme that runs script (`javascript:`), a host written with credentials, or a
16
+ # placeholder the editor would leave unfilled. A placeholder may not stand in the origin
17
+ # (scheme, host and port), because the content security policy names that origin once, at
18
+ # build time, and allows no other frame.
19
+ module Preview
20
+ # The words a template may put in braces.
21
+ PLACEHOLDERS = %w[key kind slug id].freeze
22
+
23
+ # An absolute address: the origin, then whatever follows it.
24
+ ABSOLUTE = %r{\A(?<origin>https?://[^/?#\s]+)(?<rest>.*)\z}m
25
+
26
+ module_function
27
+
28
+ # @param template [String] the row's `preview`, which may be empty
29
+ # @return [Array<String>] every reason the template is refused; empty when it is usable
30
+ def problems(template)
31
+ return [] if template.empty?
32
+
33
+ found = []
34
+ found << "must not hold whitespace" if template.match?(/\s/)
35
+ found.concat(shape_problems(template))
36
+ found.concat(placeholder_problems(template))
37
+ found.map { |problem| "preview #{template.inspect} #{problem}" }
38
+ end
39
+
40
+ # @return [String, nil] what the content security policy's `frame-src` names for the
41
+ # template: its origin, `'self'` for a path, or nil for no preview
42
+ def frame_source(template)
43
+ return nil if template.empty?
44
+
45
+ found = ABSOLUTE.match(template)
46
+ found ? found[:origin] : "'self'"
47
+ end
48
+
49
+ # @return [Array<String>] the problems with the scheme, the host and the path
50
+ def shape_problems(template)
51
+ found = ABSOLUTE.match(template)
52
+ return path_problems(template) unless found
53
+
54
+ origin_problems(found[:origin])
55
+ end
56
+
57
+ # @return [Array<String>] the problems with a path template
58
+ def path_problems(template)
59
+ return ["must be an http(s) address or a path that starts with one slash"] unless template.start_with?("/")
60
+ return ["must not start with two slashes, which names another host"] if template.start_with?("//")
61
+
62
+ []
63
+ end
64
+
65
+ # @return [Array<String>] the problems with the origin of an absolute template
66
+ def origin_problems(origin)
67
+ found = []
68
+ found << "must not name credentials in the address" if origin.include?("@")
69
+ found << "must not hold a placeholder in the scheme, host or port" if origin.include?("{")
70
+ found << "names no host" if !origin.include?("{") && URI.parse(origin).host.to_s.empty?
71
+ found
72
+ rescue URI::InvalidURIError
73
+ ["is not a valid address"]
74
+ end
75
+
76
+ # @return [Array<String>] a problem for each unknown placeholder and each stray brace
77
+ def placeholder_problems(template)
78
+ unknown = template.scan(/\{([^{}]*)\}/).flatten.uniq - PLACEHOLDERS
79
+ found = unknown.map do |name|
80
+ "names the placeholder {#{name}}; the placeholders are #{PLACEHOLDERS.map do |word|
81
+ "{#{word}}"
82
+ end.join(", ")}"
83
+ end
84
+ found << "has a brace that is not part of a placeholder" if template.gsub(/\{[^{}]*\}/, "").match?(/[{}]/)
85
+ found
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Projections
5
+ module Site
6
+ module CmsEditor
7
+ # The records that belong to one record, found by the keys they carry. Aggregate `B` is
8
+ # related to `A` when `B`'s identity is a `<kind>:<slug>` key (see `KeyKinds`) whose
9
+ # kinds include `A`'s snake-case name, so `post:first-light` keys the `B` of the `Post`
10
+ # `first-light`.
11
+ #
12
+ # The rule reads the declared pattern and what the kind resolves to (see `Pickers`),
13
+ # never a name. A `B` is shown by its shape: a **document** (a rich-text body), a
14
+ # **gallery** (a list that names pictures) or **metadata** (anything else). Its creating
15
+ # command is the first that takes the identity; its editing command is the draft-saving
16
+ # command of a document that keeps drafts (see `Drafts`), else the first command on an
17
+ # existing record that is neither a lifecycle move nor destructive and takes the body or
18
+ # the pictures.
19
+ module Relations
20
+ module_function
21
+
22
+ # @param aggregates [Array<Hash{String => Object}>] the aggregates as `Pickers` leaves
23
+ # them
24
+ # @param media [Hash{String => Object}, nil] the picture aggregate, as `Media` says
25
+ # @return [Array<Hash{String => Object}>] each aggregate, carrying `related` when other
26
+ # aggregates are keyed by it
27
+ def apply(aggregates, media = nil)
28
+ aggregates.map do |agg|
29
+ found = aggregates.reject { |other| other.equal?(agg) }.filter_map { |other| relation(other, agg, media) }
30
+ found.empty? ? agg : agg.merge("related" => found)
31
+ end
32
+ end
33
+
34
+ # @return [Hash{String => Object}, nil] how `other` is related to `agg`, or nil when it
35
+ # is not
36
+ def relation(other, agg, media)
37
+ key = other["attributes"].find { |attr| attr["name"] == other["identity"] }
38
+ return nil unless key && keyed_by?(key, agg)
39
+
40
+ shape, field = shape_of(other, media)
41
+ { "aggregate" => other["name"], "chapter" => other["chapter"], "field" => key["name"], "shape" => shape,
42
+ "create" => creating(other, key), "edit" => editing(other, field) }.compact
43
+ end
44
+
45
+ # @return [Boolean] whether `key` is declared a key of `<agg's snake name>:<slug>`
46
+ def keyed_by?(key, agg)
47
+ (key.dig("picker", "kinds") || []).any? { |kind| kind["target"] == agg["name"] && kind["chapter"] == agg["chapter"] }
48
+ end
49
+
50
+ # @return [Array(String, String)] the shape, and the attribute that gives it
51
+ def shape_of(other, media)
52
+ body = other["attributes"].find { |attr| attr["widget"] == "body" && !attr["list"] }
53
+ return ["document", body["name"]] if body
54
+
55
+ pictures = pictures_of(other, media)
56
+ pictures ? ["gallery", pictures["name"]] : ["metadata", nil]
57
+ end
58
+
59
+ # @return [Hash{String => Object}, nil] the list attribute that names pictures
60
+ def pictures_of(other, media)
61
+ return nil unless media
62
+
63
+ other["attributes"].find { |attr| attr["list"] && attr.dig("picker", "target") == media["aggregate"] }
64
+ end
65
+
66
+ # @return [String, nil] the first creating command that takes the identity
67
+ def creating(other, key)
68
+ other["commands"].find do |command|
69
+ command["creates"] && command["attributes"].any? do |attr|
70
+ attr["name"] == key["name"]
71
+ end
72
+ end&.fetch("name")
73
+ end
74
+
75
+ # @return [String, nil] the command that changes the record's body, pictures or fields
76
+ def editing(other, field)
77
+ return other.dig("drafts", "save") if other["drafts"] && field == other.dig("drafts", "live")
78
+
79
+ moves = (other.dig("lifecycle", "transitions") || []).map { |move| move["verb"] }
80
+ other["commands"].find { |command| edits?(command, field, moves) }&.fetch("name")
81
+ end
82
+
83
+ def edits?(command, field, moves)
84
+ names = command["attributes"].map { |attr| attr["name"] }
85
+ !command["creates"] && !command["destructive"] && !moves.include?(command["name"]) &&
86
+ !names.empty? && (field.nil? || names.include?(field))
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Projections
5
+ module Site
6
+ module CmsEditor
7
+ # What the `Editor` row's settings for many chapters and long lists must satisfy: chapters
8
+ # named once each, roles given for a chapter the editor edits, and a page size a list can
9
+ # hold. A chapter's name is part of an address, so it cannot be spelled as one of the
10
+ # editor's own paths.
11
+ module ScaleChecks
12
+ # The most instances a list may show to a page.
13
+ MAX_PAGE = 1000
14
+
15
+ # The first segments of the editor's own addresses, which no chapter may be named.
16
+ RESERVED = %w[assets media logout api scheduled].freeze
17
+
18
+ module_function
19
+
20
+ # @param row [Hash{Symbol => String}] the row, defaults filled
21
+ # @return [Array<String>] every problem with `chapter`, `chapter_roles` and `page_size`
22
+ def problems(row)
23
+ chapters = row[:chapter].split(",").map(&:strip)
24
+ [*chapter_problems(chapters), *role_problems(row[:chapter_roles], chapters), *page_size_problems(row[:page_size])]
25
+ end
26
+
27
+ # @return [Array<String>] the problems with an empty, repeated or reserved chapter name
28
+ def chapter_problems(chapters)
29
+ found = []
30
+ found << "chapter names no chapter" if chapters.reject(&:empty?).empty?
31
+ repeated = chapters.find { |name| chapters.count(name) > 1 }
32
+ found << "chapter names #{repeated.inspect} twice" if repeated
33
+ [*found, *reserved_problems(chapters)]
34
+ end
35
+
36
+ # @return [Array<String>] a problem for each chapter spelled as an address of the editor's
37
+ def reserved_problems(chapters)
38
+ return [] if chapters.size < 2
39
+
40
+ (chapters & RESERVED).map do |name|
41
+ "chapter #{name.inspect} is the first part of an address the editor serves; name it another way"
42
+ end
43
+ end
44
+
45
+ # @return [Array<String>] the problems with `Chapter=Role,Role;Chapter=Role` entries
46
+ def role_problems(text, chapters)
47
+ text.split(";").map(&:strip).reject(&:empty?).flat_map do |entry|
48
+ name, roles = entry.split("=", 2)
49
+ next ["chapter_roles #{entry.inspect} must read Chapter=Role,Role"] if roles.to_s.strip.empty?
50
+ next [] if chapters.include?(name.strip)
51
+
52
+ ["chapter_roles names #{name.strip.inspect}, which is not a chapter of this editor"]
53
+ end
54
+ end
55
+
56
+ # @return [Array<String>] the problem with a page size that is not a whole 1 to 1000
57
+ def page_size_problems(size)
58
+ return [] if size.match?(/\A[1-9]\d{0,3}\z/) && size.to_i <= MAX_PAGE
59
+
60
+ ["page_size #{size.inspect} must be a whole number from 1 to #{MAX_PAGE}"]
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end