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
@@ -130,7 +130,7 @@ module Hecks
130
130
  @trace_last = now
131
131
  end
132
132
 
133
- # Default `install_doors` so `QualityControl::Target` and friends are top-level constants.
133
+ # Default `install_driving` so `QualityControl::Target` and friends are top-level constants.
134
134
  def boot_ledger
135
135
  Hecks.boot(@domain_dir)
136
136
  rescue StandardError => e
@@ -164,7 +164,7 @@ Hecks.bluebook "QualityControl" do
164
164
  role "QA engineer"
165
165
  goal "Hand a chapter back to the rotation, stamped with when, what this pass was worth, and how the clean streak moves"
166
166
 
167
- # `now` is the argument and `last_swept` the field, so the door can fill the clock.
167
+ # `now` is the argument and `last_swept` the field, so the entry point can fill the clock.
168
168
  #
169
169
  # `next_streak` is the caller's to compute (`clean_streak + 1` if every check held and none
170
170
  # was ever surprised, else 0): `Target` cannot read `Sweep` state and no policy payload can
@@ -530,6 +530,7 @@ Hecks.bluebook "QualityControl" do
530
530
  end
531
531
 
532
532
  lifecycle :status, default: "sweeping" do
533
+ mark :failure, "abandoned"
533
534
  transition "Check" => "sweeping", from: "sweeping"
534
535
  transition "Waive" => "sweeping", from: "sweeping"
535
536
  transition "Conclude" => "concluded", from: "sweeping"
@@ -778,7 +779,7 @@ Hecks.bluebook "QualityControl" do
778
779
  invariant("a bug sequence is positive") { value.positive? }
779
780
  end
780
781
 
781
- # Prefixed on purpose: `Doors::RubyDoor.install` puts every aggregate name on Object, so a
782
+ # Prefixed on purpose: `Adapters::Driving::Ruby.install` puts every aggregate name on Object, so a
782
783
  # value object sharing a word with any aggregate resolves to that class once the other domain
783
784
  # boots.
784
785
  value_object "BugTitle" do
@@ -1456,6 +1457,7 @@ Hecks.bluebook "QualityControl" do
1456
1457
  # filing. `Submit` is non-creating, so `TicketSubmitted` carries `{ id: ... }`, which a
1457
1458
  # reference slot takes.
1458
1459
  lifecycle :status, default: "raised" do
1460
+ mark :failure, "refused", "abandoned"
1459
1461
  transition "Submit" => "submitting", from: ["raised", "refused"]
1460
1462
  transition "Filed" => "filed", from: "submitting"
1461
1463
  transition "Refused" => "refused", from: "submitting"
@@ -1866,6 +1868,7 @@ Hecks.bluebook "QualityControl" do
1866
1868
  # proof-only PR can be closed while still "opened" (`retire_if_settled?` hit
1867
1869
  # `LifecycleRefused`).
1868
1870
  lifecycle :status, default: "opened" do
1871
+ mark :failure, "needs_fix"
1869
1872
  transition "Land" => "landed", from: ["opened", "needs_fix"]
1870
1873
  transition "Regress" => "needs_fix", from: "landed"
1871
1874
  transition "Merge" => "merged", from: ["opened", "landed", "needs_fix"]
@@ -1983,6 +1986,7 @@ Hecks.bluebook "QualityControl" do
1983
1986
  end
1984
1987
 
1985
1988
  lifecycle :status, default: "running" do
1989
+ mark :failure, "red"
1986
1990
  transition "Passed" => "green", from: "running"
1987
1991
  transition "Failed" => "red", from: "running"
1988
1992
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hecks/vocabulary"
4
+
5
+ module Hecks
6
+ module Release
7
+ # Whether the commit that set the version carries the owner's approval of the release.
8
+ #
9
+ # Releases are the owner's decision. A bump on `main` reaches `stable` by itself, so the bump
10
+ # commit must say, in its message, that a release is wanted: a trailer line named by the
11
+ # `release_trailer` of the `Lane` row a release is cut from, `Release-Approved-By: <name>`. The
12
+ # decision is read from git history alone, so it can be made again on any later run. It reads
13
+ # only the Vocabulary table, so `release.yml` can run it with the runner's own Ruby.
14
+ module Approval
15
+ module_function
16
+
17
+ # @return [String] the trailer key, read from the `Lane` row that feeds a tag
18
+ # @raise [KeyError] when that row names no trailer
19
+ def trailer
20
+ row = Hecks::Vocabulary.rows("Lane").find { |lane| !lane["feeds"].to_s.empty? }
21
+ key = row && row["release_trailer"].to_s
22
+ raise KeyError, "the Lane row a release is cut from names no release_trailer" if key.nil? || key.empty?
23
+
24
+ key
25
+ end
26
+
27
+ # @param message [String] a commit message
28
+ # @return [String, nil] who approved, from the first trailer line with a name, or nil
29
+ def approver(message)
30
+ line = message.to_s.lines.map(&:strip).find { |text| text.match?(/\A#{Regexp.escape(trailer)}:\s*\S/) }
31
+ line&.sub(/\A[^:]*:\s*/, "")
32
+ end
33
+
34
+ # @param message [String] the message of the commit that set the version
35
+ # @param tag_exists [Boolean] whether the release's tag already stands on origin: a release
36
+ # that was begun was approved when it began, so finishing it asks again for nothing
37
+ # @param explicit [String, nil] a name the owner passed to a by-hand run
38
+ # @return [Array(Boolean, String)] whether the release may go ahead, and the words to print
39
+ def verdict(message:, tag_exists: false, explicit: nil)
40
+ return [true, "approved by #{explicit.strip} (by-hand run)"] unless explicit.to_s.strip.empty?
41
+ return [true, "the tag already stands; finishing a release that was begun"] if tag_exists
42
+
43
+ who = approver(message)
44
+ return [true, "approved by #{who} (#{trailer} trailer)"] if who
45
+
46
+ [false, "the commit that set the version has no '#{trailer}: <name>' line in its message, so it " \
47
+ "is not a release the owner asked for. Nothing was tagged or pushed. To release it, " \
48
+ "re-run by hand with the approval: gh workflow run release.yml -f tag=vX.Y.Z -f approved_by=<name>"]
49
+ end
50
+
51
+ # Reads the decision's inputs from the environment `release.yml` gives it.
52
+ #
53
+ # @param env [#[]] `COMMIT_MESSAGE`, `TAG_EXISTS` (true/false), `APPROVED_BY`
54
+ # @param out [#puts] where the words go
55
+ # @return [Integer] the exit status: 0 to go ahead, 1 to refuse
56
+ def check(env: ENV, out: $stdout)
57
+ ok, words = verdict(message: env["COMMIT_MESSAGE"], tag_exists: env["TAG_EXISTS"] == "true",
58
+ explicit: env["APPROVED_BY"])
59
+ out.puts(ok ? "release approval: #{words}" : "::error::#{words}")
60
+ ok ? 0 : 1
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,12 +1,13 @@
1
- require_relative "../doors/handle"
1
+ require_relative "../adapters/driving/handle"
2
2
 
3
3
  module Hecks
4
4
  class Router
5
5
  class NamespaceInstaller
6
6
  # One aggregate's repository and event log, read through its dispatcher.
7
- Door = Struct.new(:dispatcher, :domain, :aggregate) do
7
+ AggregateReader = Struct.new(:dispatcher, :domain, :aggregate) do
8
8
  # @param entry [Router::Entry] a router entry
9
- # @return [Door, nil] the door for the entry's aggregate; nil for a query/command-only entry
9
+ # @return [AggregateReader, nil] the reader for the entry's aggregate; nil for a
10
+ # query/command-only entry
10
11
  # whose owner isn't a real aggregate root
11
12
  def self.for(entry)
12
13
  domain = entry.fqn.domain
@@ -29,7 +30,7 @@ module Hecks
29
30
  def all = repository.all.map { |instance| handle(instance) }
30
31
 
31
32
  def handle(instance)
32
- Doors::Handle.new(dispatcher: dispatcher, domain: domain, aggregate: aggregate, instance: instance)
33
+ Adapters::Driving::Handle.new(dispatcher: dispatcher, domain: domain, aggregate: aggregate, instance: instance)
33
34
  end
34
35
  end
35
36
  end
@@ -1,6 +1,6 @@
1
1
  require_relative "../fqn"
2
2
  require_relative "options_proxy"
3
- require_relative "door"
3
+ require_relative "aggregate_reader"
4
4
 
5
5
  module Hecks
6
6
  class Router
@@ -12,11 +12,11 @@ module Hecks
12
12
  end
13
13
 
14
14
  # Installs every current-version route as a namespace constant/method, plus
15
- # the aggregate CRUD door and short `Aggregate.verb` shortcuts.
15
+ # the aggregate CRUD module and short `Aggregate.verb` shortcuts.
16
16
  def install!
17
17
  current_entries.each { |entry| install_namespace_entry(entry) }
18
18
  install_shortcuts!
19
- install_aggregate_doors!
19
+ install_aggregate_modules!
20
20
  self
21
21
  end
22
22
 
@@ -48,26 +48,26 @@ module Hecks
48
48
  end
49
49
 
50
50
  # `.find`/`.all`/`.count`/`.events`/`.repository`, matching `Hecks.boot`'s
51
- # door; grouped by (realm, domain, aggregate) since the five don't vary by verb.
52
- def install_aggregate_doors!
51
+ # module; grouped by (realm, domain, aggregate) since the five don't vary by verb.
52
+ def install_aggregate_modules!
53
53
  current_entries.reject { |entry| entry.fqn.aggregate.nil? }
54
54
  .group_by { |entry| [entry.fqn.realm, entry.fqn.domain, entry.fqn.aggregate] }
55
- .each_value { |entries| install_aggregate_door(entries.first) }
55
+ .each_value { |entries| install_aggregate_module(entries.first) }
56
56
  end
57
57
 
58
- def install_aggregate_door(entry)
59
- door = Door.for(entry)
60
- return unless door
58
+ def install_aggregate_module(entry)
59
+ reader = AggregateReader.for(entry)
60
+ return unless reader
61
61
 
62
- define_door_methods(namespace_for(entry.fqn.realm, entry.fqn.domain, entry.fqn.aggregate), door)
62
+ define_module_methods(namespace_for(entry.fqn.realm, entry.fqn.domain, entry.fqn.aggregate), reader)
63
63
  end
64
64
 
65
- def define_door_methods(target, door)
66
- target.define_singleton_method(:repository) { door.repository }
67
- target.define_singleton_method(:count) { door.repository.count }
68
- target.define_singleton_method(:events) { door.events }
69
- target.define_singleton_method(:find) { |id| door.find(id) }
70
- target.define_singleton_method(:all) { door.all }
65
+ def define_module_methods(target, reader)
66
+ target.define_singleton_method(:repository) { reader.repository }
67
+ target.define_singleton_method(:count) { reader.repository.count }
68
+ target.define_singleton_method(:events) { reader.events }
69
+ target.define_singleton_method(:find) { |id| reader.find(id) }
70
+ target.define_singleton_method(:all) { reader.all }
71
71
  end
72
72
 
73
73
  def install_shortcuts!
data/lib/hecks/router.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Hecks
2
- # The public, project-wide dispatch door. It owns address resolution only;
2
+ # The public, project-wide dispatch entry point. It owns address resolution only;
3
3
  # each discovered Bluebook keeps its own runtime and persistence bindings.
4
4
  class Router
5
5
  class UnknownAddress < StandardError; end
@@ -0,0 +1,22 @@
1
+ module Hecks
2
+ module Runtime
3
+ # The one place the runtime answers a default when a read raises, so a deliberate guard is
4
+ # named at its call site and `Hecks/SwallowedRescue` can refuse every other silent swallow.
5
+ #
6
+ # BestEffort.call(false) { adapter_class.tenant_capable? } # => false when the lookup raises
7
+ module BestEffort
8
+ module_function
9
+
10
+ # Runs the block and answers its value, or `default` when it raises a StandardError.
11
+ #
12
+ # @param default [Object] what to answer when the block raises
13
+ # @yield the read that may raise
14
+ # @return [Object] the block's value, or `default`
15
+ def call(default)
16
+ yield
17
+ rescue StandardError
18
+ default
19
+ end
20
+ end
21
+ end
22
+ end
@@ -46,8 +46,8 @@ module Hecks
46
46
  end
47
47
 
48
48
  def apply_set(instance, aggregate, mutation, args, pre)
49
- from_state = mutation.source.is_a?(StateRef)
50
- value = from_state ? pre[mutation.source.name] : @rules.resolve_source(mutation.source, args)
49
+ from_state = mutation.source.is_a?(StateRef) || mutation.source.is_a?(Computed)
50
+ value = set_source_value(mutation.source, args, pre)
51
51
  # A plain `sets` of a list takes the whole list; only `append:` and `remove:` take
52
52
  # one element.
53
53
  list_attribute = aggregate.attribute(mutation.target)
@@ -55,6 +55,16 @@ module Hecks
55
55
  instance[mutation.target] = Value.for(aggregate, mutation.target, value)
56
56
  end
57
57
 
58
+ # A `state(:field)` reads the pre-dispatch record; an expression is evaluated over the
59
+ # command's arguments, then that record; anything else resolves as an argument or literal.
60
+ def set_source_value(source, args, pre)
61
+ case source
62
+ when StateRef then pre[source.name]
63
+ when Computed then Bluebook::Expression::Resolver.resolve(source.text, pre, args)
64
+ else @rules.resolve_source(source, args)
65
+ end
66
+ end
67
+
58
68
  def apply_append(instance, aggregate, mutation, args, pre)
59
69
  instance[mutation.target] = appended(pre, aggregate, mutation, args)
60
70
  end
@@ -65,7 +65,7 @@ module Hecks
65
65
  # @return [Array] the settled instance, events, plan, persistence outcome, and outbox rows
66
66
  # @raise [StandardError] a domain refusal (Runtime::DOMAIN_REFUSALS) when a rule refuses
67
67
  # @raise [Runtime::StaleWrite, Runtime::WiringError] every retry loses, or resolve fails
68
- # rubocop:disable-next Metrics/ParameterLists -- the public dispatch signature every door calls
68
+ # rubocop:disable-next Metrics/ParameterLists -- the public dispatch signature every entry point calls
69
69
  def call(domain, aggregate, command, invocation, correlation = nil, dry_run: false)
70
70
  args = invocation.to_args
71
71
  settings = { invocation: invocation, route: invocation.target, correlation: correlation, dry_run: dry_run }
@@ -181,7 +181,8 @@ module Hecks
181
181
 
182
182
  # `ctx.pending_delegation` is only ever set by `step_delegate_to_entity`, and only when this
183
183
  # command carries a `:delegate` mutation. The same dispatch, so the same correlation —
184
- # without threading `ctx.correlation` through, a saga-driven door's events would lose their
184
+ # without threading `ctx.correlation` through, a saga-driven entry point's events would lose
185
+ # their
185
186
  # stamp here.
186
187
  def emit_events(ctx)
187
188
  command, args = ctx.pending_delegation || [ctx.command, ctx.args]
@@ -20,7 +20,7 @@ module Hecks
20
20
  stamp = { aggregate: "#{domain}::#{aggregate.hecks_name}", id: instance.id,
21
21
  payload: args, correlation: correlation }
22
22
  command.emits.map do |event_name|
23
- record_emitted(Event.new(name: event_name, occurred_at: Time.now.utc.iso8601, **stamp), repository)
23
+ record_emitted(Event.new(name: event_name, occurred_at: Event.stamp, **stamp), repository)
24
24
  end
25
25
  end
26
26
 
@@ -37,8 +37,8 @@ module Hecks
37
37
  @entities = EntityInterpreter.new(registry, rules: rules)
38
38
  @queries = QueryInterpreter.new(registry)
39
39
  @read_models = ReadModelInterpreter.new(registry)
40
- @policies = PolicyInterpreter.new(registry, door: self)
41
- @sagas = SagaInterpreter.new(registry, door: self)
40
+ @policies = PolicyInterpreter.new(registry, dispatcher: self)
41
+ @sagas = SagaInterpreter.new(registry, dispatcher: self)
42
42
  # One relay per registry: interpreters enqueue through it inside the save transaction.
43
43
  @registry.outbox.attach(policies: @policies, sagas: @sagas)
44
44
  end
@@ -141,7 +141,7 @@ module Hecks
141
141
 
142
142
  # Runs one port operation named by its parts, then the reactions its events owe.
143
143
  #
144
- # The door for an adapter outside the bluebook; no wire spelling packs a port verb.
144
+ # The dispatcher for an adapter outside the bluebook; no wire spelling packs a port verb.
145
145
  #
146
146
  # @param to [String, Hash, nil] the receiver; when nil it is read from the facts
147
147
  # @param with [Hash, nil] the operation's facts, keyed by argument name
@@ -150,7 +150,7 @@ module Hecks
150
150
  # @raise [Runtime::UnknownVerb] if the domain, aggregate, port or operation is undeclared
151
151
  # @raise [Runtime::TypeMismatch] if no receiving identity is found, or `to:`/`with:` is bad
152
152
  # @raise [Runtime::NotFound] if the receiving record does not exist
153
- # rubocop:disable-next Metrics/ParameterLists -- the public door's parts, one argument per verb segment
153
+ # rubocop:disable-next Metrics/ParameterLists -- the public dispatcher's parts, one argument per verb segment
154
154
  def dispatch_port(domain, aggregate_name, port_name, operation_name, to: nil, with: nil, flat: {})
155
155
  verb = "#{domain}::#{aggregate_name}.#{port_name}.#{operation_name}"
156
156
  aggregate = resolve_aggregate(domain, aggregate_name, verb)
@@ -17,7 +17,7 @@ module Hecks
17
17
  # Walks `chain` one hop at a time and returns the located element (or `instance`
18
18
  # itself when `chain` is empty). `route`, when given, offers each hop's identity
19
19
  # before falling back to `args`.
20
- # rubocop:disable-next Metrics/ParameterLists -- the positional door EntityInterpreter and delegation call
20
+ # rubocop:disable-next Metrics/ParameterLists -- the positional entry point EntityInterpreter and delegation call
21
21
  def locate_chain(root_aggregate, chain, instance, args, command_name, route = nil)
22
22
  container = instance
23
23
  owner = root_aggregate
@@ -5,6 +5,11 @@ module Hecks
5
5
  # An emitted domain event. `correlation` (saga head -> value) is runtime bookkeeping
6
6
  # and deliberately absent from `to_h`.
7
7
  Event = Struct.new(:name, :aggregate, :id, :payload, :occurred_at, :correlation, keyword_init: true) do
8
+ # The one place the runtime reads the wall clock, so every event is stamped the same way.
9
+ #
10
+ # @return [String] the current UTC time as ISO 8601
11
+ def self.stamp = Time.now.utc.iso8601
12
+
8
13
  # Freezes the event deep, payload and correlation included.
9
14
  #
10
15
  # @return [void]
@@ -1,4 +1,5 @@
1
1
  require_relative "../naming"
2
+ require_relative "best_effort"
2
3
  require_relative "value"
3
4
 
4
5
  module Hecks
@@ -82,12 +83,12 @@ module Hecks
82
83
  # Resolves a best-effort identity for `construct` to use as a lock key; never raises.
83
84
  # Returns nil when nothing resolves, and the caller then locks by aggregate type alone.
84
85
  def best_effort(construct, args, route = nil, reference_key: nil)
85
- route&.aggregate ||
86
- of(construct, args) ||
87
- from(construct, args, :id) ||
88
- (reference_key && from(construct, args, reference_key))
89
- rescue StandardError
90
- nil
86
+ BestEffort.call(nil) do
87
+ route&.aggregate ||
88
+ of(construct, args) ||
89
+ from(construct, args, :id) ||
90
+ (reference_key && from(construct, args, reference_key))
91
+ end
91
92
  end
92
93
  end
93
94
  end
@@ -32,7 +32,8 @@ module Hecks
32
32
 
33
33
  # Gives each argument the caller left out the default its attribute declares
34
34
  # (`attribute :runs, Count, default: 30`), so a declared default holds on every way in and
35
- # not only where a door spells it out. An argument the caller passed is kept, whatever it is.
35
+ # not only where an entry point spells it out. An argument the caller passed is kept, whatever
36
+ # it is.
36
37
  #
37
38
  # @param command [Class] the command being dispatched
38
39
  # @param args [Hash{Symbol => Object}] the arguments the caller passed
@@ -18,7 +18,7 @@ module Hecks
18
18
  # :entity, :port). Each checks its own parts in a fixed order, because
19
19
  # a malformed call's refusal depends on that order; `declaring` is
20
20
  # called exactly once, at that shape's point in the order below.
21
- # rubocop:disable-next Metrics/ParameterLists -- the public keyword door every dispatch calls
21
+ # rubocop:disable-next Metrics/ParameterLists -- the public keyword entry point every dispatch calls
22
22
  def from_call(verb, to:, with:, flat:, receiver: :aggregate, entity_depth: 0, aggregate: nil, &declaring)
23
23
  call = Call.new(verb, to, with, flat, entity_depth, aggregate, declaring)
24
24
  case receiver
@@ -15,20 +15,23 @@ module Hecks
15
15
  # the domain never attached Privacy at all.
16
16
  def seed_privacy_markings!(dispatcher, registry)
17
17
  return if registry.pending_privacy_markings.empty?
18
- return unless registry.bluebook("Privacy")
19
18
 
20
- already_marked = already_marked_by_domain(dispatcher, registry)
19
+ provider = registry.provider_of(Bluebook::Capabilities::PRIVACY)
20
+ return unless provider
21
+
22
+ already_marked = already_marked_by_domain(dispatcher, registry, provider)
21
23
  registry.pending_privacy_markings.each do |marking|
22
24
  next if already_marked[marking[:domain]].include?(marking[:attribute_path])
23
25
 
24
- dispatcher.dispatch_flat("Privacy::Marking.Mark", marking)
26
+ dispatcher.dispatch_flat(provider.provided_verb(Bluebook::Capabilities::PRIVACY, :mark_sensitive), marking)
25
27
  end
26
28
  end
27
29
 
28
30
  # Each domain with a pending marking, with the attribute paths Privacy already marks for it.
29
- def already_marked_by_domain(dispatcher, registry)
31
+ def already_marked_by_domain(dispatcher, registry, provider)
32
+ verb = provider.provided_verb(Bluebook::Capabilities::PRIVACY, :markings_for)
30
33
  registry.pending_privacy_markings.map { |marking| marking[:domain] }.uniq.to_h do |domain|
31
- [domain, dispatcher.query("Privacy::Marking.ForDomain", domain: domain).map { |row| row[:attribute_path][:value] }]
34
+ [domain, dispatcher.query(verb, domain: domain).map { |row| row[:attribute_path][:value] }]
32
35
  end
33
36
  end
34
37
 
@@ -1,4 +1,4 @@
1
- require_relative "../doors/ruby_door"
1
+ require_relative "../adapters/driving/ruby"
2
2
  require_relative "../ports/loading"
3
3
  require_relative "../ports/persistence"
4
4
  require_relative "dispatcher"
@@ -21,7 +21,7 @@ module Hecks
21
21
 
22
22
  # Boots `path`: loads its bluebook directory into a fresh Registry, runs
23
23
  # every registered boot gate, and returns the bound dispatcher. Pass
24
- # `install_doors: false` to skip the `Widget::Item.Add(...)` global
24
+ # `install_driving: false` to skip the `Widget::Item.Add(...)` global
25
25
  # facade sugar (only a caller dispatching by FQN string needs to).
26
26
  #
27
27
  # @param path [String] a domain directory to boot
@@ -31,25 +31,25 @@ module Hecks
31
31
  # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted dispatcher
32
32
  # @raise [Errno::ENOENT] if `path` names no domain directory
33
33
  # @raise [Runtime::WiringError] if a boot gate finds a wiring problem
34
- def self.boot(path, shared: nil, install_doors: true, environment: FROM_ENV)
34
+ def self.boot(path, shared: nil, install_driving: true, environment: FROM_ENV)
35
35
  described = describe(path, shared: shared, environment: environment)
36
- boot_described(described, install_doors: install_doors)
36
+ boot_described(described, install_driving: install_driving)
37
37
  end
38
38
 
39
39
  # Finishes a boot from declarations `describe` already loaded: runs every boot gate and binds
40
40
  # the dispatcher, without reading the domain's files again.
41
41
  #
42
42
  # @param described [Described] what `describe` answered for the domain to boot
43
- # @param install_doors [Boolean] install the `Widget::Item.Add(...)` global facade sugar
43
+ # @param install_driving [Boolean] install the `Widget::Item.Add(...)` global facade sugar
44
44
  # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted dispatcher
45
45
  # @raise [Runtime::WiringError] if a boot gate finds a wiring problem
46
- def self.boot_described(described, install_doors: true)
46
+ def self.boot_described(described, install_driving: true)
47
47
  registry = described.registry
48
48
  run_boot_gates!(registry, described.directory)
49
49
  dispatcher = dispatcher_for(registry)
50
50
  redrive_outbox!(dispatcher)
51
51
  seed_privacy_markings!(dispatcher, registry)
52
- install_doors ? bind_runtime(dispatcher) : dispatcher
52
+ install_driving ? bind_runtime(dispatcher) : dispatcher
53
53
  end
54
54
 
55
55
  # Loads `path`'s declarations into a fresh Registry and stops: no boot gate runs, no
@@ -118,7 +118,7 @@ module Hecks
118
118
  # defaults to `HECKS_ENVIRONMENT`, and an explicit nil loads none
119
119
  # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted dispatcher
120
120
  # @raise [Runtime::WiringError] if a boot gate finds a wiring problem
121
- def self.boot_files(paths, shared: nil, install_doors: true, environment: FROM_ENV)
121
+ def self.boot_files(paths, shared: nil, install_driving: true, environment: FROM_ENV)
122
122
  loading = Ports::Loading.bootstrap
123
123
  files = Array(paths).map { |path| File.expand_path(path) }
124
124
  directory = File.dirname(files.first)
@@ -126,7 +126,7 @@ module Hecks
126
126
 
127
127
  run_boot_gates!(registry, directory)
128
128
  dispatcher = dispatcher_for(registry)
129
- install_doors ? bind_runtime(dispatcher) : dispatcher
129
+ install_driving ? bind_runtime(dispatcher) : dispatcher
130
130
  end
131
131
 
132
132
  # Loads exactly `files` into a fresh Registry rooted beside their directory, answering it.
@@ -160,7 +160,7 @@ module Hecks
160
160
  # `dispatcher`. The binding lives in the facade's own modules, not a
161
161
  # class-level global, so two boots in one process don't share one name.
162
162
  def self.bind_runtime(dispatcher)
163
- Doors::RubyDoor.install(dispatcher)
163
+ Adapters::Driving::Ruby.install(dispatcher)
164
164
  dispatcher
165
165
  end
166
166
  end
@@ -4,7 +4,7 @@ module Hecks
4
4
  module Runtime
5
5
  class PolicyInterpreter
6
6
  # What a policy's trigger is given: the event payload or its `with:` projection, the
7
- # emitting record's identity, and the invocation the door re-enters with. Mixed into
7
+ # emitting record's identity, and the invocation the entry point re-enters with. Mixed into
8
8
  # {PolicyInterpreter}.
9
9
  module Arguments
10
10
  private
@@ -12,12 +12,15 @@ module Hecks
12
12
  # What the trigger is given: the whole event payload verbatim when no `with:` is
13
13
  # declared, otherwise the projection (a Symbol names a payload field, anything else
14
14
  # is a literal). `extra` is a fan-out's row key, merged into the source before the
15
- # projection so a `with:` can name the row it acts on.
16
- def trigger_args(policy, event, extra = {})
15
+ # projection so a `with:` can name the row it acts on. `row` is the fan-out row itself:
16
+ # its fields are offered to a projection only, below the emitter identity and the
17
+ # payload, and never reach an unprojected payload.
18
+ def trigger_args(policy, event, extra = {}, row = {})
17
19
  payload = event.payload.transform_keys(&:to_sym).merge(extra)
18
20
  return payload unless ReactionInvocation.projection_declared?(policy)
19
21
 
20
- projected_args(policy, event, emitter_identity(event).merge(payload))
22
+ source = row.transform_keys(&:to_sym).merge(emitter_identity(event)).merge(payload)
23
+ projected_args(policy, event, source)
21
24
  end
22
25
 
23
26
  def projected_args(policy, event, payload)
@@ -20,7 +20,7 @@ module Hecks
20
20
 
21
21
  rows, reference_key = for_each_rows(policy, event, domain, target)
22
22
  Array(rows).map do |row|
23
- args = trigger_args(policy, event, reference_key => row[:id])
23
+ args = trigger_args(policy, event, { reference_key => row[:id] }, row)
24
24
  deliver_for_each_row(target, record.merge(for_row: row[:id]), args, policy, event)
25
25
  end
26
26
  rescue *DOMAIN_REFUSALS => e
@@ -74,10 +74,10 @@ module Hecks
74
74
  end
75
75
 
76
76
  def deliver_for_each_row(target, row_record, args, policy, event)
77
- return depth_refusal(row_record) if @door.reaction_depth_reached?
77
+ return depth_refusal(row_record) if @dispatcher.reaction_depth_reached?
78
78
 
79
79
  # The row key is already merged by `trigger_args`, so a projection can name it.
80
- @door.reenter(target, **reaction_invocation(target, args, policy, event))
80
+ @dispatcher.reenter(target, **reaction_invocation(target, args, policy, event))
81
81
  row_record.merge(delivered: true)
82
82
  rescue *DOMAIN_REFUSALS => e
83
83
  row_record.merge(delivered: false, reason: e.message)
@@ -20,11 +20,11 @@ module Hecks
20
20
 
21
21
  # @param registry [Runtime::Registry] the booted registry whose loaded
22
22
  # bluebooks are scanned for candidate policies
23
- # @param door [Runtime::Dispatcher] the dispatcher reactions re-enter through,
23
+ # @param dispatcher [Runtime::Dispatcher] the dispatcher reactions re-enter through,
24
24
  # and whose reaction-depth guard is checked before each delivery
25
- def initialize(registry, door:)
25
+ def initialize(registry, dispatcher:)
26
26
  @registry = registry
27
- @door = door
27
+ @dispatcher = dispatcher
28
28
  end
29
29
 
30
30
  # Fires every declared policy `event` triggers, recording each outcome on
@@ -96,15 +96,15 @@ module Hecks
96
96
  # may go.
97
97
  def fire(policy, event, target, record)
98
98
  return nil unless where_holds?(policy, event)
99
- return depth_refusal(record) if @door.reaction_depth_reached?
99
+ return depth_refusal(record) if @dispatcher.reaction_depth_reached?
100
100
 
101
101
  args = trigger_args(policy, event)
102
- @door.reenter(target, **reaction_invocation(target, args, policy, event))
102
+ @dispatcher.reenter(target, **reaction_invocation(target, args, policy, event))
103
103
  record.merge(delivered: true)
104
104
  end
105
105
 
106
106
  def depth_refusal(record)
107
- record.merge(delivered: false, reason: "reaction depth #{@door.max_reaction_depth} reached")
107
+ record.merge(delivered: false, reason: "reaction depth #{@dispatcher.max_reaction_depth} reached")
108
108
  end
109
109
 
110
110
  # A defect, not a refusal: recorded with `defect: true` and warned, never
@@ -157,7 +157,7 @@ module Hecks
157
157
  aggregate: "#{ctx.domain}::#{ctx.aggregate.hecks_name}",
158
158
  id: ctx.route.aggregate,
159
159
  payload: payload,
160
- occurred_at: Time.now.utc.iso8601
160
+ occurred_at: Event.stamp
161
161
  )
162
162
  end
163
163
  end
@@ -90,7 +90,7 @@ module Hecks
90
90
  # @raise [Runtime::UnknownVerb] if `verb` does not resolve (only when `explicit`)
91
91
  # @raise [Runtime::TypeMismatch] if no receiver identity resolves for an explicit target
92
92
  # @raise [Runtime::UnknownArgument] if an explicit projection has an undeclared fact
93
- # rubocop:disable-next Metrics/ParameterLists -- the keyword door both interpreters call
93
+ # rubocop:disable-next Metrics/ParameterLists -- the keyword entry point both interpreters call
94
94
  def build(registry:, verb:, projected:, explicit:, passthrough: [], source_receiver: nil)
95
95
  args = projected.transform_keys(&:to_sym)
96
96
  return forwarded(registry, verb, args, source_receiver) unless explicit
@@ -49,7 +49,17 @@ module Hecks
49
49
  return nil if id.nil?
50
50
 
51
51
  remote = registry.repository(domain, target).find(id.to_s)
52
- remote&.[](field.remote_field)
52
+ unwrap_single_field(target, field, remote&.[](field.remote_field))
53
+ end
54
+
55
+ # A remote field typed by a single-field value object is copied as its one scalar, so the
56
+ # projecting aggregate holds `123`, never `{ value: 123 }`. Any other value is copied as is.
57
+ def unwrap_single_field(target, field, value)
58
+ attribute = target.attribute(field.remote_field)
59
+ sole = attribute && target.value_object(attribute.type)&.sole_attribute
60
+ return value unless sole && !value.nil?
61
+
62
+ value.is_a?(Hash) ? value.fetch(sole.name) { value[sole.name.to_s] } : value[sole.name]
53
63
  end
54
64
  end
55
65
  end