hecks 3.10.0 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (481) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/bluebook/pizzas.bluebook +29 -0
  3. data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
  4. data/exe/hecks +4 -4
  5. data/lib/hecks/adapters/driven/d1.rb +2 -10
  6. data/lib/hecks/adapters/driven/heki.adapter +1 -1
  7. data/lib/hecks/adapters/driven/heki.rb +2 -12
  8. data/lib/hecks/adapters/driven/lambda.adapter +1 -1
  9. data/lib/hecks/adapters/driven/lambda.rb +3 -18
  10. data/lib/hecks/adapters/driven/postgres.rb +4 -11
  11. data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
  12. data/lib/hecks/adapters/driven/sqlite.rb +2 -10
  13. data/lib/hecks/adapters/driving/admission.rb +46 -0
  14. data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
  15. data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
  16. data/lib/hecks/adapters/driving/cli.rb +127 -0
  17. data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
  18. data/lib/hecks/adapters/driving/cli_runner/resolution.rb +155 -0
  19. data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
  20. data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
  21. data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
  22. data/lib/hecks/adapters/driving/cli_runner.rb +69 -0
  23. data/lib/hecks/adapters/driving/command_request.rb +121 -0
  24. data/lib/hecks/adapters/driving/handle.rb +214 -0
  25. data/lib/hecks/adapters/driving/json.rb +136 -0
  26. data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
  27. data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
  28. data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
  29. data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
  30. data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
  31. data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
  32. data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
  33. data/lib/hecks/adapters/driving/mcp.rb +159 -0
  34. data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
  35. data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
  36. data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
  37. data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
  38. data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
  39. data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
  40. data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
  41. data/lib/hecks/adapters/driving/ruby.rb +83 -0
  42. data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
  43. data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
  44. data/lib/hecks/behaviors/expectations/boot.rb +1 -1
  45. data/lib/hecks/bench/ruby_runner.rb +1 -1
  46. data/lib/hecks/bluebook/ask_resolution.rb +130 -0
  47. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
  48. data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
  49. data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
  50. data/lib/hecks/bluebook/assembly/marks.rb +1 -0
  51. data/lib/hecks/bluebook/attribute.rb +1 -1
  52. data/lib/hecks/bluebook/behaviour/command.rb +13 -8
  53. data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
  54. data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
  55. data/lib/hecks/bluebook/capabilities.rb +91 -9
  56. data/lib/hecks/bluebook/domain_port.rb +16 -4
  57. data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
  58. data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
  59. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
  60. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
  61. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
  62. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
  63. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
  64. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
  65. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
  66. data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
  67. data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
  68. data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
  69. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
  70. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
  71. data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
  72. data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
  73. data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
  74. data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
  75. data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
  76. data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
  77. data/lib/hecks/bluebook/expression/projection.json +36 -0
  78. data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
  79. data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
  80. data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
  81. data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
  82. data/lib/hecks/bluebook/expression/resolver.rb +20 -1
  83. data/lib/hecks/bluebook/hecksagon.rb +23 -3
  84. data/lib/hecks/bluebook/lifecycle.rb +22 -3
  85. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
  86. data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
  87. data/lib/hecks/bluebook/meta_validator/readings.rb +18 -1
  88. data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
  89. data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
  90. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
  91. data/lib/hecks/bluebook/meta_validator.rb +12 -12
  92. data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
  93. data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
  94. data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
  95. data/lib/hecks/bluebook/model_check.rb +8 -2
  96. data/lib/hecks/bluebook/policy.rb +4 -2
  97. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  98. data/lib/hecks/bluebook/synthesizer.rb +2 -0
  99. data/lib/hecks/bluebook.rb +1 -0
  100. data/lib/hecks/cli/command_table.rb +1 -1
  101. data/lib/hecks/cli/console.rb +2 -1
  102. data/lib/hecks/cli/document.rb +1 -1
  103. data/lib/hecks/cli/launcher_words.rb +4 -4
  104. data/lib/hecks/cli/mcp.rb +4 -4
  105. data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
  106. data/lib/hecks/cli/project_cli/templates.rb +5 -5
  107. data/lib/hecks/cli/project_cli.rb +9 -6
  108. data/lib/hecks/cli/run.rb +2 -2
  109. data/lib/hecks/cli/smoke_http/checks.rb +4 -1
  110. data/lib/hecks/cli/smoke_http/transport.rb +3 -3
  111. data/lib/hecks/cli/smoke_http.rb +3 -0
  112. data/lib/hecks/codemod.rb +5 -2
  113. data/lib/hecks/corpus/routes.rb +7 -0
  114. data/lib/hecks/corpus/rust.rb +3 -2
  115. data/lib/hecks/deploy/bluebook/deploy.bluebook +18 -2
  116. data/lib/hecks/doc/reference/rendering.rb +6 -3
  117. data/lib/hecks/forms/app/commands.rb +8 -7
  118. data/lib/hecks/forms/app/queries.rb +2 -1
  119. data/lib/hecks/forms/app.rb +1 -1
  120. data/lib/hecks/forms/record_renderer.rb +1 -1
  121. data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
  122. data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
  123. data/lib/hecks/fuzzing/failure_triage.rb +2 -1
  124. data/lib/hecks/fuzzing/mutation/run.rb +11 -4
  125. data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
  126. data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
  127. data/lib/hecks/fuzzing/properties/guards.rb +1 -1
  128. data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
  129. data/lib/hecks/fuzzing/self_consistency.rb +6 -2
  130. data/lib/hecks/fuzzing/shrinker.rb +3 -1
  131. data/lib/hecks/fuzzing/value_generator.rb +19 -5
  132. data/lib/hecks/gate/stages.yml +4 -10
  133. data/lib/hecks/grammar/evolve.rb +1 -1
  134. data/lib/hecks/grammar/expression_operators.json +372 -0
  135. data/lib/hecks/grammar/operators.rb +3 -1
  136. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
  137. data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
  138. data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
  139. data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
  140. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
  141. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
  142. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
  143. data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
  144. data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
  145. data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
  146. data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
  147. data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
  148. data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
  149. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
  150. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
  151. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
  152. data/lib/hecks/hecks/adapters/local_files.rb +2 -2
  153. data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
  154. data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
  155. data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
  156. data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
  157. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
  158. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
  159. data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
  160. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
  161. data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +129 -0
  162. data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
  163. data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
  164. data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
  165. data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
  166. data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
  167. data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
  168. data/lib/hecks/hecks/adapters/terminal.rb +13 -11
  169. data/lib/hecks/hecks/codebase.behaviors +13 -1
  170. data/lib/hecks/hecks/codebase.bluebook +239 -5
  171. data/lib/hecks/hecks/custodian.behaviors +24 -6
  172. data/lib/hecks/hecks/custodian.bluebook +6 -1227
  173. data/lib/hecks/hecks/deploy.behaviors +1 -1
  174. data/lib/hecks/hecks/deploy.world +1 -3
  175. data/lib/hecks/hecks/hecks.behaviors +1 -1
  176. data/lib/hecks/hecks/hecks.hecksagon +53 -18
  177. data/lib/hecks/hecks/hecks.world +10 -10
  178. data/lib/hecks/hecks/rehearsal.bluebook +477 -0
  179. data/lib/hecks/hecks/site.behaviors +1 -1
  180. data/lib/hecks/hecks/site.world +0 -1
  181. data/lib/hecks/hecks/tooling.bluebook +1238 -0
  182. data/lib/hecks/indifferent_key.rb +20 -0
  183. data/lib/hecks/language/adapter.bluebook +13 -3
  184. data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
  185. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
  186. data/lib/hecks/language/bluebook/entity.bluebook +18 -0
  187. data/lib/hecks/language/bluebook/policy.bluebook +9 -1
  188. data/lib/hecks/language/bluebook/query.bluebook +1 -1
  189. data/lib/hecks/language/bluebook/vocabulary.bluebook +11 -4
  190. data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
  191. data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
  192. data/lib/hecks/language/oidc.json +10 -0
  193. data/lib/hecks/language/port.bluebook +4 -4
  194. data/lib/hecks/literal.rb +12 -3
  195. data/lib/hecks/ports/key_vault.rb +7 -2
  196. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
  197. data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
  198. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
  199. data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
  200. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +29 -0
  201. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
  202. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  203. data/lib/hecks/projections/deploy/shared.rb +5 -2
  204. data/lib/hecks/projections/model/deviations.rb +2 -2
  205. data/lib/hecks/projections/model.rb +2 -2
  206. data/lib/hecks/projections/oidc.rb +13 -1
  207. data/lib/hecks/projections/shape.rb +5 -2
  208. data/lib/hecks/projections/site/cms_editor/attributes.rb +26 -6
  209. data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
  210. data/lib/hecks/projections/site/cms_editor/checks.rb +14 -1
  211. data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
  212. data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
  213. data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
  214. data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
  215. data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
  216. data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
  217. data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
  218. data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
  219. data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
  220. data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
  221. data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
  222. data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
  223. data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
  224. data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
  225. data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
  226. data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
  227. data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
  228. data/lib/hecks/projections/site/cms_editor/schema.rb +96 -19
  229. data/lib/hecks/projections/site/cms_editor/setting.rb +52 -9
  230. data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
  231. data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +18 -1
  232. data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +251 -57
  233. data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
  234. data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +32 -24
  235. data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
  236. data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
  237. data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
  238. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
  239. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
  240. data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
  241. data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
  242. data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
  243. data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
  244. data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
  245. data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
  246. data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
  247. data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
  248. data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
  249. data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
  250. data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
  251. data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +78 -24
  252. data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +6 -0
  253. data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
  254. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +19 -2
  255. data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +31 -8
  256. data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
  257. data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +131 -2
  258. data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
  259. data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
  260. data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
  261. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +8 -5
  262. data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
  263. data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
  264. data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +229 -34
  265. data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +40 -81
  266. data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
  267. data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +29 -12
  268. data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
  269. data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
  270. data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +53 -17
  271. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +178 -88
  272. data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
  273. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
  274. data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
  275. data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
  276. data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
  277. data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
  278. data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
  279. data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
  280. data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
  281. data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
  282. data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
  283. data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +2 -1
  284. data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
  285. data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
  286. data/lib/hecks/projections/site/cms_editor.rb +49 -23
  287. data/lib/hecks/projections/site/role_probe.rb +2 -0
  288. data/lib/hecks/projector/cli_audience.rb +2 -1
  289. data/lib/hecks/projector/cli_projector/listing.rb +1 -1
  290. data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
  291. data/lib/hecks/projector/docs_projector/closing.rb +1 -1
  292. data/lib/hecks/projector/docs_projector.rb +1 -1
  293. data/lib/hecks/projector/exporter/bindings.rb +40 -10
  294. data/lib/hecks/projector/exporter/capability_values.rb +55 -0
  295. data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
  296. data/lib/hecks/projector/target.rb +11 -0
  297. data/lib/hecks/projector.rb +32 -5
  298. data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
  299. data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
  300. data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
  301. data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
  302. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
  303. data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
  304. data/lib/hecks/quality_control/quality_control.bluebook +6 -2
  305. data/lib/hecks/release/approval.rb +64 -0
  306. data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
  307. data/lib/hecks/router/namespace_installer.rb +16 -16
  308. data/lib/hecks/router.rb +1 -1
  309. data/lib/hecks/runtime/best_effort.rb +22 -0
  310. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
  311. data/lib/hecks/runtime/command_interpreter.rb +3 -2
  312. data/lib/hecks/runtime/command_rules/emission.rb +1 -1
  313. data/lib/hecks/runtime/dispatcher.rb +4 -4
  314. data/lib/hecks/runtime/entity_element/location.rb +1 -1
  315. data/lib/hecks/runtime/event.rb +5 -0
  316. data/lib/hecks/runtime/identity.rb +7 -6
  317. data/lib/hecks/runtime/interpreting.rb +2 -1
  318. data/lib/hecks/runtime/invocation_builder.rb +1 -1
  319. data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
  320. data/lib/hecks/runtime/loader.rb +10 -10
  321. data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
  322. data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
  323. data/lib/hecks/runtime/policy_interpreter.rb +6 -6
  324. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
  325. data/lib/hecks/runtime/reaction_invocation.rb +1 -1
  326. data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
  327. data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
  328. data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
  329. data/lib/hecks/runtime/registry/merging.rb +1 -1
  330. data/lib/hecks/runtime/registry/providers.rb +16 -0
  331. data/lib/hecks/runtime/registry/read_repository.rb +6 -4
  332. data/lib/hecks/runtime/registry/verification.rb +4 -1
  333. data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
  334. data/lib/hecks/runtime/registry.rb +1 -1
  335. data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
  336. data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
  337. data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
  338. data/lib/hecks/runtime/saga_interpreter.rb +3 -3
  339. data/lib/hecks/runtime/tenant_check.rb +5 -4
  340. data/lib/hecks/runtime/value/coercion.rb +2 -2
  341. data/lib/hecks/runtime/value/validation.rb +16 -4
  342. data/lib/hecks/runtime.rb +9 -9
  343. data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
  344. data/lib/hecks/rust_build/wasm.rb +1 -1
  345. data/lib/hecks/rust_build/wasm_browser.rb +1 -1
  346. data/lib/hecks/rust_build.rb +11 -0
  347. data/lib/hecks/served_options.rb +53 -0
  348. data/lib/hecks/site/bluebook/site.bluebook +1 -0
  349. data/lib/hecks/storehouse/audit.rb +1 -1
  350. data/lib/hecks/storehouse/dispatching.rb +8 -6
  351. data/lib/hecks/storehouse/introspection.rb +2 -2
  352. data/lib/hecks/storehouse/querying.rb +6 -4
  353. data/lib/hecks/storehouse.rb +4 -4
  354. data/lib/hecks/tools/comment_style/source_file.rb +4 -1
  355. data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
  356. data/lib/hecks/tools/lanes/rows.rb +6 -0
  357. data/lib/hecks/tools/oidc_manifests.rb +4 -5
  358. data/lib/hecks/tools/regeneration_run.rb +11 -4
  359. data/lib/hecks/tools/site_routes/editor_files.rb +12 -5
  360. data/lib/hecks/tools/tenant_provisioning.rb +2 -2
  361. data/lib/hecks/tools/tools_doc.rb +4 -4
  362. data/lib/hecks/version.rb +1 -1
  363. data/lib/hecks/vocabulary.rb +4 -4
  364. data/lib/hecks.rb +15 -15
  365. data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
  366. data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
  367. data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
  368. data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
  369. data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
  370. data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
  371. data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
  372. data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
  373. data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
  374. data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
  375. data/prebuilt/verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json +1 -0
  376. data/rust/.gitignore +6 -0
  377. data/rust/Cargo.toml +6 -0
  378. data/rust/build/src/binding_pass.rs +103 -0
  379. data/rust/build/src/cargo_sync.rs +6 -0
  380. data/rust/build/src/lineage_pass.rs +3 -3
  381. data/rust/build/src/main.rs +17 -5
  382. data/rust/build/src/pipeline.rs +48 -5
  383. data/rust/build/src/seams_pass.rs +123 -0
  384. data/rust/build/src/translation_pass.rs +167 -0
  385. data/rust/codegen/src/asks.rs +103 -0
  386. data/rust/codegen/src/commands.rs +13 -8
  387. data/rust/codegen/src/constraints.rs +25 -0
  388. data/rust/codegen/src/domain_generator.rs +2 -0
  389. data/rust/codegen/src/expr_emitter.rs +13 -0
  390. data/rust/codegen/src/json_codec.rs +54 -15
  391. data/rust/codegen/src/main.rs +1 -0
  392. data/rust/codegen/src/mutations.rs +128 -3
  393. data/rust/codegen/src/queries.rs +11 -2
  394. data/rust/codegen/src/reactions.rs +1 -1
  395. data/rust/codegen/src/types.rs +166 -8
  396. data/rust/host/HECKS_RELEASE +1 -1
  397. data/rust/host/src/api.rs +4 -4
  398. data/rust/host/src/bin/validate_harness.rs +43 -0
  399. data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
  400. data/rust/host/src/boundary_fuzz/ir.rs +1 -0
  401. data/rust/host/src/boundary_fuzz/server.rs +1 -1
  402. data/rust/host/src/checkout.rs +35 -19
  403. data/rust/host/src/commerce_ir.rs +408 -2
  404. data/rust/host/src/expr_json.rs +118 -0
  405. data/rust/host/src/mint.rs +1 -1
  406. data/rust/host/src/payments/tests.rs +27 -1
  407. data/rust/host/src/payments.rs +12 -4
  408. data/rust/host/src/query_step.rs +22 -4
  409. data/rust/host/src/reference_validate.rs +653 -108
  410. data/rust/host/src/resend.rs +2 -0
  411. data/rust/host/src/server.rs +19 -6
  412. data/rust/host/src/ui_schema.rs +132 -5
  413. data/rust/host/src/web/newsletter.rs +7 -8
  414. data/rust/host/src/web/newsletter_send.rs +38 -13
  415. data/rust/host/src/web/registrations.rs +229 -26
  416. data/rust/host/src/web.rs +3 -3
  417. data/rust/parser/src/emit.rs +38 -6
  418. data/rust/parser/src/expr/ast_json.rs +7 -1
  419. data/rust/parser/src/expr/resolver.rs +171 -6
  420. data/rust/parser/src/ir.rs +7 -0
  421. data/rust/parser/src/keywords.rs +24 -0
  422. data/rust/parser/src/main.rs +3 -0
  423. data/rust/parser/src/parse/aggregate.rs +2 -1
  424. data/rust/parser/src/parse/chapter.rs +2 -2
  425. data/rust/parser/src/parse/command.rs +2 -0
  426. data/rust/parser/src/parse/hecksagon.rs +5 -2
  427. data/rust/parser/src/parse/lifecycle.rs +81 -0
  428. data/rust/parser/src/parse/mod.rs +15 -1
  429. data/rust/parser/src/parse/policy.rs +13 -2
  430. data/rust/parser/src/parse/value_object.rs +13 -8
  431. data/rust/parser/src/ruby_value.rs +1 -1
  432. data/rust/src/exemplar/commands.rs +4 -4
  433. data/rust/src/exemplar/constraints.rs +1 -1
  434. data/rust/src/exemplar/json.rs +43 -1
  435. data/rust/src/kernel/cli.rs +7 -5
  436. data/rust/src/kernel/dispatch.rs +2 -2
  437. data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
  438. data/rust/src/kernel/expr.rs +66 -3
  439. data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
  440. data/rust/src/kernel/expression_operators/text.rs +26 -2
  441. data/rust/src/kernel/json.rs +15 -0
  442. data/rust/src/kernel/mod.rs +1 -1
  443. data/rust/src/kernel/orchestrate.rs +54 -5
  444. data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
  445. data/rust/src/kernel/pattern.rs +60 -0
  446. data/rust/src/kernel/reference_lookup.rs +90 -7
  447. metadata +140 -38
  448. data/lib/hecks/doors/cli_door/nesting.rb +0 -34
  449. data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
  450. data/lib/hecks/doors/cli_door.rb +0 -124
  451. data/lib/hecks/doors/cli_runner/answers.rb +0 -138
  452. data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
  453. data/lib/hecks/doors/cli_runner/settling.rb +0 -115
  454. data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
  455. data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
  456. data/lib/hecks/doors/cli_runner.rb +0 -66
  457. data/lib/hecks/doors/command_request.rb +0 -119
  458. data/lib/hecks/doors/handle.rb +0 -208
  459. data/lib/hecks/doors/json_door.rb +0 -132
  460. data/lib/hecks/doors/launcher_options.rb +0 -247
  461. data/lib/hecks/doors/mcp_door/properties.rb +0 -47
  462. data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
  463. data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
  464. data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
  465. data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
  466. data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
  467. data/lib/hecks/doors/mcp_door.rb +0 -156
  468. data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
  469. data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
  470. data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
  471. data/lib/hecks/doors/mcp_door_scope.rb +0 -201
  472. data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
  473. data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
  474. data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
  475. data/lib/hecks/doors/ruby_door.rb +0 -80
  476. data/lib/hecks/doors/usage_cache.rb +0 -139
  477. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_widget.js.tmpl +0 -282
  478. data/lib/hecks/projections/site/cms_editor/templates/src/ui/media_picker.js.tmpl +0 -109
  479. data/prebuilt/c28d6d9db14eea3e17161e9d9c262efee30b256cf97693a7cfd552bce24b2f7f.marshal +0 -0
  480. data/prebuilt/cb375cba61ca0dbe8ddb9739c306510fe05e5f4b7a9b6955caff8224c63ea0e0.marshal +0 -0
  481. data/prebuilt/verdicts-d6d584ad43b5f3cebf88aafbbdbf024c38574acf18e06adffbc555f7786b1c2f.json +0 -1
@@ -1,5 +1,5 @@
1
- //! The `Policy` construct: `on`/`trigger` fill `on_event`/`trigger_command`, `across` names the
2
- //! target domain.
1
+ //! The `Policy` construct: `on`/`trigger` fill `on_event`/`trigger_command`, `ask` names a need
2
+ //! the hecksagon maps to a port, `across` names the target domain.
3
3
 
4
4
  use crate::canonical;
5
5
  use crate::diag::{Diagnostic, ParseResult};
@@ -49,6 +49,17 @@ pub fn parse_body(
49
49
  // Shares `dispatch`'s `with:` parser so the two cannot drift.
50
50
  policy.with_spec = super::process_manager::parse_with_pairs_opt(&gated.args);
51
51
  }
52
+ // Same `with:` projection as `trigger`; which port answers is the hecksagon's to say.
53
+ "ask" => {
54
+ policy.ask = Some(super::positional_symbol(
55
+ file,
56
+ gated.line.number,
57
+ "ask",
58
+ &gated.args,
59
+ 1,
60
+ )?);
61
+ policy.with_spec = super::process_manager::parse_with_pairs_opt(&gated.args);
62
+ }
52
63
  "across" => {
53
64
  policy.target_domain = Some(super::positional_text(
54
65
  file,
@@ -64,17 +64,21 @@ fn try_reference_named_invariant(
64
64
  Ok(Some(resolved))
65
65
  }
66
66
 
67
+ /// Parses one `value_object` body. Returns the value object and the closed sets its type-position
68
+ /// `one_of(...)` attributes synthesized, which the owning aggregate installs right after it, as
69
+ /// `AggregateBuilder#value_object` does with `builder.closed_sets`.
67
70
  pub fn parse_body(
68
71
  file: &str,
69
72
  lines: &[SourceLine],
70
73
  pos: &mut usize,
71
74
  name: &str,
72
75
  owner_value_objects: &[ir::ValueObject],
73
- ) -> ParseResult<ir::ValueObject> {
76
+ ) -> ParseResult<(ir::ValueObject, Vec<ir::ValueObject>)> {
74
77
  let mut vo = ir::ValueObject {
75
78
  name: name.to_string(),
76
79
  ..Default::default()
77
80
  };
81
+ let mut synthesized: Vec<ir::ValueObject> = Vec::new();
78
82
 
79
83
  loop {
80
84
  if let Some(invariant) =
@@ -87,14 +91,15 @@ pub fn parse_body(
87
91
  let Some(gated) = super::next_line(file, lines, pos, "ValueObject")? else {
88
92
  // Mirrors `ValueObjectBuilder#build`: a non-empty `members` closes the set.
89
93
  vo.closed_set = vo.closed_set || !vo.members.is_empty();
90
- return Ok(vo);
94
+ return Ok((vo, synthesized));
91
95
  };
92
96
 
93
97
  match gated.row.word {
94
- // An inline type-position `one_of(...)` set is discarded (see build/closed_sets.rs);
95
- // `ValueObject` has no nested-value-objects field. Use the `one_of:` keyword instead.
98
+ // An inline type-position `one_of(...)` set is returned beside the value object (see
99
+ // build/closed_sets.rs); `ValueObject` has no nested-value-objects field.
96
100
  "attribute" => {
97
- let (attribute, one_of) = build_value_object_attribute(file, &gated.args)?;
101
+ let (attribute, one_of, closed_set) = build_value_object_attribute(file, &gated.args)?;
102
+ synthesized.extend(closed_set);
98
103
  if let Some(values) = one_of {
99
104
  let vo_name = vo.name.clone();
100
105
  install_inline_closed_set(
@@ -135,8 +140,8 @@ pub fn parse_body(
135
140
  fn build_value_object_attribute(
136
141
  file: &str,
137
142
  args: &super::ArgumentGateResult,
138
- ) -> ParseResult<(ir::Attribute, Option<Vec<String>>)> {
139
- let (attribute, _) = super::build_attribute(file, 0, "attribute", args)?;
143
+ ) -> ParseResult<(ir::Attribute, Option<Vec<String>>, Option<ir::ValueObject>)> {
144
+ let (attribute, closed_set) = super::build_attribute(file, 0, "attribute", args)?;
140
145
  let one_of = super::named_raw(args, "one_of").map(|raw| {
141
146
  let trimmed = raw.trim();
142
147
  let inner = trimmed
@@ -148,7 +153,7 @@ fn build_value_object_attribute(
148
153
  .map(|segment| ruby_value::to_s(&ruby_value::read(segment.trim())))
149
154
  .collect()
150
155
  });
151
- Ok((attribute, one_of))
156
+ Ok((attribute, one_of, closed_set))
152
157
  }
153
158
 
154
159
  /// Installs `attribute :name, String, one_of: [...]`; refused unless it is the value object's
@@ -41,7 +41,7 @@ pub fn render(value: &Value) -> String {
41
41
  }
42
42
 
43
43
  /// Ruby's `Float#to_s` always keeps a digit after the point (`1.0`); Rust prints `1`.
44
- fn format_ruby_float(value: f64) -> String {
44
+ pub(crate) fn format_ruby_float(value: f64) -> String {
45
45
  let text = format!("{value}");
46
46
  if text.contains('.') || text.contains('e') || text.contains("inf") || text.contains("NaN") {
47
47
  text
@@ -68,7 +68,7 @@ impl crate::kernel::ToJson for TmplRecord {
68
68
  }
69
69
  }
70
70
  impl crate::kernel::SetProjectedField for TmplRecord {
71
- fn set_projected_field(&mut self, name: &'static str, value: Option<String>) {
71
+ fn set_projected_field(&mut self, name: &'static str, value: Option<crate::kernel::Value>) {
72
72
  let _ = (name, value);
73
73
  }
74
74
  }
@@ -94,7 +94,7 @@ fn tmpl_hydrate_placeholder() -> crate::kernel::Hydrate<'static, TmplRecord> {
94
94
 
95
95
  fn tmpl_mutation_lines_placeholder(record: &mut TmplRecord) {}
96
96
 
97
- fn tmpl_seed_projections_placeholder() -> Vec<(&'static str, Option<String>)> {
97
+ fn tmpl_seed_projections_placeholder() -> Vec<(&'static str, Option<crate::kernel::Value>)> {
98
98
  Vec::new()
99
99
  }
100
100
 
@@ -160,9 +160,9 @@ fn tmpl_emit_placeholder() -> &'static str {
160
160
  ""
161
161
  }
162
162
 
163
- // `delegates_to "Entity.Command"`: the door runs `apply_entity_command` inside its closure.
163
+ // `delegates_to "Entity.Command"`: the entry point runs `apply_entity_command` inside its closure.
164
164
  // `delegate_prelude` fills the prelude slot; `delegate_apply` fills the mutation slot.
165
- // The inner `|record|` deliberately shadows the door's: entity `sets` only reach the element.
165
+ // The inner `|record|` deliberately shadows the entry point's: entity `sets` only reach the element.
166
166
  fn tmpl_aliases_placeholder() -> (&'static str, &'static str) {
167
167
  ("", "")
168
168
  }
@@ -12,7 +12,7 @@ fn tmpl_admits_check_host(tmpl_scalar: String) -> Result<(), crate::kernel::Refu
12
12
 
13
13
  fn tmpl_pattern_check_host(tmpl_scalar: String) -> Result<(), crate::kernel::Refusal> {
14
14
  // TMPL:pattern_check BEGIN
15
- if !crate::kernel::pattern::matches("tmpl_pattern_text", &tmpl_scalar) { return Err(crate::kernel::Refusal::TypeMismatch(crate::kernel::refusal_wording::TypeMismatchPatternMismatchArgs { r#type: "tmpl_pattern_owner", field: "tmpl_pattern_field", pattern: "tmpl_pattern_text", offered: format!("{:?}", tmpl_scalar).as_str() }.render_args())); }
15
+ if !crate::kernel::pattern::matches_whole("tmpl_pattern_text", &tmpl_scalar) { return Err(crate::kernel::Refusal::TypeMismatch(crate::kernel::refusal_wording::TypeMismatchPatternMismatchArgs { r#type: "tmpl_pattern_owner", field: "tmpl_pattern_field", pattern: "tmpl_pattern_text", offered: format!("{:?}", tmpl_scalar).as_str() }.render_args())); }
16
16
  // TMPL:pattern_check END
17
17
  Ok(())
18
18
  }
@@ -17,6 +17,20 @@ impl TmplKind {
17
17
  }
18
18
 
19
19
  pub fn from_json(v: &crate::kernel::Json) -> Result<Self, crate::kernel::Refusal> {
20
+ // A name the set does not declare refuses first, as `Value::FieldChecks#check_unknown_fields`
21
+ // does in `validate!`: before a missing field, and before membership.
22
+ let unknown = v.unknown_keys(&["tmpl_field_name"]);
23
+ if !unknown.is_empty() {
24
+ let unknown: Vec<&str> = unknown.iter().map(|key| key.as_str()).collect();
25
+ return Err(crate::kernel::Refusal::UnknownArgument(
26
+ crate::kernel::refusal_wording::UnknownArgumentUnknownArgsArgs {
27
+ command: "tmpl_closed_set_type",
28
+ unknown: &unknown,
29
+ declared: &["tmpl_field_name"],
30
+ }
31
+ .render_args(),
32
+ ));
33
+ }
20
34
  // A `one_of` closed set is admission-checked on the raw offered
21
35
  // value, no shape check first — `Value::Admission#admit_member`
22
36
  // runs on whatever `Value::Coercion#fields_for` auto-wrapped into
@@ -93,6 +107,10 @@ struct TmplTableRow {
93
107
  }
94
108
 
95
109
  const TMPL_TABLE: &[TmplTableRow] = &[];
110
+ const TMPL_DECLARED_NAMES: [&str; 0] = [];
111
+ const TMPL_ADMITTED: [&str; 0] = [];
112
+
113
+ fn tmpl_absent_field_checks() {}
96
114
 
97
115
  // Function-call placeholders; the marker sits flush left because plain `render` never reindents.
98
116
  fn tmpl_to_json_fields_block() -> (String, crate::kernel::Json) {
@@ -112,12 +130,36 @@ tmpl_to_json_fields_block()
112
130
  }
113
131
 
114
132
  pub fn from_json(v: &crate::kernel::Json) -> Result<Self, crate::kernel::Refusal> {
133
+ // `validate!`'s order: a name the set does not declare, then a field left out, then
134
+ // membership of the whole row.
135
+ let unknown = v.unknown_keys(&TMPL_DECLARED_NAMES);
136
+ if !unknown.is_empty() {
137
+ let unknown: Vec<&str> = unknown.iter().map(|key| key.as_str()).collect();
138
+ return Err(crate::kernel::Refusal::UnknownArgument(
139
+ crate::kernel::refusal_wording::UnknownArgumentUnknownArgsArgs {
140
+ command: "tmpl_table_type",
141
+ unknown: &unknown,
142
+ declared: &TMPL_DECLARED_NAMES,
143
+ }
144
+ .render_args(),
145
+ ));
146
+ }
147
+ tmpl_absent_field_checks();
115
148
  for row in TMPL_TABLE {
116
149
  if tmpl_from_json_conditions() {
117
150
  return Ok(row.clone());
118
151
  }
119
152
  }
120
- Err(crate::kernel::Refusal::TypeMismatch(format!("TmplTableRow: no member matches {:?}", v)))
153
+ // The refusal quotes the first column only, as Ruby's `refuse_non_member!` does.
154
+ let offered = v.get("tmpl_first_field").map(|offered| offered.inspect()).unwrap_or_else(|| "nil".to_string());
155
+ Err(crate::kernel::Refusal::InvariantViolation(
156
+ crate::kernel::refusal_wording::InvariantViolationClosedSetMemberArgs {
157
+ r#type: "tmpl_table_type",
158
+ admitted: &TMPL_ADMITTED,
159
+ offered: offered.as_str(),
160
+ }
161
+ .render_args(),
162
+ ))
121
163
  }
122
164
  }
123
165
  // TMPL:closed_set_table_codec END
@@ -162,15 +162,17 @@ pub fn run(input: &str) -> String {
162
162
  refusals.push((question.clone(), refusal));
163
163
  }
164
164
  },
165
+ // Only a query the generator did not emit reaches here; the codegen manifest lists
166
+ // each one it skipped, with the reason.
165
167
  None => refusals.push((
166
168
  question.clone(),
167
169
  Refusal::TypeMismatch(format!(
168
170
  "named/declared query {question:?} is not generated for this domain — either unknown, or a \
169
- real declared query whose shape this generator's codegen doesn't cover yet (order_by/limit/\
170
- cursor/consistency/freshness/authorization/null_semantics/inspection/index_hints, a where \
171
- clause hopping through a reference, or a literal comparator value whose true JSON type \
172
- can't be recovered from the exported IR — rust/codegen/src/queries.rs's own header has the \
173
- full argument); the wheres-only, single-aggregate field-comparator subset and the ad hoc \
171
+ real declared query whose shape this generator's codegen doesn't cover yet (cursor/\
172
+ consistency/freshness/inspection/index_hints, a where clause on a field the aggregate \
173
+ does not declare, or a literal comparator value whose true JSON type can't be \
174
+ recovered from the exported IR — hecks-codegen's manifest names which); a declared \
175
+ query with or without a where, ordered and bounded as declared, and the ad hoc \
174
176
  filter shape ({{\"aggregate\",\"field\",\"op\",\"value\"}}) both execute for real"
175
177
  )),
176
178
  )),
@@ -118,7 +118,7 @@ pub fn dispatch<'a, T, R>(
118
118
  payload: Json,
119
119
  mutations: &mut Vec<MutationRecord>,
120
120
  // The synchronous half of `projects`, applied right before `repo.save`.
121
- seed_projections: Vec<(&'static str, Option<String>)>,
121
+ seed_projections: Vec<(&'static str, Option<Value>)>,
122
122
  // The write-side tenant boundary. The router computes it (it needs every aggregate's repo)
123
123
  // but it is applied here, first in `Save`, before `seed_projections`.
124
124
  tenant_boundary_check: Result<(), Refusal>,
@@ -606,7 +606,7 @@ pub fn dispatch_entity<'a, T, E, R>(
606
606
  payload: Json,
607
607
  mutations: &mut Vec<MutationRecord>,
608
608
  // As in `dispatch`: an entity command still ends in a parent aggregate save.
609
- seed_projections: Vec<(&'static str, Option<String>)>,
609
+ seed_projections: Vec<(&'static str, Option<Value>)>,
610
610
  ) -> Result<(T, Vec<Event>), Refusal>
611
611
  where
612
612
  T: Fielded + Clone + ToJson + SetProjectedField,
@@ -60,7 +60,7 @@ impl ToJson for Acct {
60
60
  }
61
61
 
62
62
  impl SetProjectedField for Acct {
63
- fn set_projected_field(&mut self, _name: &'static str, _value: Option<String>) {}
63
+ fn set_projected_field(&mut self, _name: &'static str, _value: Option<Value>) {}
64
64
  }
65
65
 
66
66
  fn rule(description: &'static str, field: &'static str) -> InvariantSpec {
@@ -27,6 +27,39 @@ impl Value {
27
27
  pub fn truthy(&self) -> bool {
28
28
  !matches!(self, Value::Nil | Value::Bool(false))
29
29
  }
30
+
31
+ /// The text of a `Str`, else `None`; a generated `SetProjectedField` arm narrows a
32
+ /// projected scalar to its field's type with one of the `into_*` readers.
33
+ pub fn into_string(self) -> Option<String> {
34
+ match self {
35
+ Value::Str(s) => Some(s),
36
+ _ => None,
37
+ }
38
+ }
39
+
40
+ /// The integer of an `Int`, else `None`.
41
+ pub fn into_i64(self) -> Option<i64> {
42
+ match self {
43
+ Value::Int(n) => Some(n),
44
+ _ => None,
45
+ }
46
+ }
47
+
48
+ /// The float of a `Float`, else `None`.
49
+ pub fn into_f64(self) -> Option<f64> {
50
+ match self {
51
+ Value::Float(f) => Some(f),
52
+ _ => None,
53
+ }
54
+ }
55
+
56
+ /// The boolean of a `Bool`, else `None`.
57
+ pub fn into_bool(self) -> Option<bool> {
58
+ match self {
59
+ Value::Bool(b) => Some(b),
60
+ _ => None,
61
+ }
62
+ }
30
63
  }
31
64
 
32
65
  pub enum Field<'a> {
@@ -203,6 +236,10 @@ pub enum Expr {
203
236
  Bool(bool),
204
237
  Nil,
205
238
  Add(Box<Expr>, Box<Expr>),
239
+ Sub(Box<Expr>, Box<Expr>),
240
+ Mul(Box<Expr>, Box<Expr>),
241
+ /// Floor division on integers; a zero divisor is an evaluation fault.
242
+ Div(Box<Expr>, Box<Expr>),
206
243
  SignTest { op: Comparison, receiver: Box<Expr> },
207
244
  Empty(Box<Expr>),
208
245
  ToS(Box<Expr>),
@@ -232,6 +269,9 @@ pub enum Expr {
232
269
  /// `receiver.split("SEP")` — `Resolver::Split`. Produces a real
233
270
  /// `Value::Array`, not `Value::List` — see that variant's own header.
234
271
  Split { receiver: Box<Expr>, separator: String },
272
+ /// `receiver.strip` / `.lstrip` / `.rstrip` — `Resolver::Strip`. Trims Ruby's own whitespace
273
+ /// set (see `StripSide::trim`), not Unicode spaces.
274
+ Strip { receiver: Box<Expr>, side: StripSide },
235
275
  /// `receiver.start_with?("prefix")` — `Resolver::StartsWith`.
236
276
  StartsWith { receiver: Box<Expr>, substring: String },
237
277
  /// `receiver.end_with?("suffix")` — `Resolver::EndsWith`.
@@ -242,6 +282,29 @@ pub enum Expr {
242
282
  Last(Box<Expr>),
243
283
  }
244
284
 
285
+ /// Which ends of a string `Expr::Strip` trims: `.strip` both, `.lstrip` the start, `.rstrip` the end.
286
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
287
+ pub enum StripSide {
288
+ Both,
289
+ Left,
290
+ Right,
291
+ }
292
+
293
+ impl StripSide {
294
+ /// Ruby's `String#strip` family: trims null, tab, line feed, vertical tab, form feed, carriage
295
+ /// return and space, never a Unicode space. Rust's own `trim` would also drop U+00A0 and the
296
+ /// rest, so the set is spelled out.
297
+ pub fn trim(self, text: &str) -> String {
298
+ let is_stripped = |c: char| matches!(c, '\0' | '\t' | '\n' | '\u{b}' | '\u{c}' | '\r' | ' ');
299
+ match self {
300
+ StripSide::Both => text.trim_matches(is_stripped),
301
+ StripSide::Left => text.trim_start_matches(is_stripped),
302
+ StripSide::Right => text.trim_end_matches(is_stripped),
303
+ }
304
+ .to_string()
305
+ }
306
+ }
307
+
245
308
  /// `args`/`instance` from `Evaluator.call` — `args` is checked first for
246
309
  /// every `Lookup`, matching `Resolver#fetch`. Pass `&NoFields` for `{}`.
247
310
  pub struct EvalContext<'a> {
@@ -274,14 +337,14 @@ fn category_of(expr: &Expr) -> OperatorCategory {
274
337
  Or(..) | And(..) | Not(..) => OperatorCategory::Logical,
275
338
  Include { .. } => OperatorCategory::Membership,
276
339
  Compare { .. } => OperatorCategory::Comparison,
277
- Add(..) | Modulo { .. } => OperatorCategory::Arithmetic,
340
+ Add(..) | Sub(..) | Mul(..) | Div(..) | Modulo { .. } => OperatorCategory::Arithmetic,
278
341
  SignTest { .. } => OperatorCategory::SignTest,
279
342
  Empty(..) | Size(..) => OperatorCategory::Sized,
280
343
  ToS(..) => OperatorCategory::ToString,
281
344
  BlockPredicate { .. } | Find { .. } => OperatorCategory::Enumeration,
282
345
  MatchesRegex { .. } => OperatorCategory::PatternMatch,
283
346
  Presence { .. } | Assignment { .. } => OperatorCategory::Presence,
284
- Split { .. } | StartsWith { .. } | EndsWith { .. } => OperatorCategory::Text,
347
+ Split { .. } | Strip { .. } | StartsWith { .. } | EndsWith { .. } => OperatorCategory::Text,
285
348
  First(..) | Last(..) => OperatorCategory::Positional,
286
349
  Int(..) | Float(..) | Str(..) | Bool(..) | Nil | Lookup(..) | Array(..) => {
287
350
  unreachable!("interpret's own leaf arms handle these before category_of is ever called")
@@ -300,7 +363,7 @@ fn dispatch_operator(category: OperatorCategory, expr: &Expr, ctx: &EvalContext)
300
363
  OperatorCategory::Membership => membership::interpret(expr, ctx),
301
364
  OperatorCategory::Comparison => comparison::interpret(expr, ctx),
302
365
  OperatorCategory::Arithmetic => match expr {
303
- Expr::Add(..) => arithmetic::add(expr, ctx),
366
+ Expr::Add(..) | Expr::Sub(..) | Expr::Mul(..) | Expr::Div(..) => arithmetic::binary(expr, ctx),
304
367
  Expr::Modulo { .. } => arithmetic::modulo(expr, ctx),
305
368
  _ => Err(Refusal::TypeMismatch(format!("dispatch_operator(Arithmetic, ..) called with {expr:?} — a router bug"))),
306
369
  },
@@ -1,15 +1,64 @@
1
- //! `+` and `.modulo` (`Resolver::Addition`/`Modulo`); both require numeric operands.
1
+ //! `+`, `-`, `*`, `/` and `.modulo` (`Resolver::Addition`/`Subtraction`/`Multiplication`/`Division`/
2
+ //! `Modulo`); all require numeric operands.
2
3
 
3
4
  use crate::kernel::attribute_shapes::scalar;
4
5
  use crate::kernel::expr::{eval_error, interpret as eval, EvalContext, Expr, Value};
5
6
  use crate::kernel::Refusal;
6
7
 
7
- pub fn add(expr: &Expr, ctx: &EvalContext) -> Result<Value, Refusal> {
8
- let Expr::Add(l, r) = expr else {
9
- return Err(Refusal::TypeMismatch(format!("arithmetic::add called with a non-addition node {expr:?} — a router bug")));
8
+ /// `-`, `*`, `/` and `+`, each the counterpart of the Ruby `Resolver` operation of that name.
9
+ pub fn binary(expr: &Expr, ctx: &EvalContext) -> Result<Value, Refusal> {
10
+ let (name, symbol, l, r) = match expr {
11
+ Expr::Add(l, r) => ("addition", "+", l, r),
12
+ Expr::Sub(l, r) => ("subtraction", "-", l, r),
13
+ Expr::Mul(l, r) => ("multiplication", "*", l, r),
14
+ Expr::Div(l, r) => ("division", "/", l, r),
15
+ _ => return Err(Refusal::TypeMismatch(format!("arithmetic::binary called with a non-binary node {expr:?} — a router bug"))),
10
16
  };
17
+ let (left, right) = (eval(l, ctx)?, eval(r, ctx)?);
18
+ match symbol {
19
+ "+" => sum(&left, &right),
20
+ "-" => checked(name, symbol, &left, &right, i64::checked_sub, |a, b| a - b),
21
+ "*" => checked(name, symbol, &left, &right, i64::checked_mul, |a, b| a * b),
22
+ _ => divide(&left, &right),
23
+ }
24
+ }
25
+
26
+ // Integer operands use the checked operation; anything else is a Float result that must stay
27
+ // finite, as `add` does. Ruby promotes to Bignum; this kernel refuses instead.
28
+ fn checked(name: &str, symbol: &str, lhs: &Value, rhs: &Value, int_op: fn(i64, i64) -> Option<i64>, float_op: fn(f64, f64) -> f64) -> Result<Value, Refusal> {
29
+ if let (Value::Int(l), Value::Int(r)) = (lhs, rhs) {
30
+ return int_op(*l, *r).map(Value::Int).ok_or_else(|| eval_error(format!("{name} overflowed: {l} {symbol} {r} does not fit in a 64-bit integer")));
31
+ }
32
+ let l = require_number(lhs, name)?;
33
+ let r = require_number(rhs, name)?;
34
+ finite(name, symbol, l, r, float_op(l, r))
35
+ }
11
36
 
12
- sum(&eval(l, ctx)?, &eval(r, ctx)?)
37
+ fn finite(name: &str, symbol: &str, l: f64, r: f64, result: f64) -> Result<Value, Refusal> {
38
+ if result.is_finite() {
39
+ return Ok(Value::Float(result));
40
+ }
41
+ Err(eval_error(format!("{name} overflowed: {l} {symbol} {r} is not a finite number")))
42
+ }
43
+
44
+ // Integer division floors toward negative infinity, as Ruby's `Integer#/` does (Rust's `/`
45
+ // truncates). A zero divisor is the same fault `.modulo` raises.
46
+ fn divide(lhs: &Value, rhs: &Value) -> Result<Value, Refusal> {
47
+ let r = require_number(rhs, "division")?;
48
+ if r == 0.0 {
49
+ return Err(eval_error("divided by 0".to_string()));
50
+ }
51
+ if let (Value::Int(l), Value::Int(r)) = (lhs, rhs) {
52
+ return floored_div(*l, *r).map(Value::Int).ok_or_else(|| eval_error(format!("division overflowed: {l} / {r} does not fit in a 64-bit integer")));
53
+ }
54
+ let l = require_number(lhs, "division")?;
55
+ finite("division", "/", l, r, l / r)
56
+ }
57
+
58
+ // `None` only for `i64::MIN / -1`, the one quotient that does not fit.
59
+ fn floored_div(l: i64, r: i64) -> Option<i64> {
60
+ let quotient = l.checked_div(r)?;
61
+ Some(if l % r != 0 && ((l < 0) != (r < 0)) { quotient - 1 } else { quotient })
13
62
  }
14
63
 
15
64
  pub fn modulo(expr: &Expr, ctx: &EvalContext) -> Result<Value, Refusal> {
@@ -35,6 +84,47 @@ fn floored_mod(r: i64, d: i64) -> i64 {
35
84
  if raw != 0 && (raw < 0) != (d < 0) { raw + d } else { raw }
36
85
  }
37
86
 
87
+ #[cfg(test)]
88
+ mod division_tests {
89
+ use super::{checked, divide, floored_div};
90
+ use crate::kernel::expr::Value;
91
+
92
+ #[test]
93
+ fn floors_toward_negative_infinity_across_every_sign_combination() {
94
+ // Real `ruby -e 'puts X / Y'` results.
95
+ assert_eq!(floored_div(7, 2), Some(3));
96
+ assert_eq!(floored_div(-7, 2), Some(-4));
97
+ assert_eq!(floored_div(7, -2), Some(-4));
98
+ assert_eq!(floored_div(-7, -2), Some(3));
99
+ assert_eq!(floored_div(-6, 2), Some(-3));
100
+ assert_eq!(floored_div(0, -5), Some(0));
101
+ }
102
+
103
+ #[test]
104
+ fn a_money_percentage_rounds_down() {
105
+ let product = checked("multiplication", "*", &Value::Int(10801), &Value::Int(50), i64::checked_mul, |a, b| a * b).unwrap();
106
+
107
+ assert_eq!(divide(&product, &Value::Int(100)).unwrap(), Value::Int(5400));
108
+ }
109
+
110
+ #[test]
111
+ fn a_zero_divisor_is_a_fault_not_a_crash() {
112
+ assert!(divide(&Value::Int(1), &Value::Int(0)).is_err());
113
+ assert!(divide(&Value::Float(1.0), &Value::Float(0.0)).is_err());
114
+ }
115
+
116
+ #[test]
117
+ fn the_one_unrepresentable_quotient_is_a_fault() {
118
+ assert!(divide(&Value::Int(i64::MIN), &Value::Int(-1)).is_err());
119
+ }
120
+
121
+ #[test]
122
+ fn overflowing_products_and_differences_are_faults() {
123
+ assert!(checked("multiplication", "*", &Value::Int(i64::MAX), &Value::Int(2), i64::checked_mul, |a, b| a * b).is_err());
124
+ assert!(checked("subtraction", "-", &Value::Int(i64::MIN), &Value::Int(1), i64::checked_sub, |a, b| a - b).is_err());
125
+ }
126
+ }
127
+
38
128
  #[cfg(test)]
39
129
  mod tests {
40
130
  use super::floored_mod;
@@ -126,3 +216,38 @@ fn sum(lhs: &Value, rhs: &Value) -> Result<Value, Refusal> {
126
216
  pub fn require_number(v: &Value, operation: &str) -> Result<f64, Refusal> {
127
217
  scalar::numeric(v).ok_or_else(|| eval_error(format!("{operation} expects a number, got {v:?}")))
128
218
  }
219
+
220
+ #[cfg(test)]
221
+ mod path_operand_tests {
222
+ use crate::kernel::expr::{interpret, EvalContext, Expr, Field, Fielded, NoFields, Value};
223
+
224
+ // Shaped like a generated multi-field value object: `Charge { cents, currency }`.
225
+ struct Charge(i64);
226
+ impl Fielded for Charge {
227
+ fn field(&self, name: &str) -> Option<Field<'_>> {
228
+ match name {
229
+ "cents" => Some(Field::Value(Value::Int(self.0))),
230
+ "currency" => Some(Field::Value(Value::Str("USD".to_string()))),
231
+ _ => None,
232
+ }
233
+ }
234
+ }
235
+
236
+ struct Record(Charge);
237
+ impl Fielded for Record {
238
+ fn field(&self, name: &str) -> Option<Field<'_>> {
239
+ (name == "charged").then(|| Field::Nested(&self.0))
240
+ }
241
+ }
242
+
243
+ fn percent_of(path: &'static str) -> Value {
244
+ let times = Expr::Mul(Box::new(Expr::Lookup(path)), Box::new(Expr::Int(50)));
245
+ let expr = Expr::Div(Box::new(times), Box::new(Expr::Int(100)));
246
+ interpret(&expr, &EvalContext { args: &NoFields, instance: &Record(Charge(10801)) }).expect("evaluates")
247
+ }
248
+
249
+ #[test]
250
+ fn an_operand_reads_a_field_inside_a_value_object_by_path() {
251
+ assert_eq!(percent_of("charged.cents"), Value::Int(5400));
252
+ }
253
+ }
@@ -1,5 +1,5 @@
1
- //! The "text" expression-operator category: `.split`, `.start_with?`, `.end_with?`.
2
- //! String receivers only; `expr.rs` routes only these three nodes here.
1
+ //! The "text" expression-operator category: `.split`, `.strip` (and `.lstrip`/`.rstrip`),
2
+ //! `.start_with?`, `.end_with?`. String receivers only; `expr.rs` routes only these nodes here.
3
3
 
4
4
  use crate::kernel::expr::{eval_error, interpret as eval, EvalContext, Expr, Value};
5
5
  use crate::kernel::Refusal;
@@ -15,6 +15,10 @@ pub fn interpret(expr: &Expr, ctx: &EvalContext) -> Result<Value, Refusal> {
15
15
  }
16
16
  Ok(Value::Array(parts.into_iter().map(|p| Value::Str(p.to_string())).collect()))
17
17
  }
18
+ Expr::Strip { receiver, side } => {
19
+ let text = require_str(&eval(receiver, ctx)?, "strip")?;
20
+ Ok(Value::Str(side.trim(&text)))
21
+ }
18
22
  Expr::StartsWith { receiver, substring } => {
19
23
  let text = require_str(&eval(receiver, ctx)?, "start_with?")?;
20
24
  Ok(Value::Bool(text.starts_with(substring.as_str())))
@@ -63,6 +67,26 @@ mod tests {
63
67
  assert_eq!(run(phrase), Value::Array(vec![Value::Str("abc".to_string())]));
64
68
  }
65
69
 
70
+ #[test]
71
+ fn strip_trims_rubys_whitespace_set_and_nothing_else() {
72
+ use crate::kernel::expr::StripSide;
73
+ let strip = |side, text: &str| run(Expr::Strip { receiver: Box::new(str(text)), side });
74
+ assert_eq!(strip(StripSide::Both, " \t\n a b \r\u{b}\u{c}\0"), Value::Str("a b".to_string()));
75
+ assert_eq!(strip(StripSide::Left, "\0 a "), Value::Str("a ".to_string()));
76
+ assert_eq!(strip(StripSide::Right, " a \0 "), Value::Str(" a".to_string()));
77
+ assert_eq!(strip(StripSide::Both, " "), Value::Str(String::new()));
78
+ // A non-breaking space is not Ruby whitespace, so it stays.
79
+ assert_eq!(strip(StripSide::Both, "\u{a0}a\u{a0}"), Value::Str("\u{a0}a\u{a0}".to_string()));
80
+ }
81
+
82
+ #[test]
83
+ fn strip_on_a_non_string_refuses_with_the_operator_named() {
84
+ use crate::kernel::expr::StripSide;
85
+ let ctx = EvalContext { args: &NoFields, instance: &NoFields };
86
+ let err = eval(&Expr::Strip { receiver: Box::new(Expr::Int(1)), side: StripSide::Both }, &ctx).unwrap_err();
87
+ assert!(format!("{err:?}").contains("strip expects a string"), "{err:?}");
88
+ }
89
+
66
90
  #[test]
67
91
  fn start_and_end_with() {
68
92
  assert_eq!(run(Expr::StartsWith { receiver: Box::new(str("{}")), substring: "{".to_string() }), Value::Bool(true));
@@ -592,6 +592,18 @@ mod value_object_shape_tests {
592
592
  }
593
593
  }
594
594
 
595
+ #[cfg(test)]
596
+ mod escaped_string_tests {
597
+ use super::*;
598
+
599
+ #[test]
600
+ fn control_characters_are_written_the_way_rubys_json_generate_writes_them() {
601
+ let mut out = String::new();
602
+ write_escaped_string("\u{8}\u{c}\n\r\t\u{b}\0\u{1f}/\u{7f}", &mut out);
603
+ assert_eq!(out, "\"\\b\\f\\n\\r\\t\\u000b\\u0000\\u001f/\u{7f}\"");
604
+ }
605
+ }
606
+
595
607
  fn write_escaped_string(s: &str, out: &mut String) {
596
608
  out.push('"');
597
609
  for c in s.chars() {
@@ -601,6 +613,9 @@ fn write_escaped_string(s: &str, out: &mut String) {
601
613
  '\n' => out.push_str("\\n"),
602
614
  '\t' => out.push_str("\\t"),
603
615
  '\r' => out.push_str("\\r"),
616
+ // Ruby's `JSON.generate` writes these two short, not as `\u0008` / `\u000c`.
617
+ '\u{8}' => out.push_str("\\b"),
618
+ '\u{c}' => out.push_str("\\f"),
604
619
  c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
605
620
  c => out.push(c),
606
621
  }
@@ -21,7 +21,7 @@ pub mod repository;
21
21
  pub mod routing;
22
22
 
23
23
  pub use dispatch::{aggregate_step_site, apply_entity_command, decode_aggregate_arguments, decode_entity_arguments, dispatch, dispatch_entity, enforce_invariants, entity_step_site, ArgumentGates, EnsuresSpec, EntityInvariants, GivenSpec, Hydrate, InvariantSet, InvariantSpec, StepSite, TransitionCheck};
24
- pub use expr::{interpret, BlockMode, Bound, Comparison, EvalContext, Expr, Field, Fielded, NoFields, Value, WithParent};
24
+ pub use expr::{interpret, BlockMode, Bound, Comparison, EvalContext, Expr, Field, Fielded, NoFields, StripSide, Value, WithParent};
25
25
  pub use json::Json;
26
26
  pub use named_query::{QueryCondition, QueryConditionValue, QueryDef};
27
27
  pub use orchestrate::{