hecks 3.10.0 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (481) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/bluebook/pizzas.bluebook +29 -0
  3. data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
  4. data/exe/hecks +4 -4
  5. data/lib/hecks/adapters/driven/d1.rb +2 -10
  6. data/lib/hecks/adapters/driven/heki.adapter +1 -1
  7. data/lib/hecks/adapters/driven/heki.rb +2 -12
  8. data/lib/hecks/adapters/driven/lambda.adapter +1 -1
  9. data/lib/hecks/adapters/driven/lambda.rb +3 -18
  10. data/lib/hecks/adapters/driven/postgres.rb +4 -11
  11. data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
  12. data/lib/hecks/adapters/driven/sqlite.rb +2 -10
  13. data/lib/hecks/adapters/driving/admission.rb +46 -0
  14. data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
  15. data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
  16. data/lib/hecks/adapters/driving/cli.rb +127 -0
  17. data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
  18. data/lib/hecks/adapters/driving/cli_runner/resolution.rb +155 -0
  19. data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
  20. data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
  21. data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
  22. data/lib/hecks/adapters/driving/cli_runner.rb +69 -0
  23. data/lib/hecks/adapters/driving/command_request.rb +121 -0
  24. data/lib/hecks/adapters/driving/handle.rb +214 -0
  25. data/lib/hecks/adapters/driving/json.rb +136 -0
  26. data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
  27. data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
  28. data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
  29. data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
  30. data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
  31. data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
  32. data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
  33. data/lib/hecks/adapters/driving/mcp.rb +159 -0
  34. data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
  35. data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
  36. data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
  37. data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
  38. data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
  39. data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
  40. data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
  41. data/lib/hecks/adapters/driving/ruby.rb +83 -0
  42. data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
  43. data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
  44. data/lib/hecks/behaviors/expectations/boot.rb +1 -1
  45. data/lib/hecks/bench/ruby_runner.rb +1 -1
  46. data/lib/hecks/bluebook/ask_resolution.rb +130 -0
  47. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
  48. data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
  49. data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
  50. data/lib/hecks/bluebook/assembly/marks.rb +1 -0
  51. data/lib/hecks/bluebook/attribute.rb +1 -1
  52. data/lib/hecks/bluebook/behaviour/command.rb +13 -8
  53. data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
  54. data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
  55. data/lib/hecks/bluebook/capabilities.rb +91 -9
  56. data/lib/hecks/bluebook/domain_port.rb +16 -4
  57. data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
  58. data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
  59. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
  60. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
  61. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
  62. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
  63. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
  64. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
  65. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
  66. data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
  67. data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
  68. data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
  69. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
  70. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
  71. data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
  72. data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
  73. data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
  74. data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
  75. data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
  76. data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
  77. data/lib/hecks/bluebook/expression/projection.json +36 -0
  78. data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
  79. data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
  80. data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
  81. data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
  82. data/lib/hecks/bluebook/expression/resolver.rb +20 -1
  83. data/lib/hecks/bluebook/hecksagon.rb +23 -3
  84. data/lib/hecks/bluebook/lifecycle.rb +22 -3
  85. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
  86. data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
  87. data/lib/hecks/bluebook/meta_validator/readings.rb +18 -1
  88. data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
  89. data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
  90. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
  91. data/lib/hecks/bluebook/meta_validator.rb +12 -12
  92. data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
  93. data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
  94. data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
  95. data/lib/hecks/bluebook/model_check.rb +8 -2
  96. data/lib/hecks/bluebook/policy.rb +4 -2
  97. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  98. data/lib/hecks/bluebook/synthesizer.rb +2 -0
  99. data/lib/hecks/bluebook.rb +1 -0
  100. data/lib/hecks/cli/command_table.rb +1 -1
  101. data/lib/hecks/cli/console.rb +2 -1
  102. data/lib/hecks/cli/document.rb +1 -1
  103. data/lib/hecks/cli/launcher_words.rb +4 -4
  104. data/lib/hecks/cli/mcp.rb +4 -4
  105. data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
  106. data/lib/hecks/cli/project_cli/templates.rb +5 -5
  107. data/lib/hecks/cli/project_cli.rb +9 -6
  108. data/lib/hecks/cli/run.rb +2 -2
  109. data/lib/hecks/cli/smoke_http/checks.rb +4 -1
  110. data/lib/hecks/cli/smoke_http/transport.rb +3 -3
  111. data/lib/hecks/cli/smoke_http.rb +3 -0
  112. data/lib/hecks/codemod.rb +5 -2
  113. data/lib/hecks/corpus/routes.rb +7 -0
  114. data/lib/hecks/corpus/rust.rb +3 -2
  115. data/lib/hecks/deploy/bluebook/deploy.bluebook +18 -2
  116. data/lib/hecks/doc/reference/rendering.rb +6 -3
  117. data/lib/hecks/forms/app/commands.rb +8 -7
  118. data/lib/hecks/forms/app/queries.rb +2 -1
  119. data/lib/hecks/forms/app.rb +1 -1
  120. data/lib/hecks/forms/record_renderer.rb +1 -1
  121. data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
  122. data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
  123. data/lib/hecks/fuzzing/failure_triage.rb +2 -1
  124. data/lib/hecks/fuzzing/mutation/run.rb +11 -4
  125. data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
  126. data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
  127. data/lib/hecks/fuzzing/properties/guards.rb +1 -1
  128. data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
  129. data/lib/hecks/fuzzing/self_consistency.rb +6 -2
  130. data/lib/hecks/fuzzing/shrinker.rb +3 -1
  131. data/lib/hecks/fuzzing/value_generator.rb +19 -5
  132. data/lib/hecks/gate/stages.yml +4 -10
  133. data/lib/hecks/grammar/evolve.rb +1 -1
  134. data/lib/hecks/grammar/expression_operators.json +372 -0
  135. data/lib/hecks/grammar/operators.rb +3 -1
  136. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
  137. data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
  138. data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
  139. data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
  140. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
  141. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
  142. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
  143. data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
  144. data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
  145. data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
  146. data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
  147. data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
  148. data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
  149. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
  150. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
  151. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
  152. data/lib/hecks/hecks/adapters/local_files.rb +2 -2
  153. data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
  154. data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
  155. data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
  156. data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
  157. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
  158. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
  159. data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
  160. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
  161. data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +129 -0
  162. data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
  163. data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
  164. data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
  165. data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
  166. data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
  167. data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
  168. data/lib/hecks/hecks/adapters/terminal.rb +13 -11
  169. data/lib/hecks/hecks/codebase.behaviors +13 -1
  170. data/lib/hecks/hecks/codebase.bluebook +239 -5
  171. data/lib/hecks/hecks/custodian.behaviors +24 -6
  172. data/lib/hecks/hecks/custodian.bluebook +6 -1227
  173. data/lib/hecks/hecks/deploy.behaviors +1 -1
  174. data/lib/hecks/hecks/deploy.world +1 -3
  175. data/lib/hecks/hecks/hecks.behaviors +1 -1
  176. data/lib/hecks/hecks/hecks.hecksagon +53 -18
  177. data/lib/hecks/hecks/hecks.world +10 -10
  178. data/lib/hecks/hecks/rehearsal.bluebook +477 -0
  179. data/lib/hecks/hecks/site.behaviors +1 -1
  180. data/lib/hecks/hecks/site.world +0 -1
  181. data/lib/hecks/hecks/tooling.bluebook +1238 -0
  182. data/lib/hecks/indifferent_key.rb +20 -0
  183. data/lib/hecks/language/adapter.bluebook +13 -3
  184. data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
  185. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
  186. data/lib/hecks/language/bluebook/entity.bluebook +18 -0
  187. data/lib/hecks/language/bluebook/policy.bluebook +9 -1
  188. data/lib/hecks/language/bluebook/query.bluebook +1 -1
  189. data/lib/hecks/language/bluebook/vocabulary.bluebook +11 -4
  190. data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
  191. data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
  192. data/lib/hecks/language/oidc.json +10 -0
  193. data/lib/hecks/language/port.bluebook +4 -4
  194. data/lib/hecks/literal.rb +12 -3
  195. data/lib/hecks/ports/key_vault.rb +7 -2
  196. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
  197. data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
  198. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
  199. data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
  200. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +29 -0
  201. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
  202. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  203. data/lib/hecks/projections/deploy/shared.rb +5 -2
  204. data/lib/hecks/projections/model/deviations.rb +2 -2
  205. data/lib/hecks/projections/model.rb +2 -2
  206. data/lib/hecks/projections/oidc.rb +13 -1
  207. data/lib/hecks/projections/shape.rb +5 -2
  208. data/lib/hecks/projections/site/cms_editor/attributes.rb +26 -6
  209. data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
  210. data/lib/hecks/projections/site/cms_editor/checks.rb +14 -1
  211. data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
  212. data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
  213. data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
  214. data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
  215. data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
  216. data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
  217. data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
  218. data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
  219. data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
  220. data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
  221. data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
  222. data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
  223. data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
  224. data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
  225. data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
  226. data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
  227. data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
  228. data/lib/hecks/projections/site/cms_editor/schema.rb +96 -19
  229. data/lib/hecks/projections/site/cms_editor/setting.rb +52 -9
  230. data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
  231. data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +18 -1
  232. data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +251 -57
  233. data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
  234. data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +32 -24
  235. data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
  236. data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
  237. data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
  238. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
  239. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
  240. data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
  241. data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
  242. data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
  243. data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
  244. data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
  245. data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
  246. data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
  247. data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
  248. data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
  249. data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
  250. data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
  251. data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +78 -24
  252. data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +6 -0
  253. data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
  254. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +19 -2
  255. data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +31 -8
  256. data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
  257. data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +131 -2
  258. data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
  259. data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
  260. data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
  261. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +8 -5
  262. data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
  263. data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
  264. data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +229 -34
  265. data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +40 -81
  266. data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
  267. data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +29 -12
  268. data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
  269. data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
  270. data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +53 -17
  271. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +178 -88
  272. data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
  273. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
  274. data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
  275. data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
  276. data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
  277. data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
  278. data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
  279. data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
  280. data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
  281. data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
  282. data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
  283. data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +2 -1
  284. data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
  285. data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
  286. data/lib/hecks/projections/site/cms_editor.rb +49 -23
  287. data/lib/hecks/projections/site/role_probe.rb +2 -0
  288. data/lib/hecks/projector/cli_audience.rb +2 -1
  289. data/lib/hecks/projector/cli_projector/listing.rb +1 -1
  290. data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
  291. data/lib/hecks/projector/docs_projector/closing.rb +1 -1
  292. data/lib/hecks/projector/docs_projector.rb +1 -1
  293. data/lib/hecks/projector/exporter/bindings.rb +40 -10
  294. data/lib/hecks/projector/exporter/capability_values.rb +55 -0
  295. data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
  296. data/lib/hecks/projector/target.rb +11 -0
  297. data/lib/hecks/projector.rb +32 -5
  298. data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
  299. data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
  300. data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
  301. data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
  302. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
  303. data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
  304. data/lib/hecks/quality_control/quality_control.bluebook +6 -2
  305. data/lib/hecks/release/approval.rb +64 -0
  306. data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
  307. data/lib/hecks/router/namespace_installer.rb +16 -16
  308. data/lib/hecks/router.rb +1 -1
  309. data/lib/hecks/runtime/best_effort.rb +22 -0
  310. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
  311. data/lib/hecks/runtime/command_interpreter.rb +3 -2
  312. data/lib/hecks/runtime/command_rules/emission.rb +1 -1
  313. data/lib/hecks/runtime/dispatcher.rb +4 -4
  314. data/lib/hecks/runtime/entity_element/location.rb +1 -1
  315. data/lib/hecks/runtime/event.rb +5 -0
  316. data/lib/hecks/runtime/identity.rb +7 -6
  317. data/lib/hecks/runtime/interpreting.rb +2 -1
  318. data/lib/hecks/runtime/invocation_builder.rb +1 -1
  319. data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
  320. data/lib/hecks/runtime/loader.rb +10 -10
  321. data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
  322. data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
  323. data/lib/hecks/runtime/policy_interpreter.rb +6 -6
  324. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
  325. data/lib/hecks/runtime/reaction_invocation.rb +1 -1
  326. data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
  327. data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
  328. data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
  329. data/lib/hecks/runtime/registry/merging.rb +1 -1
  330. data/lib/hecks/runtime/registry/providers.rb +16 -0
  331. data/lib/hecks/runtime/registry/read_repository.rb +6 -4
  332. data/lib/hecks/runtime/registry/verification.rb +4 -1
  333. data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
  334. data/lib/hecks/runtime/registry.rb +1 -1
  335. data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
  336. data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
  337. data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
  338. data/lib/hecks/runtime/saga_interpreter.rb +3 -3
  339. data/lib/hecks/runtime/tenant_check.rb +5 -4
  340. data/lib/hecks/runtime/value/coercion.rb +2 -2
  341. data/lib/hecks/runtime/value/validation.rb +16 -4
  342. data/lib/hecks/runtime.rb +9 -9
  343. data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
  344. data/lib/hecks/rust_build/wasm.rb +1 -1
  345. data/lib/hecks/rust_build/wasm_browser.rb +1 -1
  346. data/lib/hecks/rust_build.rb +11 -0
  347. data/lib/hecks/served_options.rb +53 -0
  348. data/lib/hecks/site/bluebook/site.bluebook +1 -0
  349. data/lib/hecks/storehouse/audit.rb +1 -1
  350. data/lib/hecks/storehouse/dispatching.rb +8 -6
  351. data/lib/hecks/storehouse/introspection.rb +2 -2
  352. data/lib/hecks/storehouse/querying.rb +6 -4
  353. data/lib/hecks/storehouse.rb +4 -4
  354. data/lib/hecks/tools/comment_style/source_file.rb +4 -1
  355. data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
  356. data/lib/hecks/tools/lanes/rows.rb +6 -0
  357. data/lib/hecks/tools/oidc_manifests.rb +4 -5
  358. data/lib/hecks/tools/regeneration_run.rb +11 -4
  359. data/lib/hecks/tools/site_routes/editor_files.rb +12 -5
  360. data/lib/hecks/tools/tenant_provisioning.rb +2 -2
  361. data/lib/hecks/tools/tools_doc.rb +4 -4
  362. data/lib/hecks/version.rb +1 -1
  363. data/lib/hecks/vocabulary.rb +4 -4
  364. data/lib/hecks.rb +15 -15
  365. data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
  366. data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
  367. data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
  368. data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
  369. data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
  370. data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
  371. data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
  372. data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
  373. data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
  374. data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
  375. data/prebuilt/verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json +1 -0
  376. data/rust/.gitignore +6 -0
  377. data/rust/Cargo.toml +6 -0
  378. data/rust/build/src/binding_pass.rs +103 -0
  379. data/rust/build/src/cargo_sync.rs +6 -0
  380. data/rust/build/src/lineage_pass.rs +3 -3
  381. data/rust/build/src/main.rs +17 -5
  382. data/rust/build/src/pipeline.rs +48 -5
  383. data/rust/build/src/seams_pass.rs +123 -0
  384. data/rust/build/src/translation_pass.rs +167 -0
  385. data/rust/codegen/src/asks.rs +103 -0
  386. data/rust/codegen/src/commands.rs +13 -8
  387. data/rust/codegen/src/constraints.rs +25 -0
  388. data/rust/codegen/src/domain_generator.rs +2 -0
  389. data/rust/codegen/src/expr_emitter.rs +13 -0
  390. data/rust/codegen/src/json_codec.rs +54 -15
  391. data/rust/codegen/src/main.rs +1 -0
  392. data/rust/codegen/src/mutations.rs +128 -3
  393. data/rust/codegen/src/queries.rs +11 -2
  394. data/rust/codegen/src/reactions.rs +1 -1
  395. data/rust/codegen/src/types.rs +166 -8
  396. data/rust/host/HECKS_RELEASE +1 -1
  397. data/rust/host/src/api.rs +4 -4
  398. data/rust/host/src/bin/validate_harness.rs +43 -0
  399. data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
  400. data/rust/host/src/boundary_fuzz/ir.rs +1 -0
  401. data/rust/host/src/boundary_fuzz/server.rs +1 -1
  402. data/rust/host/src/checkout.rs +35 -19
  403. data/rust/host/src/commerce_ir.rs +408 -2
  404. data/rust/host/src/expr_json.rs +118 -0
  405. data/rust/host/src/mint.rs +1 -1
  406. data/rust/host/src/payments/tests.rs +27 -1
  407. data/rust/host/src/payments.rs +12 -4
  408. data/rust/host/src/query_step.rs +22 -4
  409. data/rust/host/src/reference_validate.rs +653 -108
  410. data/rust/host/src/resend.rs +2 -0
  411. data/rust/host/src/server.rs +19 -6
  412. data/rust/host/src/ui_schema.rs +132 -5
  413. data/rust/host/src/web/newsletter.rs +7 -8
  414. data/rust/host/src/web/newsletter_send.rs +38 -13
  415. data/rust/host/src/web/registrations.rs +229 -26
  416. data/rust/host/src/web.rs +3 -3
  417. data/rust/parser/src/emit.rs +38 -6
  418. data/rust/parser/src/expr/ast_json.rs +7 -1
  419. data/rust/parser/src/expr/resolver.rs +171 -6
  420. data/rust/parser/src/ir.rs +7 -0
  421. data/rust/parser/src/keywords.rs +24 -0
  422. data/rust/parser/src/main.rs +3 -0
  423. data/rust/parser/src/parse/aggregate.rs +2 -1
  424. data/rust/parser/src/parse/chapter.rs +2 -2
  425. data/rust/parser/src/parse/command.rs +2 -0
  426. data/rust/parser/src/parse/hecksagon.rs +5 -2
  427. data/rust/parser/src/parse/lifecycle.rs +81 -0
  428. data/rust/parser/src/parse/mod.rs +15 -1
  429. data/rust/parser/src/parse/policy.rs +13 -2
  430. data/rust/parser/src/parse/value_object.rs +13 -8
  431. data/rust/parser/src/ruby_value.rs +1 -1
  432. data/rust/src/exemplar/commands.rs +4 -4
  433. data/rust/src/exemplar/constraints.rs +1 -1
  434. data/rust/src/exemplar/json.rs +43 -1
  435. data/rust/src/kernel/cli.rs +7 -5
  436. data/rust/src/kernel/dispatch.rs +2 -2
  437. data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
  438. data/rust/src/kernel/expr.rs +66 -3
  439. data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
  440. data/rust/src/kernel/expression_operators/text.rs +26 -2
  441. data/rust/src/kernel/json.rs +15 -0
  442. data/rust/src/kernel/mod.rs +1 -1
  443. data/rust/src/kernel/orchestrate.rs +54 -5
  444. data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
  445. data/rust/src/kernel/pattern.rs +60 -0
  446. data/rust/src/kernel/reference_lookup.rs +90 -7
  447. metadata +140 -38
  448. data/lib/hecks/doors/cli_door/nesting.rb +0 -34
  449. data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
  450. data/lib/hecks/doors/cli_door.rb +0 -124
  451. data/lib/hecks/doors/cli_runner/answers.rb +0 -138
  452. data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
  453. data/lib/hecks/doors/cli_runner/settling.rb +0 -115
  454. data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
  455. data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
  456. data/lib/hecks/doors/cli_runner.rb +0 -66
  457. data/lib/hecks/doors/command_request.rb +0 -119
  458. data/lib/hecks/doors/handle.rb +0 -208
  459. data/lib/hecks/doors/json_door.rb +0 -132
  460. data/lib/hecks/doors/launcher_options.rb +0 -247
  461. data/lib/hecks/doors/mcp_door/properties.rb +0 -47
  462. data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
  463. data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
  464. data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
  465. data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
  466. data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
  467. data/lib/hecks/doors/mcp_door.rb +0 -156
  468. data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
  469. data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
  470. data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
  471. data/lib/hecks/doors/mcp_door_scope.rb +0 -201
  472. data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
  473. data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
  474. data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
  475. data/lib/hecks/doors/ruby_door.rb +0 -80
  476. data/lib/hecks/doors/usage_cache.rb +0 -139
  477. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_widget.js.tmpl +0 -282
  478. data/lib/hecks/projections/site/cms_editor/templates/src/ui/media_picker.js.tmpl +0 -109
  479. data/prebuilt/c28d6d9db14eea3e17161e9d9c262efee30b256cf97693a7cfd552bce24b2f7f.marshal +0 -0
  480. data/prebuilt/cb375cba61ca0dbe8ddb9739c306510fe05e5f4b7a9b6955caff8224c63ea0e0.marshal +0 -0
  481. data/prebuilt/verdicts-d6d584ad43b5f3cebf88aafbbdbf024c38574acf18e06adffbc555f7786b1c2f.json +0 -1
@@ -1,8 +1,12 @@
1
+ require_relative "capabilities/attribute_values"
2
+
1
3
  module Hecks
2
4
  module Bluebook
3
5
  # What a chapter may declare it provides — trusted in place of a literal
4
6
  # chapter-name check by role checks, adapters and the fuzzer.
5
7
  module Capabilities
8
+ extend AttributeValues
9
+
6
10
  AUTHORIZATION = "authorization".freeze
7
11
  # Who may sign in and with what role, resolved by declaration rather
8
12
  # than an env var naming the aggregate.
@@ -30,6 +34,31 @@ module Hecks
30
34
  # declaration rather than the literal name "PaymentConnection".
31
35
  PAYMENT_CONNECTION = "payment_connection".freeze
32
36
 
37
+ # The checkout boundary in front of a payment processor: how long a
38
+ # signed webhook stays fresh and how long a session holds its seat,
39
+ # recognised by declaration rather than the literal chapter name.
40
+ CHECKOUT = "checkout".freeze
41
+
42
+ # Marking a domain's attributes sensitive and reading the markings back, recognised by
43
+ # declaration rather than the literal chapter name "Privacy".
44
+ PRIVACY = "privacy".freeze
45
+
46
+ # A subject's key record, shredded to erase them, recognised by declaration rather than the
47
+ # literal names "Privacy" and "SubjectKey".
48
+ SUBJECT_KEYS = "subject_keys".freeze
49
+
50
+ # The kinds of contract entry a capability may name: `:command` and
51
+ # `:query` ("Aggregate.Name"), `:port_operation` ("Aggregate.Port.Operation")
52
+ # and `:mark` ("Aggregate.mark_name", the states of that aggregate's
53
+ # lifecycle mark) and `:duration` ("Aggregate.attribute", the whole-seconds
54
+ # `default:` of that attribute, ADR 0098). `:mark` and `:duration` entries are
55
+ # optional; every other entry is required.
56
+ #
57
+ # ADR 0099 adds two more optional kinds: `:text` ("Aggregate.attribute", the string
58
+ # `default:` of that attribute, a word the domain owns) and `:attribute`
59
+ # ("Aggregate.attribute", the attribute's own name, for the host to read a field by).
60
+ OPTIONAL_KINDS = %i[mark duration text attribute].freeze
61
+
33
62
  CONTRACTS = {
34
63
  AUTHORIZATION => {
35
64
  # every assignment an actor holds, current or historical
@@ -57,13 +86,25 @@ module Hecks
57
86
  }.freeze,
58
87
  NEWSLETTER => {
59
88
  # a guest signs up; the aggregate it names is the subscriber
60
- subscribe: :command,
89
+ subscribe: :command,
61
90
  # attach a display name to an existing subscriber
62
- add_name: :command,
91
+ add_name: :command,
63
92
  # a subscriber confirms their address from the emailed link
64
- confirm: :command,
93
+ confirm: :command,
65
94
  # a subscriber leaves from the emailed link
66
- unsubscribe: :command
95
+ unsubscribe: :command,
96
+ # Optional: the subscriber states that await the emailed confirm
97
+ # link, spelled "Subscriber.awaiting_confirmation"
98
+ awaiting_confirmation: :mark,
99
+ # Optional: the subscriber states that receive each issue sent
100
+ receives_issues: :mark,
101
+ # Optional: the subscriber states of someone who has left
102
+ left: :mark,
103
+ # Optional: how many seconds the emailed confirm link stays valid,
104
+ # spelled "Subscriber.confirm_window"
105
+ confirm_window: :duration,
106
+ # Optional: how many seconds an emailed unsubscribe link stays valid
107
+ unsubscribe_window: :duration
67
108
  }.freeze,
68
109
  NEWSLETTER_ISSUES => {
69
110
  # mark an issue sent; the issue aggregate is the one it names
@@ -74,17 +115,31 @@ module Hecks
74
115
  }.freeze,
75
116
  PAYMENTS => {
76
117
  # start a payment; the aggregate it names is the payment
77
- initiate: :command,
118
+ initiate: :command,
78
119
  # the processor reports the money arrived
79
- succeeded: :port_operation,
120
+ succeeded: :port_operation,
80
121
  # the processor reports the payment failed or expired
81
- failed: :port_operation
122
+ failed: :port_operation,
123
+ # Optional: the lifecycle states of the payment that hold a seat,
124
+ # spelled "Payment.holds_seat" (Aggregate.mark_name); resolves to the
125
+ # states of that aggregate's lifecycle `mark :holds_seat`
126
+ holds_seat: :mark,
127
+ # Optional: the failure reason a lapsed checkout hold records, spelled
128
+ # "Payment.lapse_reason" (the string `default:` of that attribute)
129
+ lapse_reason: :text
82
130
  }.freeze,
83
131
  REGISTRATIONS => {
84
132
  # schedule a session; the aggregate it names is the event
85
- schedule: :command,
133
+ schedule: :command,
86
134
  # a guest asks for a place; the aggregate it names is the registration
87
- request: :command
135
+ request: :command,
136
+ # Optional: the registration attribute that stamps when it was asked for,
137
+ # spelled "Registration.requested_at"
138
+ registered_at: :attribute,
139
+ # Optional: the registration states that give a seat back, spelled
140
+ # "Registration.frees_seat" (Aggregate.mark_name); resolves to the
141
+ # states of that aggregate's lifecycle `mark :frees_seat`
142
+ frees_seat: :mark
88
143
  }.freeze,
89
144
  PAYMENT_CONNECTION => {
90
145
  # link the processor account; the aggregate it names is the connection
@@ -101,8 +156,35 @@ module Hecks
101
156
  enable: :command,
102
157
  # switch taking payments off
103
158
  disable: :command
159
+ }.freeze,
160
+ PRIVACY => {
161
+ # flag one attribute of a domain sensitive
162
+ mark_sensitive: :command,
163
+ # every marking of one domain, for masking a read
164
+ markings_for: :query
165
+ }.freeze,
166
+ SUBJECT_KEYS => {
167
+ # the key record of one subject, if any
168
+ key_for: :query,
169
+ # record the subject's key as destroyed
170
+ shred: :command
171
+ }.freeze,
172
+ CHECKOUT => {
173
+ # Optional: how many seconds either side of now a signed webhook's
174
+ # timestamp may sit, spelled "WebhookReceipt.tolerance"
175
+ webhook_tolerance: :duration,
176
+ # Optional: how many seconds a session holds its seat
177
+ session_hold: :duration
104
178
  }.freeze
105
179
  }.freeze
180
+
181
+ # The keys a capability's `provides` must name.
182
+ #
183
+ # @param capability [String] a `CONTRACTS` key
184
+ # @return [Array<Symbol>] the keys whose kind is not optional
185
+ def self.required_keys(capability)
186
+ CONTRACTS.fetch(capability).reject { |_, kind| OPTIONAL_KINDS.include?(kind) }.keys
187
+ end
106
188
  end
107
189
  end
108
190
  end
@@ -14,13 +14,22 @@ module Hecks
14
14
 
15
15
  attr_reader :hecks_name, :attributes, :emits, :direction, :answers, :refuses, :to
16
16
 
17
+ # Marks this operation as the one the hecksagon picks (`ask_via`) when its ask name is
18
+ # declared on more than one port of the aggregate.
19
+ #
20
+ # @return [true]
21
+ def choose! = @chosen = true
22
+
23
+ # @return [Boolean] whether the hecksagon picked this operation with `ask_via`
24
+ def chosen? = @chosen == true
25
+
17
26
  # `:inbound` (`tells`) is an external fact arriving as an event; it emits and nothing more.
18
27
  # `:outbound` (`asks`) is the domain wanting something from outside, naming both
19
28
  # `answers` and `refuses` so the failure is visible to the model.
20
29
  #
21
30
  # Every optional keyword and what it holds when the declaration omits it.
22
31
  FIELD_DEFAULTS = {
23
- attributes: [], emits: [], direction: :inbound, answers: nil, refuses: nil, to: nil
32
+ attributes: [], emits: [], direction: :inbound, answers: nil, refuses: nil, to: nil, chosen: false
24
33
  }.freeze
25
34
 
26
35
  # @param name [String, Symbol] the operation's declared name
@@ -30,6 +39,7 @@ module Hecks
30
39
  # @param answers [String, nil] an outbound operation's event for the adapter's answer
31
40
  # @param refuses [String, nil] an outbound operation's event for the adapter's refusal
32
41
  # @param to [String, nil] the aggregate this operation routes to, if any
42
+ # @param chosen [Boolean] whether the hecksagon picked this operation with `ask_via`
33
43
  def initialize(name:, **given)
34
44
  fields = KeywordFields.fill(given, FIELD_DEFAULTS)
35
45
  @hecks_name = name.to_s
@@ -48,15 +58,17 @@ module Hecks
48
58
  # @return [Boolean] whether this operation is a `tells`/`operation`
49
59
  def inbound? = @direction == :inbound
50
60
 
51
- # `direction`/`answers`/`refuses`/`to` are merged in only when set: the Rust parser
52
- # emits none of them, so an unconditional key would break parser_parity_spec.
61
+ # `direction`/`answers`/`refuses`/`to`/`chosen` are merged in only when set: the Rust parser
62
+ # emits none of them unprompted, so an unconditional key would break parser_parity_spec.
53
63
  #
54
64
  # @return [Hash] the declared emission, plus `direction`/`answers`/`refuses` for an
55
- # outbound operation and `to` when a routing target is declared
65
+ # outbound operation, `to` when a routing target is declared, and `chosen` when the
66
+ # hecksagon picked it with `ask_via`
56
67
  def to_h
57
68
  shape = super
58
69
  shape = shape.merge(direction: @direction.to_s, answers: @answers, refuses: @refuses) unless inbound?
59
70
  shape = shape.merge(to: @to) if @to
71
+ shape = shape.merge(chosen: true) if chosen?
60
72
  shape
61
73
  end
62
74
  end
@@ -8,13 +8,17 @@ module Hecks
8
8
  class AdapterBuilder
9
9
  GRAMMAR_CONTEXT = "Adapter".freeze
10
10
 
11
+ # Marks a field that gives no `default:`; `nil` and `false` are defaults too.
12
+ NO_DEFAULT = Object.new.freeze
13
+
11
14
  include WordGate
12
15
 
13
16
  # @param name [String] the adapter's name, as written after `Hecks.adapter`
14
17
  def initialize(name)
15
- @name = name
16
- @fields = []
17
- @secrets = []
18
+ @name = name
19
+ @fields = []
20
+ @secrets = []
21
+ @defaults = {}
18
22
  end
19
23
 
20
24
  # Names the port this adapter implements; a repeated call replaces the earlier name.
@@ -26,8 +30,13 @@ module Hecks
26
30
  # Declares one plain setting a `.world` file may supply for this adapter.
27
31
  #
28
32
  # @param name [Symbol, String] the setting's name, such as `:database`
33
+ # @param default [Object] the value used when the world gives none; a string may hold
34
+ # `{name}` or `{storage_name}`, filled from the aggregate the adapter is built for
29
35
  # @return [Array<Symbol>] every field declared so far, this one last
30
- def field(name) = @fields << name.to_sym
36
+ def field(name, default: NO_DEFAULT)
37
+ @defaults[name.to_sym] = default unless default.equal?(NO_DEFAULT)
38
+ @fields << name.to_sym
39
+ end
31
40
 
32
41
  # Declares one setting whose value is a secret, such as an API token.
33
42
  #
@@ -35,12 +44,15 @@ module Hecks
35
44
  # @return [Array<Symbol>] every secret declared so far, this one last
36
45
  def secret(name) = @secrets << name.to_sym
37
46
 
38
- # Assembles the collected port, fields and secrets, judged by the adapter language.
47
+ # Assembles the collected port, fields, defaults and secrets, judged by the adapter
48
+ # language.
39
49
  #
40
50
  # @return [Bluebook::Adapter] the adapter, returned once the language accepts it
41
51
  # @raise [Bluebook::DSL::Malformed] if the adapter language refuses the declaration
42
52
  def build
43
- MetaValidator.call_adapter(Adapter.new(name: @name, port: @port, fields: @fields, secrets: @secrets))
53
+ MetaValidator.call_adapter(
54
+ Adapter.new(name: @name, port: @port, fields: @fields, secrets: @secrets, defaults: @defaults)
55
+ )
44
56
  end
45
57
 
46
58
  # Evaluates an `Hecks.adapter` block against a fresh builder and returns what it built.
@@ -29,6 +29,18 @@ module Hecks
29
29
  self
30
30
  end
31
31
 
32
+ # Picks which port answers an ask this aggregate declares on more than one port.
33
+ #
34
+ # @param name [String, Symbol] the ask's declared name, such as `"Probe"`
35
+ # @param port [String, Symbol] the port whose `asks` of that name is the one policies reach
36
+ # @return [BindingProxy] self
37
+ # @raise [Bluebook::DSL::Malformed] if no such aggregate, port or ask is declared yet
38
+ def ask_via(name, port:)
39
+ domain, aggregate_name = @fqn.split("::")
40
+ AskResolution.pick!(Hecks.current_registry.bluebook(domain), aggregate_name, name, port)
41
+ self
42
+ end
43
+
32
44
  def method_missing(verb, *args, **kwargs, &block)
33
45
  # A bare call (no args/kwargs/block) starts a Privacy marking chain instead
34
46
  # of a Bind — no real `.hecksagon` bind is ever called with zero arguments,
@@ -68,6 +80,7 @@ module Hecks
68
80
  operation.attributes.select(&:reference?).each { |attribute| attribute.type.declared_in = aggregate_ir }
69
81
  end
70
82
  aggregate_ir.add_port(built)
83
+ AskResolution.bind_resolved(Hecks.current_registry.bluebook(@fqn.split("::").first))
71
84
  end
72
85
  end
73
86
 
@@ -0,0 +1,98 @@
1
+ module Hecks
2
+ module Bluebook
3
+ module DSL
4
+ class BluebookBuilder
5
+ module Validation
6
+ # Checks that every name and dotted path a computed `sets` source reads resolves, once
7
+ # every aggregate and value object in the chapter exists.
8
+ module ComputedSources
9
+ private
10
+
11
+ # Refuses a computed source whose operand path names nothing declared, runs through
12
+ # a list or a reference, or ends on a value object with more than one field.
13
+ def validate_computed_sources!(bluebook)
14
+ value_objects = bluebook.aggregates.flat_map(&:value_objects).to_h { |vo| [vo.hecks_name.to_s, vo] }
15
+ bluebook.aggregates.each do |aggregate|
16
+ aggregate.commands.each { |command| check_computed_command!(aggregate, command, value_objects) }
17
+ end
18
+ end
19
+
20
+ def check_computed_command!(aggregate, command, value_objects)
21
+ command.mutations.select { |mutation| mutation.source.is_a?(Computed) }.each do |mutation|
22
+ operand_paths(Expression::Resolver.parse(mutation.source.text)).each do |path|
23
+ check_operand_path!(PathCheck.new(aggregate, command, mutation, value_objects), path)
24
+ end
25
+ end
26
+ end
27
+
28
+ # Where a path is read: the aggregate and command it belongs to, the mutation, and the
29
+ # chapter's value objects by name.
30
+ PathCheck = Struct.new(:aggregate, :command, :mutation, :value_objects)
31
+ private_constant :PathCheck
32
+
33
+ def operand_paths(node)
34
+ return [node.path] if node.is_a?(Expression::Resolver::Lookup)
35
+ return [] unless node.is_a?(Struct)
36
+
37
+ node.members.flat_map { |member| operand_paths(node[member]) }
38
+ end
39
+
40
+ def check_operand_path!(check, path)
41
+ head, *segments = path.split(".")
42
+ attribute = operand_head(check, head)
43
+ return if attribute.nil? && segments.empty? && stored_scalar?(check, head)
44
+
45
+ refuse_operand!(check, path, "names no argument or field of #{check.aggregate.hecks_name}") unless attribute
46
+ walk_operand_path!(check, path, attribute, segments)
47
+ end
48
+
49
+ # The command's argument, else the owner's field.
50
+ def operand_head(check, name)
51
+ check.command.attributes.find { |attr| attr.name.to_s == name } ||
52
+ check.aggregate.attributes.find { |attr| attr.name.to_s == name }
53
+ end
54
+
55
+ # A lifecycle or projected field is a plain scalar of the record, so a name that stops
56
+ # there needs no further check.
57
+ def stored_scalar?(check, name)
58
+ check.aggregate.lifecycle&.field.to_s == name || check.aggregate.projected_fields.any? { |f| f.name.to_s == name }
59
+ end
60
+
61
+ def walk_operand_path!(check, path, attribute, segments)
62
+ segments.each do |segment|
63
+ refuse_operand!(check, path, "runs through #{attribute.name}, which is a list") if attribute.list?
64
+ refuse_operand!(check, path, "runs through #{attribute.name}, which is a reference") if attribute.reference?
65
+ attribute = field_of(check, path, attribute, segment)
66
+ end
67
+ refuse_operand!(check, path, "is a list") if attribute.list?
68
+ refuse_operand!(check, path, "is a reference") if attribute.reference?
69
+ refuse_multi_field!(check, path, attribute)
70
+ end
71
+
72
+ def field_of(check, path, attribute, segment)
73
+ holder = check.value_objects[attribute.type.to_s]
74
+ refuse_operand!(check, path, "runs through #{attribute.name}, which is not a value object") unless holder
75
+ holder.attributes.find { |field| field.name.to_s == segment } ||
76
+ refuse_operand!(check, path, "names #{segment}, which #{holder.hecks_name} does not declare")
77
+ end
78
+
79
+ # A name that stops on a value object reads as its one field, so it needs exactly one.
80
+ def refuse_multi_field!(check, path, attribute)
81
+ holder = check.value_objects[attribute.type.to_s]
82
+ return unless holder && holder.attributes.size > 1
83
+
84
+ example = "#{path}.#{holder.attributes.first.name}"
85
+ refuse_operand!(check, path, "is a #{holder.hecks_name} with several fields — name one, as #{example}")
86
+ end
87
+
88
+ def refuse_operand!(check, path, reason)
89
+ raise Malformed,
90
+ "#{check.aggregate.hecks_name}.#{check.command.hecks_name}'s sets :#{check.mutation.target} reads " \
91
+ "#{path}, which #{reason}"
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,42 @@
1
+ module Hecks
2
+ module Bluebook
3
+ module DSL
4
+ class BluebookBuilder
5
+ module Validation
6
+ # The names a `for_each` policy's `with:` may read off a fan-out row, mirroring what
7
+ # `PolicyInterpreter::Arguments#trigger_args` offers a projection at runtime.
8
+ module FanOutRows
9
+ private
10
+
11
+ # The names a row of the policy's `for_each` query carries: the row's id, the
12
+ # aggregate's attributes, lifecycle field and projected fields, and the key the
13
+ # trigger addresses a row by. Nil when the query's aggregate is not in this chapter,
14
+ # which leaves the source unchecked.
15
+ #
16
+ # @return [Array<Symbol>, nil]
17
+ def fan_out_row_names(policy, aggregates, target)
18
+ return nil if policy.for_each.to_s.empty? || policy.for_each.to_s.include?("::")
19
+
20
+ aggregate_name = policy.for_each.to_s.split(".", 2).first
21
+ aggregate = aggregates.find { |candidate| candidate.hecks_name == aggregate_name }
22
+ return nil unless aggregate
23
+
24
+ [:id, *row_field_names(aggregate), *row_key_names(target, aggregate_name)]
25
+ end
26
+
27
+ def row_field_names(aggregate)
28
+ names = aggregate.attributes.map(&:name) + aggregate.projected_fields.map(&:name)
29
+ names += aggregate.identity_heads.map(&:to_sym)
30
+ aggregate.lifecycle ? names << aggregate.lifecycle.field.to_sym : names
31
+ end
32
+
33
+ def row_key_names(target, aggregate_name)
34
+ key = target&.addressing_key_for(aggregate_name)
35
+ key ? [key.to_sym] : []
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -4,7 +4,8 @@ module Hecks
4
4
  class BluebookBuilder
5
5
  module Validation
6
6
  # Checks that each `projects` reference resolves to an aggregate declaring the remote
7
- # field as a scalar, once every aggregate in the chapter exists.
7
+ # field as a scalar or single-field value object, once every aggregate in the chapter
8
+ # exists.
8
9
  module ProjectedFields
9
10
  private
10
11
 
@@ -65,8 +66,19 @@ module Hecks
65
66
  "value, never a reference, a value object, or a list"
66
67
  end
67
68
 
69
+ # A scalar, or a single-field value object, which a projection unwraps to its one
70
+ # scalar (ADR 0025 addendum); a multi-field value object, a reference and a list are
71
+ # not.
68
72
  def projectable_scalar?(target, attribute)
69
- !attribute.list? && !attribute.reference? && target.value_object(attribute.type).nil?
73
+ return false if attribute.list? || attribute.reference?
74
+
75
+ value_object = target.value_object(attribute.type)
76
+ value_object.nil? || single_scalar_field?(value_object)
77
+ end
78
+
79
+ def single_scalar_field?(value_object)
80
+ sole = value_object.sole_attribute
81
+ !sole.nil? && !sole.list? && !sole.reference?
70
82
  end
71
83
  end
72
84
  end
@@ -5,6 +5,15 @@ module Hecks
5
5
  module Validation
6
6
  # Checks every `provides` row of a chapter against `Capabilities::CONTRACTS`.
7
7
  module Provisions
8
+ # The checks for the kinds whose verb is not a command or a query.
9
+ SPECIAL_KIND_CHECKS = {
10
+ port_operation: :validate_provided_port_operation!,
11
+ mark: :validate_provided_mark!,
12
+ duration: :validate_provided_duration!,
13
+ text: :validate_provided_text!,
14
+ attribute: :validate_provided_attribute!
15
+ }.freeze
16
+
8
17
  private
9
18
 
10
19
  # Checks every `provides` row against `Capabilities::CONTRACTS`.
@@ -26,14 +35,26 @@ module Hecks
26
35
 
27
36
  def refuse_wrong_provision_keys!(bluebook, capability, rows, contract)
28
37
  keys = rows.map { |row| row.key.to_sym }
29
- return if keys.sort == contract.keys.sort
38
+ required = Capabilities.required_keys(capability)
39
+ return if provision_keys_valid?(keys, required, contract.keys)
30
40
 
31
41
  raise Malformed, "#{bluebook.name} provides #{capability.inspect} with #{keys.join(", ")}, but " \
32
- "#{capability} needs exactly #{contract.keys.join(", ")}"
42
+ "#{capability} needs #{provision_keys_phrase(required, contract.keys - required)}"
43
+ end
44
+
45
+ def provision_keys_valid?(keys, required, allowed)
46
+ (keys - allowed).empty? && (required - keys).empty? && keys.uniq.size == keys.size
47
+ end
48
+
49
+ def provision_keys_phrase(required, optional)
50
+ return "exactly #{required.join(", ")}" if optional.empty?
51
+
52
+ "#{required.join(", ")} and may add #{optional.join(", ")}"
33
53
  end
34
54
 
35
55
  def validate_provided_verb!(bluebook, capability, row, kind)
36
- return validate_provided_port_operation!(bluebook, capability, row) if kind == :port_operation
56
+ special = SPECIAL_KIND_CHECKS[kind]
57
+ return send(special, bluebook, capability, row) if special
37
58
 
38
59
  aggregate_name, member = row.verb.split(".", 2)
39
60
  aggregate = bluebook.aggregate(aggregate_name)
@@ -54,6 +75,51 @@ module Hecks
54
75
  "which is not spelled \"Aggregate.Port.Operation\" over an aggregate this chapter declares"
55
76
  end
56
77
 
78
+ # A mark verb is spelled "Aggregate.mark_name" and must name a mark declared on that
79
+ # aggregate's lifecycle.
80
+ def validate_provided_mark!(bluebook, capability, row)
81
+ aggregate_name, mark = row.verb.split(".", 2)
82
+ aggregate = bluebook.aggregate(aggregate_name)
83
+ return if aggregate && mark && aggregate.lifecycle&.marked(mark)
84
+
85
+ raise Malformed, "#{bluebook.name} provides #{capability.inspect} #{row.key}: #{row.verb.inspect}, " \
86
+ "which names no lifecycle mark this chapter declares (spelled " \
87
+ "\"Aggregate.mark_name\", with `mark :mark_name, ...` in that aggregate's lifecycle)"
88
+ end
89
+
90
+ # A duration verb is spelled "Aggregate.attribute" and must name an attribute of
91
+ # that aggregate whose `default:` is a whole, positive number of seconds (a bare
92
+ # integer, or the `{ value: N }` fill of a one-field value object).
93
+ def validate_provided_duration!(bluebook, capability, row)
94
+ return if Capabilities.duration_of(bluebook, row.verb)
95
+
96
+ raise Malformed, "#{bluebook.name} provides #{capability.inspect} #{row.key}: #{row.verb.inspect}, " \
97
+ "which names no attribute with a whole-seconds default this chapter declares " \
98
+ "(spelled \"Aggregate.attribute\", with `attribute :attribute, Seconds, " \
99
+ "default: { value: 1800 }` in that aggregate)"
100
+ end
101
+
102
+ # A text verb is spelled "Aggregate.attribute" and must name an attribute of that
103
+ # aggregate whose `default:` is a non-empty string (bare, or a one-field value object's
104
+ # `{ value: "..." }` fill).
105
+ def validate_provided_text!(bluebook, capability, row)
106
+ return if Capabilities.text_of(bluebook, row.verb)
107
+
108
+ raise Malformed, "#{bluebook.name} provides #{capability.inspect} #{row.key}: #{row.verb.inspect}, " \
109
+ "which names no attribute with a text default this chapter declares " \
110
+ "(spelled \"Aggregate.attribute\", with `attribute :attribute, Reason, " \
111
+ "default: { value: \"word\" }` in that aggregate)"
112
+ end
113
+
114
+ # An attribute verb is spelled "Aggregate.attribute" and must name an attribute of that
115
+ # aggregate.
116
+ def validate_provided_attribute!(bluebook, capability, row)
117
+ return if Capabilities.attribute_of(bluebook, row.verb)
118
+
119
+ raise Malformed, "#{bluebook.name} provides #{capability.inspect} #{row.key}: #{row.verb.inspect}, " \
120
+ "which names no attribute this chapter declares (spelled \"Aggregate.attribute\")"
121
+ end
122
+
57
123
  def provided_member_names(aggregate, kind)
58
124
  kind == :command ? aggregate.commands.map(&:hecks_name) : aggregate.queries.map(&:name)
59
125
  end
@@ -11,18 +11,20 @@ module Hecks
11
11
  WithContext = Struct.new(:lookup, :aggregates, :correlation_heads)
12
12
  # One `with:` to check: the command it targets, the event it reads from, the spec
13
13
  # itself, how a refusal names it, and the process manager when it is a saga leg.
14
- WithSpec = Struct.new(:command_ref, :event_name, :with_spec, :label, :process_manager)
14
+ WithSpec = Struct.new(:command_ref, :event_name, :with_spec, :label, :process_manager, :row_names)
15
15
  # What a `with:` source may read: the correlation key, the emitter's identity heads and
16
- # the event shapes whose fields are legal.
17
- Readable = Struct.new(:correlation, :identity, :shapes)
16
+ # the event shapes whose fields are legal, and a fan-out row's names (empty off a
17
+ # fan-out).
18
+ Readable = Struct.new(:correlation, :identity, :shapes, :row)
18
19
  private_constant :WithContext, :WithSpec, :Readable
19
20
 
20
21
  private
21
22
 
22
23
  # Checks `with:` projections against the source event's shape and the target's fields.
23
24
  # Same-chapter only: an unresolvable cross-chapter source or target is skipped.
24
- # A for_each policy's source is a query row with no known shape, so only its target
25
- # is checked.
25
+ # A for_each policy's source is the event plus a query row; the row's names are known
26
+ # only when the query's aggregate is in this chapter, otherwise only the target is
27
+ # checked.
26
28
  def validate_with_projections!(policies, process_managers, aggregates)
27
29
  context = WithContext.new(command_lookup(aggregates), aggregates, correlation_heads(process_managers))
28
30
 
@@ -37,9 +39,13 @@ module Hecks
37
39
  def check_policy_with!(policy, context)
38
40
  return if policy.with_spec.to_a.empty?
39
41
 
40
- source_event = policy.for_each.to_s.empty? ? policy.on_event : nil
41
- check_with_spec!(WithSpec.new(policy.trigger_command, source_event, policy.with_spec,
42
- "#{policy.name}'s trigger", nil), context)
42
+ check_with_spec!(policy_with_spec(policy, context), context)
43
+ end
44
+
45
+ def policy_with_spec(policy, context)
46
+ row = fan_out_row_names(policy, context.aggregates, context.lookup[policy.trigger_command])
47
+ source_event = policy.for_each.to_s.empty? || row ? policy.on_event : nil
48
+ WithSpec.new(policy.trigger_command, source_event, policy.with_spec, "#{policy.name}'s trigger", nil, row)
43
49
  end
44
50
 
45
51
  def check_dispatch_with!(process_manager, handler, dispatch, context)
@@ -47,7 +53,7 @@ module Hecks
47
53
 
48
54
  label = "#{process_manager.name}'s dispatch #{dispatch.command_name}"
49
55
  check_with_spec!(WithSpec.new(dispatch.command_name, handler.event_type, dispatch.with_spec,
50
- label, process_manager), context)
56
+ label, process_manager, nil), context)
51
57
  end
52
58
 
53
59
  # Checks one `with:` spec: each key must be a field the target command accepts,
@@ -73,7 +79,7 @@ module Hecks
73
79
  identity = pm.nil? && event_name ? event_identity_heads_for(event_name, aggregates) : []
74
80
  shapes = [event_name && event_shape_for(event_name, aggregates),
75
81
  pm && event_shape_for(pm.starts_on, aggregates)].compact
76
- Readable.new(pm&.correlates_by && pm.correlation_head, identity, shapes)
82
+ Readable.new(pm&.correlates_by && pm.correlation_head, identity, shapes, spec.row_names.to_a)
77
83
  end
78
84
 
79
85
  def refuse_undeclared_with_field!(spec, field)
@@ -84,6 +90,7 @@ module Hecks
84
90
  def refuse_unreadable_with_source!(spec, source, readable)
85
91
  return unless source.is_a?(::Symbol)
86
92
  return if source == readable.correlation || readable.identity.include?(source)
93
+ return if readable.row.include?(source)
87
94
  return if readable.shapes.empty? || shape_reads?(readable.shapes, source)
88
95
 
89
96
  raise Malformed,
@@ -1,9 +1,11 @@
1
1
  require_relative "validation/provisions"
2
2
  require_relative "validation/references"
3
3
  require_relative "validation/event_shapes"
4
+ require_relative "validation/fan_out_rows"
4
5
  require_relative "validation/with_projections"
5
6
  require_relative "validation/query_hops"
6
7
  require_relative "validation/projected_fields"
8
+ require_relative "validation/computed_sources"
7
9
  require_relative "validation/correlation_keys"
8
10
 
9
11
  module Hecks
@@ -17,9 +19,11 @@ module Hecks
17
19
  include Provisions
18
20
  include References
19
21
  include EventShapes
22
+ include FanOutRows
20
23
  include WithProjections
21
24
  include QueryHops
22
25
  include ProjectedFields
26
+ include ComputedSources
23
27
  include CorrelationKeys
24
28
 
25
29
  # Runs every whole-chapter check against one assembled chapter.
@@ -58,6 +62,7 @@ module Hecks
58
62
  infer_hop_query_arguments!(bluebook)
59
63
  validate_query_hops!(bluebook)
60
64
  validate_projected_fields!(bluebook)
65
+ validate_computed_sources!(bluebook)
61
66
  end
62
67
  end
63
68
  end