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,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Projections
5
+ module Site
6
+ module CmsEditor
7
+ # An aggregate whose records are actions to run later, found by shape. The editor makes
8
+ # and changes the records and never runs an action: a runner elsewhere does.
9
+ #
10
+ # It has a **subject** (a plain `<kind>:<slug>` key, see `KeyKinds`), an **action** from
11
+ # a closed set (`one_of`), a **due time** that is a moment (see `Moment`), a
12
+ # **lifecycle** whose first state moves to at least two states that move nowhere, and a
13
+ # **creating command** that takes the three. The command that reschedules takes the due
14
+ # time; the one that cancels is a destructive move from the first state to a final one;
15
+ # the reason is the first optional plain text left over; the query takes the subject
16
+ # alone. The first such aggregate is the one.
17
+ module Scheduling
18
+ # What a scheduling aggregate's attributes are called in its record: `subject`,
19
+ # `action`, `due`.
20
+ ROLES = %w[subject action due].freeze
21
+
22
+ # The widgets of an integer that is a moment.
23
+ MOMENTS = %w[date datetime].freeze
24
+
25
+ module_function
26
+
27
+ # @param aggregates [Array<Hash{String => Object}>] the aggregates as `Pickers` leaves
28
+ # them
29
+ # @return [Hash{String => Object}, nil] the scheduling aggregate and its parts; nil when
30
+ # none
31
+ def read(aggregates)
32
+ aggregates.lazy.filter_map { |agg| shape(agg) }.first
33
+ end
34
+
35
+ # @return [Hash{String => Object}, nil] `agg` as the scheduling aggregate, or nil
36
+ def shape(agg)
37
+ parts = parts_of(agg)
38
+ schedule = parts && endings(agg["lifecycle"]).size > 1 && creating(agg, parts)
39
+ return nil unless schedule
40
+
41
+ { "aggregate" => agg["name"], "chapter" => agg["chapter"], **parts, "pending" => agg.dig("lifecycle", "default"),
42
+ "schedule" => schedule["name"], **optional(agg, parts, schedule) }.compact
43
+ end
44
+
45
+ # @return [Hash{String => Object}, nil] the subject, action, actions and due time, or nil
46
+ def parts_of(agg)
47
+ return nil unless agg["lifecycle"]
48
+
49
+ plain = agg["attributes"].reject { |attr| attr["list"] }
50
+ found = [subject_of(agg, plain), plain.find { |attr| attr["options"] }, plain.find { |attr| moment?(attr) }]
51
+ describe(*found) if found.all?
52
+ end
53
+
54
+ # @return [Boolean] whether the attribute is an integer that is a moment
55
+ def moment?(attr) = MOMENTS.include?(attr["widget"])
56
+
57
+ # @return [Hash{String => Object}] the three attributes' names, and the action's members
58
+ def describe(subject, action, due)
59
+ { "subject" => subject["name"], "action" => action["name"], "actions" => action["options"], "due" => due["name"] }
60
+ end
61
+
62
+ # @return [Hash{String => Object}, nil] the attribute that is a key of a kind, the
63
+ # identity last
64
+ def subject_of(agg, plain)
65
+ plain.select { |attr| attr.dig("picker", "kinds") }.min_by { |attr| attr["name"] == agg["identity"] ? 1 : 0 }
66
+ end
67
+
68
+ # @return [Array<String>] the final states the first state moves to: states nothing
69
+ # moves out of
70
+ def endings(lifecycle)
71
+ moves = lifecycle["transitions"]
72
+ first = moves.select { |move| move["from"].include?(lifecycle["default"]) }.map { |move| move["to"] }.uniq
73
+ first - moves.flat_map { |move| move["from"] }
74
+ end
75
+
76
+ # @return [Hash{String => Object}, nil] the creating command that takes the subject,
77
+ # action and due time
78
+ def creating(agg, parts)
79
+ agg["commands"].find { |cmd| cmd["creates"] && takes?(cmd, parts.values_at(*ROLES)) }
80
+ end
81
+
82
+ # @return [Boolean] whether the command takes every one of the named attributes
83
+ def takes?(cmd, names) = (names - cmd["attributes"].map { |attr| attr["name"] }).empty?
84
+
85
+ # @return [Hash{String => Object}] the commands, query and reason that may be missing
86
+ def optional(agg, parts, schedule)
87
+ { "reschedule" => rescheduling(agg, parts), "cancel" => cancelling(agg), "query" => querying(agg, parts),
88
+ "reason" => reason(agg, parts), "generated" => generated?(agg, parts, schedule) }
89
+ end
90
+
91
+ # @return [String, nil] the command on a record, not a lifecycle move, that takes the
92
+ # due time
93
+ def rescheduling(agg, parts)
94
+ agg["commands"].find { |cmd| !cmd["creates"] && !move?(agg, cmd) && takes?(cmd, [parts["due"]]) }&.fetch("name")
95
+ end
96
+
97
+ # @return [String, nil] the destructive lifecycle move from the first state to a final
98
+ # one
99
+ def cancelling(agg)
100
+ agg["commands"].find { |cmd| cmd["destructive"] && ends?(agg, cmd) }&.fetch("name")
101
+ end
102
+
103
+ # @return [String, nil] the query whose one argument is the subject
104
+ def querying(agg, parts)
105
+ agg["queries"].find { |query| query["attributes"].map { |attr| attr["name"] } == [parts["subject"]] }&.fetch("name")
106
+ end
107
+
108
+ # @return [Boolean] whether the command is a lifecycle move
109
+ def move?(agg, cmd) = agg["lifecycle"]["transitions"].any? { |move| move["verb"] == cmd["name"] }
110
+
111
+ # @return [Boolean] whether the command moves a first-state record to a final state
112
+ def ends?(agg, cmd)
113
+ lifecycle = agg["lifecycle"]
114
+ final = endings(lifecycle)
115
+ lifecycle["transitions"].any? do |move|
116
+ move["verb"] == cmd["name"] && move["from"].include?(lifecycle["default"]) && final.include?(move["to"])
117
+ end
118
+ end
119
+
120
+ # @return [String, nil] the first optional plain text attribute no other part uses
121
+ def reason(agg, parts)
122
+ used = [agg["identity"], *parts.values_at(*ROLES)]
123
+ spare = agg["attributes"].find { |attr| attr["optional"] && !used.include?(attr["name"]) && plain_text?(agg, attr) }
124
+ spare&.fetch("name")
125
+ end
126
+
127
+ # @return [Boolean] whether the attribute is one plain text, alone or as a value
128
+ # object's only part
129
+ def plain_text?(agg, attr)
130
+ return false if %w[list picker options widget].any? { |key| attr[key] }
131
+
132
+ parts = agg["valueObjects"][attr["type"]]
133
+ parts ? lone_text?(parts) : attr["kind"] == "text"
134
+ end
135
+
136
+ # @return [Boolean] whether the parts are one plain text
137
+ def lone_text?(parts) = parts.size == 1 && parts.first["kind"] == "text" && !parts.first["list"]
138
+
139
+ # @return [true, nil] whether the creating command takes an identity that is not the
140
+ # subject,
141
+ # which the editor then makes itself
142
+ def generated?(agg, parts, schedule)
143
+ taken = schedule["attributes"].any? { |attr| attr["name"] == agg["identity"] }
144
+ true if taken && agg["identity"] != parts["subject"]
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -1,7 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "attributes"
4
+ require_relative "blocks"
5
+ require_relative "clearing"
6
+ require_relative "composition"
7
+ require_relative "destructive"
8
+ require_relative "drafts"
9
+ require_relative "limits"
4
10
  require_relative "media"
11
+ require_relative "pickers"
12
+ require_relative "relations"
13
+ require_relative "scheduling"
5
14
 
6
15
  module Hecks
7
16
  module Projections
@@ -18,17 +27,55 @@ module Hecks
18
27
  # A query that `returns` a value object is answered outside the domain and is left out: the
19
28
  # host refuses it, so a list cannot be built from it.
20
29
  class Schema
21
- # @param chapter [Bluebook::Chapter] the domain's chapter
22
- # @param skip [Array<String>] aggregates to leave out
23
- # @return [Hash{String => Object}] the domain's name and its aggregates
24
- # @raise [ArgumentError] when no aggregate is left
25
- def self.read(chapter, skip: [])
26
- aggregates = chapter.aggregates.reject { |agg| skip.include?(agg.hecks_name) }
27
- raise ArgumentError, "the #{chapter.name} chapter has no aggregate to edit" if aggregates.empty?
28
-
29
- shaped = aggregates.map { |agg| new(agg).to_h }
30
- media = Media.read(shaped)
31
- { "domain" => chapter.name, "aggregates" => shaped, **(media ? { "media" => media } : {}) }
30
+ # @param chapters [Bluebook::Chapter, Array<Bluebook::Chapter>] the chapter, or the
31
+ # chapters in the order the row names them; the first names the domain addressed
32
+ # @param skip [Array<String>] aggregates to leave out: `Name`, or `Chapter::Name`
33
+ # @param pictures [Bluebook::Chapter, nil] another chapter whose picture aggregate the
34
+ # editor's pictures use, in place of any in `chapters`
35
+ # @param roles [Hash{String => Array<String>}] the roles each chapter is limited to
36
+ # @return [Hash{String => Object}] the domain's name and its aggregates; `chapters` too
37
+ # when there are several
38
+ # @raise [ArgumentError] when no aggregate is left, or `pictures` has no picture aggregate
39
+ def self.read(chapters, skip: [], pictures: nil, roles: {})
40
+ list = chapters.is_a?(Array) ? chapters : [chapters]
41
+ shaped = Composition.aggregates(list, skip: skip, roles: roles) { |agg| new(agg).to_h }
42
+ raise ArgumentError, "the #{list.map(&:name).join(", ")} chapter has no aggregate to edit" if shaped.empty?
43
+
44
+ media = pictures ? elsewhere(pictures) : within(shaped, list.first.name)
45
+ assemble(list, Relations.apply(Pickers.apply(shaped, media), media), media)
46
+ end
47
+
48
+ # @return [Hash{String => Object}] the schema: the domain, the chapters when several, the
49
+ # aggregates, and the picture and scheduling aggregates when there are such
50
+ def self.assemble(list, aggregates, media)
51
+ chapters = list.size > 1 ? { "chapters" => list.map(&:name) } : {}
52
+ scheduling = Scheduling.read(aggregates)
53
+ { "domain" => list.first.name, **chapters, "aggregates" => aggregates, **(media ? { "media" => media } : {}),
54
+ **(scheduling ? { "scheduling" => scheduling } : {}) }
55
+ end
56
+
57
+ # The picture aggregate among the editor's own, with the chapter that holds it when that
58
+ # is not the first, since the host is addressed by chapter.
59
+ #
60
+ # @return [Hash{String => Object}, nil] the picture aggregate, as `Media` describes it
61
+ def self.within(shaped, first)
62
+ found = Media.read(shaped)
63
+ chapter = found&.delete("chapter")
64
+ chapter && chapter != first ? found.merge("domain" => chapter) : found
65
+ end
66
+
67
+ # The picture aggregate of another chapter: the same record as one in the editor's own
68
+ # chapter, with the chapter's name and the aggregate itself, since the editor's own
69
+ # aggregates do not hold it.
70
+ #
71
+ # @return [Hash{String => Object}] the picture aggregate, as `Media` describes it
72
+ # @raise [ArgumentError] when the chapter has no aggregate that registers pictures
73
+ def self.elsewhere(chapter)
74
+ shaped = chapter.aggregates.map { |agg| new(agg).to_h }
75
+ found = Media.read(shaped)
76
+ raise ArgumentError, "the #{chapter.name} chapter has no aggregate that registers pictures" unless found
77
+
78
+ found.merge("domain" => chapter.name, "definition" => shaped.find { |agg| agg["name"] == found["aggregate"] })
32
79
  end
33
80
 
34
81
  # @param aggregate [Bluebook::Aggregate] one aggregate of the chapter
@@ -39,28 +86,51 @@ module Hecks
39
86
 
40
87
  # @return [Hash{String => Object}] the aggregate as the editor reads it
41
88
  def to_h
89
+ held = aggregate_attributes
90
+ drafts = Drafts.of(@agg, held)
42
91
  { "name" => @agg.hecks_name, "description" => @agg.description, "identity" => @agg.identified_by.to_s,
43
- "lifecycle" => lifecycle, "attributes" => attributes(@agg.attributes.reject { |a| a.name == lifecycle_field }),
44
- "valueObjects" => value_objects, "commands" => @agg.commands.map { |command| command(command) },
45
- "queries" => queries }
92
+ "lifecycle" => lifecycle, "attributes" => held, "valueObjects" => value_objects,
93
+ "commands" => @agg.commands.map { |command| command(command) }, "queries" => queries,
94
+ **(drafts ? { "drafts" => drafts } : {}) }
46
95
  end
47
96
 
48
97
  private
49
98
 
50
99
  def lifecycle_field = @agg.lifecycle&.field
51
100
 
52
- def attributes(list) = list.map { |attribute| @attributes.of(attribute) }
101
+ # The aggregate's own attributes, bounded by the aggregate's invariants (see `Limits`).
102
+ def aggregate_attributes
103
+ bounded(attributes(@agg.attributes.reject { |a| a.name == lifecycle_field }), @agg.invariants)
104
+ end
105
+
106
+ def attributes(list) = list.map { |attribute| limited(@attributes.of(attribute)) }
107
+
108
+ # `list` with what the holder's rules say about the most each may hold (see `Limits`).
109
+ def bounded(list, rules) = Limits.apply(list, rules)
110
+
111
+ # `attr` with the most its block list may hold, when a command's `given` says.
112
+ def limited(attr)
113
+ most = attr.key?("blockList") ? Blocks.maximum(@agg.commands, attr["name"]) : nil
114
+ most ? attr.merge("max" => most) : attr
115
+ end
53
116
 
54
117
  def value_objects
55
- @agg.value_objects.to_h { |object| [object.hecks_name, attributes(object.attributes)] }
118
+ @agg.value_objects.to_h do |object|
119
+ [object.hecks_name, bounded(attributes(object.attributes), object.invariants)]
120
+ end
56
121
  end
57
122
 
58
123
  def lifecycle
124
+ @lifecycle ||= shaped_lifecycle
125
+ end
126
+
127
+ def shaped_lifecycle
59
128
  lifecycle = @agg.lifecycle
60
129
  return nil unless lifecycle
61
130
 
62
- { "field" => lifecycle.field.to_s, "default" => lifecycle.default,
63
- "transitions" => lifecycle.transitions.map { |verb, move| transition(verb, move) } }
131
+ shaped = { "field" => lifecycle.field.to_s, "default" => lifecycle.default,
132
+ "transitions" => lifecycle.transitions.map { |verb, move| transition(verb, move) } }
133
+ shaped.merge("tones" => Destructive.tones(shaped))
64
134
  end
65
135
 
66
136
  def transition(verb, move)
@@ -69,9 +139,16 @@ module Hecks
69
139
 
70
140
  def command(command)
71
141
  { "name" => command.hecks_name, "goal" => command.goal, "role" => command.role, "creates" => command.creates?,
72
- "on" => command.creates? ? nil : command.references.to_s, "attributes" => attributes(command.attributes) }
142
+ "on" => command.creates? ? nil : command.references.to_s,
143
+ "destructive" => Destructive.command?(command.hecks_name, lifecycle),
144
+ "attributes" => bounded(attributes(command.attributes), command.givens) }
145
+ .then { |shaped| clearing(shaped, command) }
73
146
  end
74
147
 
148
+ # Leaves the arguments that only clear (see `Clearing`) out of the command's form, and
149
+ # names those that are lists as `empty`, for the editor to send as empty lists.
150
+ def clearing(shaped, command) = Clearing.apply(shaped, Clearing.of(command, @agg))
151
+
75
152
  def queries
76
153
  @agg.queries.reject(&:returns).map do |query|
77
154
  { "name" => query.name, "description" => query.description, "attributes" => attributes(query.attributes) }
@@ -4,6 +4,8 @@ require "json"
4
4
  require_relative "../root_rows"
5
5
  require_relative "../routes_ts"
6
6
  require_relative "checks"
7
+ require_relative "preview"
8
+ require_relative "../../../version"
7
9
 
8
10
  module Hecks
9
11
  module Projections
@@ -20,11 +22,14 @@ module Hecks
20
22
  "Editor",
21
23
  fields: { domain: String, chapter: String, base_path: String, sso_path: String, session_cookie: String,
22
24
  host_cookie: String, host_env: String, host_default: String, roles: String, login: String,
23
- title: String, skip: String, media_dir: String, media_max_bytes: String },
25
+ title: String, brand: String, accent: String, logo: String, skip: String, media: String,
26
+ media_dir: String, media_max_bytes: String, chapter_roles: String, page_size: [String, Integer],
27
+ preview: String },
24
28
  required: %i[domain chapter host_env login],
25
29
  defaults: { base_path: "/editor", session_cookie: "hecks_editor", host_cookie: "hecks_session",
26
- host_default: "http://127.0.0.1:4322", roles: "Admin,Owner", title: "Editor", skip: "",
27
- media_dir: "media", media_max_bytes: "5242880" }
30
+ host_default: "http://127.0.0.1:4322", roles: "Admin,Owner", title: "Editor", brand: "",
31
+ accent: "", logo: "", skip: "", media: "", media_dir: "media", media_max_bytes: "5242880",
32
+ chapter_roles: "", page_size: "25", preview: "" }
28
33
  )
29
34
 
30
35
  # @return [Hash{Symbol => String}] the checked row, defaults filled
@@ -45,7 +50,7 @@ module Hecks
45
50
  # @param table [Table, nil] the checked route table
46
51
  # @raise [Table::Invalid] naming every problem when there is one
47
52
  def initialize(row, table: nil)
48
- @row = { sso_path: "#{row[:base_path]}/api/sso" }.merge(row)
53
+ @row = { sso_path: "#{row[:base_path]}/api/sso" }.merge(row, page_size: row[:page_size].to_s)
49
54
  problems = Checks.problems(@row, roles, table)
50
55
  return if problems.empty?
51
56
 
@@ -55,10 +60,40 @@ module Hecks
55
60
  # @return [String] the domain's directory, relative to the project
56
61
  def domain = row.fetch(:domain)
57
62
 
58
- # @return [String] the chapter the editor edits
59
- def chapter = row.fetch(:chapter)
63
+ # @return [String] the first chapter the editor edits, which names the generated package
64
+ def chapter = chapters.first.to_s
60
65
 
61
- # @return [Array<String>] the aggregates left out
66
+ # @return [Array<String>] every chapter the editor edits, in the order the row names them:
67
+ # `chapter` is one name or several, comma separated
68
+ def chapters = row.fetch(:chapter).split(",").map(&:strip).reject(&:empty?)
69
+
70
+ # @return [Hash{String => Array<String>}] the roles a chapter's aggregates are limited to,
71
+ # from `Chapter=Role,Role;Chapter=Role`; a chapter not named is open to every role the
72
+ # editor admits
73
+ def chapter_roles
74
+ row.fetch(:chapter_roles).split(";").map(&:strip).reject(&:empty?).to_h do |entry|
75
+ name, roles = entry.split("=", 2)
76
+ [name.to_s.strip, roles.to_s.split(",").map(&:strip).reject(&:empty?)]
77
+ end
78
+ end
79
+
80
+ # @return [String, nil] the URL template a record is previewed at, or nil for no preview
81
+ def preview = row.fetch(:preview).strip.then { |template| template.empty? ? nil : template }
82
+
83
+ # @return [Integer] how many instances a list shows to a page
84
+ def page_size = row.fetch(:page_size).to_i
85
+
86
+ # @return [String, nil] the other chapter whose picture aggregate the editor's pictures
87
+ # use, or nil when the pictures (if any) are in the editor's own chapter
88
+ def media_chapter = row.fetch(:media).strip.then { |name| name.empty? ? nil : name }
89
+
90
+ # @return [String, nil] the product name the header shows, or nil for the domain's name
91
+ def brand = row.fetch(:brand).strip.then { |name| name.empty? ? nil : name }
92
+
93
+ # @return [String, nil] the accent colour as hex, or nil for the default theme
94
+ def accent = row.fetch(:accent).strip.then { |hex| hex.empty? ? nil : hex }
95
+
96
+ # @return [Array<String>] the aggregates left out: `Name`, or `Chapter::Name`
62
97
  def skip = row.fetch(:skip).split(",").map(&:strip).reject(&:empty?)
63
98
 
64
99
  # @return [Array<String>] the roles that count as an editor
@@ -70,14 +105,22 @@ module Hecks
70
105
  # @return [Hash{String => String}] each placeholder to the text that replaces it
71
106
  def tokens
72
107
  { "__BANNER__" => RoutesTs::BANNER, "__PACKAGE__" => package, "__ROLES__" => RoutesTs.literal(roles),
73
- "__MEDIA_MAX_BYTES__" => row.fetch(:media_max_bytes), **quoted }
108
+ "__MEDIA_MAX_BYTES__" => row.fetch(:media_max_bytes), "__PAGE_SIZE__" => page_size.to_s,
109
+ "__CLIENT_VERSION__" => Hecks::VERSION, **preview_tokens, **quoted }
110
+ end
111
+
112
+ # @return [Hash{String => String}] the preview template as a literal, and the policy line
113
+ # that lets the editor frame its origin, which is empty when there is no preview
114
+ def preview_tokens
115
+ source = Preview.frame_source(row.fetch(:preview).strip)
116
+ { "__PREVIEW__" => JSON.generate(preview), "__FRAME_POLICY__" => source ? " \"frame-src #{source}\"," : "" }
74
117
  end
75
118
 
76
119
  # @return [Hash{String => String}] each text field's placeholder to the field as JSON
77
120
  def quoted
78
121
  { "TITLE" => :title, "BASE_PATH" => :base_path, "SSO_PATH" => :sso_path, "LOGIN" => :login,
79
122
  "SESSION_COOKIE" => :session_cookie, "HOST_COOKIE" => :host_cookie, "HOST_ENV" => :host_env,
80
- "HOST_DEFAULT" => :host_default, "MEDIA_DIR" => :media_dir }
123
+ "HOST_DEFAULT" => :host_default, "MEDIA_DIR" => :media_dir, "LOGO" => :logo }
81
124
  .to_h { |token, field| ["__#{token}__", JSON.generate(row.fetch(field))] }
82
125
  end
83
126
  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 aggregates an attribute can name, found among the aggregates of one editor, and what
8
+ # a picker needs to know about each: its key, the attribute that labels it and the query
9
+ # that lists the ones to offer.
10
+ class Targets
11
+ # The attribute names that label an instance, in the order they are tried; an aggregate
12
+ # with none is labelled by its identity.
13
+ LABELS = %w[title name label].freeze
14
+
15
+ # The zero-argument queries that list what is on offer, tried in this order: ones that
16
+ # list everything, then the one that lists what is in use.
17
+ LISTINGS = %w[all list listing active].freeze
18
+
19
+ # @param aggregates [Array<Hash{String => Object}>] the aggregates as `Schema` shapes them
20
+ # @param media [Hash{String => Object}, nil] the picture aggregate, as `Media` says
21
+ def initialize(aggregates, media = nil)
22
+ @aggregates = aggregates
23
+ @media = media
24
+ end
25
+
26
+ # @return [Hash{String => Object}, nil] the aggregate named `name`, as a picker names it
27
+ def named(name, from)
28
+ pick(@aggregates.select { |agg| agg["name"] == name }, from)
29
+ end
30
+
31
+ # @return [Hash{String => Object}, nil] the aggregate whose identity is of type `stem`, or
32
+ # that is named `stem`, as a picker names it
33
+ def keyed_by(stem, from)
34
+ pick(@aggregates.select { |agg| identity_type(agg) == stem }, from) || named(stem, from)
35
+ end
36
+
37
+ # @return [Hash{String => Object}, nil] the aggregate that is `word` in snake case
38
+ def kind(word, from)
39
+ pick(@aggregates.select { |agg| snake(agg["name"]) == word }, from)
40
+ end
41
+
42
+ # @return [Hash{String => Object}, nil] the picture aggregate, which offers keys and
43
+ # accepts any other text as well, since a picture may be named by an address
44
+ def pictures
45
+ return nil unless @media
46
+
47
+ agg = @media["definition"] || @aggregates.find { |candidate| candidate["name"] == @media["aggregate"] }
48
+ return nil unless agg
49
+
50
+ described = describe(agg, agg["chapter"] || @media["domain"], @media["listing"])
51
+ described.merge("label" => @media.dig("fields", "alt", "name"), "strict" => false)
52
+ end
53
+
54
+ private
55
+
56
+ def pick(found, from)
57
+ own = found.select { |agg| agg["chapter"] == from["chapter"] }
58
+ chosen = own.first || (found.first if found.size == 1)
59
+ chosen && describe(chosen, chosen["chapter"], listing(chosen)).merge("strict" => true)
60
+ end
61
+
62
+ def describe(agg, chapter, listing)
63
+ { "target" => agg["name"], "chapter" => chapter, "key" => agg["identity"], "label" => label(agg),
64
+ "listing" => listing }.compact
65
+ end
66
+
67
+ def identity_type(agg)
68
+ agg["attributes"].find { |attr| attr["name"] == agg["identity"] }&.fetch("type", nil)
69
+ end
70
+
71
+ def label(agg)
72
+ plain = agg["attributes"].reject { |attr| attr["list"] || attr["widget"] == "body" }.map { |attr| attr["name"] }
73
+ LABELS.find { |name| plain.include?(name) } || agg["identity"]
74
+ end
75
+
76
+ def listing(agg)
77
+ named = agg["queries"].select { |query| query["attributes"].empty? }.map { |query| query["name"] }
78
+ LISTINGS.filter_map { |word| named.find { |name| name.downcase == word } }.first
79
+ end
80
+
81
+ def snake(name) = name.gsub(/([a-z0-9])([A-Z])/, "\\1_\\2").downcase
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -6,14 +6,31 @@
6
6
  "node": ">=22.18"
7
7
  },
8
8
  "scripts": {
9
+ "clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"",
10
+ "build": "npm run clean && npm run build:css && npm run build:js",
11
+ "build:css": "tailwindcss --input src/browser/app.css --output dist/editor.css --minify",
12
+ "build:js": "esbuild src/browser/main.js --bundle --splitting --format=esm --target=es2022 --minify --outdir=dist --entry-names=editor --chunk-names=[name]-[hash]",
13
+ "prestart": "npm run build",
9
14
  "start": "node src/server.ts",
10
15
  "check": "tsc -p tsconfig.json"
11
16
  },
12
17
  "dependencies": {
13
- "@hecks/client": "^3.8.0"
18
+ "@hecks/client": "^__CLIENT_VERSION__",
19
+ "@tiptap/core": "3.31.4",
20
+ "@tiptap/extension-blockquote": "3.31.4",
21
+ "@tiptap/extension-image": "3.31.4",
22
+ "@tiptap/extensions": "3.31.4",
23
+ "@tiptap/pm": "3.31.4",
24
+ "@tiptap/starter-kit": "3.31.4",
25
+ "tiptap-extension-flat-list": "0.4.0"
14
26
  },
15
27
  "devDependencies": {
28
+ "@tailwindcss/cli": "4.3.3",
29
+ "@tailwindcss/typography": "0.5.20",
16
30
  "@types/node": "^22.10.0",
31
+ "daisyui": "5.7.47",
32
+ "esbuild": "0.28.2",
33
+ "tailwindcss": "4.3.3",
17
34
  "typescript": "^5.8.0"
18
35
  }
19
36
  }