hecks 3.9.1 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (504) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/bluebook/pizzas.bluebook +29 -0
  3. data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
  4. data/exe/hecks +4 -4
  5. data/lib/hecks/adapters/driven/d1.rb +2 -10
  6. data/lib/hecks/adapters/driven/heki.adapter +1 -1
  7. data/lib/hecks/adapters/driven/heki.rb +2 -12
  8. data/lib/hecks/adapters/driven/lambda.adapter +1 -1
  9. data/lib/hecks/adapters/driven/lambda.rb +3 -18
  10. data/lib/hecks/adapters/driven/postgres.rb +4 -11
  11. data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
  12. data/lib/hecks/adapters/driven/sqlite.rb +2 -10
  13. data/lib/hecks/adapters/driving/admission.rb +46 -0
  14. data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
  15. data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
  16. data/lib/hecks/adapters/driving/cli.rb +127 -0
  17. data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
  18. data/lib/hecks/adapters/driving/cli_runner/resolution.rb +155 -0
  19. data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
  20. data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
  21. data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
  22. data/lib/hecks/adapters/driving/cli_runner.rb +69 -0
  23. data/lib/hecks/adapters/driving/command_request.rb +121 -0
  24. data/lib/hecks/adapters/driving/handle.rb +214 -0
  25. data/lib/hecks/adapters/driving/json.rb +136 -0
  26. data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
  27. data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
  28. data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
  29. data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
  30. data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
  31. data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
  32. data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
  33. data/lib/hecks/adapters/driving/mcp.rb +159 -0
  34. data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
  35. data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
  36. data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
  37. data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
  38. data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
  39. data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
  40. data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
  41. data/lib/hecks/adapters/driving/ruby.rb +83 -0
  42. data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
  43. data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
  44. data/lib/hecks/behaviors/expectations/boot.rb +1 -1
  45. data/lib/hecks/bench/ruby_runner.rb +1 -1
  46. data/lib/hecks/bluebook/ask_resolution.rb +130 -0
  47. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
  48. data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
  49. data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
  50. data/lib/hecks/bluebook/assembly/marks.rb +1 -0
  51. data/lib/hecks/bluebook/attribute.rb +1 -1
  52. data/lib/hecks/bluebook/behaviour/command.rb +13 -8
  53. data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
  54. data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
  55. data/lib/hecks/bluebook/capabilities.rb +91 -9
  56. data/lib/hecks/bluebook/domain_port.rb +16 -4
  57. data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
  58. data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
  59. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
  60. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
  61. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
  62. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
  63. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
  64. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
  65. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
  66. data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
  67. data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
  68. data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
  69. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
  70. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
  71. data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
  72. data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
  73. data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
  74. data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
  75. data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
  76. data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
  77. data/lib/hecks/bluebook/expression/projection.json +36 -0
  78. data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
  79. data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
  80. data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
  81. data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
  82. data/lib/hecks/bluebook/expression/resolver.rb +20 -1
  83. data/lib/hecks/bluebook/hecksagon.rb +23 -3
  84. data/lib/hecks/bluebook/lifecycle.rb +22 -3
  85. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
  86. data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
  87. data/lib/hecks/bluebook/meta_validator/readings.rb +18 -1
  88. data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
  89. data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
  90. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
  91. data/lib/hecks/bluebook/meta_validator.rb +12 -12
  92. data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
  93. data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
  94. data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
  95. data/lib/hecks/bluebook/model_check.rb +8 -2
  96. data/lib/hecks/bluebook/policy.rb +4 -2
  97. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  98. data/lib/hecks/bluebook/synthesizer.rb +2 -0
  99. data/lib/hecks/bluebook.rb +1 -0
  100. data/lib/hecks/cli/command_table.rb +1 -1
  101. data/lib/hecks/cli/console.rb +2 -1
  102. data/lib/hecks/cli/document.rb +1 -1
  103. data/lib/hecks/cli/launcher_words.rb +4 -4
  104. data/lib/hecks/cli/mcp.rb +4 -4
  105. data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
  106. data/lib/hecks/cli/project_cli/templates.rb +5 -5
  107. data/lib/hecks/cli/project_cli.rb +9 -6
  108. data/lib/hecks/cli/run.rb +2 -2
  109. data/lib/hecks/cli/smoke_http/checks.rb +4 -1
  110. data/lib/hecks/cli/smoke_http/transport.rb +3 -3
  111. data/lib/hecks/cli/smoke_http.rb +3 -0
  112. data/lib/hecks/codemod.rb +5 -2
  113. data/lib/hecks/corpus/routes.rb +7 -0
  114. data/lib/hecks/corpus/rust.rb +3 -2
  115. data/lib/hecks/deploy/bluebook/deploy.bluebook +124 -2
  116. data/lib/hecks/deploy/bluebook/deploy.hecksagon +8 -0
  117. data/lib/hecks/deploy/oidc.json +16 -3
  118. data/lib/hecks/doc/reference/rendering.rb +6 -3
  119. data/lib/hecks/forms/app/commands.rb +8 -7
  120. data/lib/hecks/forms/app/queries.rb +2 -1
  121. data/lib/hecks/forms/app.rb +1 -1
  122. data/lib/hecks/forms/record_renderer.rb +1 -1
  123. data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
  124. data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
  125. data/lib/hecks/fuzzing/failure_triage.rb +2 -1
  126. data/lib/hecks/fuzzing/mutation/run.rb +11 -4
  127. data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
  128. data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
  129. data/lib/hecks/fuzzing/properties/guards.rb +1 -1
  130. data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
  131. data/lib/hecks/fuzzing/self_consistency.rb +6 -2
  132. data/lib/hecks/fuzzing/shrinker.rb +3 -1
  133. data/lib/hecks/fuzzing/value_generator.rb +19 -5
  134. data/lib/hecks/gate/stages.yml +4 -10
  135. data/lib/hecks/grammar/evolve.rb +1 -1
  136. data/lib/hecks/grammar/expression_operators.json +372 -0
  137. data/lib/hecks/grammar/operators.rb +3 -1
  138. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
  139. data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
  140. data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
  141. data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
  142. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
  143. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
  144. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
  145. data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
  146. data/lib/hecks/hecks/adapters/deploy_toolchain/clearing.rb +51 -0
  147. data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
  148. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +2 -0
  149. data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
  150. data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
  151. data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
  152. data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
  153. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
  154. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
  155. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
  156. data/lib/hecks/hecks/adapters/local_files.rb +2 -2
  157. data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
  158. data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
  159. data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
  160. data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
  161. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
  162. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
  163. data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
  164. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
  165. data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +129 -0
  166. data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
  167. data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
  168. data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
  169. data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
  170. data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
  171. data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
  172. data/lib/hecks/hecks/adapters/site_toolchain/role_prober.rb +78 -0
  173. data/lib/hecks/hecks/adapters/site_toolchain.rb +29 -4
  174. data/lib/hecks/hecks/adapters/terminal.rb +13 -11
  175. data/lib/hecks/hecks/adapters/world_clearing.rb +65 -0
  176. data/lib/hecks/hecks/codebase.behaviors +13 -1
  177. data/lib/hecks/hecks/codebase.bluebook +239 -5
  178. data/lib/hecks/hecks/custodian.behaviors +24 -6
  179. data/lib/hecks/hecks/custodian.bluebook +6 -1227
  180. data/lib/hecks/hecks/deploy.behaviors +1 -1
  181. data/lib/hecks/hecks/deploy.world +1 -3
  182. data/lib/hecks/hecks/hecks.behaviors +1 -1
  183. data/lib/hecks/hecks/hecks.hecksagon +61 -18
  184. data/lib/hecks/hecks/hecks.world +11 -10
  185. data/lib/hecks/hecks/rehearsal.bluebook +477 -0
  186. data/lib/hecks/hecks/site.behaviors +1 -1
  187. data/lib/hecks/hecks/site.world +0 -1
  188. data/lib/hecks/hecks/tooling.bluebook +1238 -0
  189. data/lib/hecks/indifferent_key.rb +20 -0
  190. data/lib/hecks/language/adapter.bluebook +13 -3
  191. data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
  192. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
  193. data/lib/hecks/language/bluebook/entity.bluebook +18 -0
  194. data/lib/hecks/language/bluebook/policy.bluebook +9 -1
  195. data/lib/hecks/language/bluebook/query.bluebook +1 -1
  196. data/lib/hecks/language/bluebook/vocabulary.bluebook +11 -4
  197. data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
  198. data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
  199. data/lib/hecks/language/oidc.json +10 -0
  200. data/lib/hecks/language/port.bluebook +4 -4
  201. data/lib/hecks/literal.rb +12 -3
  202. data/lib/hecks/ports/key_vault.rb +7 -2
  203. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
  204. data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
  205. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
  206. data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
  207. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +29 -0
  208. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
  209. data/lib/hecks/projections/deploy/fargate/settings/tuning.rb +15 -0
  210. data/lib/hecks/projections/deploy/fargate/settings.rb +3 -1
  211. data/lib/hecks/projections/deploy/fargate/stack/readers.rb +1 -0
  212. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  213. data/lib/hecks/projections/deploy/shared.rb +5 -2
  214. data/lib/hecks/projections/deploy/templates/fargate/Makefile.tmpl +7 -4
  215. data/lib/hecks/projections/model/deviations.rb +2 -2
  216. data/lib/hecks/projections/model.rb +2 -2
  217. data/lib/hecks/projections/oidc.rb +13 -1
  218. data/lib/hecks/projections/shape.rb +5 -2
  219. data/lib/hecks/projections/site/cms_editor/attributes.rb +95 -0
  220. data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
  221. data/lib/hecks/projections/site/cms_editor/checks.rb +77 -0
  222. data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
  223. data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
  224. data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
  225. data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
  226. data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
  227. data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
  228. data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
  229. data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
  230. data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
  231. data/lib/hecks/projections/site/cms_editor/media.rb +85 -0
  232. data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
  233. data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
  234. data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
  235. data/lib/hecks/projections/site/cms_editor/pretty.rb +40 -0
  236. data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
  237. data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
  238. data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
  239. data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
  240. data/lib/hecks/projections/site/cms_editor/schema.rb +161 -0
  241. data/lib/hecks/projections/site/cms_editor/setting.rb +130 -0
  242. data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
  243. data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +36 -0
  244. data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +370 -0
  245. data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
  246. data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +87 -0
  247. data/lib/hecks/projections/site/cms_editor/templates/src/auth/session.ts.tmpl +36 -0
  248. data/lib/hecks/projections/site/cms_editor/templates/src/auth/sso.ts.tmpl +20 -0
  249. data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
  250. data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
  251. data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
  252. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
  253. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
  254. data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
  255. data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
  256. data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
  257. data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
  258. data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
  259. data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
  260. data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
  261. data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
  262. data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
  263. data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
  264. data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
  265. data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +133 -0
  266. data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +29 -0
  267. data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
  268. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +43 -0
  269. data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +177 -0
  270. data/lib/hecks/projections/site/cms_editor/templates/src/media/multipart.ts.tmpl +53 -0
  271. data/lib/hecks/projections/site/cms_editor/templates/src/media/sniff.ts.tmpl +94 -0
  272. data/lib/hecks/projections/site/cms_editor/templates/src/media/storage.ts.tmpl +50 -0
  273. data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
  274. data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +214 -0
  275. data/lib/hecks/projections/site/cms_editor/templates/src/server.ts.tmpl +65 -0
  276. data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
  277. data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
  278. data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
  279. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +123 -0
  280. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_parse.js.tmpl +320 -0
  281. data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
  282. data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
  283. data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +275 -0
  284. data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +61 -0
  285. data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
  286. data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +92 -0
  287. data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
  288. data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
  289. data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +63 -0
  290. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +213 -0
  291. data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
  292. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
  293. data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
  294. data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
  295. data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
  296. data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
  297. data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
  298. data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
  299. data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
  300. data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
  301. data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
  302. data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +17 -0
  303. data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
  304. data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
  305. data/lib/hecks/projections/site/cms_editor.rb +133 -0
  306. data/lib/hecks/projections/site/role_probe.rb +74 -0
  307. data/lib/hecks/projector/cli_audience.rb +2 -1
  308. data/lib/hecks/projector/cli_projector/listing.rb +1 -1
  309. data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
  310. data/lib/hecks/projector/docs_projector/closing.rb +1 -1
  311. data/lib/hecks/projector/docs_projector.rb +1 -1
  312. data/lib/hecks/projector/exporter/bindings.rb +40 -10
  313. data/lib/hecks/projector/exporter/capability_values.rb +55 -0
  314. data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
  315. data/lib/hecks/projector/target.rb +11 -0
  316. data/lib/hecks/projector.rb +32 -5
  317. data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
  318. data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
  319. data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
  320. data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
  321. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
  322. data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
  323. data/lib/hecks/quality_control/quality_control.bluebook +6 -2
  324. data/lib/hecks/release/approval.rb +64 -0
  325. data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
  326. data/lib/hecks/router/namespace_installer.rb +16 -16
  327. data/lib/hecks/router.rb +1 -1
  328. data/lib/hecks/runtime/best_effort.rb +22 -0
  329. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
  330. data/lib/hecks/runtime/command_interpreter.rb +3 -2
  331. data/lib/hecks/runtime/command_rules/emission.rb +1 -1
  332. data/lib/hecks/runtime/dispatcher.rb +4 -4
  333. data/lib/hecks/runtime/entity_element/location.rb +1 -1
  334. data/lib/hecks/runtime/event.rb +5 -0
  335. data/lib/hecks/runtime/identity.rb +7 -6
  336. data/lib/hecks/runtime/interpreting.rb +2 -1
  337. data/lib/hecks/runtime/invocation_builder.rb +1 -1
  338. data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
  339. data/lib/hecks/runtime/loader.rb +10 -10
  340. data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
  341. data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
  342. data/lib/hecks/runtime/policy_interpreter.rb +6 -6
  343. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
  344. data/lib/hecks/runtime/reaction_invocation.rb +1 -1
  345. data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
  346. data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
  347. data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
  348. data/lib/hecks/runtime/registry/merging.rb +1 -1
  349. data/lib/hecks/runtime/registry/providers.rb +16 -0
  350. data/lib/hecks/runtime/registry/read_repository.rb +6 -4
  351. data/lib/hecks/runtime/registry/verification.rb +4 -1
  352. data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
  353. data/lib/hecks/runtime/registry.rb +1 -1
  354. data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
  355. data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
  356. data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
  357. data/lib/hecks/runtime/saga_interpreter.rb +3 -3
  358. data/lib/hecks/runtime/tenant_check.rb +5 -4
  359. data/lib/hecks/runtime/value/coercion.rb +2 -2
  360. data/lib/hecks/runtime/value/validation.rb +16 -4
  361. data/lib/hecks/runtime.rb +9 -9
  362. data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
  363. data/lib/hecks/rust_build/wasm.rb +1 -1
  364. data/lib/hecks/rust_build/wasm_browser.rb +1 -1
  365. data/lib/hecks/rust_build.rb +11 -0
  366. data/lib/hecks/served_options.rb +53 -0
  367. data/lib/hecks/site/bluebook/site.bluebook +29 -1
  368. data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
  369. data/lib/hecks/storehouse/audit.rb +1 -1
  370. data/lib/hecks/storehouse/dispatching.rb +8 -6
  371. data/lib/hecks/storehouse/introspection.rb +2 -2
  372. data/lib/hecks/storehouse/querying.rb +6 -4
  373. data/lib/hecks/storehouse.rb +4 -4
  374. data/lib/hecks/tools/comment_style/source_file.rb +4 -1
  375. data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
  376. data/lib/hecks/tools/lanes/rows.rb +6 -0
  377. data/lib/hecks/tools/oidc_manifests.rb +4 -5
  378. data/lib/hecks/tools/regeneration_run.rb +11 -4
  379. data/lib/hecks/tools/site_routes/cli.rb +1 -1
  380. data/lib/hecks/tools/site_routes/editor_files.rb +45 -0
  381. data/lib/hecks/tools/site_routes.rb +14 -7
  382. data/lib/hecks/tools/tenant_provisioning.rb +2 -2
  383. data/lib/hecks/tools/tools_doc.rb +4 -4
  384. data/lib/hecks/version.rb +1 -1
  385. data/lib/hecks/vocabulary.rb +4 -4
  386. data/lib/hecks.rb +15 -15
  387. data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
  388. data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
  389. data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
  390. data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
  391. data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
  392. data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
  393. data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
  394. data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
  395. data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
  396. data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
  397. data/prebuilt/verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json +1 -0
  398. data/rust/.gitignore +6 -0
  399. data/rust/Cargo.toml +9 -1
  400. data/rust/build/src/binding_pass.rs +103 -0
  401. data/rust/build/src/cargo_sync.rs +6 -0
  402. data/rust/build/src/lineage_pass.rs +3 -3
  403. data/rust/build/src/main.rs +17 -5
  404. data/rust/build/src/pipeline.rs +48 -5
  405. data/rust/build/src/seams_pass.rs +123 -0
  406. data/rust/build/src/translation_pass.rs +167 -0
  407. data/rust/codegen/src/asks.rs +103 -0
  408. data/rust/codegen/src/bridging.rs +1 -1
  409. data/rust/codegen/src/commands.rs +13 -8
  410. data/rust/codegen/src/constraints.rs +25 -0
  411. data/rust/codegen/src/domain_generator.rs +11 -9
  412. data/rust/codegen/src/expr_emitter.rs +13 -0
  413. data/rust/codegen/src/json_codec.rs +54 -15
  414. data/rust/codegen/src/main.rs +2 -1
  415. data/rust/codegen/src/mutations.rs +174 -4
  416. data/rust/codegen/src/naming.rs +30 -0
  417. data/rust/codegen/src/queries.rs +11 -2
  418. data/rust/codegen/src/reactions.rs +1 -1
  419. data/rust/codegen/src/types.rs +166 -8
  420. data/rust/host/HECKS_RELEASE +1 -1
  421. data/rust/host/src/api.rs +4 -4
  422. data/rust/host/src/bin/validate_harness.rs +43 -0
  423. data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
  424. data/rust/host/src/boundary_fuzz/ir.rs +1 -0
  425. data/rust/host/src/boundary_fuzz/server.rs +1 -1
  426. data/rust/host/src/checkout.rs +35 -19
  427. data/rust/host/src/commerce_ir.rs +408 -2
  428. data/rust/host/src/expr_json.rs +118 -0
  429. data/rust/host/src/mint.rs +1 -1
  430. data/rust/host/src/payments/tests.rs +27 -1
  431. data/rust/host/src/payments.rs +12 -4
  432. data/rust/host/src/query_step.rs +22 -4
  433. data/rust/host/src/reference_validate.rs +653 -108
  434. data/rust/host/src/resend.rs +2 -0
  435. data/rust/host/src/server.rs +19 -6
  436. data/rust/host/src/ui_schema.rs +132 -5
  437. data/rust/host/src/web/newsletter.rs +7 -8
  438. data/rust/host/src/web/newsletter_send.rs +38 -13
  439. data/rust/host/src/web/registrations.rs +229 -26
  440. data/rust/host/src/web.rs +3 -3
  441. data/rust/parser/src/emit.rs +38 -6
  442. data/rust/parser/src/expr/ast_json.rs +7 -1
  443. data/rust/parser/src/expr/resolver.rs +171 -6
  444. data/rust/parser/src/ir.rs +7 -0
  445. data/rust/parser/src/keywords.rs +24 -0
  446. data/rust/parser/src/main.rs +3 -0
  447. data/rust/parser/src/parse/aggregate.rs +2 -1
  448. data/rust/parser/src/parse/chapter.rs +2 -2
  449. data/rust/parser/src/parse/command.rs +2 -0
  450. data/rust/parser/src/parse/hecksagon.rs +5 -2
  451. data/rust/parser/src/parse/lifecycle.rs +81 -0
  452. data/rust/parser/src/parse/mod.rs +15 -1
  453. data/rust/parser/src/parse/policy.rs +13 -2
  454. data/rust/parser/src/parse/value_object.rs +13 -8
  455. data/rust/parser/src/ruby_value.rs +1 -1
  456. data/rust/src/exemplar/commands.rs +4 -4
  457. data/rust/src/exemplar/constraints.rs +1 -1
  458. data/rust/src/exemplar/json.rs +43 -1
  459. data/rust/src/kernel/cli.rs +7 -5
  460. data/rust/src/kernel/dispatch.rs +2 -2
  461. data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
  462. data/rust/src/kernel/expr.rs +73 -4
  463. data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
  464. data/rust/src/kernel/expression_operators/presence.rs +41 -3
  465. data/rust/src/kernel/expression_operators/text.rs +26 -2
  466. data/rust/src/kernel/json.rs +15 -0
  467. data/rust/src/kernel/mod.rs +1 -1
  468. data/rust/src/kernel/orchestrate.rs +54 -5
  469. data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
  470. data/rust/src/kernel/pattern.rs +60 -0
  471. data/rust/src/kernel/reference_lookup.rs +90 -7
  472. metadata +174 -36
  473. data/lib/hecks/doors/cli_door/nesting.rb +0 -34
  474. data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
  475. data/lib/hecks/doors/cli_door.rb +0 -124
  476. data/lib/hecks/doors/cli_runner/answers.rb +0 -138
  477. data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
  478. data/lib/hecks/doors/cli_runner/settling.rb +0 -115
  479. data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
  480. data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
  481. data/lib/hecks/doors/cli_runner.rb +0 -66
  482. data/lib/hecks/doors/command_request.rb +0 -119
  483. data/lib/hecks/doors/handle.rb +0 -208
  484. data/lib/hecks/doors/json_door.rb +0 -132
  485. data/lib/hecks/doors/launcher_options.rb +0 -247
  486. data/lib/hecks/doors/mcp_door/properties.rb +0 -47
  487. data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
  488. data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
  489. data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
  490. data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
  491. data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
  492. data/lib/hecks/doors/mcp_door.rb +0 -156
  493. data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
  494. data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
  495. data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
  496. data/lib/hecks/doors/mcp_door_scope.rb +0 -201
  497. data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
  498. data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
  499. data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
  500. data/lib/hecks/doors/ruby_door.rb +0 -80
  501. data/lib/hecks/doors/usage_cache.rb +0 -139
  502. data/prebuilt/0ee7f83cba59ce7d1c45c65ba166b071779f70143449f584f33ff7989f64eccb.marshal +0 -0
  503. data/prebuilt/6e4a6bcf0aa880e2e6b056e1846b70e5a768090b9dc998bbadeaf5d6bcfd3db8.marshal +0 -0
  504. data/prebuilt/verdicts-91aaf93ce8981bd9a754948f075d95650dc2ec6435015376f5c60992b36a24b1.json +0 -1
@@ -0,0 +1,69 @@
1
+ __BANNER__
2
+
3
+ // A record's values shown read-only, by shape: a moment in the viewer's zone, a rich-text body as
4
+ // typeset text, a key with a button that copies it, anything else as `outline` shows it. The detail
5
+ // page lists them as a document, and a related record's panel lists the ones it holds.
6
+ import type { Aggregate, Attr } from "../schema.ts";
7
+ import { bodyToHtml } from "./body_model.js";
8
+ import { kindOf, summaryOf } from "./block_list.js";
9
+ import { copyable, esc } from "./html.ts";
10
+ import { isMoment, momentValue } from "./moments.ts";
11
+ import { outline } from "./outline.ts";
12
+ import { label } from "./words.ts";
13
+
14
+ /** A body shown on a page sits under the page's h1 and its section's h2, so its own headings start at h3. */
15
+ const READ_ONLY_DEMOTE = 2;
16
+
17
+ type State = Record<string, unknown>;
18
+
19
+ /** A list of mixed blocks as a numbered list: each block's kind and the line that stands for it. */
20
+ function blockLines(agg: Aggregate, attr: Attr, held: unknown): string {
21
+ const blocks = Array.isArray(held) ? (held as Record<string, unknown>[]) : [];
22
+ if (blocks.length === 0 || !attr.blockList) return `<span class="text-muted">None</span>`;
23
+ const parts = agg.valueObjects[attr.type] ?? [];
24
+ const lines = blocks.map((block) => {
25
+ const kind = kindOf(attr.blockList, block);
26
+ const text = String(summaryOf(attr.blockList, parts, block));
27
+ return `<li class="flex flex-wrap items-baseline gap-2"><span class="badge badge-soft badge-neutral font-semibold">${esc(label(kind))}</span><span class="min-w-0 break-words">${esc(text)}</span></li>`;
28
+ });
29
+ return `<ol class="grid list-decimal gap-1 pl-6">${lines.join("")}</ol>`;
30
+ }
31
+
32
+ const isBlank = (value: unknown): boolean => value === undefined || value === null || value === "" || (Array.isArray(value) && value.length === 0);
33
+
34
+ /** A value object of several parts as its named parts, each shown by what it is (a body typeset, a picture by its description, a list as a run). */
35
+ function parts(agg: Aggregate, type: string, held: unknown, depth: number): string {
36
+ const value = held && typeof held === "object" ? (held as State) : {};
37
+ const rows = (agg.valueObjects[type] ?? [])
38
+ .filter((part) => !isBlank(value[part.name]))
39
+ .map((part) => `<div class="grid gap-1 sm:grid-cols-[8rem_minmax(0,1fr)] sm:gap-3"><dt class="text-muted">${esc(label(part.name))}</dt><dd class="min-w-0 break-words">${valueOf(agg, part, value, depth + 1)}</dd></div>`);
40
+ return rows.length === 0 ? `<span class="text-muted">Not set</span>` : `<dl class="grid gap-2">${rows.join("")}</dl>`;
41
+ }
42
+
43
+ /** Whether the attribute's value object has several parts to show (a one-part value object is shown as its part). */
44
+ const composite = (agg: Aggregate, attr: Attr): boolean => attr.kind === "object" && (agg.valueObjects[attr.type] ?? []).length > 1;
45
+
46
+ /** One attribute's value as markup. */
47
+ export function valueOf(agg: Aggregate, attr: Attr, values: State, depth = 0): string {
48
+ const held = values[attr.name];
49
+ if (isMoment(attr)) return momentValue(attr, held);
50
+ if (attr.widget === "body") return held && typeof held === "object" ? `<div class="prose">${bodyToHtml(held, READ_ONLY_DEMOTE)}</div>` : `<span class="text-muted">Not set</span>`;
51
+ if (attr.blockList && attr.list) return blockLines(agg, attr, held);
52
+ if (composite(agg, attr) && depth < 5) {
53
+ if (!attr.list) return parts(agg, attr.type, held, depth);
54
+ const items = Array.isArray(held) ? held : [];
55
+ return items.length === 0 ? `<span class="text-muted">None</span>` : `<ol class="grid list-decimal gap-3 pl-6">${items.map((item) => `<li class="pl-1">${parts(agg, attr.type, item, depth)}</li>`).join("")}</ol>`;
56
+ }
57
+ const copy = attr.name === agg.identity || attr.kind === "reference";
58
+ return copy && typeof held !== "object" && held !== undefined && held !== null && held !== "" ? copyable(String(held), label(attr.name).toLowerCase()) : outline(held);
59
+ }
60
+
61
+ /** `attrs` as a list of names and values, a wide value (a body, a list, a value object) on a line of its own. */
62
+ export function valueRows(agg: Aggregate, attrs: Attr[], values: State): string {
63
+ const rows = attrs.map((attr) => {
64
+ const wide = attr.widget === "body" || attr.list || attr.kind === "object";
65
+ const layout = wide ? "grid gap-2 py-4" : "grid gap-1 py-4 sm:grid-cols-[11rem_minmax(0,1fr)] sm:gap-4";
66
+ return `<div class="${layout}"><dt class="font-medium text-muted">${esc(label(attr.name))}</dt><dd class="min-w-0 break-words">${valueOf(agg, attr, values)}</dd></div>`;
67
+ });
68
+ return `<dl class="divide-y divide-base-300">\n${rows.join("\n")}\n</dl>`;
69
+ }
@@ -0,0 +1,97 @@
1
+ __BANNER__
2
+
3
+ // The words the editor shows people. The domain's names are CamelCase and singular; people read
4
+ // sentence case, plural for a list, and the same verb through a whole flow: the button that says
5
+ // "Publish" leaves a notice that says "Published." Nothing here asks the domain; it reads names.
6
+ import type { Aggregate, Command } from "../schema.ts";
7
+
8
+ /** The lower-case words of a CamelCase or snake_case name: "MediaItem" is "media item". */
9
+ export const humanize = (name: string): string =>
10
+ name
11
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
12
+ .replace(/_/g, " ")
13
+ .toLowerCase();
14
+
15
+ /** `text` with its first letter capitalised. */
16
+ export const capital = (text: string): string => text.charAt(0).toUpperCase() + text.slice(1);
17
+
18
+ /** A name as a sentence-case label: "SaveDraft" is "Save draft". */
19
+ export const label = (name: string): string => capital(humanize(name));
20
+
21
+ /** The plural of an English noun, for the common regular shapes. */
22
+ export function pluralOf(noun: string): string {
23
+ if (/[^aeiou]y$/.test(noun)) return `${noun.slice(0, -1)}ies`;
24
+ if (/(s|x|z|ch|sh)$/.test(noun)) return `${noun}es`;
25
+ return `${noun}s`;
26
+ }
27
+
28
+ /** An aggregate as a list's name: "MediaItem" is "Media items". */
29
+ export function plural(name: string): string {
30
+ const words = humanize(name).split(" ");
31
+ words[words.length - 1] = pluralOf(words[words.length - 1]);
32
+ return capital(words.join(" "));
33
+ }
34
+
35
+ /** The limits a list's holder gives it as a sentence ("At least 1 and at most 60."), or nothing when it gives none. */
36
+ export function limitsNote(min?: number, max?: number): string {
37
+ if (min && max) return `At least ${min} and at most ${max}.`;
38
+ return min ? `At least ${min}.` : max ? `At most ${max}.` : "";
39
+ }
40
+
41
+ /** An aggregate as one thing, lower case: "MediaItem" is "media item". */
42
+ export const singular = (name: string): string => humanize(name);
43
+
44
+ const IRREGULAR: Record<string, string> = {
45
+ set: "set", put: "put", send: "sent", make: "made", undo: "undone", redo: "redone", show: "shown", withdraw: "withdrawn",
46
+ draw: "drawn", hide: "hidden", write: "written", take: "taken", give: "given", keep: "kept", read: "read", pay: "paid", sell: "sold",
47
+ begin: "begun", run: "run", get: "got", find: "found", build: "built", choose: "chosen", leave: "left", hold: "held", lose: "lost",
48
+ break: "broken", bring: "brought", cut: "cut", shut: "shut", split: "split", lay: "laid", say: "said", spend: "spent", lend: "lent",
49
+ bind: "bound", wind: "wound", know: "known", grow: "grown", throw: "thrown", see: "seen", do: "done", go: "gone", have: "had",
50
+ };
51
+
52
+ const DOUBLED = new Set([
53
+ "submit", "admit", "commit", "omit", "plan", "drop", "ship", "stop", "skip", "tag", "pin", "log", "map", "ban", "trim", "wrap",
54
+ "grab", "strip", "refer", "transfer", "permit", "equip", "occur", "defer", "prefer",
55
+ ]);
56
+
57
+ const PREFIXES = ["re", "un", "over", "out", "pre", "mis"];
58
+
59
+ /** The irregular participle of `verb`, or of the verb under a prefix ("reset" is "set" again), or undefined. */
60
+ function irregular(verb: string): string | undefined {
61
+ if (Object.hasOwn(IRREGULAR, verb)) return IRREGULAR[verb];
62
+ const prefix = PREFIXES.find((one) => verb.startsWith(one) && Object.hasOwn(IRREGULAR, verb.slice(one.length)));
63
+ return prefix ? `${prefix}${IRREGULAR[verb.slice(prefix.length)]}` : undefined;
64
+ }
65
+
66
+ /** The past participle of a verb: "publish" is "published", "submit" is "submitted", "begin" is "begun", "cancel" is "canceled". */
67
+ export function past(verb: string): string {
68
+ const odd = irregular(verb);
69
+ if (odd) return odd;
70
+ if (verb.endsWith("e")) return `${verb}d`;
71
+ if (/[^aeiou]y$/.test(verb)) return `${verb.slice(0, -1)}ied`;
72
+ return DOUBLED.has(verb) ? `${verb}${verb.slice(-1)}ed` : `${verb}ed`;
73
+ }
74
+
75
+ /** A word cut back to what its forms share: "schedule", "scheduled" and "scheduling" are all "schedul". */
76
+ const stem = (word: string): string => word.replace(/(ing|ed|es|e|d|s)$/, "");
77
+
78
+ /**
79
+ * What a command's button says. A command named by one word acts on a thing the page already
80
+ * names, except one that makes something new: "Draft" on the articles page is "Draft article".
81
+ */
82
+ export function commandLabel(agg: Aggregate, command: Command): string {
83
+ const words = humanize(command.name).split(" ");
84
+ return capital(command.creates && words.length === 1 ? `${words[0]} ${singular(agg.name)}` : words.join(" "));
85
+ }
86
+
87
+ /**
88
+ * What the notice says when the command has run, in the same verb as its button: "Published draft."
89
+ * When the thing's name already starts with the verb ("Schedule" making a "scheduled action") the
90
+ * repeated word is dropped, so the notice reads "Scheduled action." and never "Scheduled scheduled action."
91
+ */
92
+ export function done(agg: Aggregate, command: Command): string {
93
+ const [verb, ...rest] = commandLabel(agg, command).toLowerCase().split(" ");
94
+ const repeated = rest.length > 0 && stem(rest[0]) === stem(verb);
95
+ const object = repeated ? rest.slice(1) : rest;
96
+ return capital(`${[past(verb), ...object].join(" ")}.`);
97
+ }
@@ -0,0 +1,35 @@
1
+ __BANNER__
2
+
3
+ // What a page of one record may read of the rest of the domain: the records that go with it, and the
4
+ // actions scheduled for it. The server builds it from the one read of the host the page already makes
5
+ // (app.ts), so a page with panels costs no more reads than a page without.
6
+ import type { Aggregate, Related } from "../schema.ts";
7
+ import { SCHEMA } from "../schema.ts";
8
+
9
+ type State = Record<string, unknown>;
10
+
11
+ export interface World {
12
+ /** The person's role, which decides which aggregates' records may be shown. */
13
+ role: string | null;
14
+ /** One record of an aggregate by its identity, or undefined when the host holds none. */
15
+ lookup(agg: Aggregate, id: string): State | undefined;
16
+ /** Every record of an aggregate, as `[id, state]` pairs. */
17
+ rows(agg: Aggregate): [string, State][];
18
+ /** The scheduled actions of the record being shown, when the host answered a query for them. */
19
+ actions?: [string, State][];
20
+ }
21
+
22
+ /** The aggregate a `related` entry names. */
23
+ export const relatedAggregate = (rel: Related): Aggregate | undefined =>
24
+ SCHEMA.aggregates.find((candidate) => candidate.name === rel.aggregate && candidate.chapter === rel.chapter);
25
+
26
+ /** The aggregate that registers pictures, when the editor has it among its own. */
27
+ export function pictureAggregate(): Aggregate | undefined {
28
+ const media = SCHEMA.media;
29
+ if (!media) return undefined;
30
+ return SCHEMA.aggregates.find((agg) => agg.name === media.aggregate && (media.domain ? agg.chapter === media.domain : !agg.chapter || agg.chapter === SCHEMA.domain));
31
+ }
32
+
33
+ /** The aggregate whose records are the scheduled actions, when the editor has one. */
34
+ export const schedulingAggregate = (): Aggregate | undefined =>
35
+ SCHEMA.scheduling && SCHEMA.aggregates.find((candidate) => candidate.name === SCHEMA.scheduling?.aggregate && candidate.chapter === SCHEMA.scheduling?.chapter);
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "noEmit": true,
8
+ "allowImportingTsExtensions": true,
9
+ "allowJs": true,
10
+ "erasableSyntaxOnly": true,
11
+ "verbatimModuleSyntax": true,
12
+ "skipLibCheck": true,
13
+ "types": ["node"]
14
+ },
15
+ "include": ["src"],
16
+ "exclude": ["src/browser"]
17
+ }
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "palette"
4
+
5
+ module Hecks
6
+ module Projections
7
+ module Site
8
+ module CmsEditor
9
+ # The editor's theme as stylesheet text: the light and dark colour properties of a
10
+ # `Palette`, as the lines that go inside the two daisyUI themes `src/browser/app.css`
11
+ # declares (`editor-light`, and `editor-dark` for a visitor whose system prefers dark).
12
+ module Theme
13
+ module_function
14
+
15
+ # @param accent [String, nil] the Editor row's accent, a six-digit hex colour, or nil
16
+ # @return [Hash{String => String}] the placeholders of `app.css.tmpl` and their text
17
+ def tokens(accent)
18
+ themes = Palette.new(accent).themes
19
+ { "__LIGHT__" => lines(themes[:light]), "__DARK__" => lines(themes[:dark]) }
20
+ end
21
+
22
+ # @param properties [Hash{String => String}] custom properties and their values
23
+ # @return [String] one declaration per line
24
+ def lines(properties) = properties.map { |name, value| " #{name}: #{value};" }.join("\n")
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "color"
4
+
5
+ module Hecks
6
+ module Projections
7
+ module Site
8
+ module CmsEditor
9
+ # What the `Editor` row's look settings must satisfy: an accent that is a six-digit hex
10
+ # colour, a logo that is a relative path to a picture file the server can read from its
11
+ # own directory, and a brand that is one line of text.
12
+ module ThemeChecks
13
+ # The file types a logo may be.
14
+ LOGO_TYPES = %w[png jpg jpeg webp gif avif svg].freeze
15
+
16
+ # The longest brand the header has room for.
17
+ BRAND_LENGTH = 60
18
+
19
+ module_function
20
+
21
+ # @param row [Hash{Symbol => String}] the row, defaults filled
22
+ # @return [Array<String>] every problem with `brand`, `accent` and `logo`
23
+ def problems(row)
24
+ [*accent_problems(row[:accent].strip), *logo_problems(row[:logo].strip), *brand_problems(row[:brand].strip)]
25
+ end
26
+
27
+ # @return [Array<String>] the problem with an accent that is not blank or six hex digits
28
+ def accent_problems(accent)
29
+ return [] if accent.empty? || Color::HEX.match?(accent)
30
+
31
+ ["accent #{accent.inspect} must be a six-digit hex colour such as #1f7a6d"]
32
+ end
33
+
34
+ # @return [Array<String>] the problem with a logo that is not a safe relative picture path
35
+ def logo_problems(logo)
36
+ return [] if logo.empty? || safe_path?(logo)
37
+
38
+ ["logo #{logo.inspect} must be a relative path to a #{LOGO_TYPES.join(", ")} file, with no .. or hidden part"]
39
+ end
40
+
41
+ # @return [Array<String>] the problem with a brand that is long or has more than one line
42
+ def brand_problems(brand)
43
+ return [] if brand.length <= BRAND_LENGTH && !brand.match?(/[[:cntrl:]]/)
44
+
45
+ ["brand must be one line of at most #{BRAND_LENGTH} characters"]
46
+ end
47
+
48
+ # @return [Boolean] whether `path` is relative, has no hidden part, and is a picture
49
+ def safe_path?(path)
50
+ LOGO_TYPES.include?(File.extname(path).delete_prefix(".").downcase) && !path.start_with?("/") &&
51
+ path.split("/", -1).none? { |part| part.empty? || part.start_with?(".") } && !path.match?(/[\\[:cntrl:]]/)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "../../projector"
5
+ require_relative "cms_editor/setting"
6
+ require_relative "cms_editor/schema"
7
+ require_relative "cms_editor/pretty"
8
+ require_relative "cms_editor/icons"
9
+ require_relative "cms_editor/theme"
10
+
11
+ module Hecks
12
+ module Projections
13
+ module Site
14
+ # A small Node/TypeScript editor for a domain's content, as the files of a package: a server
15
+ # that signs editors in through the site's own hand-off and shows a page per aggregate, a list
16
+ # from its queries, a detail page per instance and a form per command.
17
+ #
18
+ # It is generic by aggregate. The pages and forms are not written per aggregate; they read
19
+ # `src/schema.ts`, which is the domain chapter's aggregates, attributes, value objects,
20
+ # lifecycles, commands and queries as one typed constant. The domain holds no editor data: the
21
+ # editor reads and dispatches through the host like any other client, and the output is a pure
22
+ # function of the chapter and the `Editor` row, so `--check` can hold it current.
23
+ module CmsEditor
24
+ extend Projector::Target
25
+
26
+ projects_as :site_cms_editor, emits: :files
27
+
28
+ # Where the template files are.
29
+ TEMPLATES = File.join(__dir__, "cms_editor", "templates")
30
+
31
+ # The files, by path relative to the directory they are written to.
32
+ FILES = %w[
33
+ package.json tsconfig.json src/config.ts src/schema.ts src/names.ts src/host.ts src/commands.ts src/choices.ts
34
+ src/app.ts src/server.ts src/assets.ts src/flash.ts src/auth/membership.ts src/auth/session.ts src/auth/sso.ts
35
+ src/ui/html.ts src/ui/words.ts src/ui/icons.ts src/ui/shell.ts src/ui/outline.ts src/ui/fields.ts
36
+ src/ui/input.ts src/ui/table.ts src/ui/paging.ts src/ui/pickers.ts src/ui/moments.ts src/ui/detail.ts
37
+ src/ui/pages.ts src/ui/theme.js src/ui/body_model.js src/ui/body_parse.js src/ui/availability.ts src/ui/dialogs.ts
38
+ src/ui/links.ts src/ui/preview.ts src/ui/related.ts src/ui/schedule.ts src/ui/values.ts src/ui/world.ts
39
+ src/browser/app.css src/browser/main.js src/browser/shell.js src/browser/forms.js src/browser/toast.js
40
+ src/browser/lists.js src/browser/pickers.js src/browser/epoch.js src/browser/moments.js
41
+ src/browser/body_doc.js src/browser/body_editor.js src/browser/autosave.js src/browser/preview.js
42
+ src/ui/blocks.ts src/ui/block_list.js src/browser/blocks.js
43
+ ].freeze
44
+
45
+ # The files only an editor of a chapter with a picture aggregate has: the upload, the
46
+ # storage port with its local-disk adapter, and the picker the rich-text editor opens.
47
+ MEDIA_FILES = %w[
48
+ src/media/sniff.ts src/media/multipart.ts src/media/storage.ts src/media/handler.ts src/browser/media_picker.js
49
+ ].freeze
50
+
51
+ # What `app.ts` and the body editor hold for a picture upload; each is left out when the
52
+ # chapter has none.
53
+ MEDIA_LINES = {
54
+ "__MEDIA_IMPORT__" => ["import { mediaHandler, mediaOpen } from \"./media/handler.ts\";",
55
+ "import type { MediaStorage } from \"./media/storage.ts\";"].join("\n"),
56
+ "__MEDIA_OPTION__" => [" /** Where picture bytes are kept: the local disk unless given (see media/storage.ts). */",
57
+ " storage?: MediaStorage;"].join("\n"),
58
+ "__MEDIA_SETUP__" => " const media = mediaHandler(options.storage, { url: options.url, fetch: options.fetch });",
59
+ "__MEDIA_ROUTE__" => [" if (segments[0] === \"media\") {",
60
+ " if (!mediaOpen(role)) return plain(\"Pictures are not open to your role.\", 403);",
61
+ " return media(client, request, segments.slice(1));", " }"].join("\n"),
62
+ "__PICKER_LOADER__" => "() => import(\"./media_picker.js\")"
63
+ }.freeze
64
+
65
+ module_function
66
+
67
+ # Renders the editor's files for a project that declares an `Editor` row.
68
+ #
69
+ # @param bluebook [Bluebook::Chapter] the chapter that declares the route table and the row
70
+ # @param options [Hash{Symbol => Object}] `:domain_chapters` the chapters to edit, in the
71
+ # order the row names them (`:domain_chapter` for one);
72
+ # `:media_chapter` the chapter the row's `media` names, whose picture aggregate the
73
+ # pictures use; `:table` the checked route table, when the login page is to be checked
74
+ # against it
75
+ # @return [Hash{String => String}] each file's path relative to the editor's directory to
76
+ # its text; empty when the project declares no `Editor` row
77
+ # @raise [Table::Invalid] when the row is refused
78
+ # @raise [ArgumentError] when the chapter has no aggregate to edit
79
+ def call(bluebook:, options: {})
80
+ setting = Setting.read(bluebook, table: options[:table])
81
+ return {} unless setting
82
+
83
+ chapters = options[:domain_chapters] || options.fetch(:domain_chapter)
84
+ schema = Schema.read(chapters, skip: setting.skip, pictures: options[:media_chapter], roles: setting.chapter_roles)
85
+ media = schema.key?("media")
86
+ tokens = tokens(setting, schema, media)
87
+ [*FILES, *(MEDIA_FILES if media)].to_h { |path| [path, fill(File.read(File.join(TEMPLATES, "#{path}.tmpl")), tokens)] }
88
+ end
89
+
90
+ # @return [Hash{String => String}] each placeholder of the templates and its text
91
+ def tokens(setting, schema, media)
92
+ setting.tokens.merge(look_tokens(setting, schema), "__SCHEMA__" => Pretty.generate(schema), **media_tokens(media))
93
+ end
94
+
95
+ # @param media [Boolean] whether the chapter has a picture aggregate
96
+ # @return [Hash{String => String}] each media placeholder to its text, empty when none
97
+ def media_tokens(media)
98
+ return MEDIA_LINES if media
99
+
100
+ MEDIA_LINES.transform_values { "" }.merge("__PICKER_LOADER__" => "null")
101
+ end
102
+
103
+ # @param setting [Setting] the checked row
104
+ # @param schema [Hash{String => Object}] the chapter as the editor reads it
105
+ # @return [Hash{String => String}] the brand, the icon sprite and the theme's colours
106
+ def look_tokens(setting, schema)
107
+ { "__BRAND__" => JSON.generate(setting.brand || schema["domain"]), "__ICONS__" => Icons.sprite,
108
+ "__CSS_BANNER__" => "/* #{RoutesTs::BANNER.delete_prefix("// ")} */", **Theme.tokens(setting.accent) }
109
+ end
110
+
111
+ # @param text [String] a template with `__NAME__` placeholders
112
+ # @param tokens [Hash{String => String}] each placeholder to its text
113
+ # @return [String] `text` with each placeholder replaced in one pass, so a value that looks
114
+ # like a placeholder is left as it is; a placeholder alone on its line whose text is empty
115
+ # takes the line with it
116
+ def fill(text, tokens)
117
+ text.gsub(/^(__[A-Z_]+__)\n|__[A-Z_]+__/) do
118
+ line = Regexp.last_match(1)
119
+ token = line || Regexp.last_match(0)
120
+ replacement(tokens.fetch(token, token), alone: !line.nil?)
121
+ end
122
+ end
123
+
124
+ # @return [String] `value`, with its line's newline when it stood alone and is not empty
125
+ def replacement(value, alone:)
126
+ return value unless alone
127
+
128
+ value.empty? ? "" : "#{value}\n"
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../bluebook/synthesizer"
4
+
5
+ module Hecks
6
+ module Projections
7
+ module Site
8
+ # The commands of a project that declare a role, and the verdict on a host's answer to each
9
+ # sent with well-formed arguments but no role and no actor. It sends nothing: the caller
10
+ # dispatches each command and hands the answer back.
11
+ #
12
+ # The arguments are synthesized from the declaration, because a host checks that arguments are
13
+ # present and well-typed before it checks who is calling. A host that enforces roles refuses
14
+ # such a caller as `Unauthorized`. Any other answer (an accepted command, or a refusal for
15
+ # another reason) means the role did not stop it.
16
+ class RoleProbe
17
+ # One command that declares a role.
18
+ #
19
+ # @!attribute [r] verb
20
+ # @return [String] the command as a host is asked to run it, `Chapter::Aggregate.Command`
21
+ # @!attribute [r] role
22
+ # @return [String] the role the command declares
23
+ # @!attribute [r] arguments
24
+ # @return [Hash] one synthesized value per declared attribute
25
+ Check = Struct.new(:verb, :role, :arguments, keyword_init: true)
26
+
27
+ # The refusal kind a host enforcing roles answers a caller of the wrong role with.
28
+ REFUSAL = "Unauthorized"
29
+
30
+ # The refusal kinds of an argument the host would not build, before it asks who is calling.
31
+ ARGUMENT_REFUSALS = %w[AbsentArgument TypeMismatch InvariantViolation].freeze
32
+
33
+ # @param registry [Runtime::Registry] the registry a project booted into
34
+ # @return [Array<Check>] every command that declares a role, in the order declared
35
+ def self.checks(registry)
36
+ registry.bluebooks.values.flat_map do |chapter|
37
+ chapter.aggregates.flat_map do |aggregate|
38
+ aggregate.commands.select(&:role).map { |command| check_for(chapter, aggregate, command) }
39
+ end
40
+ end
41
+ end
42
+
43
+ # @return [Check] the check for one command that declares a role
44
+ def self.check_for(chapter, aggregate, command)
45
+ Check.new(verb: "#{chapter.name}::#{aggregate.hecks_name}.#{command.hecks_name}", role: command.role,
46
+ arguments: Bluebook::Synthesizer.args_for(chapter, aggregate, command))
47
+ end
48
+
49
+ # Whether the host refused the synthesized arguments themselves, so the role gate was never
50
+ # reached and the check proves nothing either way.
51
+ #
52
+ # @param answer [Hash] the host's parsed JSON answer
53
+ # @return [Boolean] true when every refusal is an argument refusal
54
+ def self.unchecked?(answer)
55
+ kinds = Array(answer["refusals"]).map { |refusal| refusal["kind"] }
56
+ kinds.any? && (kinds - ARGUMENT_REFUSALS).empty?
57
+ end
58
+
59
+ # Judges a host's answer to one check.
60
+ #
61
+ # @param check [Check] the command that was dispatched with no role and no actor
62
+ # @param answer [Hash] the host's parsed JSON answer
63
+ # @return [String, nil] why the answer is wrong, or nil when the host refused the caller
64
+ def self.verdict(check, answer)
65
+ refusals = Array(answer["refusals"])
66
+ return nil if refusals.any? { |refusal| refusal["kind"] == REFUSAL }
67
+ return "was refused, but not as #{REFUSAL}: #{refusals.map { |refusal| refusal["kind"] }.join(", ")}" if refusals.any?
68
+
69
+ "was not refused for its role (#{check.role}); is the host running with HECKS_ROLE_ENFORCEMENT=enforce?"
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -2,7 +2,8 @@ module Hecks
2
2
  module Projector
3
3
  # What a launcher's help leaves out and points at for whoever typed the line: the aggregates
4
4
  # only a maintainer of a hecks checkout runs, and the attached chapters worth a line each.
5
- # `CliProjector` renders the help; the runner decides the audience (`Doors::LauncherOptions`).
5
+ # `CliProjector` renders the help; the runner decides the audience
6
+ # (`Adapters::Driving::LauncherOptions`).
6
7
  module CliAudience
7
8
  module_function
8
9
 
@@ -67,7 +67,7 @@ module Hecks
67
67
  end
68
68
 
69
69
  # " (also: mcp!)" for a spec the chapter gave a short name, else nothing. A short name that
70
- # is only the command's own name (`init` for `door.init`) is already in the line.
70
+ # is only the command's own name (`init` for `launch.init`) is already in the line.
71
71
  def alias_note(spec)
72
72
  return "" if spec[:short_was].nil? || spec[:short_was].split(".").last == spec[:short]
73
73
 
@@ -29,7 +29,7 @@ module Hecks
29
29
  # One option per field of a value object, recursing into a field that is itself one.
30
30
  #
31
31
  # The list flag rides on each leaf: without it a repeated flag overwrote the
32
- # leaf silently, and CliDoor only ever sees a path and a spec.
32
+ # leaf silently, and Cli only ever sees a path and a spec.
33
33
  def leaf_options(value_object, aggregate, path, optional)
34
34
  value_object.attributes.flat_map do |field|
35
35
  nested = value_object_for(field, value_object, aggregate)
@@ -18,7 +18,7 @@ module Hecks
18
18
  return [] if bluebook.policies.empty?
19
19
 
20
20
  rows = bluebook.policies.map do |policy|
21
- ["`#{policy.on_event}`", "`#{policy.trigger_command}`", policy.target_domain || bluebook.name]
21
+ ["`#{policy.on_event}`", "`#{policy.reaches}`", policy.target_domain || bluebook.name]
22
22
  end
23
23
  [DocsProjector.h(depth, "Reactions"), "",
24
24
  "These fire on their own. Issuing the verb on the left also causes the one on the right.", "",
@@ -110,7 +110,7 @@ module Hecks
110
110
  out.compact.join("\n")
111
111
  end
112
112
 
113
- # An entity has no door of its own; its verbs go through the holding aggregate.
113
+ # An entity has no entry point of its own; its verbs go through the holding aggregate.
114
114
  def entity_addressing(aggregate, entity)
115
115
  "Addressed through its holder — `#{aggregate.hecks_name}.#{entity.hecks_name}.<Verb>`, " \
116
116
  "passing the #{aggregate.hecks_name}'s `id` and this element's " \