hecks 3.9.1 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (504) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/bluebook/pizzas.bluebook +29 -0
  3. data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
  4. data/exe/hecks +4 -4
  5. data/lib/hecks/adapters/driven/d1.rb +2 -10
  6. data/lib/hecks/adapters/driven/heki.adapter +1 -1
  7. data/lib/hecks/adapters/driven/heki.rb +2 -12
  8. data/lib/hecks/adapters/driven/lambda.adapter +1 -1
  9. data/lib/hecks/adapters/driven/lambda.rb +3 -18
  10. data/lib/hecks/adapters/driven/postgres.rb +4 -11
  11. data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
  12. data/lib/hecks/adapters/driven/sqlite.rb +2 -10
  13. data/lib/hecks/adapters/driving/admission.rb +46 -0
  14. data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
  15. data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
  16. data/lib/hecks/adapters/driving/cli.rb +127 -0
  17. data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
  18. data/lib/hecks/adapters/driving/cli_runner/resolution.rb +155 -0
  19. data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
  20. data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
  21. data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
  22. data/lib/hecks/adapters/driving/cli_runner.rb +69 -0
  23. data/lib/hecks/adapters/driving/command_request.rb +121 -0
  24. data/lib/hecks/adapters/driving/handle.rb +214 -0
  25. data/lib/hecks/adapters/driving/json.rb +136 -0
  26. data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
  27. data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
  28. data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
  29. data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
  30. data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
  31. data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
  32. data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
  33. data/lib/hecks/adapters/driving/mcp.rb +159 -0
  34. data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
  35. data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
  36. data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
  37. data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
  38. data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
  39. data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
  40. data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
  41. data/lib/hecks/adapters/driving/ruby.rb +83 -0
  42. data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
  43. data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
  44. data/lib/hecks/behaviors/expectations/boot.rb +1 -1
  45. data/lib/hecks/bench/ruby_runner.rb +1 -1
  46. data/lib/hecks/bluebook/ask_resolution.rb +130 -0
  47. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
  48. data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
  49. data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
  50. data/lib/hecks/bluebook/assembly/marks.rb +1 -0
  51. data/lib/hecks/bluebook/attribute.rb +1 -1
  52. data/lib/hecks/bluebook/behaviour/command.rb +13 -8
  53. data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
  54. data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
  55. data/lib/hecks/bluebook/capabilities.rb +91 -9
  56. data/lib/hecks/bluebook/domain_port.rb +16 -4
  57. data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
  58. data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
  59. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
  60. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
  61. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
  62. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
  63. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
  64. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
  65. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
  66. data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
  67. data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
  68. data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
  69. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
  70. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
  71. data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
  72. data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
  73. data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
  74. data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
  75. data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
  76. data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
  77. data/lib/hecks/bluebook/expression/projection.json +36 -0
  78. data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
  79. data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
  80. data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
  81. data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
  82. data/lib/hecks/bluebook/expression/resolver.rb +20 -1
  83. data/lib/hecks/bluebook/hecksagon.rb +23 -3
  84. data/lib/hecks/bluebook/lifecycle.rb +22 -3
  85. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
  86. data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
  87. data/lib/hecks/bluebook/meta_validator/readings.rb +18 -1
  88. data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
  89. data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
  90. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
  91. data/lib/hecks/bluebook/meta_validator.rb +12 -12
  92. data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
  93. data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
  94. data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
  95. data/lib/hecks/bluebook/model_check.rb +8 -2
  96. data/lib/hecks/bluebook/policy.rb +4 -2
  97. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  98. data/lib/hecks/bluebook/synthesizer.rb +2 -0
  99. data/lib/hecks/bluebook.rb +1 -0
  100. data/lib/hecks/cli/command_table.rb +1 -1
  101. data/lib/hecks/cli/console.rb +2 -1
  102. data/lib/hecks/cli/document.rb +1 -1
  103. data/lib/hecks/cli/launcher_words.rb +4 -4
  104. data/lib/hecks/cli/mcp.rb +4 -4
  105. data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
  106. data/lib/hecks/cli/project_cli/templates.rb +5 -5
  107. data/lib/hecks/cli/project_cli.rb +9 -6
  108. data/lib/hecks/cli/run.rb +2 -2
  109. data/lib/hecks/cli/smoke_http/checks.rb +4 -1
  110. data/lib/hecks/cli/smoke_http/transport.rb +3 -3
  111. data/lib/hecks/cli/smoke_http.rb +3 -0
  112. data/lib/hecks/codemod.rb +5 -2
  113. data/lib/hecks/corpus/routes.rb +7 -0
  114. data/lib/hecks/corpus/rust.rb +3 -2
  115. data/lib/hecks/deploy/bluebook/deploy.bluebook +124 -2
  116. data/lib/hecks/deploy/bluebook/deploy.hecksagon +8 -0
  117. data/lib/hecks/deploy/oidc.json +16 -3
  118. data/lib/hecks/doc/reference/rendering.rb +6 -3
  119. data/lib/hecks/forms/app/commands.rb +8 -7
  120. data/lib/hecks/forms/app/queries.rb +2 -1
  121. data/lib/hecks/forms/app.rb +1 -1
  122. data/lib/hecks/forms/record_renderer.rb +1 -1
  123. data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
  124. data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
  125. data/lib/hecks/fuzzing/failure_triage.rb +2 -1
  126. data/lib/hecks/fuzzing/mutation/run.rb +11 -4
  127. data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
  128. data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
  129. data/lib/hecks/fuzzing/properties/guards.rb +1 -1
  130. data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
  131. data/lib/hecks/fuzzing/self_consistency.rb +6 -2
  132. data/lib/hecks/fuzzing/shrinker.rb +3 -1
  133. data/lib/hecks/fuzzing/value_generator.rb +19 -5
  134. data/lib/hecks/gate/stages.yml +4 -10
  135. data/lib/hecks/grammar/evolve.rb +1 -1
  136. data/lib/hecks/grammar/expression_operators.json +372 -0
  137. data/lib/hecks/grammar/operators.rb +3 -1
  138. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
  139. data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
  140. data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
  141. data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
  142. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
  143. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
  144. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
  145. data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
  146. data/lib/hecks/hecks/adapters/deploy_toolchain/clearing.rb +51 -0
  147. data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
  148. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +2 -0
  149. data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
  150. data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
  151. data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
  152. data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
  153. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
  154. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
  155. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
  156. data/lib/hecks/hecks/adapters/local_files.rb +2 -2
  157. data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
  158. data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
  159. data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
  160. data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
  161. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
  162. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
  163. data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
  164. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
  165. data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +129 -0
  166. data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
  167. data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
  168. data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
  169. data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
  170. data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
  171. data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
  172. data/lib/hecks/hecks/adapters/site_toolchain/role_prober.rb +78 -0
  173. data/lib/hecks/hecks/adapters/site_toolchain.rb +29 -4
  174. data/lib/hecks/hecks/adapters/terminal.rb +13 -11
  175. data/lib/hecks/hecks/adapters/world_clearing.rb +65 -0
  176. data/lib/hecks/hecks/codebase.behaviors +13 -1
  177. data/lib/hecks/hecks/codebase.bluebook +239 -5
  178. data/lib/hecks/hecks/custodian.behaviors +24 -6
  179. data/lib/hecks/hecks/custodian.bluebook +6 -1227
  180. data/lib/hecks/hecks/deploy.behaviors +1 -1
  181. data/lib/hecks/hecks/deploy.world +1 -3
  182. data/lib/hecks/hecks/hecks.behaviors +1 -1
  183. data/lib/hecks/hecks/hecks.hecksagon +61 -18
  184. data/lib/hecks/hecks/hecks.world +11 -10
  185. data/lib/hecks/hecks/rehearsal.bluebook +477 -0
  186. data/lib/hecks/hecks/site.behaviors +1 -1
  187. data/lib/hecks/hecks/site.world +0 -1
  188. data/lib/hecks/hecks/tooling.bluebook +1238 -0
  189. data/lib/hecks/indifferent_key.rb +20 -0
  190. data/lib/hecks/language/adapter.bluebook +13 -3
  191. data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
  192. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
  193. data/lib/hecks/language/bluebook/entity.bluebook +18 -0
  194. data/lib/hecks/language/bluebook/policy.bluebook +9 -1
  195. data/lib/hecks/language/bluebook/query.bluebook +1 -1
  196. data/lib/hecks/language/bluebook/vocabulary.bluebook +11 -4
  197. data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
  198. data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
  199. data/lib/hecks/language/oidc.json +10 -0
  200. data/lib/hecks/language/port.bluebook +4 -4
  201. data/lib/hecks/literal.rb +12 -3
  202. data/lib/hecks/ports/key_vault.rb +7 -2
  203. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
  204. data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
  205. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
  206. data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
  207. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +29 -0
  208. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
  209. data/lib/hecks/projections/deploy/fargate/settings/tuning.rb +15 -0
  210. data/lib/hecks/projections/deploy/fargate/settings.rb +3 -1
  211. data/lib/hecks/projections/deploy/fargate/stack/readers.rb +1 -0
  212. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  213. data/lib/hecks/projections/deploy/shared.rb +5 -2
  214. data/lib/hecks/projections/deploy/templates/fargate/Makefile.tmpl +7 -4
  215. data/lib/hecks/projections/model/deviations.rb +2 -2
  216. data/lib/hecks/projections/model.rb +2 -2
  217. data/lib/hecks/projections/oidc.rb +13 -1
  218. data/lib/hecks/projections/shape.rb +5 -2
  219. data/lib/hecks/projections/site/cms_editor/attributes.rb +95 -0
  220. data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
  221. data/lib/hecks/projections/site/cms_editor/checks.rb +77 -0
  222. data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
  223. data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
  224. data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
  225. data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
  226. data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
  227. data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
  228. data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
  229. data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
  230. data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
  231. data/lib/hecks/projections/site/cms_editor/media.rb +85 -0
  232. data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
  233. data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
  234. data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
  235. data/lib/hecks/projections/site/cms_editor/pretty.rb +40 -0
  236. data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
  237. data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
  238. data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
  239. data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
  240. data/lib/hecks/projections/site/cms_editor/schema.rb +161 -0
  241. data/lib/hecks/projections/site/cms_editor/setting.rb +130 -0
  242. data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
  243. data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +36 -0
  244. data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +370 -0
  245. data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
  246. data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +87 -0
  247. data/lib/hecks/projections/site/cms_editor/templates/src/auth/session.ts.tmpl +36 -0
  248. data/lib/hecks/projections/site/cms_editor/templates/src/auth/sso.ts.tmpl +20 -0
  249. data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
  250. data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
  251. data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
  252. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
  253. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
  254. data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
  255. data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
  256. data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
  257. data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
  258. data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
  259. data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
  260. data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
  261. data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
  262. data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
  263. data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
  264. data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
  265. data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +133 -0
  266. data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +29 -0
  267. data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
  268. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +43 -0
  269. data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +177 -0
  270. data/lib/hecks/projections/site/cms_editor/templates/src/media/multipart.ts.tmpl +53 -0
  271. data/lib/hecks/projections/site/cms_editor/templates/src/media/sniff.ts.tmpl +94 -0
  272. data/lib/hecks/projections/site/cms_editor/templates/src/media/storage.ts.tmpl +50 -0
  273. data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
  274. data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +214 -0
  275. data/lib/hecks/projections/site/cms_editor/templates/src/server.ts.tmpl +65 -0
  276. data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
  277. data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
  278. data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
  279. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +123 -0
  280. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_parse.js.tmpl +320 -0
  281. data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
  282. data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
  283. data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +275 -0
  284. data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +61 -0
  285. data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
  286. data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +92 -0
  287. data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
  288. data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
  289. data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +63 -0
  290. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +213 -0
  291. data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
  292. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
  293. data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
  294. data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
  295. data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
  296. data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
  297. data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
  298. data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
  299. data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
  300. data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
  301. data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
  302. data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +17 -0
  303. data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
  304. data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
  305. data/lib/hecks/projections/site/cms_editor.rb +133 -0
  306. data/lib/hecks/projections/site/role_probe.rb +74 -0
  307. data/lib/hecks/projector/cli_audience.rb +2 -1
  308. data/lib/hecks/projector/cli_projector/listing.rb +1 -1
  309. data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
  310. data/lib/hecks/projector/docs_projector/closing.rb +1 -1
  311. data/lib/hecks/projector/docs_projector.rb +1 -1
  312. data/lib/hecks/projector/exporter/bindings.rb +40 -10
  313. data/lib/hecks/projector/exporter/capability_values.rb +55 -0
  314. data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
  315. data/lib/hecks/projector/target.rb +11 -0
  316. data/lib/hecks/projector.rb +32 -5
  317. data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
  318. data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
  319. data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
  320. data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
  321. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
  322. data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
  323. data/lib/hecks/quality_control/quality_control.bluebook +6 -2
  324. data/lib/hecks/release/approval.rb +64 -0
  325. data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
  326. data/lib/hecks/router/namespace_installer.rb +16 -16
  327. data/lib/hecks/router.rb +1 -1
  328. data/lib/hecks/runtime/best_effort.rb +22 -0
  329. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
  330. data/lib/hecks/runtime/command_interpreter.rb +3 -2
  331. data/lib/hecks/runtime/command_rules/emission.rb +1 -1
  332. data/lib/hecks/runtime/dispatcher.rb +4 -4
  333. data/lib/hecks/runtime/entity_element/location.rb +1 -1
  334. data/lib/hecks/runtime/event.rb +5 -0
  335. data/lib/hecks/runtime/identity.rb +7 -6
  336. data/lib/hecks/runtime/interpreting.rb +2 -1
  337. data/lib/hecks/runtime/invocation_builder.rb +1 -1
  338. data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
  339. data/lib/hecks/runtime/loader.rb +10 -10
  340. data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
  341. data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
  342. data/lib/hecks/runtime/policy_interpreter.rb +6 -6
  343. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
  344. data/lib/hecks/runtime/reaction_invocation.rb +1 -1
  345. data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
  346. data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
  347. data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
  348. data/lib/hecks/runtime/registry/merging.rb +1 -1
  349. data/lib/hecks/runtime/registry/providers.rb +16 -0
  350. data/lib/hecks/runtime/registry/read_repository.rb +6 -4
  351. data/lib/hecks/runtime/registry/verification.rb +4 -1
  352. data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
  353. data/lib/hecks/runtime/registry.rb +1 -1
  354. data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
  355. data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
  356. data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
  357. data/lib/hecks/runtime/saga_interpreter.rb +3 -3
  358. data/lib/hecks/runtime/tenant_check.rb +5 -4
  359. data/lib/hecks/runtime/value/coercion.rb +2 -2
  360. data/lib/hecks/runtime/value/validation.rb +16 -4
  361. data/lib/hecks/runtime.rb +9 -9
  362. data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
  363. data/lib/hecks/rust_build/wasm.rb +1 -1
  364. data/lib/hecks/rust_build/wasm_browser.rb +1 -1
  365. data/lib/hecks/rust_build.rb +11 -0
  366. data/lib/hecks/served_options.rb +53 -0
  367. data/lib/hecks/site/bluebook/site.bluebook +29 -1
  368. data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
  369. data/lib/hecks/storehouse/audit.rb +1 -1
  370. data/lib/hecks/storehouse/dispatching.rb +8 -6
  371. data/lib/hecks/storehouse/introspection.rb +2 -2
  372. data/lib/hecks/storehouse/querying.rb +6 -4
  373. data/lib/hecks/storehouse.rb +4 -4
  374. data/lib/hecks/tools/comment_style/source_file.rb +4 -1
  375. data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
  376. data/lib/hecks/tools/lanes/rows.rb +6 -0
  377. data/lib/hecks/tools/oidc_manifests.rb +4 -5
  378. data/lib/hecks/tools/regeneration_run.rb +11 -4
  379. data/lib/hecks/tools/site_routes/cli.rb +1 -1
  380. data/lib/hecks/tools/site_routes/editor_files.rb +45 -0
  381. data/lib/hecks/tools/site_routes.rb +14 -7
  382. data/lib/hecks/tools/tenant_provisioning.rb +2 -2
  383. data/lib/hecks/tools/tools_doc.rb +4 -4
  384. data/lib/hecks/version.rb +1 -1
  385. data/lib/hecks/vocabulary.rb +4 -4
  386. data/lib/hecks.rb +15 -15
  387. data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
  388. data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
  389. data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
  390. data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
  391. data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
  392. data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
  393. data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
  394. data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
  395. data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
  396. data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
  397. data/prebuilt/verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json +1 -0
  398. data/rust/.gitignore +6 -0
  399. data/rust/Cargo.toml +9 -1
  400. data/rust/build/src/binding_pass.rs +103 -0
  401. data/rust/build/src/cargo_sync.rs +6 -0
  402. data/rust/build/src/lineage_pass.rs +3 -3
  403. data/rust/build/src/main.rs +17 -5
  404. data/rust/build/src/pipeline.rs +48 -5
  405. data/rust/build/src/seams_pass.rs +123 -0
  406. data/rust/build/src/translation_pass.rs +167 -0
  407. data/rust/codegen/src/asks.rs +103 -0
  408. data/rust/codegen/src/bridging.rs +1 -1
  409. data/rust/codegen/src/commands.rs +13 -8
  410. data/rust/codegen/src/constraints.rs +25 -0
  411. data/rust/codegen/src/domain_generator.rs +11 -9
  412. data/rust/codegen/src/expr_emitter.rs +13 -0
  413. data/rust/codegen/src/json_codec.rs +54 -15
  414. data/rust/codegen/src/main.rs +2 -1
  415. data/rust/codegen/src/mutations.rs +174 -4
  416. data/rust/codegen/src/naming.rs +30 -0
  417. data/rust/codegen/src/queries.rs +11 -2
  418. data/rust/codegen/src/reactions.rs +1 -1
  419. data/rust/codegen/src/types.rs +166 -8
  420. data/rust/host/HECKS_RELEASE +1 -1
  421. data/rust/host/src/api.rs +4 -4
  422. data/rust/host/src/bin/validate_harness.rs +43 -0
  423. data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
  424. data/rust/host/src/boundary_fuzz/ir.rs +1 -0
  425. data/rust/host/src/boundary_fuzz/server.rs +1 -1
  426. data/rust/host/src/checkout.rs +35 -19
  427. data/rust/host/src/commerce_ir.rs +408 -2
  428. data/rust/host/src/expr_json.rs +118 -0
  429. data/rust/host/src/mint.rs +1 -1
  430. data/rust/host/src/payments/tests.rs +27 -1
  431. data/rust/host/src/payments.rs +12 -4
  432. data/rust/host/src/query_step.rs +22 -4
  433. data/rust/host/src/reference_validate.rs +653 -108
  434. data/rust/host/src/resend.rs +2 -0
  435. data/rust/host/src/server.rs +19 -6
  436. data/rust/host/src/ui_schema.rs +132 -5
  437. data/rust/host/src/web/newsletter.rs +7 -8
  438. data/rust/host/src/web/newsletter_send.rs +38 -13
  439. data/rust/host/src/web/registrations.rs +229 -26
  440. data/rust/host/src/web.rs +3 -3
  441. data/rust/parser/src/emit.rs +38 -6
  442. data/rust/parser/src/expr/ast_json.rs +7 -1
  443. data/rust/parser/src/expr/resolver.rs +171 -6
  444. data/rust/parser/src/ir.rs +7 -0
  445. data/rust/parser/src/keywords.rs +24 -0
  446. data/rust/parser/src/main.rs +3 -0
  447. data/rust/parser/src/parse/aggregate.rs +2 -1
  448. data/rust/parser/src/parse/chapter.rs +2 -2
  449. data/rust/parser/src/parse/command.rs +2 -0
  450. data/rust/parser/src/parse/hecksagon.rs +5 -2
  451. data/rust/parser/src/parse/lifecycle.rs +81 -0
  452. data/rust/parser/src/parse/mod.rs +15 -1
  453. data/rust/parser/src/parse/policy.rs +13 -2
  454. data/rust/parser/src/parse/value_object.rs +13 -8
  455. data/rust/parser/src/ruby_value.rs +1 -1
  456. data/rust/src/exemplar/commands.rs +4 -4
  457. data/rust/src/exemplar/constraints.rs +1 -1
  458. data/rust/src/exemplar/json.rs +43 -1
  459. data/rust/src/kernel/cli.rs +7 -5
  460. data/rust/src/kernel/dispatch.rs +2 -2
  461. data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
  462. data/rust/src/kernel/expr.rs +73 -4
  463. data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
  464. data/rust/src/kernel/expression_operators/presence.rs +41 -3
  465. data/rust/src/kernel/expression_operators/text.rs +26 -2
  466. data/rust/src/kernel/json.rs +15 -0
  467. data/rust/src/kernel/mod.rs +1 -1
  468. data/rust/src/kernel/orchestrate.rs +54 -5
  469. data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
  470. data/rust/src/kernel/pattern.rs +60 -0
  471. data/rust/src/kernel/reference_lookup.rs +90 -7
  472. metadata +174 -36
  473. data/lib/hecks/doors/cli_door/nesting.rb +0 -34
  474. data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
  475. data/lib/hecks/doors/cli_door.rb +0 -124
  476. data/lib/hecks/doors/cli_runner/answers.rb +0 -138
  477. data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
  478. data/lib/hecks/doors/cli_runner/settling.rb +0 -115
  479. data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
  480. data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
  481. data/lib/hecks/doors/cli_runner.rb +0 -66
  482. data/lib/hecks/doors/command_request.rb +0 -119
  483. data/lib/hecks/doors/handle.rb +0 -208
  484. data/lib/hecks/doors/json_door.rb +0 -132
  485. data/lib/hecks/doors/launcher_options.rb +0 -247
  486. data/lib/hecks/doors/mcp_door/properties.rb +0 -47
  487. data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
  488. data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
  489. data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
  490. data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
  491. data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
  492. data/lib/hecks/doors/mcp_door.rb +0 -156
  493. data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
  494. data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
  495. data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
  496. data/lib/hecks/doors/mcp_door_scope.rb +0 -201
  497. data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
  498. data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
  499. data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
  500. data/lib/hecks/doors/ruby_door.rb +0 -80
  501. data/lib/hecks/doors/usage_cache.rb +0 -139
  502. data/prebuilt/0ee7f83cba59ce7d1c45c65ba166b071779f70143449f584f33ff7989f64eccb.marshal +0 -0
  503. data/prebuilt/6e4a6bcf0aa880e2e6b056e1846b70e5a768090b9dc998bbadeaf5d6bcfd3db8.marshal +0 -0
  504. data/prebuilt/verdicts-91aaf93ce8981bd9a754948f075d95650dc2ec6435015376f5c60992b36a24b1.json +0 -1
@@ -0,0 +1,103 @@
1
+ //! Resolution of a policy's `ask :name` to the port operation the hecksagon declared for it,
2
+ //! mirroring `Hecks::Bluebook::AskResolution`: by the event's aggregate and the ask's name, with
3
+ //! `chosen` (`ask_via`) breaking a tie between ports.
4
+
5
+ use crate::json::Json;
6
+ use crate::naming;
7
+
8
+ /// Policies whose `ask` is replaced by the `Aggregate::Port.Operation` trigger it resolves to,
9
+ /// so every table below reads the verb as it reads a port-naming `trigger`.
10
+ ///
11
+ /// Panics when an ask resolves to nothing or to several operations: the Ruby boot refuses both
12
+ /// before the IR is ever exported.
13
+ pub fn resolve_asks(policies: &[Json], aggregates: &[Json]) -> Vec<Json> {
14
+ policies
15
+ .iter()
16
+ .map(|policy| match policy.get("ask").map(Json::to_s) {
17
+ Some(ask) if !ask.is_empty() => with_trigger(policy, resolve(policy, &ask, aggregates)),
18
+ _ => policy.clone(),
19
+ })
20
+ .collect()
21
+ }
22
+
23
+ fn with_trigger(policy: &Json, verb: String) -> Json {
24
+ let mut resolved = policy.clone();
25
+ resolved.set("trigger_command", Json::String(verb));
26
+ resolved
27
+ }
28
+
29
+ fn resolve(policy: &Json, ask: &str, aggregates: &[Json]) -> String {
30
+ let name = policy.get("name").map(Json::to_s).unwrap_or_default();
31
+ let on_event = policy.get("on_event").map(Json::to_s).unwrap_or_default();
32
+ let aggregate = event_aggregate(&on_event, aggregates)
33
+ .unwrap_or_else(|| panic!("policy {name}: ask :{ask} reacts to {on_event:?}, whose aggregate is not declared"));
34
+ let agg_name = aggregate.get("name").map(Json::to_s).unwrap_or_default();
35
+ let mut found = candidates(aggregate, ask);
36
+ if found.len() > 1 && found.iter().any(|(_, op)| op.get("chosen").is_some_and(Json::as_bool)) {
37
+ found.retain(|(_, op)| op.get("chosen").is_some_and(Json::as_bool));
38
+ }
39
+ match found.as_slice() {
40
+ [(port, op)] => format!("{agg_name}::{port}.{}", op.get("name").map(Json::to_s).unwrap_or_default()),
41
+ [] => panic!("policy {name}: ask :{ask} matches no `asks` declared on {agg_name}"),
42
+ _ => panic!("policy {name}: ask :{ask} is asked on several ports of {agg_name}; pick one with ask_via"),
43
+ }
44
+ }
45
+
46
+ // The aggregate the event is qualified with, or else the one whose commands emit the bare event.
47
+ fn event_aggregate<'a>(on_event: &str, aggregates: &'a [Json]) -> Option<&'a Json> {
48
+ let named = |wanted: &str| aggregates.iter().find(|a| a.get("name").map(Json::to_s).unwrap_or_default() == wanted);
49
+ match on_event.split_once('.') {
50
+ Some((qualifier, _)) => named(qualifier.rsplit("::").next().unwrap_or(qualifier)),
51
+ None => aggregates.iter().find(|a| {
52
+ a.get("commands")
53
+ .map(Json::each)
54
+ .unwrap_or(&[])
55
+ .iter()
56
+ .any(|c| c.get("emits").map(Json::to_s).unwrap_or_default() == on_event)
57
+ }),
58
+ }
59
+ }
60
+
61
+ // Every outbound operation of the aggregate's ports whose snake-cased name is the ask.
62
+ fn candidates<'a>(aggregate: &'a Json, ask: &str) -> Vec<(String, &'a Json)> {
63
+ let mut found = Vec::new();
64
+ for port in aggregate.get("ports").map(Json::each).unwrap_or(&[]) {
65
+ let port_name = port.get("name").map(Json::to_s).unwrap_or_default();
66
+ for op in port.get("operations").map(Json::each).unwrap_or(&[]) {
67
+ let outbound = op.get("direction").map(Json::to_s).as_deref() == Some("outbound");
68
+ let spelled = naming::snake(&op.get("name").map(Json::to_s).unwrap_or_default());
69
+ if outbound && spelled == ask {
70
+ found.push((port_name.clone(), op));
71
+ }
72
+ }
73
+ }
74
+ found
75
+ }
76
+
77
+ #[cfg(test)]
78
+ mod tests {
79
+ use super::*;
80
+ use std::path::PathBuf;
81
+
82
+ // The neutral corpus fixture `spec/corpus/asks/errand.json`: the Ruby spec holds the same
83
+ // `targets` against the Ruby runtime's reaction log, so the two cannot drift.
84
+ fn corpus(name: &str) -> Json {
85
+ let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../spec/corpus/asks").join(name);
86
+ Json::parse(&std::fs::read_to_string(path).expect("corpus file")).expect("corpus json")
87
+ }
88
+
89
+ #[test]
90
+ fn every_ask_resolves_to_the_target_the_corpus_freezes() {
91
+ let ir = corpus("errand.ir.json");
92
+ let expect = corpus("errand.json");
93
+ let domain = ir.get("name").map(Json::to_s).unwrap_or_default();
94
+ let resolved = resolve_asks(ir.get("policies").unwrap().each(), ir.get("aggregates").unwrap().each());
95
+
96
+ for policy in &resolved {
97
+ let name = policy.get("name").map(Json::to_s).unwrap_or_default();
98
+ let target = format!("{domain}::{}", policy.get("trigger_command").map(Json::to_s).unwrap_or_default());
99
+ let frozen = expect.get("targets").and_then(|t| t.get(&name)).map(Json::to_s);
100
+ assert_eq!(frozen, Some(target), "{name}");
101
+ }
102
+ }
103
+ }
@@ -29,7 +29,7 @@ pub fn cross_aggregate_vo_imports(aggregate: &Json, domain_value_object_owner: &
29
29
 
30
30
  let mut pairs: Vec<(String, String)> = foreign_types.into_iter().filter_map(|type_name| domain_value_object_owner.get(&type_name).map(|owner| (type_name, owner.clone()))).collect();
31
31
  pairs.sort_by(|(a_type, a_owner), (b_type, b_owner)| (a_owner, a_type).cmp(&(b_owner, b_type)));
32
- pairs.into_iter().map(|(type_name, owner)| format!("use crate::generated::{mod_name}::{}::{};", owner.to_lowercase(), naming::rust_ident(&type_name))).collect()
32
+ pairs.into_iter().map(|(type_name, owner)| format!("use crate::generated::{mod_name}::{}::{};", naming::aggregate_module(&owner), naming::rust_ident(&type_name))).collect()
33
33
  }
34
34
 
35
35
  /// True when a value object rebuilds into a differently-named one by matching field names.
@@ -168,6 +168,11 @@ fn command_skip_reason_with(command: &Json, aggregate: &Json, value_objects_by_n
168
168
  return Some(skip("state_source", format!("sets state source(s): {}", state_problems.join("; "))));
169
169
  }
170
170
 
171
+ let expression_problems = mutations::expression_source_problems(command, aggregate, value_objects_by_name);
172
+ if !expression_problems.is_empty() {
173
+ return Some(skip("expression_source", format!("sets expression source(s): {}", expression_problems.join("; "))));
174
+ }
175
+
171
176
  let remove_problems = mutations::remove_field_problems(command, aggregate, value_objects_by_name);
172
177
  if !remove_problems.is_empty() {
173
178
  return Some(skip("remove_field", format!("sets remove field(s): {}", remove_problems.join("; "))));
@@ -678,37 +683,37 @@ pub fn delegate_skip_reason(command: &Json, aggregate: &Json, value_objects_by_n
678
683
  }
679
684
  let mapping = delegate_mapping(delegation);
680
685
  let source_name_for = |name: &str| mapping.iter().find(|(t, _)| t == name).map(|(_, s)| s.clone()).unwrap_or_else(|| name.to_string());
681
- let door_attrs = command.get("attributes").map(Json::each).unwrap_or(&[]);
686
+ let argument_attrs = command.get("attributes").map(Json::each).unwrap_or(&[]);
682
687
  for attr in target.get("attributes").map(Json::each).unwrap_or(&[]) {
683
688
  let name = crate::attr::name(attr);
684
689
  let source_name = source_name_for(name);
685
- let source = door_attrs.iter().find(|a| crate::attr::name(a) == source_name);
690
+ let source = argument_attrs.iter().find(|a| crate::attr::name(a) == source_name);
686
691
  let Some(source) = source else {
687
692
  if crate::attr::optional(attr) {
688
693
  continue;
689
694
  }
690
- return Some(format!("{label}: target argument {name} has no source on the door"));
695
+ return Some(format!("{label}: target argument {name} has no source on the entry point"));
691
696
  };
692
697
  if crate::attr::list(source) != crate::attr::list(attr) {
693
- return Some(format!("{label}: door argument {source_name} is a list, target wants a scalar (or vice versa) — not generated yet"));
698
+ return Some(format!("{label}: entry point argument {source_name} is a list, target wants a scalar (or vice versa) — not generated yet"));
694
699
  }
695
700
  // Bridgeable, not exact type-name equality: the alias-then-deserialize delegate never
696
701
  // compares declared type names (docs/decisions/0045).
697
702
  if !crate::bridging::bridgeable_value_types(crate::attr::type_name(source), crate::attr::type_name(attr), value_objects_by_name) {
698
- return Some(format!("{label}: door argument {source_name} is {}, target wants {} — not generated yet", crate::attr::type_name(source), crate::attr::type_name(attr)));
703
+ return Some(format!("{label}: entry point argument {source_name} is {}, target wants {} — not generated yet", crate::attr::type_name(source), crate::attr::type_name(attr)));
699
704
  }
700
705
  if crate::attr::optional(source) && !crate::attr::optional(attr) {
701
- return Some(format!("{label}: optional door argument {source_name} feeds required {name}"));
706
+ return Some(format!("{label}: optional entry point argument {source_name} feeds required {name}"));
702
707
  }
703
708
  }
704
709
  for path in entity.get("identified_by").map(Json::each).unwrap_or(&[]) {
705
710
  let path = path.to_s();
706
711
  let head = path.split('.').next().unwrap_or("").to_string();
707
712
  let source_name = source_name_for(&head);
708
- if door_attrs.iter().any(|a| crate::attr::name(a) == source_name && !crate::attr::optional(a)) {
713
+ if argument_attrs.iter().any(|a| crate::attr::name(a) == source_name && !crate::attr::optional(a)) {
709
714
  continue;
710
715
  }
711
- return Some(format!("{label}: the element's identity {head} has no source on the door"));
716
+ return Some(format!("{label}: the element's identity {head} has no source on the entry point"));
712
717
  }
713
718
  None
714
719
  }
@@ -107,3 +107,28 @@ pub fn emit_pattern_check(exemplar: &Exemplar, value_expr: &str, attr: &Json, ow
107
107
  );
108
108
  Some(wrap_if_optional(check, optional_source))
109
109
  }
110
+
111
+ /// A `pattern:` on a list of Strings, checked once per member: Ruby's `check_patterns` walks
112
+ /// `offered_items`, so every element of the list must match, and an optional list left unset has
113
+ /// none to check. Lists of anything but plain Strings get no check.
114
+ pub fn emit_list_pattern_check(exemplar: &Exemplar, value_expr: &str, attr: &Json, owner_type_name: &str) -> Option<String> {
115
+ let pattern = crate::attr::pattern(attr)?;
116
+ if !crate::attr::list(attr) || naming::effective_scalar_type(crate::attr::type_name(attr)) != Some("String") {
117
+ return None;
118
+ }
119
+
120
+ let check = exemplar.render(
121
+ "pattern_check",
122
+ &[
123
+ ("\"tmpl_pattern_text\"", naming::ruby_inspect_string(pattern)),
124
+ ("tmpl_scalar", "item".to_string()),
125
+ ("\"tmpl_pattern_owner\"", naming::ruby_inspect_string(owner_type_name)),
126
+ ("\"tmpl_pattern_field\"", naming::ruby_inspect_string(crate::attr::name(attr))),
127
+ ],
128
+ );
129
+ Some(if crate::attr::optional(attr) {
130
+ format!("if let Some(items) = &{value_expr} {{ for item in items {{ {check} }} }}")
131
+ } else {
132
+ format!("for item in &{value_expr} {{ {check} }}")
133
+ })
134
+ }
@@ -85,8 +85,8 @@ fn reference_checks(
85
85
  target_mod: target
86
86
  .get("name")
87
87
  .and_then(Json::as_str)
88
- .unwrap_or("")
89
- .to_lowercase(),
88
+ .map(crate::naming::aggregate_module)
89
+ .unwrap_or_default(),
90
90
  target_name: target
91
91
  .get("name")
92
92
  .and_then(Json::as_str)
@@ -161,8 +161,8 @@ fn state_reference_checks(
161
161
  target_mod: target
162
162
  .get("name")
163
163
  .and_then(Json::as_str)
164
- .unwrap_or("")
165
- .to_lowercase(),
164
+ .map(crate::naming::aggregate_module)
165
+ .unwrap_or_default(),
166
166
  target_name: target
167
167
  .get("name")
168
168
  .and_then(Json::as_str)
@@ -225,7 +225,7 @@ fn tenant_boundary_checks(
225
225
 
226
226
  Some(TenantBoundaryCheck {
227
227
  reference_field: crate::attr::name(attr).to_string(),
228
- target_mod: target_decl_name.to_lowercase(),
228
+ target_mod: crate::naming::aggregate_module(&target_decl_name),
229
229
  target_name: target_decl_name,
230
230
  aggregate_name: aggregate_name.clone(),
231
231
  own_tenant_field: own_tenant_field.clone(),
@@ -289,6 +289,8 @@ pub fn generate(
289
289
  source_label: &str,
290
290
  mod_name: &str,
291
291
  ) -> GeneratedDomain {
292
+ let resolved_ir = types::with_resolved_projection_types(ir);
293
+ let ir = &resolved_ir;
292
294
  let domain_name = ir.get("name").and_then(Json::as_str).unwrap_or("");
293
295
  let all_aggregates = ir.get("aggregates").map(Json::each).unwrap_or(&[]);
294
296
  let aggregates_by_name: HashMap<String, &Json> = all_aggregates
@@ -1036,13 +1038,13 @@ pub fn generate(
1036
1038
  }
1037
1039
 
1038
1040
  aggregate_files.push(GeneratedFile {
1039
- name: format!("{}.rs", agg_name.to_lowercase()),
1041
+ name: format!("{}.rs", crate::naming::aggregate_module(agg_name)),
1040
1042
  content: out,
1041
1043
  });
1042
1044
 
1043
1045
  registry_aggregates.push(AggregateEntry {
1044
1046
  name: agg_name.to_string(),
1045
- module_name: agg_name.to_lowercase(),
1047
+ module_name: crate::naming::aggregate_module(agg_name),
1046
1048
  record: record_name,
1047
1049
  commands: registry_commands,
1048
1050
  entity_commands,
@@ -1092,7 +1094,7 @@ pub fn generate(
1092
1094
  query_defs.push(queries::QueryDef {
1093
1095
  verb: format!("{domain_name}::{agg_name}.{query_name}"),
1094
1096
  aggregate: format!("{domain_name}::{agg_name}"),
1095
- arg_checks: queries::query_arg_checks(query, &format!("crate::generated::{mod_name}::{}", agg_name.to_lowercase()), &value_objects_by_name),
1097
+ arg_checks: queries::query_arg_checks(query, &format!("crate::generated::{mod_name}::{}", crate::naming::aggregate_module(agg_name)), &value_objects_by_name),
1096
1098
  conditions,
1097
1099
  reference_hop_conditions,
1098
1100
  order_by: query.get("order_by").map(|ob| queries::emit_query_order_by(ob, query.get("null_semantics"))),
@@ -1273,7 +1275,7 @@ pub fn generate(
1273
1275
  puts_str(&mut mod_rs, "pub mod metadata;");
1274
1276
  puts_str(&mut mod_rs, "pub mod registry;");
1275
1277
  for a in &registry_aggregates {
1276
- puts_str(&mut mod_rs, &format!("pub mod {};", a.name.to_lowercase()));
1278
+ puts_str(&mut mod_rs, &format!("pub mod {};", a.module_name));
1277
1279
  }
1278
1280
 
1279
1281
  GeneratedDomain {
@@ -23,6 +23,9 @@ pub fn emit_ast(node: &Json) -> String {
23
23
  "nil" => "Expr::Nil".to_string(),
24
24
  "lookup" => format!("Expr::Lookup({})", ruby_inspect_string(&path_of(node, op).join("."))),
25
25
  "add" => format!("Expr::Add({}, {})", boxed("left"), boxed("right")),
26
+ "sub" => format!("Expr::Sub({}, {})", boxed("left"), boxed("right")),
27
+ "mul" => format!("Expr::Mul({}, {})", boxed("left"), boxed("right")),
28
+ "div" => format!("Expr::Div({}, {})", boxed("left"), boxed("right")),
26
29
  "sign_test" => format!("Expr::SignTest {{ op: {}, receiver: {} }}", emit_comparison(field(node, op, "cmp")), boxed("receiver")),
27
30
  "empty" => format!("Expr::Empty({})", boxed("receiver")),
28
31
  "to_s" => format!("Expr::ToS({})", boxed("receiver")),
@@ -50,6 +53,7 @@ pub fn emit_ast(node: &Json) -> String {
50
53
  "presence" => format!("Expr::Presence {{ receiver: {}, negated: {} }}", boxed("receiver"), field(node, op, "negated").to_s()),
51
54
  "assignment" => format!("Expr::Assignment {{ receiver: {}, negated: {} }}", boxed("receiver"), field(node, op, "negated").to_s()),
52
55
  "split" => format!("Expr::Split {{ receiver: {}, separator: {}.to_string() }}", boxed("receiver"), text("separator")),
56
+ "strip" => format!("Expr::Strip {{ receiver: {}, side: crate::kernel::StripSide::{} }}", boxed("receiver"), strip_side(node, op)),
53
57
  "starts_with" => format!("Expr::StartsWith {{ receiver: {}, substring: {}.to_string() }}", boxed("receiver"), text("substring")),
54
58
  "ends_with" => format!("Expr::EndsWith {{ receiver: {}, substring: {}.to_string() }}", boxed("receiver"), text("substring")),
55
59
  "first" => format!("Expr::First({})", boxed("receiver")),
@@ -77,6 +81,15 @@ fn path_of(node: &Json, op: &str) -> Vec<String> {
77
81
  .collect()
78
82
  }
79
83
 
84
+ fn strip_side(node: &Json, op: &str) -> &'static str {
85
+ match field(node, op, "side").as_str() {
86
+ Some("both") => "Both",
87
+ Some("left") => "Left",
88
+ Some("right") => "Right",
89
+ other => panic!("strip's side {other:?} is none of both/left/right: {node:?}"),
90
+ }
91
+ }
92
+
80
93
  fn block_mode(node: &Json, op: &str) -> &'static str {
81
94
  match field(node, op, "mode").as_str() {
82
95
  Some("all") => "All",
@@ -17,20 +17,22 @@ pub fn list_shape_error(struct_name: &str, key: &str, element_type: &str, value_
17
17
  format!("crate::kernel::Refusal::TypeMismatch(format!({}, {value_var}.inspect()))", naming::ruby_inspect_string(&template))
18
18
  }
19
19
 
20
- // A String field refuses only composite-shaped (or null) values, as Ruby's
21
- // `Value::Coercion#check_scalar_shapes` does; the offered shape is only known at runtime.
20
+ // A String field refuses any value that is not a String, and an Integer or Float field any that
21
+ // is not a number, as Ruby's `Value::Coercion#check_scalar_shapes` and `check_numeric_item!`
22
+ // do; the offered shape is only known at runtime, so the message renders it. A whole number
23
+ // beyond signed 64 bits words as Ruby's `integer_range` site.
22
24
  pub fn scalar_type_error(struct_name: &str, key: &str, scalar_type: &str, value_var: &str) -> String {
23
25
  if scalar_type != "Integer" && scalar_type != "Float" && scalar_type != "String" {
24
26
  return json_type_error(struct_name, key, scalar_type);
25
27
  }
26
28
  let template = format!("{struct_name}.{key} expects {scalar_type}, got {{}}");
27
29
  let proper = format!("crate::kernel::Refusal::TypeMismatch(format!({}, {value_var}.inspect()))", naming::ruby_inspect_string(&template));
28
- if scalar_type != "String" {
30
+ if scalar_type != "Integer" {
29
31
  return proper;
30
32
  }
31
- let generic = json_type_error(struct_name, key, scalar_type);
32
- // `Json::Null` words like a composite: Ruby's `check_required_fields` says "got nil".
33
- format!("if matches!({value_var}, crate::kernel::Json::Array(_) | crate::kernel::Json::Object(_) | crate::kernel::Json::Null) {{ {proper} }} else {{ {generic} }}")
33
+ let range_template = format!("{struct_name}.{key} must fit in a 64-bit integer, got {{}}");
34
+ let range = format!("crate::kernel::Refusal::TypeMismatch(format!({}, {value_var}.inspect()))", naming::ruby_inspect_string(&range_template));
35
+ format!("if matches!({value_var}, crate::kernel::Json::Num(n, _) if n.is_finite() && n.fract() == 0.0 && n.abs() >= 9.223372036854775808e18) {{ {range} }} else {{ {proper} }}")
34
36
  }
35
37
 
36
38
  /// A missing non-optional field refuses as "{type}.{field} expects {expected}, got nil".
@@ -274,6 +276,9 @@ pub fn emit_from_json_flat(
274
276
  aggregates_by_name: Option<&HashMap<String, &Json>>,
275
277
  ) -> String {
276
278
  let command_name = command_name.unwrap_or(struct_name);
279
+ // A refusal names the command (`Write.slugs expects ...`), as Ruby's `Value.for_attribute`
280
+ // does, not the generated `WriteArgs` struct that holds its arguments.
281
+ let error_name = if struct_name == format!("{command_name}Args") { command_name } else { struct_name };
277
282
  let idents: Vec<String> = attributes.iter().map(|attr| naming::rust_ident_field(crate::attr::name(attr))).collect();
278
283
  let field_exprs: Vec<String> = attributes
279
284
  .iter()
@@ -285,33 +290,33 @@ pub fn emit_from_json_flat(
285
290
  let optional = crate::attr::optional(attr);
286
291
 
287
292
  let rhs = if list && optional {
288
- let mapper = list_element_from_json_mapper(struct_name, &key, attr, value_objects_by_name);
289
- let array_error = json_type_error(struct_name, &key, "an array");
293
+ let mapper = list_element_from_json_mapper(error_name, &key, attr, value_objects_by_name);
294
+ let array_error = json_type_error(error_name, &key, "an array");
290
295
  format!(
291
296
  "match v.get({}) {{ Some(crate::kernel::Json::Null) | None => None, Some(x) => Some(x.as_array().ok_or_else(|| {array_error})?.iter().map({mapper}).collect::<Result<Vec<_>, crate::kernel::Refusal>>()?) }}",
292
297
  naming::ruby_inspect_string(&key)
293
298
  )
294
299
  } else if list {
295
- let mapper = list_element_from_json_mapper(struct_name, &key, attr, value_objects_by_name);
300
+ let mapper = list_element_from_json_mapper(error_name, &key, attr, value_objects_by_name);
296
301
  // A list argument is an array: a lone scalar is refused, as Ruby's
297
302
  // `Value.refuse_scalar_list` does; null and an absent key are the empty list.
298
- let shape_error = list_shape_error(struct_name, &key, crate::attr::type_name(attr), "x");
303
+ let shape_error = list_shape_error(error_name, &key, crate::attr::type_name(attr), "x");
299
304
  format!(
300
305
  "match v.get({}) {{ Some(crate::kernel::Json::Null) | None => Vec::new(), Some(x) => x.as_array().ok_or_else(|| {shape_error})?.iter().map({mapper}).collect::<Result<Vec<_>, crate::kernel::Refusal>>()?, }}",
301
306
  naming::ruby_inspect_string(&key)
302
307
  )
303
308
  } else if optional && scalar.is_some() {
304
309
  // An optional argument offered as null is the same absence as an omitted key.
305
- format!("match v.get({}) {{ Some(crate::kernel::Json::Null) | None => None, Some(x) => Some({}) }}", naming::ruby_inspect_string(&key), scalar_from_json_value_expr(struct_name, &key, scalar.unwrap(), "x"))
310
+ format!("match v.get({}) {{ Some(crate::kernel::Json::Null) | None => None, Some(x) => Some({}) }}", naming::ruby_inspect_string(&key), scalar_from_json_value_expr(error_name, &key, scalar.unwrap(), "x"))
306
311
  } else if optional {
307
312
  format!("match v.get({}) {{ Some(crate::kernel::Json::Null) | None => None, Some(x) => Some({}) }}", naming::ruby_inspect_string(&key), composite_from_json_expr(attr, value_objects_by_name, "x"))
308
313
  } else if let Some(scalar) = scalar {
309
- scalar_from_json_expr(struct_name, &key, scalar, crate::attr::default(attr))
314
+ scalar_from_json_expr(error_name, &key, scalar, crate::attr::default(attr))
310
315
  } else if absent_argument_check {
311
- // The argument door only; see `required_composite_argument_expr`.
312
- required_composite_argument_expr(struct_name, &key, attr, value_objects_by_name)
316
+ // The argument entry point only; see `required_composite_argument_expr`.
317
+ required_composite_argument_expr(error_name, &key, attr, value_objects_by_name)
313
318
  } else {
314
- composite_from_json_expr(attr, value_objects_by_name, &required_field_expr(struct_name, &key, crate::attr::type_name(attr)))
319
+ composite_from_json_expr(attr, value_objects_by_name, &required_field_expr(error_name, &key, crate::attr::type_name(attr)))
315
320
  };
316
321
 
317
322
  if interleave_checks {
@@ -588,6 +593,35 @@ pub fn emit_closed_set_table_codec(exemplar: &Exemplar, vo: &Json) -> String {
588
593
  })
589
594
  .collect();
590
595
 
596
+ let type_name = vo.get("name").and_then(Json::as_str).unwrap_or("").to_string();
597
+ let field_names: Vec<String> = attributes.iter().map(|attr| naming::ruby_inspect_string(&naming::rust_field(crate::attr::name(attr)))).collect();
598
+ // A field left out (or null) refuses as "{type}.{field} expects {expected}, got nil", before
599
+ // membership is judged.
600
+ let absent_checks: Vec<String> = attributes
601
+ .iter()
602
+ .map(|attr| {
603
+ let key = naming::rust_field(crate::attr::name(attr));
604
+ let message = format!("{type_name}.{} expects {}, got nil", crate::attr::name(attr), crate::attr::type_name(attr));
605
+ format!(
606
+ " if matches!(v.get({}), None | Some(crate::kernel::Json::Null)) {{\n return Err(crate::kernel::Refusal::TypeMismatch({}.to_string()));\n }}",
607
+ naming::ruby_inspect_string(&key),
608
+ naming::ruby_inspect_string(&message)
609
+ )
610
+ })
611
+ .collect();
612
+ // The refusal quotes the first column of every row, as Ruby's `refuse_non_member!` does.
613
+ let admitted: Vec<String> = vo
614
+ .get("members")
615
+ .map(Json::each)
616
+ .unwrap_or(&[])
617
+ .iter()
618
+ .map(|row| {
619
+ let first = row.as_array().unwrap_or(&[]).first().and_then(Json::as_array).unwrap_or(&[]);
620
+ naming::ruby_inspect_string(&first.get(1).map(Json::to_s).unwrap_or_default())
621
+ })
622
+ .collect();
623
+ let first_field = attributes.first().map(|attr| naming::rust_field(crate::attr::name(attr))).unwrap_or_default();
624
+
591
625
  exemplar.render(
592
626
  "closed_set_table_codec",
593
627
  &[
@@ -595,6 +629,11 @@ pub fn emit_closed_set_table_codec(exemplar: &Exemplar, vo: &Json) -> String {
595
629
  ("tmpl_to_json_fields_block()", to_json_fields_block),
596
630
  ("TMPL_TABLE", const_name),
597
631
  ("tmpl_from_json_conditions()", match_conditions.join(" && ")),
632
+ ("\"tmpl_table_type\"", naming::ruby_inspect_string(&type_name)),
633
+ ("TMPL_DECLARED_NAMES", format!("[{}]", field_names.join(", "))),
634
+ ("TMPL_ADMITTED", format!("[{}]", admitted.join(", "))),
635
+ ("tmpl_absent_field_checks();", absent_checks.join("\n")),
636
+ ("\"tmpl_first_field\"", naming::ruby_inspect_string(&first_field)),
598
637
  ],
599
638
  )
600
639
  }
@@ -3,6 +3,7 @@
3
3
  // Some ported `naming.rs` helpers are not called yet.
4
4
  #![allow(dead_code)]
5
5
 
6
+ mod asks;
6
7
  mod attr;
7
8
  mod bridging;
8
9
  mod commands;
@@ -73,7 +74,7 @@ fn run_prelude(args: &[String]) -> Result<(), String> {
73
74
  let name = aggregate.get("name").and_then(Json::as_str).unwrap_or("");
74
75
  match prelude::aggregate_prelude(&ex, &ir, aggregate, source_label) {
75
76
  Some(text) => {
76
- let path = format!("{out_dir}/{}.rs", name.to_lowercase());
77
+ let path = format!("{out_dir}/{}.rs", naming::aggregate_module(name));
77
78
  std::fs::write(&path, text).map_err(|e| format!("writing {path}: {e}"))?;
78
79
  println!("wrote {path}");
79
80
  }