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
@@ -0,0 +1,121 @@
1
+ require_relative "../../runtime/errors"
2
+
3
+ module Hecks
4
+ module Adapters
5
+ module Driving
6
+ # Turns an external command request into the dispatcher's `to:`/`with:` envelope.
7
+ #
8
+ # aggregate command: { to: "record-id", with: { declared: "facts" } }
9
+ # entity command: { to: { aggregate: "...", entity: "..." }, with: { ... } }
10
+ module CommandRequest
11
+ module_function
12
+
13
+ # Splits a request, envelope or flat Hash, into routing (`to:`) and facts (`with:`),
14
+ # symbolizing keys at every depth; in a flat Hash all but the receiver are facts.
15
+ #
16
+ # @param input [Hash] the request, with String or Symbol keys
17
+ # @param receiver [Symbol, nil] `:aggregate`, `:entity`, or `nil` for a command with none
18
+ # @param legacy_receiver [Symbol, String, Hash, nil] the flat key that may carry the
19
+ # receiver instead of `to:`: a name for `:aggregate`, `{ aggregate:, entity: }` for
20
+ # `:entity`
21
+ # @return [Hash{Symbol => Object}] `{ with: facts }`, plus `to:` when `receiver` is set
22
+ # @raise [Runtime::TypeMismatch] if the request is malformed or the route does not fit
23
+ # the receiver kind
24
+ # @raise [ArgumentError] if `receiver` is not `nil`, `:aggregate` or `:entity`
25
+ def normalize(input, receiver:, legacy_receiver: nil)
26
+ request = symbolize(input)
27
+ raise Runtime::TypeMismatch, "a command request must be a hash" unless request.is_a?(Hash)
28
+
29
+ route, facts = split(request, receiver: receiver, legacy_receiver: legacy_receiver)
30
+ validate_route!(route, receiver)
31
+
32
+ envelope = { with: facts }
33
+ envelope[:to] = route if receiver
34
+ envelope
35
+ end
36
+
37
+ def split(request, receiver:, legacy_receiver:)
38
+ return split_envelope(request) if request.key?(:with)
39
+
40
+ flat = request.dup
41
+ route = flat.delete(:to)
42
+ route = take_legacy_route(flat, receiver, legacy_receiver) if route.nil?
43
+ [route, flat]
44
+ end
45
+ private_class_method :split
46
+
47
+ def split_envelope(request)
48
+ loose = request.keys - [:to, :with]
49
+ unless loose.empty?
50
+ raise Runtime::TypeMismatch,
51
+ "an explicit command envelope takes routing in to: and facts in with:, not loose #{loose.sort.join(", ")}"
52
+ end
53
+
54
+ facts = request[:with]
55
+ raise Runtime::TypeMismatch, "with: must be a hash of command facts" unless facts.is_a?(Hash)
56
+
57
+ [request[:to], facts]
58
+ end
59
+ private_class_method :split_envelope
60
+
61
+ def take_legacy_route(flat, receiver, legacy_receiver)
62
+ return unless legacy_receiver
63
+
64
+ return flat.delete(legacy_receiver.to_sym) if receiver == :aggregate
65
+
66
+ return unless receiver == :entity && legacy_receiver.is_a?(Hash)
67
+
68
+ aggregate_key = legacy_receiver.fetch(:aggregate).to_sym
69
+ entity_key = legacy_receiver.fetch(:entity).to_sym
70
+ return unless flat.key?(aggregate_key) || flat.key?(entity_key)
71
+
72
+ { aggregate: flat.delete(aggregate_key), entity: flat.delete(entity_key) }
73
+ end
74
+ private_class_method :take_legacy_route
75
+
76
+ # One self-contained check per closed receiver kind, plus a backstop.
77
+ def validate_route!(route, receiver)
78
+ case receiver
79
+ when nil then refuse_receiver(route)
80
+ when :aggregate then require_identity(route)
81
+ when :entity then require_entity_route(route)
82
+ else raise ArgumentError, "unknown receiver kind #{receiver.inspect}"
83
+ end
84
+ end
85
+ private_class_method :validate_route!
86
+
87
+ def refuse_receiver(route)
88
+ raise Runtime::TypeMismatch, "this command does not take a receiver in to:" unless route.nil?
89
+ end
90
+ private_class_method :refuse_receiver
91
+
92
+ def require_identity(route)
93
+ return unless route.nil? || route.to_s.empty? || route.is_a?(Hash)
94
+
95
+ raise Runtime::TypeMismatch, "to: must name the receiving aggregate identity"
96
+ end
97
+ private_class_method :require_identity
98
+
99
+ def require_entity_route(route)
100
+ raise Runtime::TypeMismatch, "to: for an entity command must contain aggregate: and entity:" unless route.is_a?(Hash)
101
+
102
+ extra = route.keys - [:aggregate, :entity]
103
+ missing = [:aggregate, :entity].select { |key| route[key].nil? || route[key].to_s.empty? }
104
+ return if extra.empty? && missing.empty?
105
+
106
+ raise Runtime::TypeMismatch, "to: for an entity command must contain only aggregate: and entity:"
107
+ end
108
+ private_class_method :require_entity_route
109
+
110
+ def symbolize(value)
111
+ case value
112
+ when Hash then value.to_h { |key, nested| [key.to_sym, symbolize(nested)] }
113
+ when Array then value.map { |nested| symbolize(nested) }
114
+ else value
115
+ end
116
+ end
117
+ private_class_method :symbolize
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,214 @@
1
+ require_relative "../../naming"
2
+ require_relative "../../runtime/caller"
3
+ require_relative "../../ports/authorization"
4
+
5
+ module Hecks
6
+ module Adapters
7
+ module Driving
8
+ # A record in hand: what `Pizza.create_pizza!(...)` and `Pizza.find(id)`
9
+ # give back. One shared class per aggregate; verbs are per-handle singleton methods.
10
+ #
11
+ # Pizza.create_pizza!(...).add_topping!(...).purchase!(...)
12
+ class Handle
13
+ attr_reader :id
14
+
15
+ # @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted
16
+ # dispatcher this record's verbs, `events` and `reload` go through
17
+ # @param domain [String] the owning chapter's name, the first half of `fqn`
18
+ # @param aggregate [Bluebook::Aggregate] the IR of the aggregate this record is one of
19
+ # @param instance [Runtime::Instance] the stored record; its `id` and `state` are
20
+ # read once here and the instance itself is not retained
21
+ def initialize(dispatcher:, domain:, aggregate:, instance:)
22
+ @dispatcher = dispatcher
23
+ @domain = domain
24
+ @ir = aggregate
25
+ @id = instance.id
26
+ @state = instance.state
27
+ define_reference_accessors
28
+ define_verb_methods
29
+ end
30
+
31
+ # Reads one field's raw stored value, without hydrating a reference the way the
32
+ # reference accessor of the same name does.
33
+ #
34
+ # @param key [Symbol, String] the attribute name
35
+ # @return [Object, nil] the value held in state (a scalar, a value object, a list,
36
+ # or a referenced record's id); `nil` when the field is unset or not in state
37
+ def [](key) = redacted(key.to_sym)
38
+
39
+ # Answers the record's state as a Hash, with `:id` merged in last so it
40
+ # always wins over a same-named `id` attribute (BurningManPrep::Item).
41
+ #
42
+ # @return [Hash{Symbol => Object}] every state field by attribute name, plus `:id`
43
+ def to_h = @state.to_h { |key, _| [key, redacted(key)] }.merge(id: @id)
44
+
45
+ # Names the aggregate this record belongs to, in the form every dispatch verb and
46
+ # event is addressed by.
47
+ #
48
+ # @return [String] the fully qualified aggregate name, such as `"Pizzas::Pizza"`
49
+ def fqn = "#{@domain}::#{@ir.hecks_name}"
50
+
51
+ # Lists the events this one record has emitted, filtered out of the dispatcher's
52
+ # whole event log on each call.
53
+ #
54
+ # @return [Array<Runtime::Event>] this record's events in the order the log holds
55
+ # them; `[]` when it has emitted none
56
+ def events
57
+ @dispatcher.events.select { |event| event.aggregate == fqn && event.id == @id }
58
+ end
59
+
60
+ # Refreshes this handle's state from the repository, picking up writes made through
61
+ # another handle or aggregate module. Keeps the current state when the record is not found.
62
+ #
63
+ # @return [Adapters::Driving::Handle] this handle, so the call chains
64
+ # @raise [Runtime::WiringError] if the aggregate's persistence bind cannot be
65
+ # resolved into a repository
66
+ def reload
67
+ stored = repository.find(@id)
68
+ @state = stored.state if stored
69
+ self
70
+ end
71
+
72
+ # Equality is (fqn, id): two handles to the same record are equal, and
73
+ # `other.is_a?(self.class)` can't tell aggregates apart since they share one class.
74
+ #
75
+ # @param other [Object] anything; only another `Handle` can be equal
76
+ # @return [Boolean] true when `other` is a `Handle` with the same `fqn` and `id`
77
+ def ==(other) = other.is_a?(Handle) && other.fqn == fqn && other.id == @id
78
+ alias eql? ==
79
+ def hash = [Handle, fqn, @id].hash
80
+
81
+ def inspect
82
+ fields = @state.map { |key, value| "#{key}=#{value.inspect}" }.join(" ")
83
+ "#<#{@ir.hecks_name} #{@id} #{fields}>"
84
+ end
85
+ alias to_s inspect
86
+
87
+ # Answers a field reader: `pizza.name` reads `name` out of state. A
88
+ # declared field not yet written arrives here too, as `nil`.
89
+ #
90
+ # @param name [Symbol] the method called, read as an attribute or lifecycle field name
91
+ # @param args [Array<Object>] ignored by a reader; passed on to `super` otherwise
92
+ # @param kwargs [Hash{Symbol => Object}] ignored by a reader; passed on to `super` otherwise
93
+ # @return [Object, nil] the field's value; `nil` when nothing is written yet
94
+ # @raise [NoMethodError] if `name` is neither a key in state nor a declared field
95
+ def method_missing(name, *args, **kwargs, &)
96
+ return redacted(name) if @state.key?(name) || reader?(name)
97
+
98
+ super
99
+ end
100
+
101
+ def respond_to_missing?(name, include_private = false)
102
+ @state.key?(name) || reader?(name) || super
103
+ end
104
+
105
+ private
106
+
107
+ def repository = @dispatcher.registry.repository(@domain, @ir)
108
+
109
+ # Masks any Privacy::Marking leaf the caller isn't granted to read, and
110
+ # masks an unidentified caller outright (a leak is worse than a refusal
111
+ # commands allow). Nesting is masked one level deep only.
112
+ def redacted(field)
113
+ raw = @state[field]
114
+ marked_rows(field).each do |row|
115
+ raw = masked(raw, row) unless authorized_for?(row[:readable_by][:value].to_s)
116
+ end
117
+
118
+ raw
119
+ end
120
+
121
+ # The privacy markings that name `field` or a path beneath it.
122
+ def marked_rows(field)
123
+ marked_paths.select { |row| row[:attribute_path][:value].to_s.split(".", 2).first == field.to_s }
124
+ end
125
+
126
+ # `raw` with the marked leaf masked: the whole value for a marked field, one nested leaf of
127
+ # a
128
+ # value object for a marked path beneath it.
129
+ def masked(raw, row)
130
+ segments = row[:attribute_path][:value].to_s.split(".", 2)
131
+ return "[redacted]" if segments.size == 1
132
+
133
+ raw.is_a?(Runtime::Value) ? raw.with(segments[1], "[redacted]") : raw
134
+ end
135
+
136
+ # Cached per handle so an unattached-Privacy domain never pays for a query
137
+ # dispatch on every read.
138
+ def marked_paths
139
+ return @marked_paths if defined?(@marked_paths)
140
+
141
+ provider = @dispatcher.registry.provider_of(Bluebook::Capabilities::PRIVACY)
142
+ return @marked_paths = [] unless provider
143
+
144
+ verb = provider.provided_verb(Bluebook::Capabilities::PRIVACY, :markings_for)
145
+ @marked_paths = @dispatcher.query(verb, domain: fqn)
146
+ end
147
+
148
+ # False outright for an unidentified caller or an unattached domain, never
149
+ # the weak fallback a command's own role check allows.
150
+ def authorized_for?(role)
151
+ caller = Runtime::Caller.current
152
+ return false unless caller&.actor_id
153
+ return false unless @dispatcher.registry.authorization_provider_for(@domain)
154
+
155
+ Ports::Authorization.holds_role?(@dispatcher.registry, actor_id: caller.actor_id, role: role,
156
+ as_of: caller.as_of, scope: caller.scope)
157
+ end
158
+
159
+ def reader?(name)
160
+ !@ir.attribute(name).nil? || @ir.lifecycle&.field&.to_sym == name
161
+ end
162
+
163
+ # Real singleton methods, not method_missing: a verb named `freeze` or
164
+ # `send` (both real, e.g. Account::Freeze, ExternalTransfer::Send) would
165
+ # otherwise silently hit the Kernel method instead of dispatching.
166
+ def define_verb_methods
167
+ @ir.commands.reject(&:creates?).each do |command|
168
+ define_singleton_method("#{Naming.snake(command.hecks_name)}!") do |**args|
169
+ run(command, **args)
170
+ end
171
+ end
172
+ end
173
+
174
+ # `@ir.identified_by` is only the single-head shorthand (nil for a
175
+ # composite identity, e.g. SafeDepositBox's branch_code/box_number);
176
+ # this reads every head through `identity_heads` instead.
177
+ def run(command, **args)
178
+ @state = @dispatcher.dispatch("#{fqn}.#{command.hecks_name}", to: @id, with: args).instance.state
179
+ self
180
+ end
181
+
182
+ # Lazy hop: `transfer.source_account` resolves to the actual record only
183
+ # when called; nothing loads until then, so chains like
184
+ # `payment.disputed_by_customer.name` stay lazy at every step.
185
+ #
186
+ # Defined before verb methods so a same-named verb wins the name on the
187
+ # rare collision; `initialize` calls this first for that reason.
188
+ #
189
+ # The accessor is named like the attribute itself, never `<name>_id`
190
+ # (ADR 0025) — `piece.account` and `piece[:account]` (bracket access,
191
+ # the raw id) never collide despite sharing a name.
192
+ def define_reference_accessors
193
+ @ir.attributes.select(&:reference?).each { |attribute| define_reference_accessor(attribute) }
194
+ end
195
+
196
+ # One lazy reader for a reference attribute.
197
+ def define_reference_accessor(attribute)
198
+ target = attribute.type.resolve
199
+ # Cross-domain, or otherwise unresolvable — no accessor rather than a guess.
200
+ return unless target
201
+
202
+ target_fqn = "#{@domain}::#{target.hecks_name}"
203
+ define_singleton_method(attribute.name) do
204
+ value = self[attribute.name]
205
+ resolved = Object.const_get(target_fqn)
206
+ next Array(value).map { |identity| resolved.find(identity) } if attribute.list?
207
+
208
+ value && resolved.find(value)
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,136 @@
1
+ require "json"
2
+ require_relative "handle"
3
+ require_relative "command_request"
4
+ require_relative "../../naming"
5
+ require_relative "../../runtime/errors"
6
+ require_relative "../../runtime/value"
7
+
8
+ module Hecks
9
+ module Adapters
10
+ module Driving
11
+ # The JSON adapter: translates URL segments and parsed JSON bodies to and from the facade.
12
+ # No HTTP lives here; every miss raises `Runtime::NotFound`, distinguished by message.
13
+ module Json
14
+ module_function
15
+
16
+ # Resolves a domain name and an aggregate name, as two URL segments carry them, to
17
+ # that aggregate module.
18
+ #
19
+ # The name is checked against the current boot's IR before Ruby's constant table, so
20
+ # a stale same-named constant from an earlier boot is never returned.
21
+ #
22
+ # @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted dispatcher
23
+ # @param domain [String, Symbol] the chapter name, such as `"Banking"`
24
+ # @param name [String, Symbol] the aggregate's declared name, such as `"Customer"`
25
+ # @return [Module] the aggregate module installed at `domain::name`
26
+ # @raise [Runtime::NotFound] if the chapter or its aggregate is not declared
27
+ # @raise [NameError] if the aggregate has no facade constant (`install_driving: false`)
28
+ def aggregate(dispatcher, domain, name)
29
+ Admission.admit!(dispatcher.registry, "Json")
30
+ ir = dispatcher.registry.bluebook(domain)&.aggregate(name)
31
+ raise Runtime::NotFound, "#{domain} declares no aggregate named #{name.inspect}" unless ir
32
+
33
+ Object.const_get("#{domain}::#{ir.hecks_name}")
34
+ end
35
+
36
+ # Names the module method that creates a record of this aggregate: the first command
37
+ # it declares that `creates?`, spelled as a Ruby caller would (`"create_pizza!"`).
38
+ #
39
+ # @param klass [Module] an aggregate module, as `aggregate` returns
40
+ # @return [String] the method name to `public_send` to the module
41
+ # @raise [Runtime::NotFound] if the aggregate declares no creating command
42
+ def creating_command(klass)
43
+ creating = klass.ir.commands.find(&:creates?)
44
+ raise Runtime::NotFound, "#{klass.ir.hecks_name} declares no creating command" unless creating
45
+
46
+ "#{Naming.snake(creating.hecks_name)}!"
47
+ end
48
+
49
+ # Confirms that a command name arriving as text is one a `Handle` of this aggregate
50
+ # answers, before a caller `public_send`s it.
51
+ #
52
+ # A `Handle` defines only the non-creating commands, so the creating command is
53
+ # rejected here rather than failing later as a raw `NoMethodError`.
54
+ #
55
+ # @param klass [Module] an aggregate module, as `aggregate` returns
56
+ # @param name [String, Symbol] the wanted method name with its bang, such as
57
+ # `"add_topping!"`
58
+ # @return [String] `name` as a String, unchanged, when a `Handle` answers it
59
+ # @raise [Runtime::NotFound] if no non-creating command has that method name
60
+ def validate_command!(klass, name)
61
+ wanted = name.to_s
62
+ dispatchable = klass.ir.commands.reject(&:creates?).map { |command| "#{Naming.snake(command.hecks_name)}!" }
63
+ return wanted if dispatchable.include?(wanted)
64
+
65
+ raise Runtime::NotFound, "#{klass.ir.hecks_name} declares no command named #{wanted.inspect}"
66
+ end
67
+
68
+ # Fetches one record by id, raising instead of answering nil on a miss.
69
+ #
70
+ # @param klass [Module] an aggregate module, as `aggregate` returns
71
+ # @param id [String] the record's identity, as the URL carried it
72
+ # @return [Adapters::Driving::Handle] the record in hand
73
+ # @raise [Runtime::NotFound] if the repository holds no record with that id
74
+ def find!(klass, id)
75
+ klass.find(id) or raise Runtime::NotFound, "no #{klass.ir.hecks_name} found for id #{id.inspect}"
76
+ end
77
+
78
+ # Converts every Hash key to a Symbol, at every depth of a parsed JSON body.
79
+ #
80
+ # @param value [Hash, Array, Object] parsed JSON: a Hash or Array is walked, any
81
+ # other value is a leaf
82
+ # @return [Hash{Symbol => Object}, Array, Object] a new structure of the same shape
83
+ # with Symbol keys; a leaf is returned as it came
84
+ def deep_symbolize(value)
85
+ case value
86
+ when Hash then value.to_h { |k, v| [k.to_sym, deep_symbolize(v)] }
87
+ when Array then value.map { |item| deep_symbolize(item) }
88
+ else value
89
+ end
90
+ end
91
+
92
+ # Unwraps a record, a query row, or any value holding `Runtime::Value`s into plain
93
+ # Hashes, Arrays and scalars a JSON encoder can walk.
94
+ #
95
+ # Delegates to `Runtime::Value.materialize`; the only addition is `#to_h` on a
96
+ # `Handle`, which `materialize` does not recognize.
97
+ #
98
+ # @param value [Adapters::Driving::Handle, Runtime::Value, Hash, Array, Object] what to
99
+ # unwrap;
100
+ # a `Handle` is read through its `to_h`, so its `:id` comes along
101
+ # @return [Hash, Array, Object] the same data with every `Runtime::Value` replaced
102
+ # by a Hash of its fields; any other value is returned as it came
103
+ def materialize(value)
104
+ value = value.to_h if value.is_a?(Handle)
105
+ Runtime::Value.materialize(value)
106
+ end
107
+
108
+ # Turns a command's JSON body, raw or already parsed, into the `to:`/`with:`
109
+ # envelope a dispatcher takes. The result splats into `Dispatcher#dispatch`.
110
+ #
111
+ # @param body [String, Hash] raw JSON text, or the Hash it parses to
112
+ # @param receiver [Symbol, nil] `:aggregate`, `:entity`, or `nil` for none
113
+ # (see `CommandRequest.normalize`)
114
+ # @param legacy_receiver [Symbol, String, Hash{Symbol => Symbol, String}, nil] the flat key,
115
+ # or pair of keys a body without `to` may name its receiver under; `nil` accepts none
116
+ # @return [Hash{Symbol => Object}] `{ with: facts }`, plus `to:` unless `receiver` is `nil`
117
+ # @raise [JSON::ParserError] if `body` is a String that is not valid JSON
118
+ # @raise [Runtime::TypeMismatch] if the body is not a JSON object or is badly routed
119
+ # @raise [ArgumentError] if `receiver` is not `nil`, `:aggregate` or `:entity`
120
+ def command_request(body, receiver:, legacy_receiver: nil)
121
+ input = body.is_a?(String) ? parse(body) : body
122
+ CommandRequest.normalize(input, receiver: receiver, legacy_receiver: legacy_receiver)
123
+ end
124
+
125
+ # Parses a raw request body, leaving keys as Strings. `JSON::ParserError` propagates
126
+ # unwrapped.
127
+ #
128
+ # @param raw_json [String] JSON text, such as a POST body
129
+ # @return [Hash{String => Object}, Array, String, Numeric, Boolean, nil] whatever
130
+ # the text encodes
131
+ # @raise [JSON::ParserError] if the text is not valid JSON
132
+ def parse(raw_json) = JSON.parse(raw_json)
133
+ end
134
+ end
135
+ end
136
+ end