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
data/rust/.gitignore CHANGED
@@ -1,2 +1,8 @@
1
1
  /target
2
2
  /dist
3
+
4
+ # Generated from the bluebooks by `hecks regeneration_run.regenerate_corpus --confirm`; mod.rs and the
5
+ # Cargo features stay tracked. pizzas stays committed: its translations carry SQL only the Ruby
6
+ # toolchain compiles, so hecks-build cannot reproduce it.
7
+ /src/generated/*/
8
+ !/src/generated/pizzas/
data/rust/Cargo.toml CHANGED
@@ -6,6 +6,12 @@
6
6
  # `--no-default-features --features <name>`.
7
7
  [features]
8
8
  default = ["reserved_names_fixture"]
9
+ attribute_constraints_fixture = []
10
+ optional_nested_value_object_fixture = []
11
+ computed_sets_fixture = []
12
+ fan_out_row_fields_fixture = []
13
+ for_each_identity_fixture = []
14
+ projected_single_field_fixture = []
9
15
  optional_value_object_fixture = []
10
16
  reserved_names_fixture = []
11
17
  composite_line_ledger = []
@@ -0,0 +1,103 @@
1
+ //! Rust port of the binding facts `lib/hecks/rust_build/project_rust/target_ir.rb` adds beside the
2
+ //! declared IR: `persistence`, `translations` and `source_text`. The capability seams
3
+ //! (authorization, identity, payments, ...) are `seams_pass`'s.
4
+
5
+ use std::collections::HashMap;
6
+ use std::path::{Path, PathBuf};
7
+
8
+ use crate::json::Json;
9
+ use crate::lineage_pass;
10
+ use crate::translation_pass;
11
+
12
+ /// Sets `persistence`, the first of the binding facts, in `TargetIr#call`'s order.
13
+ pub fn run(ir: &mut Json, hecksagon_path: Option<&Path>, world_path: Option<&Path>) -> Result<(), String> {
14
+ let chapter_name = ir.get("name").and_then(Json::as_str).unwrap_or_default().to_string();
15
+ let persistence = persistence(ir, &chapter_name, hecksagon_path, world_path)?;
16
+ ir.set("persistence", persistence);
17
+ Ok(())
18
+ }
19
+
20
+ /// Sets `translations` and `source_text`, after the capability seams so the key order matches
21
+ /// `TargetIr#call`.
22
+ ///
23
+ /// `translations` carries each `translations/*.bluebook` edge with its precompiled SQL, from
24
+ /// `translation_pass`; an edge using a rule kind that pass does not compile is refused, never
25
+ /// shipped without its SQL. A `translates` declaration in a bluebook is refused outright.
26
+ pub fn finish(ir: &mut Json, bluebook_files: &[PathBuf], bluebook_dir: &Path) -> Result<(), String> {
27
+ let mut texts = Vec::new();
28
+ for path in bluebook_files {
29
+ let text = read(path)?;
30
+ if text.lines().any(declares_translation) {
31
+ return Err(format!(
32
+ "{} declares a translation; hecks-build cannot compile its SQL yet, so generate this domain with the Ruby toolchain",
33
+ path.display()
34
+ ));
35
+ }
36
+ texts.push(text);
37
+ }
38
+ ir.set("translations", Json::Array(translation_pass::edges(bluebook_dir)?));
39
+ if !texts.is_empty() {
40
+ ir.set("source_text", Json::String(texts.join("\n")));
41
+ }
42
+ Ok(())
43
+ }
44
+
45
+ fn declares_translation(line: &str) -> bool {
46
+ let line = line.trim_start();
47
+ line.starts_with("translates") || line.starts_with("translation ")
48
+ }
49
+
50
+ fn persistence(ir: &Json, chapter: &str, hecksagon: Option<&Path>, world: Option<&Path>) -> Result<Json, String> {
51
+ let (binds, hecksagon_default) = match hecksagon {
52
+ Some(path) => {
53
+ let text = read(path)?;
54
+ (lineage_pass::persistence_binds(&text), domain_default(&text, chapter))
55
+ }
56
+ None => (HashMap::new(), None),
57
+ };
58
+ let world_default = match world {
59
+ Some(path) => lineage_pass::default_adapter_name(&read(path)?, chapter),
60
+ None => None,
61
+ };
62
+ let declared = hecksagon_default.or(world_default);
63
+ let mut aggregates = Vec::new();
64
+ for aggregate in ir.get("aggregates").map(Json::each).unwrap_or(&[]) {
65
+ let name = aggregate.get("name").and_then(Json::as_str).unwrap_or_default().to_string();
66
+ let adapter = match (binds.get(&name), &declared, hecksagon.is_some()) {
67
+ (Some(adapter), _, _) => adapter.clone(),
68
+ (None, Some(adapter), _) => adapter.clone(),
69
+ (None, None, false) => "Memory".to_string(),
70
+ (None, None, true) => {
71
+ return Err(format!("{chapter}::{name} has no persisted_by bind, and its hecksagon names no default adapter"));
72
+ }
73
+ };
74
+ aggregates.push(Json::Object(vec![
75
+ ("name".to_string(), Json::String(name.clone())),
76
+ ("storage_name".to_string(), Json::String(lineage_pass::snake_case(&name))),
77
+ ("adapter".to_string(), Json::String(adapter)),
78
+ ]));
79
+ }
80
+ Ok(Json::Object(vec![("aggregates".to_string(), Json::Array(aggregates))]))
81
+ }
82
+
83
+ // The bare `persisted_by "X"` line of the chapter's own `Hecks.hecksagon` block: every aggregate
84
+ // without a bind of its own inherits it. The last one in the block wins.
85
+ fn domain_default(text: &str, chapter: &str) -> Option<String> {
86
+ let mut in_chapter = false;
87
+ let mut found = None;
88
+ for line in text.lines() {
89
+ if let Some(rest) = line.strip_prefix("Hecks.hecksagon") {
90
+ in_chapter = rest.contains(&format!("\"{chapter}\""));
91
+ continue;
92
+ }
93
+ let Some(rest) = line.trim_start().strip_prefix("persisted_by ") else { continue };
94
+ let Some(open) = rest.find('"').filter(|_| in_chapter) else { continue };
95
+ let quoted = &rest[open + 1..];
96
+ found = quoted.find('"').map(|close| quoted[..close].to_string());
97
+ }
98
+ found
99
+ }
100
+
101
+ fn read(path: &Path) -> Result<String, String> {
102
+ std::fs::read_to_string(path).map_err(|e| format!("reading {}: {e}", path.display()))
103
+ }
@@ -212,6 +212,12 @@ fn is_default_line(line_content: &str) -> bool {
212
212
  trimmed.strip_prefix("default").map(|rest| rest.trim_start().starts_with('=')).unwrap_or(false)
213
213
  }
214
214
 
215
+ /// Rewrites only the root `mod.rs`, for a generation into a directory other than `rust/src/generated`.
216
+ pub fn write_mod_only(out_root: &Path, target_mod_name: &str) -> Result<(), String> {
217
+ let (all_dirs, domains) = scan_generated_dirs(out_root)?;
218
+ write_root_mod(out_root, &all_dirs, &domains, target_mod_name)
219
+ }
220
+
215
221
  /// Rewrites the root `mod.rs` and syncs `rust/Cargo.toml`'s `[features]` for the domains on disk.
216
222
  pub fn run(out_root: &Path, cargo_toml_path: &Path, target_mod_name: &str) -> Result<(), String> {
217
223
  let (all_dirs, domains) = scan_generated_dirs(out_root)?;
@@ -57,7 +57,7 @@ pub fn run(ir: &mut Json, hecksagon_path: Option<&Path>, world_path: Option<&Pat
57
57
  }
58
58
 
59
59
  // Skips any line naming `role:` — only a role-less bind is authoritative.
60
- fn persistence_binds(text: &str) -> HashMap<String, String> {
60
+ pub(crate) fn persistence_binds(text: &str) -> HashMap<String, String> {
61
61
  let mut binds = HashMap::new();
62
62
 
63
63
  for line in text.lines() {
@@ -87,7 +87,7 @@ fn persistence_binds(text: &str) -> HashMap<String, String> {
87
87
 
88
88
  // Only lines inside the target chapter's own `Hecks.world` block count; the
89
89
  // last `default_adapter` there wins, matching the Ruby builder.
90
- fn default_adapter_name(text: &str, chapter: &str) -> Option<String> {
90
+ pub(crate) fn default_adapter_name(text: &str, chapter: &str) -> Option<String> {
91
91
  let mut in_target_world = false;
92
92
  let mut found = None;
93
93
 
@@ -182,7 +182,7 @@ fn top_level_class_name(text: &str) -> Option<String> {
182
182
 
183
183
  // Hand-ported `Naming.snake` (Ruby): split acronym boundaries, then camelCase
184
184
  // boundaries, then downcase — no regex dependency.
185
- fn snake_case(name: &str) -> String {
185
+ pub(crate) fn snake_case(name: &str) -> String {
186
186
  let after_pass1 = split_acronym_boundaries(name);
187
187
  let after_pass2 = split_camel_boundaries(&after_pass1);
188
188
  after_pass2.to_ascii_lowercase()
@@ -1,4 +1,4 @@
1
- //! `hecks-build <domain> [--wasm] [--no-build]` compiles a domain's `.bluebook` to a binary.
1
+ //! `hecks-build <domain> [--wasm] [--no-build] [--out <dir>]` compiles a domain's `.bluebook` to a binary.
2
2
  //! `<domain>` is a domain directory such as `examples/pizzas`; `--wasm` also cross-compiles to
3
3
  //! `wasm32-wasip1`, and `--no-build` generates source only.
4
4
 
@@ -9,13 +9,16 @@ mod cargo_sync;
9
9
  #[allow(dead_code)]
10
10
  #[path = "../../codegen/src/json.rs"]
11
11
  mod json;
12
+ mod binding_pass;
12
13
  mod lineage_pass;
13
14
  mod pipeline;
14
15
  mod reserved_names;
15
16
  mod resolve;
16
17
  mod root;
18
+ mod seams_pass;
17
19
  mod subprocess;
18
20
  mod tmp;
21
+ mod translation_pass;
19
22
 
20
23
  use std::process::ExitCode;
21
24
 
@@ -34,20 +37,29 @@ fn run(args: &[String]) -> Result<(), String> {
34
37
  let mut domain: Option<String> = None;
35
38
  let mut build_wasm = false;
36
39
  let mut no_build = false;
40
+ let mut out_dir: Option<String> = None;
37
41
 
38
- for arg in args {
42
+ let mut iter = args.iter();
43
+ while let Some(arg) = iter.next() {
39
44
  match arg.as_str() {
45
+ "--out" => out_dir = Some(iter.next().ok_or("--out needs a directory")?.clone()),
40
46
  "--wasm" => build_wasm = true,
41
47
  "--no-build" => no_build = true,
42
48
  other if !other.starts_with("--") && domain.is_none() => domain = Some(other.to_string()),
43
- other => return Err(format!("unrecognized argument {other:?} — usage: hecks-build <domain> [--wasm] [--no-build]")),
49
+ other => return Err(format!("unrecognized argument {other:?} — usage: hecks-build <domain> [--wasm] [--no-build] [--out <dir>]")),
44
50
  }
45
51
  }
46
52
 
47
- let domain = domain.ok_or_else(|| "usage: hecks-build <domain> [--wasm] [--no-build]".to_string())?;
53
+ let domain = domain.ok_or_else(|| "usage: hecks-build <domain> [--wasm] [--no-build] [--out <dir>]".to_string())?;
48
54
  let root = root::find()?;
49
55
 
50
- let opts = pipeline::Options { build_native: !no_build, build_wasm: build_wasm && !no_build };
56
+ // `--out` generates the module tree into that directory only: no `Cargo.toml` rewrite, no
57
+ // native or wasm build, so a `build.rs` can call it from inside a cargo build.
58
+ let opts = pipeline::Options {
59
+ build_native: !no_build && out_dir.is_none(),
60
+ build_wasm: build_wasm && !no_build && out_dir.is_none(),
61
+ out_dir: out_dir.map(std::path::PathBuf::from),
62
+ };
51
63
 
52
64
  pipeline::run(&root, &domain, &opts)
53
65
  }
@@ -3,17 +3,20 @@
3
3
 
4
4
  use std::path::{Path, PathBuf};
5
5
 
6
+ use crate::binding_pass;
6
7
  use crate::build_artifact;
7
8
  use crate::cargo_sync;
8
9
  use crate::json::Json;
9
10
  use crate::lineage_pass;
10
11
  use crate::resolve;
12
+ use crate::seams_pass;
11
13
  use crate::subprocess;
12
14
  use crate::tmp::TempDir;
13
15
 
14
16
  pub struct Options {
15
17
  pub build_native: bool,
16
18
  pub build_wasm: bool,
19
+ pub out_dir: Option<PathBuf>,
17
20
  }
18
21
 
19
22
  struct Chapter {
@@ -58,10 +61,19 @@ pub fn run(root: &Path, domain: &str, opts: &Options) -> Result<(), String> {
58
61
 
59
62
  let target_chapter_name = resolve::header_chapter_name(first_bluebook)?;
60
63
  let mut target_files = Vec::new();
64
+ // Chapters declared beside the target but not attached to it: the registry still loads them, so
65
+ // one may provide a capability (payments) the target uses.
66
+ let mut sibling_files: Vec<(String, Vec<PathBuf>)> = Vec::new();
61
67
  for path in bluebook_paths {
62
- if resolve::header_chapter_name(&path)? == target_chapter_name {
63
- target_files.push(path);
68
+ let chapter = resolve::header_chapter_name(&path)?;
69
+ if chapter != target_chapter_name {
70
+ match sibling_files.iter_mut().find(|(name, _)| *name == chapter) {
71
+ Some((_, files)) => files.push(path),
72
+ None => sibling_files.push((chapter, vec![path])),
73
+ }
74
+ continue;
64
75
  }
76
+ target_files.push(path);
65
77
  }
66
78
 
67
79
  let (gem_chapter_names, vendored_package_names) = match &hecksagon_path {
@@ -72,6 +84,7 @@ pub fn run(root: &Path, domain: &str, opts: &Options) -> Result<(), String> {
72
84
  None => (Vec::new(), Vec::new()),
73
85
  };
74
86
 
87
+ let source_files = target_files.clone();
75
88
  if let Some(p) = &hecksagon_path {
76
89
  target_files.push(p.clone());
77
90
  }
@@ -81,7 +94,7 @@ pub fn run(root: &Path, domain: &str, opts: &Options) -> Result<(), String> {
81
94
  // rather than chaining a live object through both passes.
82
95
  let mut target_ir = Json::parse(&target_ir_text).map_err(|e| format!("re-parsing target ir.json for lineage: {e}"))?;
83
96
  lineage_pass::run(&mut target_ir, hecksagon_path.as_deref(), world_path.as_deref(), root)?;
84
- let target_ir_text = crate::json::write(&target_ir);
97
+ binding_pass::run(&mut target_ir, hecksagon_path.as_deref(), world_path.as_deref())?;
85
98
 
86
99
  // Every other chapter the gem supplies by `attaches "Name"`, resolved through the
87
100
  // same directory listing (`resolve::framework_members`) the Ruby
@@ -150,6 +163,22 @@ pub fn run(root: &Path, domain: &str, opts: &Options) -> Result<(), String> {
150
163
  });
151
164
  }
152
165
 
166
+ // A chapter declared in the domain's own directory is a chapter of the build too, as every
167
+ // bluebook the registry loads is; its ports come from its own block of the domain's hecksagon.
168
+ // The registry loads the directory's bluebooks before it resolves what the hecksagon attaches,
169
+ // so they come first.
170
+ let mut siblings = Vec::new();
171
+ for (name, files) in &sibling_files {
172
+ let mut sources = files.clone();
173
+ sources.extend(hecksagon_path.iter().cloned());
174
+ siblings.push(Chapter {
175
+ mod_name: name.to_lowercase(),
176
+ source_label: format!("{domain} (attaches {name:?})"),
177
+ ir_text: parse_chapter(&parser_bin, name, &sources)?,
178
+ });
179
+ }
180
+ chapters.splice(0..0, siblings);
181
+
153
182
  // Guards against a gem-chapter and a vendored-package
154
183
  // entry sharing a `mod_name`, which would silently overwrite one
155
184
  // chapter's sidecars with another's.
@@ -167,12 +196,22 @@ pub fn run(root: &Path, domain: &str, opts: &Options) -> Result<(), String> {
167
196
  }
168
197
  }
169
198
 
199
+ // The capability seams read the attached chapters' own `provides`, so they are decided only
200
+ // once every chapter is loaded; `translations` and `source_text` close the binding facts.
201
+ let mut member_irs = Vec::new();
202
+ for c in &chapters {
203
+ member_irs.push(Json::parse(&c.ir_text).map_err(|e| format!("re-parsing {} ir.json for its seams: {e}", c.mod_name))?);
204
+ }
205
+ seams_pass::run(&mut target_ir, &member_irs)?;
206
+ binding_pass::finish(&mut target_ir, &source_files, &bluebook_directory)?;
207
+ let target_ir_text = crate::json::write(&target_ir);
208
+
170
209
  // The self-hosted language, compiled in too, under the same "Bluebook"
171
210
  // chapter name.
172
211
  let grammar_files = resolve::grammar_files(root)?;
173
212
  let meta_ir_text = parse_chapter(&parser_bin, "Bluebook", &grammar_files)?;
174
213
 
175
- let out_root = root.join("rust/src/generated");
214
+ let out_root = opts.out_dir.clone().unwrap_or_else(|| root.join("rust/src/generated"));
176
215
  std::fs::create_dir_all(&out_root).map_err(|e| format!("creating {}: {e}", out_root.display()))?;
177
216
 
178
217
  // Scoped clearing — identical reasoning to both Ruby pipelines'
@@ -212,7 +251,11 @@ pub fn run(root: &Path, domain: &str, opts: &Options) -> Result<(), String> {
212
251
  run_codegen_full(&codegen_bin, &codegen_args)?;
213
252
 
214
253
  let cargo_toml_path = root.join("rust/Cargo.toml");
215
- cargo_sync::run(&out_root, &cargo_toml_path, &target_mod_name)?;
254
+ if opts.out_dir.is_some() {
255
+ cargo_sync::write_mod_only(&out_root, &target_mod_name)?;
256
+ } else {
257
+ cargo_sync::run(&out_root, &cargo_toml_path, &target_mod_name)?;
258
+ }
216
259
 
217
260
  let rust_dir = root.join("rust");
218
261
  if opts.build_native {
@@ -0,0 +1,123 @@
1
+ //! Rust port of `Exporter::Bindings` (`lib/hecks/projector/exporter/bindings.rb`) and the provider
2
+ //! lookups of `Registry::Providers`: which attached chapter answers each capability the target
3
+ //! domain uses, read from every chapter's own `provides` entries in its IR.
4
+
5
+ use crate::json::Json;
6
+
7
+ /// One capability's exported shape: the keys it exports as qualified verbs, then each derived
8
+ /// `name => verb key` aggregate, in the order `Bindings` builds them, then its optional marks.
9
+ struct Seam {
10
+ capability: &'static str,
11
+ export_as: &'static str,
12
+ verbs: &'static [&'static str],
13
+ aggregates: &'static [(&'static str, &'static str)],
14
+ marks: &'static [&'static str],
15
+ }
16
+
17
+ // In `TargetIr::SEAMS` order, which is the order the keys land in `ir.json`.
18
+ const SEAMS: &[Seam] = &[
19
+ Seam { capability: "authorization", export_as: "authorization", verbs: &["grant", "assignments"], aggregates: &[("assignment_aggregate", "assignments")], marks: &[] },
20
+ Seam { capability: "membership", export_as: "membership", verbs: &["admit", "grant", "people"], aggregates: &[("aggregate", "admit")], marks: &[] },
21
+ Seam { capability: "identity", export_as: "identity", verbs: &["register", "link", "resolve"], aggregates: &[], marks: &[] },
22
+ Seam {
23
+ capability: "newsletter",
24
+ export_as: "newsletter",
25
+ verbs: &["subscribe", "add_name", "confirm", "unsubscribe"],
26
+ aggregates: &[("aggregate", "subscribe")],
27
+ marks: &["awaiting_confirmation", "receives_issues", "left"],
28
+ },
29
+ Seam {
30
+ capability: "newsletter_issues",
31
+ export_as: "newsletter_issues",
32
+ verbs: &["send_issue", "record_delivery"],
33
+ aggregates: &[("issue_aggregate", "send_issue"), ("delivery_aggregate", "record_delivery")],
34
+ marks: &[],
35
+ },
36
+ Seam { capability: "payments", export_as: "payments", verbs: &["initiate", "succeeded", "failed"], aggregates: &[("aggregate", "initiate")], marks: &["holds_seat"] },
37
+ Seam {
38
+ capability: "registrations",
39
+ export_as: "registrations",
40
+ verbs: &["schedule", "request"],
41
+ aggregates: &[("event_aggregate", "schedule"), ("registration_aggregate", "request")],
42
+ marks: &["frees_seat"],
43
+ },
44
+ Seam {
45
+ capability: "payment_connection",
46
+ export_as: "payment_connection",
47
+ verbs: &["connect", "reconnect", "disconnect", "suspend", "resume", "enable", "disable"],
48
+ aggregates: &[("aggregate", "connect")],
49
+ marks: &[],
50
+ },
51
+ ];
52
+
53
+ /// Sets each seam some chapter provides on `target`, leaving it out otherwise, as
54
+ /// `TargetIr#add_seams` does. `members` are the chapters the target attaches, in attach order; the
55
+ /// target itself is searched first.
56
+ pub fn run(target: &mut Json, members: &[Json]) -> Result<(), String> {
57
+ let mut chapters: Vec<Json> = vec![target.clone()];
58
+ chapters.extend(members.iter().cloned());
59
+
60
+ let mut membership = false;
61
+ let mut identity = false;
62
+ for seam in SEAMS {
63
+ let Some(provider) = chapters.iter().find(|chapter| provision(chapter, seam.capability).is_some()) else { continue };
64
+ let exported = export(seam, provider);
65
+ membership |= seam.capability == "membership";
66
+ identity |= seam.capability == "identity";
67
+ target.set(seam.export_as, exported);
68
+ }
69
+ if membership && !identity {
70
+ return Err(format!(
71
+ "{} provides membership but not identity — rust/host Google sign-in cannot register or link an identity from ir.json. \
72
+ Attach Identity so the hecksagon, not a deploy-time guess, names the identity verbs.",
73
+ name_of(target)
74
+ ));
75
+ }
76
+ Ok(())
77
+ }
78
+
79
+ fn export(seam: &Seam, provider: &Json) -> Json {
80
+ let name = name_of(provider);
81
+ let entries = provision(provider, seam.capability).unwrap_or_default();
82
+ let verb = |key: &str| entries.iter().find(|(k, _)| k == key).map(|(_, v)| format!("{name}::{v}"));
83
+
84
+ let mut out = vec![("provider".to_string(), Json::String(name.clone()))];
85
+ for key in seam.verbs {
86
+ out.push((key.to_string(), verb(key).map_or(Json::Null, Json::String)));
87
+ }
88
+ for (exported, key) in seam.aggregates {
89
+ let aggregate = verb(key).map(|v| v.split('.').next().unwrap_or_default().to_string());
90
+ out.push((exported.to_string(), aggregate.map_or(Json::Null, Json::String)));
91
+ }
92
+ for key in seam.marks {
93
+ if let Some(states) = marked_states(provider, entries.iter().find(|(k, _)| k == key).map(|(_, v)| v.as_str())) {
94
+ out.push((key.to_string(), states));
95
+ }
96
+ }
97
+ Json::Object(out)
98
+ }
99
+
100
+ // `[(key, verb)]` this chapter provides under `capability`, or `None` when it provides none.
101
+ fn provision(chapter: &Json, capability: &str) -> Option<Vec<(String, String)>> {
102
+ let entries: Vec<(String, String)> = chapter
103
+ .get("provides")
104
+ .map(Json::each)
105
+ .unwrap_or(&[])
106
+ .iter()
107
+ .filter(|entry| entry.get("capability").and_then(Json::as_str) == Some(capability))
108
+ .filter_map(|entry| Some((entry.get("key")?.as_str()?.to_string(), entry.get("verb")?.as_str()?.to_string())))
109
+ .collect();
110
+ (!entries.is_empty()).then_some(entries)
111
+ }
112
+
113
+ // The states of the aggregate lifecycle mark a `"Aggregate.mark"` entry names; `None` when the
114
+ // entry is not declared or the mark is.
115
+ fn marked_states(provider: &Json, entry: Option<&str>) -> Option<Json> {
116
+ let (aggregate_name, mark) = entry?.split_once('.')?;
117
+ let aggregate = provider.get("aggregates").map(Json::each)?.iter().find(|a| a.get("name").and_then(Json::as_str) == Some(aggregate_name))?;
118
+ aggregate.get("lifecycle")?.get("marks")?.get(mark).cloned()
119
+ }
120
+
121
+ fn name_of(chapter: &Json) -> String {
122
+ chapter.get("name").and_then(Json::as_str).unwrap_or_default().to_string()
123
+ }
@@ -0,0 +1,167 @@
1
+ //! Rust port of the part of `Projector::Exporter::Translations` that a `translations/*.bluebook`
2
+ //! edge made of renames and moves needs: the declared rules plus the SQL expression
3
+ //! `Translation::RuleCompiler.compile_rules` builds for them.
4
+ //!
5
+ //! A rule kind this pass does not compile (convert, drop, retype, compute, rekey, backfill) is
6
+ //! refused by name, so a domain never ships an edge whose SQL was silently left out.
7
+
8
+ use std::path::Path;
9
+
10
+ use crate::json::Json;
11
+
12
+ /// One aggregate's rules, in declaration order.
13
+ struct Aggregate {
14
+ name: String,
15
+ was: Option<String>,
16
+ renames: Vec<(String, String)>,
17
+ moves: Vec<(String, String)>,
18
+ }
19
+
20
+ /// The `translations` IR list for the `.bluebook` files under `bluebook_dir/translations`, ordered
21
+ /// by file name (an era-step id), as the registry loads them.
22
+ pub fn edges(bluebook_dir: &Path) -> Result<Vec<Json>, String> {
23
+ let Ok(entries) = std::fs::read_dir(bluebook_dir.join("translations")) else { return Ok(Vec::new()) };
24
+ let mut paths: Vec<_> = entries
25
+ .filter_map(Result::ok)
26
+ .map(|entry| entry.path())
27
+ .filter(|path| path.extension().and_then(|e| e.to_str()) == Some("bluebook"))
28
+ .collect();
29
+ paths.sort();
30
+ let mut edges = Vec::new();
31
+ for path in paths {
32
+ let text = std::fs::read_to_string(&path).map_err(|e| format!("reading {}: {e}", path.display()))?;
33
+ edges.push(edge(&text).map_err(|why| format!("{}: {why}", path.display()))?);
34
+ }
35
+ Ok(edges)
36
+ }
37
+
38
+ fn edge(text: &str) -> Result<Json, String> {
39
+ let mut header: Option<(String, String, String)> = None;
40
+ let mut aggregates: Vec<Aggregate> = Vec::new();
41
+ for raw in text.lines() {
42
+ let line = raw.trim();
43
+ if line.is_empty() || line.starts_with('#') || line == "end" {
44
+ continue;
45
+ }
46
+ if let Some(rest) = line.strip_prefix("Hecks.data_translation") {
47
+ header = Some((first_string(rest)?, keyword(rest, "from")?, keyword(rest, "to")?));
48
+ } else if let Some(rest) = line.strip_prefix("aggregate ") {
49
+ let was = keyword(rest, "was").ok();
50
+ aggregates.push(Aggregate { name: first_string(rest)?, was, renames: Vec::new(), moves: Vec::new() });
51
+ } else if let Some(rest) = line.strip_prefix("move ") {
52
+ let aggregate = aggregates.last_mut().ok_or("a move outside an aggregate")?;
53
+ aggregate.moves.push((first_word(rest)?, keyword(rest, "to")?));
54
+ } else if let Some(rest) = line.strip_prefix("rename ") {
55
+ let aggregate = aggregates.last_mut().ok_or("a rename outside an aggregate")?;
56
+ aggregate.renames.push((first_word(rest)?, keyword(rest, "to")?));
57
+ } else {
58
+ return Err(format!(
59
+ "hecks-build cannot compile the translation rule `{line}` yet (only rename and move are ported), \
60
+ so generate this domain with the Ruby toolchain"
61
+ ));
62
+ }
63
+ }
64
+ let (domain, from, to) = header.ok_or("no Hecks.data_translation header")?;
65
+ Ok(Json::Object(vec![
66
+ ("domain".into(), Json::String(domain)),
67
+ ("from".into(), Json::String(from)),
68
+ ("to".into(), Json::String(to)),
69
+ ("retired".into(), Json::Array(Vec::new())),
70
+ ("aggregates".into(), Json::Array(aggregates.iter().map(aggregate_json).collect())),
71
+ ]))
72
+ }
73
+
74
+ fn aggregate_json(aggregate: &Aggregate) -> Json {
75
+ let renames = aggregate.renames.iter().map(|(old, new)| (old.clone(), Json::String(new.clone()))).collect();
76
+ let moves = aggregate
77
+ .moves
78
+ .iter()
79
+ .map(|(from, to)| Json::Object(vec![("from".into(), Json::String(from.clone())), ("to".into(), Json::String(to.clone()))]))
80
+ .collect();
81
+ let empty = || Json::Array(Vec::new());
82
+ Json::Object(vec![
83
+ ("name".into(), Json::String(aggregate.name.clone())),
84
+ ("was".into(), aggregate.was.clone().map_or(Json::Null, Json::String)),
85
+ ("renames".into(), Json::Object(renames)),
86
+ ("moves".into(), Json::Array(moves)),
87
+ ("converts".into(), empty()),
88
+ ("drops".into(), empty()),
89
+ ("retypes".into(), empty()),
90
+ ("computes".into(), empty()),
91
+ ("rekeys".into(), empty()),
92
+ ("backfills".into(), empty()),
93
+ ("compiled_state_expression".into(), Json::String(compile(aggregate))),
94
+ ("compiled_id_expression".into(), Json::Null),
95
+ ])
96
+ }
97
+
98
+ /// `RuleCompiler.compile_rules`: renames, then moves, each wrapping the expression so far.
99
+ fn compile(aggregate: &Aggregate) -> String {
100
+ let mut sql = "state".to_string();
101
+ for (old, new) in &aggregate.renames {
102
+ sql = format!("hecks_tr_rename({sql}, {}, {})", text_literal(old), text_literal(new));
103
+ }
104
+ for (from, to) in &aggregate.moves {
105
+ sql = format!(
106
+ "hecks_tr_move({sql}, {}, {}, {})",
107
+ path_literal(from),
108
+ path_literal(to),
109
+ text_literal(&format!("move {from} to: {to}"))
110
+ );
111
+ }
112
+ sql
113
+ }
114
+
115
+ fn text_literal(text: &str) -> String {
116
+ format!("'{}'", text.replace('\'', "''"))
117
+ }
118
+
119
+ fn path_literal(path: &str) -> String {
120
+ let segments: Vec<String> = path.split('.').map(text_literal).collect();
121
+ format!("ARRAY[{}]::text[]", segments.join(", "))
122
+ }
123
+
124
+ /// The first `"quoted"` string of `text`.
125
+ fn first_string(text: &str) -> Result<String, String> {
126
+ let open = text.find('"').ok_or_else(|| format!("no quoted name in `{text}`"))?;
127
+ let rest = &text[open + 1..];
128
+ let close = rest.find('"').ok_or_else(|| format!("unterminated string in `{text}`"))?;
129
+ Ok(rest[..close].to_string())
130
+ }
131
+
132
+ /// The leading argument, a `:symbol` or a `"string"`, before the first comma.
133
+ fn first_word(text: &str) -> Result<String, String> {
134
+ let word = text.split(',').next().unwrap_or_default().trim();
135
+ let word = word.strip_prefix(':').unwrap_or(word).trim_matches('"');
136
+ if word.is_empty() { Err(format!("no source in `{text}`")) } else { Ok(word.to_string()) }
137
+ }
138
+
139
+ /// The value of `key: value` in `text`, a string or a symbol.
140
+ fn keyword(text: &str, key: &str) -> Result<String, String> {
141
+ let marker = format!("{key}:");
142
+ let at = text.find(&marker).ok_or_else(|| format!("no `{key}:` in `{text}`"))?;
143
+ let value = text[at + marker.len()..].trim_start();
144
+ if let Some(symbol) = value.strip_prefix(':') {
145
+ let end = symbol.find(|c: char| !(c.is_alphanumeric() || c == '_')).unwrap_or(symbol.len());
146
+ return Ok(symbol[..end].to_string());
147
+ }
148
+ first_string(value)
149
+ }
150
+
151
+ #[cfg(test)]
152
+ mod tests {
153
+ use super::*;
154
+
155
+ #[test]
156
+ fn compiles_moves_as_ruby_does() {
157
+ let text = "Hecks.data_translation \"Pizzas\", from: \"e3f3d7\", to: \"77625c\" do\n aggregate \"Order\", was: \"Pizza\" do\n move :price_cents, to: \"pizza.price_cents\"\n move :size, to: \"pizza.size\"\n end\nend\n";
158
+ let json = crate::json::write(&edge(text).unwrap());
159
+ assert!(json.contains("hecks_tr_move(hecks_tr_move(state, ARRAY['price_cents']::text[], ARRAY['pizza', 'price_cents']::text[], 'move price_cents to: pizza.price_cents'), ARRAY['size']::text[], ARRAY['pizza', 'size']::text[], 'move size to: pizza.size')"));
160
+ }
161
+
162
+ #[test]
163
+ fn refuses_a_rule_it_cannot_compile() {
164
+ let text = "Hecks.data_translation \"A\", from: \"1\", to: \"2\" do\n aggregate \"B\" do\n compute :a, to: \"b\", sql: \"1\"\n end\nend\n";
165
+ assert!(edge(text).unwrap_err().contains("cannot compile"));
166
+ }
167
+ }