hecks 3.14.0 → 5.0.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 (427) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/adapters/fake_processor.rb +34 -0
  3. data/examples/pizzas/adapters/payment_signature.rb +66 -0
  4. data/examples/pizzas/adapters/payment_webhook.rb +87 -0
  5. data/examples/pizzas/bluebook/pizzas.behaviors +33 -0
  6. data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
  7. data/examples/pizzas/config.ru +22 -0
  8. data/exe/hecks +1 -1
  9. data/lib/hecks/adapters/driven/prism.rb +0 -2
  10. data/lib/hecks/adapters/driven.rb +0 -2
  11. data/lib/hecks/adapters/driving/json.rb +9 -16
  12. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
  13. data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
  14. data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
  15. data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
  16. data/lib/hecks/bluebook/hecksagon.rb +1 -1
  17. data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
  18. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
  19. data/lib/hecks/bluebook/meta_validator.rb +0 -11
  20. data/lib/hecks/bluebook/translation.rb +18 -10
  21. data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
  22. data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
  23. data/lib/hecks/bluebook/translation_expression.rb +39 -0
  24. data/lib/hecks/bluebook/translation_plan.rb +70 -0
  25. data/lib/hecks/bluebook.rb +2 -0
  26. data/lib/hecks/chapters.rb +3 -3
  27. data/lib/hecks/cli/project_cli/launcher_source.rb +1 -1
  28. data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
  29. data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
  30. data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
  31. data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
  32. data/lib/hecks/composer/adapters/jev.adapter +3 -0
  33. data/lib/hecks/composer/adapters/jev.rb +66 -0
  34. data/lib/hecks/composer/agent.rb +113 -0
  35. data/lib/hecks/composer/assistant.rb +110 -0
  36. data/lib/hecks/composer/behavior_run.rb +53 -0
  37. data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
  38. data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
  39. data/lib/hecks/composer/board.rb +65 -0
  40. data/lib/hecks/composer/board_edit/command_text.rb +39 -0
  41. data/lib/hecks/composer/board_edit/source.rb +84 -0
  42. data/lib/hecks/composer/board_edit.rb +144 -0
  43. data/lib/hecks/composer/check.rb +89 -0
  44. data/lib/hecks/composer/domains.rb +81 -0
  45. data/lib/hecks/composer/jobs.rb +134 -0
  46. data/lib/hecks/composer/layout.rb +37 -0
  47. data/lib/hecks/composer/live.rb +150 -0
  48. data/lib/hecks/composer/outline.rb +48 -0
  49. data/lib/hecks/composer/page.html +989 -0
  50. data/lib/hecks/composer/panels.rb +142 -0
  51. data/lib/hecks/composer/parse.rb +83 -0
  52. data/lib/hecks/composer/plain.rb +28 -0
  53. data/lib/hecks/composer/ports/composer_agent.port +7 -0
  54. data/lib/hecks/composer/session.rb +141 -0
  55. data/lib/hecks/composer/toolbox.rb +166 -0
  56. data/lib/hecks/composer/web.rb +156 -0
  57. data/lib/hecks/composer/workbench.rb +51 -0
  58. data/lib/hecks/composer/workspace.rb +81 -0
  59. data/lib/hecks/composer.rb +17 -0
  60. data/lib/hecks/corpus/adapters.rb +22 -0
  61. data/lib/hecks/corpus/rust.rb +1 -1
  62. data/lib/hecks/corpus/worlds.rb +19 -0
  63. data/lib/hecks/corpus.rb +5 -0
  64. data/lib/hecks/forms/app/commands.rb +2 -3
  65. data/lib/hecks/forms/app/queries.rb +2 -3
  66. data/lib/hecks/forms/app/records.rb +17 -5
  67. data/lib/hecks/forms/app/responses.rb +3 -1
  68. data/lib/hecks/forms/command_form_renderer.rb +42 -16
  69. data/lib/hecks/forms/field_controls.rb +2 -2
  70. data/lib/hecks/forms/field_renderer.rb +1 -1
  71. data/lib/hecks/forms/field_shape.rb +36 -17
  72. data/lib/hecks/forms/index_renderer.rb +2 -1
  73. data/lib/hecks/forms/page.rb +1 -1
  74. data/lib/hecks/forms/params.rb +0 -13
  75. data/lib/hecks/forms/query_form_renderer.rb +7 -6
  76. data/lib/hecks/forms/record_renderer.rb +11 -7
  77. data/lib/hecks/forms/rule_words.rb +100 -0
  78. data/lib/hecks/forms/value_object_shape.rb +2 -1
  79. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
  80. data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
  81. data/lib/hecks/fuzzing/replay/filters.rb +2 -6
  82. data/lib/hecks/gate/stages.yml +25 -0
  83. data/lib/hecks/grammar/evolve/seeds.rb +0 -6
  84. data/lib/hecks/grammar/evolve.rb +0 -3
  85. data/lib/hecks/grammar/translation.bluebook +1 -1
  86. data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
  87. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
  88. data/lib/hecks/hecks/adapters/first_admin.rb +3 -3
  89. data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
  90. data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
  91. data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
  92. data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
  93. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
  94. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
  95. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
  96. data/lib/hecks/hecks/codebase.behaviors +6 -0
  97. data/lib/hecks/hecks/codebase.bluebook +21 -0
  98. data/lib/hecks/hecks/composer.behaviors +68 -0
  99. data/lib/hecks/hecks/context_map.hecksagon +1 -1
  100. data/lib/hecks/hecks/custodian.behaviors +0 -6
  101. data/lib/hecks/hecks/custodian.bluebook +3 -80
  102. data/lib/hecks/hecks/hecks.hecksagon +2 -2
  103. data/lib/hecks/hecks/hecks.world +1 -1
  104. data/lib/hecks/hecks/rehearsal.bluebook +7 -13
  105. data/lib/hecks/kernel_capabilities/templates.rb +4 -4
  106. data/lib/hecks/kernel_capabilities.rb +1 -1
  107. data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
  108. data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
  109. data/lib/hecks/ports/access_control.rb +2 -2
  110. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
  111. data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
  112. data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
  113. data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
  114. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
  115. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
  116. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
  117. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
  118. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
  119. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
  120. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
  121. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
  122. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
  123. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
  124. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
  125. data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
  126. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
  127. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
  128. data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
  129. data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
  130. data/lib/hecks/ports.rb +0 -1
  131. data/lib/hecks/projection_files.rb +3 -1
  132. data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
  133. data/lib/hecks/projections/glossary/entries.rb +10 -4
  134. data/lib/hecks/projections/glossary/groups.rb +42 -0
  135. data/lib/hecks/projections/glossary/html/page.rb +7 -2
  136. data/lib/hecks/projections/glossary/html/parser.rb +1 -1
  137. data/lib/hecks/projections/glossary/html/sections.rb +31 -11
  138. data/lib/hecks/projections/glossary/html.rb +1 -2
  139. data/lib/hecks/projections/glossary/markdown.rb +31 -28
  140. data/lib/hecks/projections/glossary/mermaid.rb +3 -0
  141. data/lib/hecks/projections/glossary/page.css +143 -153
  142. data/lib/hecks/projections/glossary/page.js +63 -3
  143. data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
  144. data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
  145. data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
  146. data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
  147. data/lib/hecks/projections/glossary/sentences.rb +13 -3
  148. data/lib/hecks/projections/glossary/slugs.rb +4 -1
  149. data/lib/hecks/projections/glossary.rb +2 -1
  150. data/lib/hecks/projections/plain_english.rb +97 -0
  151. data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
  152. data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
  153. data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
  154. data/lib/hecks/projections/rust_vocabulary.rb +14 -6
  155. data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
  156. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +7 -2
  157. data/lib/hecks/projections/statements/actions.rb +99 -0
  158. data/lib/hecks/projections/statements/relationships.rb +86 -0
  159. data/lib/hecks/projections/statements.rb +15 -24
  160. data/lib/hecks/projector/exporter/adapters.rb +112 -0
  161. data/lib/hecks/projector/exporter/translations.rb +18 -35
  162. data/lib/hecks/projector/exporter/worlds.rb +103 -0
  163. data/lib/hecks/projector/exporter.rb +5 -1
  164. data/lib/hecks/quality_control/adapters/agent.rb +6 -7
  165. data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
  166. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
  167. data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
  168. data/lib/hecks/runtime/outbox.rb +2 -9
  169. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
  170. data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
  171. data/lib/hecks/runtime/value/unwrapping.rb +60 -0
  172. data/lib/hecks/runtime/value.rb +10 -21
  173. data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
  174. data/lib/hecks/rust_build/wasm.rb +0 -12
  175. data/lib/hecks/rust_build/write_if_changed.rb +12 -12
  176. data/lib/hecks/rust_format.rb +77 -0
  177. data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
  178. data/lib/hecks/tools/fuzz_sweep.rb +1 -3
  179. data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
  180. data/lib/hecks/tools/translation_audit.rb +8 -10
  181. data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
  182. data/lib/hecks/version.rb +1 -1
  183. data/lib/hecks/vocabulary.rb +2 -1
  184. data/lib/hecks.rb +3 -0
  185. data/prebuilt/{1a6f596be124228be93eba1d86dbc9d2933190fa389b284331241cb9f27115a0.marshal → ad68f9e06c8068c2a4176b57abf3083d591d5d8980c2b3e897b97331f965b643.marshal} +0 -0
  186. data/prebuilt/{a883a7e568667aa5ac904c82daff356bdf9e996252a36af6def4a4a43efb967e.marshal → ee22ec8642c4006717ba2b80f46e5efb81de459eef9dfec043e838c0ad4fd720.marshal} +0 -0
  187. data/prebuilt/{verdicts-872993b1aa26d73fe48f28251374b8585fed145d02aab42be0f00de386b6ff80.json → verdicts-b69f5dcbba79c20daa0493476fa7c317004a30aa2bc3d3dc6a722ea70ae2b43b.json} +1 -1
  188. data/rust/Cargo.toml +40 -0
  189. data/rust/build/Cargo.toml +36 -0
  190. data/rust/build/clippy.toml +4 -0
  191. data/rust/build/src/adapters_pass.rs +196 -0
  192. data/rust/build/src/binding_pass.rs +54 -13
  193. data/rust/build/src/build_artifact.rs +23 -5
  194. data/rust/build/src/cargo_sync.rs +69 -26
  195. data/rust/build/src/lineage_pass.rs +101 -87
  196. data/rust/build/src/main.rs +11 -2
  197. data/rust/build/src/pipeline.rs +80 -19
  198. data/rust/build/src/reserved_names.rs +6 -50
  199. data/rust/build/src/resolve.rs +82 -35
  200. data/rust/build/src/rustfmt.rs +100 -0
  201. data/rust/build/src/seams_pass.rs +85 -16
  202. data/rust/build/src/subprocess.rs +30 -6
  203. data/rust/build/src/tmp.rs +5 -2
  204. data/rust/build/src/translation_pass.rs +72 -22
  205. data/rust/build/src/world_pass.rs +109 -0
  206. data/rust/clippy.toml +4 -0
  207. data/rust/codegen/Cargo.toml +36 -0
  208. data/rust/codegen/clippy.toml +4 -0
  209. data/rust/codegen/src/asks.rs +40 -14
  210. data/rust/codegen/src/attr.rs +2 -2
  211. data/rust/codegen/src/bridging.rs +676 -117
  212. data/rust/codegen/src/commands.rs +979 -373
  213. data/rust/codegen/src/constraints.rs +88 -25
  214. data/rust/codegen/src/dependency_planning.rs +128 -34
  215. data/rust/codegen/src/domain_generator.rs +306 -170
  216. data/rust/codegen/src/exemplar.rs +87 -32
  217. data/rust/codegen/src/expr_emitter.rs +54 -6
  218. data/rust/codegen/src/fielded.rs +193 -41
  219. data/rust/codegen/src/hecks_naming.rs +124 -16
  220. data/rust/codegen/src/hecks_naming_tests.rs +81 -0
  221. data/rust/codegen/src/json.rs +80 -44
  222. data/rust/codegen/src/json_codec.rs +585 -149
  223. data/rust/codegen/src/literal.rs +104 -138
  224. data/rust/codegen/src/main.rs +151 -52
  225. data/rust/codegen/src/manifest.rs +76 -14
  226. data/rust/codegen/src/mutations.rs +1212 -239
  227. data/rust/codegen/src/naming.rs +111 -60
  228. data/rust/codegen/src/optional_pass.rs +90 -28
  229. data/rust/codegen/src/ports.rs +5 -4
  230. data/rust/codegen/src/prelude.rs +139 -49
  231. data/rust/codegen/src/presenters.rs +335 -0
  232. data/rust/codegen/src/queries.rs +601 -226
  233. data/rust/codegen/src/reactions.rs +268 -74
  234. data/rust/codegen/src/read_models.rs +574 -229
  235. data/rust/codegen/src/reference_specs.rs +16 -3
  236. data/rust/codegen/src/registry.rs +128 -79
  237. data/rust/codegen/src/reserved_names.rs +6 -50
  238. data/rust/codegen/src/shared.rs +71 -22
  239. data/rust/codegen/src/sidecars.rs +15 -5
  240. data/rust/codegen/src/skip_reason.rs +8 -2
  241. data/rust/codegen/src/types.rs +306 -108
  242. data/rust/host/Cargo.lock +1 -0
  243. data/rust/host/Cargo.toml +40 -4
  244. data/rust/host/HECKS_RELEASE +1 -1
  245. data/rust/host/clippy.toml +4 -0
  246. data/rust/host/src/api.rs +698 -218
  247. data/rust/host/src/auth.rs +491 -214
  248. data/rust/host/src/b64.rs +91 -0
  249. data/rust/host/src/bin/expr_harness.rs +6 -2
  250. data/rust/host/src/bin/lineage_harness.rs +47 -11
  251. data/rust/host/src/bin/mint_harness.rs +70 -37
  252. data/rust/host/src/bin/validate_harness.rs +16 -8
  253. data/rust/host/src/boot.rs +144 -68
  254. data/rust/host/src/boundary_fuzz/api.rs +130 -32
  255. data/rust/host/src/boundary_fuzz/auth.rs +108 -23
  256. data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
  257. data/rust/host/src/boundary_fuzz/ir.rs +164 -12
  258. data/rust/host/src/boundary_fuzz/server.rs +283 -36
  259. data/rust/host/src/boundary_fuzz/web.rs +56 -8
  260. data/rust/host/src/checkout.rs +78 -32
  261. data/rust/host/src/clock.rs +8 -0
  262. data/rust/host/src/commerce_ir.rs +333 -95
  263. data/rust/host/src/crypto.rs +33 -0
  264. data/rust/host/src/dispatch.rs +1037 -331
  265. data/rust/host/src/expr_json.rs +1021 -273
  266. data/rust/host/src/extension.rs +24 -4
  267. data/rust/host/src/field_hints.rs +11 -7
  268. data/rust/host/src/fuzz_support.rs +85 -13
  269. data/rust/host/src/host.rs +100 -0
  270. data/rust/host/src/http.rs +22 -0
  271. data/rust/host/src/ir.rs +326 -16
  272. data/rust/host/src/journal.rs +277 -176
  273. data/rust/host/src/lambda_client.rs +219 -53
  274. data/rust/host/src/lib.rs +16 -4
  275. data/rust/host/src/log.rs +62 -11
  276. data/rust/host/src/mint.rs +1153 -326
  277. data/rust/host/src/needs.rs +185 -44
  278. data/rust/host/src/payments/keystore.rs +197 -45
  279. data/rust/host/src/payments/roles.rs +39 -7
  280. data/rust/host/src/payments/tests.rs +1427 -282
  281. data/rust/host/src/payments.rs +391 -112
  282. data/rust/host/src/platform_signin/attack_tests.rs +5 -5
  283. data/rust/host/src/platform_signin/config.rs +11 -11
  284. data/rust/host/src/platform_signin/cookie.rs +2 -2
  285. data/rust/host/src/platform_signin/extension.rs +2 -1
  286. data/rust/host/src/platform_signin/handler.rs +9 -8
  287. data/rust/host/src/platform_signin/jti.rs +4 -2
  288. data/rust/host/src/platform_signin/jwks.rs +2 -1
  289. data/rust/host/src/platform_signin/jws.rs +2 -37
  290. data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
  291. data/rust/host/src/platform_signin/mode_tests.rs +5 -3
  292. data/rust/host/src/platform_signin/pages.rs +20 -20
  293. data/rust/host/src/platform_signin/session.rs +8 -1
  294. data/rust/host/src/platform_signin/settings.rs +6 -6
  295. data/rust/host/src/platform_signin/strict_json.rs +12 -9
  296. data/rust/host/src/platform_signin/verify.rs +41 -41
  297. data/rust/host/src/platform_signin/wiring_support.rs +13 -18
  298. data/rust/host/src/presentation.rs +207 -63
  299. data/rust/host/src/presentation_write.rs +502 -187
  300. data/rust/host/src/query_step.rs +69 -18
  301. data/rust/host/src/rate_limit.rs +541 -142
  302. data/rust/host/src/reference_transform.rs +77 -25
  303. data/rust/host/src/reference_validate.rs +443 -104
  304. data/rust/host/src/resend.rs +190 -55
  305. data/rust/host/src/secrets.rs +116 -21
  306. data/rust/host/src/server.rs +555 -104
  307. data/rust/host/src/storage_shape.rs +88 -21
  308. data/rust/host/src/test_pg.rs +17 -7
  309. data/rust/host/src/ui_schema.rs +495 -207
  310. data/rust/host/src/wasm_runner.rs +21 -6
  311. data/rust/host/src/web/commerce.rs +101 -23
  312. data/rust/host/src/web/newsletter/cooldown.rs +10 -3
  313. data/rust/host/src/web/newsletter/template.rs +26 -7
  314. data/rust/host/src/web/newsletter/tests.rs +112 -23
  315. data/rust/host/src/web/newsletter.rs +209 -59
  316. data/rust/host/src/web/newsletter_send.rs +251 -79
  317. data/rust/host/src/web/registration_receipt/tests.rs +16 -4
  318. data/rust/host/src/web/registration_receipt.rs +100 -24
  319. data/rust/host/src/web/registrations.rs +1384 -332
  320. data/rust/host/src/web/resend_webhook.rs +138 -80
  321. data/rust/host/src/web.rs +2416 -508
  322. data/rust/lsp/Cargo.toml +36 -0
  323. data/rust/lsp/clippy.toml +4 -0
  324. data/rust/lsp/src/json.rs +29 -22
  325. data/rust/lsp/src/main.rs +81 -31
  326. data/rust/lsp/src/outline.rs +23 -7
  327. data/rust/lsp/src/rpc.rs +17 -4
  328. data/rust/parser/Cargo.toml +36 -0
  329. data/rust/parser/clippy.toml +4 -0
  330. data/rust/parser/src/build/closed_sets.rs +6 -1
  331. data/rust/parser/src/build/identity.rs +4 -3
  332. data/rust/parser/src/build/mod.rs +1 -1
  333. data/rust/parser/src/build/named_pattern.rs +4 -1
  334. data/rust/parser/src/build/naming.rs +2 -80
  335. data/rust/parser/src/build/pattern_subset.rs +6 -8
  336. data/rust/parser/src/build/query_derive.rs +3 -1
  337. data/rust/parser/src/build/query_inference.rs +14 -5
  338. data/rust/parser/src/build/read_model.rs +4 -1
  339. data/rust/parser/src/build/references.rs +4 -3
  340. data/rust/parser/src/canonical.rs +34 -15
  341. data/rust/parser/src/emit.rs +187 -105
  342. data/rust/parser/src/expr/ast_json.rs +134 -42
  343. data/rust/parser/src/expr/evaluator.rs +27 -9
  344. data/rust/parser/src/expr/mod.rs +54 -13
  345. data/rust/parser/src/expr/resolver.rs +299 -92
  346. data/rust/parser/src/ir.rs +35 -3
  347. data/rust/parser/src/keywords.rs +7168 -516
  348. data/rust/parser/src/lex.rs +11 -25
  349. data/rust/parser/src/lib.rs +1 -0
  350. data/rust/parser/src/main.rs +65 -6
  351. data/rust/parser/src/parse/adapter.rs +157 -0
  352. data/rust/parser/src/parse/aggregate.rs +19 -10
  353. data/rust/parser/src/parse/chapter.rs +16 -17
  354. data/rust/parser/src/parse/command.rs +35 -16
  355. data/rust/parser/src/parse/domain_port.rs +4 -7
  356. data/rust/parser/src/parse/entity.rs +21 -11
  357. data/rust/parser/src/parse/hecksagon.rs +10 -4
  358. data/rust/parser/src/parse/lifecycle.rs +9 -5
  359. data/rust/parser/src/parse/mod.rs +22 -91
  360. data/rust/parser/src/parse/needs.rs +21 -12
  361. data/rust/parser/src/parse/policy.rs +2 -2
  362. data/rust/parser/src/parse/process_manager.rs +9 -11
  363. data/rust/parser/src/parse/query.rs +8 -4
  364. data/rust/parser/src/parse/read_model.rs +57 -16
  365. data/rust/parser/src/parse/value_object.rs +7 -2
  366. data/rust/parser/src/parse/world.rs +322 -0
  367. data/rust/parser/src/ruby_value.rs +7 -6
  368. data/rust/src/exemplar/json.rs +4 -4
  369. data/rust/src/exemplar/mutations.rs +18 -0
  370. data/rust/src/exemplar/read_models.rs +1 -10
  371. data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
  372. data/rust/src/kernel/attribute_shapes/list.rs +2 -2
  373. data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
  374. data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
  375. data/rust/src/kernel/cli.rs +244 -114
  376. data/rust/src/kernel/dispatch.rs +323 -115
  377. data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
  378. data/rust/src/kernel/expr.rs +203 -86
  379. data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
  380. data/rust/src/kernel/expression_operators/comparison.rs +87 -2
  381. data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
  382. data/rust/src/kernel/expression_operators/logical.rs +9 -3
  383. data/rust/src/kernel/expression_operators/membership.rs +3 -1
  384. data/rust/src/kernel/expression_operators/mod.rs +34 -21
  385. data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
  386. data/rust/src/kernel/expression_operators/positional.rs +84 -30
  387. data/rust/src/kernel/expression_operators/presence.rs +52 -13
  388. data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
  389. data/rust/src/kernel/expression_operators/sized.rs +24 -8
  390. data/rust/src/kernel/expression_operators/text.rs +120 -27
  391. data/rust/src/kernel/expression_operators/to_string.rs +10 -4
  392. data/rust/src/kernel/json.rs +244 -148
  393. data/rust/src/kernel/mod.rs +47 -33
  394. data/rust/src/kernel/named_query.rs +63 -15
  395. data/rust/src/kernel/needs.rs +112 -25
  396. data/rust/src/kernel/orchestrate.rs +980 -581
  397. data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
  398. data/rust/src/kernel/pattern.rs +107 -34
  399. data/rust/src/kernel/present.rs +261 -0
  400. data/rust/src/kernel/query_comparators.rs +118 -41
  401. data/rust/src/kernel/query_ordering.rs +13 -5
  402. data/rust/src/kernel/read_model.rs +479 -181
  403. data/rust/src/kernel/reference_lookup.rs +122 -26
  404. data/rust/src/kernel/repository.rs +293 -66
  405. data/rust/src/kernel/routing.rs +58 -42
  406. data/rust/src/kernel/ruby_float.rs +139 -0
  407. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
  408. data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
  409. data/rust/src/kernel/vocab/field_hint.rs +19 -25
  410. data/rust/src/kernel/vocab/mod.rs +6 -6
  411. data/rust/src/kernel/vocab/query_comparator.rs +23 -24
  412. data/rust/src/kernel/vocab/refusal_template.rs +501 -297
  413. data/rust/src/lib.rs +50 -0
  414. data/rust/src/main.rs +4 -1
  415. data/rust/web/Cargo.toml +36 -0
  416. data/rust/web/clippy.toml +4 -0
  417. metadata +85 -15
  418. data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
  419. data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
  420. data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
  421. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
  422. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
  423. data/lib/hecks/ports/authentication.port +0 -6
  424. data/lib/hecks/ports/authentication.rb +0 -67
  425. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
  426. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
  427. data/rust/host/src/approval.rs +0 -652
@@ -17,8 +17,14 @@ pub fn reference_specs(domain_name: &str, attributes: &[Json]) -> Vec<ReferenceS
17
17
  .filter_map(|attr| {
18
18
  let target = crate::naming::reference_target(crate::attr::type_name(attr))?;
19
19
  let field = crate::attr::name(attr).to_string();
20
- let as_name = field.strip_suffix("_id").map(str::to_string).unwrap_or_else(|| field.clone());
21
- Some(ReferenceSpec { field, as_name, target: format!("{domain_name}::{target}") })
20
+ let as_name = field
21
+ .strip_suffix("_id")
22
+ .map_or_else(|| field.clone(), str::to_string);
23
+ Some(ReferenceSpec {
24
+ field,
25
+ as_name,
26
+ target: format!("{domain_name}::{target}"),
27
+ })
22
28
  })
23
29
  .collect()
24
30
  }
@@ -36,5 +42,12 @@ pub fn emit_reference_specs_literal(specs: &[ReferenceSpec]) -> String {
36
42
  if specs.is_empty() {
37
43
  return "&[]".to_string();
38
44
  }
39
- format!("&[{}]", specs.iter().map(emit_reference_spec).collect::<Vec<_>>().join(", "))
45
+ format!(
46
+ "&[{}]",
47
+ specs
48
+ .iter()
49
+ .map(emit_reference_spec)
50
+ .collect::<Vec<_>>()
51
+ .join(", ")
52
+ )
40
53
  }
@@ -4,6 +4,7 @@
4
4
  use crate::exemplar::Exemplar;
5
5
  use crate::naming;
6
6
  use crate::reference_specs::{self, ReferenceSpec};
7
+ use std::fmt::Write as _;
7
8
 
8
9
  #[derive(Clone)]
9
10
  pub struct ReferenceCheck {
@@ -77,7 +78,6 @@ pub struct NestedEntityCommandEntry {
77
78
  pub args_struct: String,
78
79
  pub reference_checks: Vec<ReferenceCheck>,
79
80
  pub reference_specs: Vec<ReferenceSpec>,
80
- pub attributes: Vec<String>,
81
81
  pub invariant_check_lines: Vec<String>,
82
82
  pub role: Option<String>,
83
83
  pub entity_name: String,
@@ -91,7 +91,6 @@ pub struct NestedEntityCommandEntry {
91
91
 
92
92
  pub struct PortEntry {
93
93
  pub verb: String,
94
- pub name: String,
95
94
  pub fn_name: String,
96
95
  pub args_struct: String,
97
96
  pub reference_checks: Vec<ReferenceCheck>,
@@ -146,32 +145,64 @@ pub fn emit_role_check(
146
145
 
147
146
  /// Port of the retired Ruby generator's `registry.rb#emit_tenant_boundary_check`.
148
147
  /// Hand-built: the target accessor differs for a single-attribute value object and a bare scalar.
148
+ // reason: the emitted Rust contains literal `{:?}` format templates, which the lint mistakes for unfilled arguments.
149
+ #[allow(clippy::literal_string_with_formatting_args)]
149
150
  pub fn emit_tenant_boundary_check(check: &TenantBoundaryCheck) -> String {
150
151
  let ref_ident = naming::rust_ident_field(&check.reference_field);
151
- let own_expr = format!("args.{}.clone()", naming::rust_ident_field(&check.own_accessor));
152
+ let own_expr = format!(
153
+ "args.{}.clone()",
154
+ naming::rust_ident_field(&check.own_accessor)
155
+ );
152
156
  let target_expr = match check.target_accessor.split_once('.') {
153
157
  Some((head, inner)) => format!(
154
158
  "record.{}.as_ref().map(|v| v.{}.clone())",
155
159
  naming::rust_ident_field(head),
156
160
  naming::rust_ident_field(inner)
157
161
  ),
158
- None => format!("record.{}.clone()", naming::rust_ident_field(&check.target_accessor)),
162
+ None => format!(
163
+ "record.{}.clone()",
164
+ naming::rust_ident_field(&check.target_accessor)
165
+ ),
159
166
  };
160
167
 
161
168
  let mut out = String::new();
162
- out.push_str(&format!("if let Some(record) = store.{}.find(&args.{ref_ident}) {{ ", check.target_mod));
163
- out.push_str(&format!("if let Some(target_tenant) = {target_expr} {{ "));
164
- out.push_str(&format!("let own_tenant = {own_expr}; "));
169
+ let _ = write!(
170
+ out,
171
+ "if let Some(record) = store.{}.find(&args.{ref_ident}) {{ ",
172
+ check.target_mod
173
+ );
174
+ let _ = write!(out, "if let Some(target_tenant) = {target_expr} {{ ");
175
+ let _ = write!(out, "let own_tenant = {own_expr}; ");
165
176
  out.push_str("if target_tenant != own_tenant { ");
166
177
  out.push_str(
167
178
  "return Err(crate::kernel::Refusal::Unauthorized(crate::kernel::refusal_wording::UnauthorizedCrossTenantReferenceArgs { ",
168
179
  );
169
- out.push_str(&format!("aggregate: {}, ", naming::ruby_inspect_string(&check.aggregate_name)));
170
- out.push_str(&format!("field: {}, ", naming::ruby_inspect_string(&check.own_tenant_field)));
180
+ let _ = write!(
181
+ out,
182
+ "aggregate: {}, ",
183
+ naming::ruby_inspect_string(&check.aggregate_name)
184
+ );
185
+ let _ = write!(
186
+ out,
187
+ "field: {}, ",
188
+ naming::ruby_inspect_string(&check.own_tenant_field)
189
+ );
171
190
  out.push_str("tenant: &format!(\"{:?}\", own_tenant), ");
172
- out.push_str(&format!("attribute: {}, ", naming::ruby_inspect_string(&check.reference_field)));
173
- out.push_str(&format!("target: {}, ", naming::ruby_inspect_string(&check.target_name)));
174
- out.push_str(&format!("target_field: {}, ", naming::ruby_inspect_string(&check.target_tenant_field)));
191
+ let _ = write!(
192
+ out,
193
+ "attribute: {}, ",
194
+ naming::ruby_inspect_string(&check.reference_field)
195
+ );
196
+ let _ = write!(
197
+ out,
198
+ "target: {}, ",
199
+ naming::ruby_inspect_string(&check.target_name)
200
+ );
201
+ let _ = write!(
202
+ out,
203
+ "target_field: {}, ",
204
+ naming::ruby_inspect_string(&check.target_tenant_field)
205
+ );
175
206
  out.push_str("other: &format!(\"{:?}\", target_tenant)");
176
207
  out.push_str(" }.render_args())); ");
177
208
  out.push_str("} } }");
@@ -180,20 +211,32 @@ pub fn emit_tenant_boundary_check(check: &TenantBoundaryCheck) -> String {
180
211
 
181
212
  /// `kernel::ArgumentGates` literal for one command (`registry.rb#emit_argument_gates_literal`).
182
213
  /// The last two fields are closures because they need `store`, which exists only at the router.
183
- fn emit_argument_gates_literal(args_path: &str, invariant_check_lines: &[String], role_line: Option<String>, reference_lines: &[String]) -> String {
214
+ fn emit_argument_gates_literal(
215
+ args_path: &str,
216
+ invariant_check_lines: &[String],
217
+ role_line: Option<String>,
218
+ reference_lines: &[String],
219
+ ) -> String {
184
220
  let mut normalize = vec![format!("let args = {args_path}::from_json(v)?;")];
185
221
  normalize.extend(invariant_check_lines.iter().map(|l| squeeze(l)));
186
222
  normalize.push("Ok(args)".to_string());
187
223
 
188
- let role = match role_line.filter(|l| !l.is_empty()) {
189
- Some(line) => format!("&|| {{ {} Ok(()) }}", squeeze(&line)),
190
- None => "&|| Ok(())".to_string(),
191
- };
192
- let references: Vec<String> = reference_lines.iter().filter(|l| !l.is_empty()).map(|l| squeeze(l)).collect();
224
+ let role = role_line.filter(|l| !l.is_empty()).map_or_else(
225
+ || "&|| Ok(())".to_string(),
226
+ |line| format!("&|| {{ {} Ok(()) }}", squeeze(&line)),
227
+ );
228
+ let references: Vec<String> = reference_lines
229
+ .iter()
230
+ .filter(|l| !l.is_empty())
231
+ .map(|l| squeeze(l))
232
+ .collect();
193
233
  let references = if references.is_empty() {
194
234
  format!("&|_args: &{args_path}| Ok(())")
195
235
  } else {
196
- format!("&|args: &{args_path}| {{ {} Ok(()) }}", references.join(" "))
236
+ format!(
237
+ "&|args: &{args_path}| {{ {} Ok(()) }}",
238
+ references.join("\n")
239
+ )
197
240
  };
198
241
 
199
242
  format!(
@@ -201,13 +244,18 @@ fn emit_argument_gates_literal(args_path: &str, invariant_check_lines: &[String]
201
244
  refuse_unknown_arguments: &{args_path}::refuse_unknown_arguments, \
202
245
  refuse_absent_arguments: &{args_path}::refuse_absent_arguments, \
203
246
  normalize_args: &|v: &crate::kernel::Json| {{ {} }}, refuse_role_mismatch: {role}, resolve_references: {references} }}",
204
- normalize.join(" ")
247
+ normalize.join("\n")
205
248
  )
206
249
  }
207
250
 
208
- /// Joins a multi-line emitted check onto one line so it can sit inside a closure expression.
251
+ /// Trims the lines of an emitted check so it can sit inside a closure expression. The lines stay
252
+ /// separate: joined onto one, `}` and the `if` after it would read as a missing `else`.
209
253
  fn squeeze(text: &str) -> String {
210
- text.lines().map(str::trim).filter(|l| !l.is_empty()).collect::<Vec<_>>().join(" ")
254
+ text.lines()
255
+ .map(str::trim)
256
+ .filter(|l| !l.is_empty())
257
+ .collect::<Vec<_>>()
258
+ .join("\n")
211
259
  }
212
260
 
213
261
  pub fn emit_reference_check(exemplar: &Exemplar, check: &ReferenceCheck) -> String {
@@ -259,6 +307,18 @@ fn chapter_path(a: &AggregateEntry) -> String {
259
307
  format!("crate::generated::{}::{}", a.chapter_mod, a.module_name)
260
308
  }
261
309
 
310
+ /// One `"verb" => { .. }` arm of the registry's dispatch match, its body lines indented.
311
+ fn verb_arm(verb: &str, body: &[String]) -> String {
312
+ format!(
313
+ " {} => {{\n{}\n }}",
314
+ naming::ruby_inspect_string(verb),
315
+ body.iter()
316
+ .map(|l| format!(" {l}"))
317
+ .collect::<Vec<_>>()
318
+ .join("\n")
319
+ )
320
+ }
321
+
262
322
  pub fn emit_registry(exemplar: &Exemplar, aggregates: &[AggregateEntry]) -> String {
263
323
  let store_fields: Vec<String> = aggregates
264
324
  .iter()
@@ -352,10 +412,13 @@ pub fn emit_registry(exemplar: &Exemplar, aggregates: &[AggregateEntry]) -> Stri
352
412
  )
353
413
  })
354
414
  .collect();
355
- let extra_pass: String = extra_idents
356
- .iter()
357
- .map(|ident| format!("&{ident}, "))
358
- .collect();
415
+ let extra_pass: String =
416
+ extra_idents
417
+ .iter()
418
+ .fold(String::new(), |mut passed, ident| {
419
+ let _ = write!(passed, "&{ident}, ");
420
+ passed
421
+ });
359
422
 
360
423
  // A creating command's dispatch fn takes `route` so it can tell create from find.
361
424
  let dispatch_call = format!(
@@ -394,8 +457,7 @@ pub fn emit_registry(exemplar: &Exemplar, aggregates: &[AggregateEntry]) -> Stri
394
457
  &format!("{mod_path}::{}", c.args_struct),
395
458
  &c.invariant_check_lines,
396
459
  emit_role_check(exemplar, c.role.as_deref(), &c.name),
397
- &c
398
- .reference_checks
460
+ &c.reference_checks
399
461
  .iter()
400
462
  .map(|check| emit_reference_check(exemplar, check))
401
463
  .collect::<Vec<_>>()
@@ -414,7 +476,8 @@ pub fn emit_registry(exemplar: &Exemplar, aggregates: &[AggregateEntry]) -> Stri
414
476
  .map(emit_tenant_boundary_check)
415
477
  .collect();
416
478
  let tenant_boundary_check_line = if tenant_boundary_check_bodies.is_empty() {
417
- "let tenant_boundary_check: Result<(), crate::kernel::Refusal> = Ok(());".to_string()
479
+ "let tenant_boundary_check: Result<(), crate::kernel::Refusal> = Ok(());"
480
+ .to_string()
418
481
  } else {
419
482
  format!(
420
483
  "let tenant_boundary_check: Result<(), crate::kernel::Refusal> = \
@@ -462,14 +525,7 @@ pub fn emit_registry(exemplar: &Exemplar, aggregates: &[AggregateEntry]) -> Stri
462
525
  "{dispatch_call}.map(|(_, events)| stamp_payload(events, &payload))"
463
526
  ));
464
527
 
465
- aggregate_arms.push(format!(
466
- " {} => {{\n{}\n }}",
467
- naming::ruby_inspect_string(&c.verb),
468
- body.iter()
469
- .map(|l| format!(" {l}"))
470
- .collect::<Vec<_>>()
471
- .join("\n")
472
- ));
528
+ aggregate_arms.push(verb_arm(&c.verb, &body));
473
529
  }
474
530
  }
475
531
 
@@ -484,8 +540,7 @@ pub fn emit_registry(exemplar: &Exemplar, aggregates: &[AggregateEntry]) -> Stri
484
540
  &format!("{mod_path}::{}", c.args_struct),
485
541
  &c.invariant_check_lines,
486
542
  emit_role_check(exemplar, c.role.as_deref(), &c.name),
487
- &c
488
- .reference_checks
543
+ &c.reference_checks
489
544
  .iter()
490
545
  .map(|check| emit_reference_check(exemplar, check))
491
546
  .collect::<Vec<_>>()
@@ -501,7 +556,10 @@ pub fn emit_registry(exemplar: &Exemplar, aggregates: &[AggregateEntry]) -> Stri
501
556
  // A missing identity is wrapped as NotFound, as in the aggregate arm's `id_line`.
502
557
  let entity_parent_no_identity_message = format!(
503
558
  "{} acts on a {}'s {} — pass {}:",
504
- c.name, a.record, c.entity_name, a.identified_by.join(", ")
559
+ c.name,
560
+ a.record,
561
+ c.entity_name,
562
+ a.identified_by.join(", ")
505
563
  );
506
564
  let entity_element_no_identity_message = format!(
507
565
  "{} acts on one {} — pass {}:",
@@ -519,7 +577,8 @@ pub fn emit_registry(exemplar: &Exemplar, aggregates: &[AggregateEntry]) -> Stri
519
577
  );
520
578
 
521
579
  let mut body: Vec<String> = vec![
522
- "let invocation = crate::kernel::CommandInvocation::from_json(args_json)?;".to_string(),
580
+ "let invocation = crate::kernel::CommandInvocation::from_json(args_json)?;"
581
+ .to_string(),
523
582
  "let route = invocation.route();".to_string(),
524
583
  "let facts_json = invocation.facts();".to_string(),
525
584
  "if let Some(route) = route { route.require_depth(1)?; }".to_string(),
@@ -547,14 +606,7 @@ pub fn emit_registry(exemplar: &Exemplar, aggregates: &[AggregateEntry]) -> Stri
547
606
  );
548
607
  body.push(dispatch_call);
549
608
 
550
- entity_arms.push(format!(
551
- " {} => {{\n{}\n }}",
552
- naming::ruby_inspect_string(&c.verb),
553
- body.iter()
554
- .map(|l| format!(" {l}"))
555
- .collect::<Vec<_>>()
556
- .join("\n")
557
- ));
609
+ entity_arms.push(verb_arm(&c.verb, &body));
558
610
  }
559
611
  }
560
612
 
@@ -571,8 +623,7 @@ pub fn emit_registry(exemplar: &Exemplar, aggregates: &[AggregateEntry]) -> Stri
571
623
  &format!("{mod_path}::{}", c.args_struct),
572
624
  &c.invariant_check_lines,
573
625
  emit_role_check(exemplar, c.role.as_deref(), &c.name),
574
- &c
575
- .reference_checks
626
+ &c.reference_checks
576
627
  .iter()
577
628
  .map(|check| emit_reference_check(exemplar, check))
578
629
  .collect::<Vec<_>>()
@@ -587,7 +638,11 @@ pub fn emit_registry(exemplar: &Exemplar, aggregates: &[AggregateEntry]) -> Stri
587
638
  // wrong-depth route first. A missing identity wraps into a per-hop NotFound message.
588
639
  let entity_parent_no_identity_message = format!(
589
640
  "{} acts on a {}'s {}.{} — pass {}:",
590
- c.name, a.record, c.entity_name, c.nested_name, a.identified_by.join(", ")
641
+ c.name,
642
+ a.record,
643
+ c.entity_name,
644
+ c.nested_name,
645
+ a.identified_by.join(", ")
591
646
  );
592
647
  let hop1_no_identity_message = format!(
593
648
  "{} acts on one {} — pass {}:",
@@ -622,7 +677,8 @@ pub fn emit_registry(exemplar: &Exemplar, aggregates: &[AggregateEntry]) -> Stri
622
677
  };
623
678
 
624
679
  let mut body: Vec<String> = vec![
625
- "let invocation = crate::kernel::CommandInvocation::from_json(args_json)?;".to_string(),
680
+ "let invocation = crate::kernel::CommandInvocation::from_json(args_json)?;"
681
+ .to_string(),
626
682
  "let route = invocation.route();".to_string(),
627
683
  "let facts_json = invocation.facts();".to_string(),
628
684
  ];
@@ -645,14 +701,7 @@ pub fn emit_registry(exemplar: &Exemplar, aggregates: &[AggregateEntry]) -> Stri
645
701
  );
646
702
  body.push(dispatch_call);
647
703
 
648
- nested_entity_arms.push(format!(
649
- " {} => {{\n{}\n }}",
650
- naming::ruby_inspect_string(&c.verb),
651
- body.iter()
652
- .map(|l| format!(" {l}"))
653
- .collect::<Vec<_>>()
654
- .join("\n")
655
- ));
704
+ nested_entity_arms.push(verb_arm(&c.verb, &body));
656
705
  }
657
706
  }
658
707
 
@@ -670,21 +719,19 @@ pub fn emit_registry(exemplar: &Exemplar, aggregates: &[AggregateEntry]) -> Stri
670
719
  .legacy_receiver_field
671
720
  .as_deref()
672
721
  .map(naming::ruby_inspect_string)
673
- .map(|field| format!("Some({field})"))
674
- .unwrap_or_else(|| "None".to_string());
722
+ .map_or_else(|| "None".to_string(), |field| format!("Some({field})"));
675
723
  let to_receiver = p
676
724
  .to_receiver_field
677
725
  .as_deref()
678
726
  .map(naming::ruby_inspect_string)
679
- .map(|field| format!("Some({field})"))
680
- .unwrap_or_else(|| "None".to_string());
727
+ .map_or_else(|| "None".to_string(), |field| format!("Some({field})"));
681
728
 
682
729
  let mut body: Vec<String> = vec![
683
730
  "let invocation = crate::kernel::CommandInvocation::from_json(args_json)?;".to_string(),
684
731
  format!("let (id, port_facts) = invocation.split_aggregate_receiver({legacy_receiver}, {to_receiver})?;"),
685
732
  "let facts_json = &port_facts;".to_string(),
686
733
  format!(
687
- "let _instance = store.{}.find(&id).ok_or_else(|| crate::kernel::Refusal::NotFound(format!(\"{} {{:?}} does not exist\", id)))?;",
734
+ "let _instance = store.{}.find(&id).ok_or_else(|| crate::kernel::Refusal::NotFound(format!(\"{} {{id:?}} does not exist\")))?;",
688
735
  a.module_name, a.name
689
736
  ),
690
737
  format!("let args = {mod_path}::{}::from_json(facts_json)?;", p.args_struct),
@@ -696,14 +743,7 @@ pub fn emit_registry(exemplar: &Exemplar, aggregates: &[AggregateEntry]) -> Stri
696
743
  );
697
744
  body.push(dispatch_call);
698
745
 
699
- port_arms.push(format!(
700
- " {} => {{\n{}\n }}",
701
- naming::ruby_inspect_string(&p.verb),
702
- body.iter()
703
- .map(|l| format!(" {l}"))
704
- .collect::<Vec<_>>()
705
- .join("\n")
706
- ));
746
+ port_arms.push(verb_arm(&p.verb, &body));
707
747
  }
708
748
  }
709
749
 
@@ -729,7 +769,10 @@ pub fn emit_registry(exemplar: &Exemplar, aggregates: &[AggregateEntry]) -> Stri
729
769
  ("tmpl_dump_arm_placeholder();", dump_arms.join("\n")),
730
770
  ("tmpl_seed_arm_placeholder();", seed_arms.join("\n")),
731
771
  ("tmpl_query_arm_placeholder();", query_arms.join("\n")),
732
- ("tmpl_scan_each_arm_placeholder();", scan_each_arms.join("\n")),
772
+ (
773
+ "tmpl_scan_each_arm_placeholder();",
774
+ scan_each_arms.join("\n"),
775
+ ),
733
776
  (
734
777
  "\"tmpl_verb\" => { tmpl_dispatch_arm_placeholder() }",
735
778
  dispatch_arms.join("\n"),
@@ -841,7 +884,6 @@ mod tests {
841
884
  nested_entity_commands: Vec::new(),
842
885
  ports: vec![PortEntry {
843
886
  verb: "Banking::SafeDepositBox.PaymentGateway.Receive".to_string(),
844
- name: "Receive".to_string(),
845
887
  fn_name: "paymentgateway_receive".to_string(),
846
888
  args_struct: "PaymentGatewayReceiveArgs".to_string(),
847
889
  reference_checks: Vec::new(),
@@ -862,7 +904,9 @@ mod tests {
862
904
  assert!(generated.contains("let box_number = facts_json.dig(\"box_number\")"));
863
905
  // `from_json` is reached through the `normalize_args` hook; route depth leads the body and
864
906
  // identity resolves after every gate.
865
- assert!(generated.contains("crate::kernel::decode_aggregate_arguments(facts_json, &crate::kernel::ArgumentGates {"));
907
+ assert!(generated.contains(
908
+ "crate::kernel::decode_aggregate_arguments(facts_json, &crate::kernel::ArgumentGates {"
909
+ ));
866
910
  assert!(generated.contains("decode_arguments: &crate::generated::banking::safedepositbox::RentArgs::decode_arguments"));
867
911
  assert!(generated.contains("refuse_unknown_arguments: &crate::generated::banking::safedepositbox::RentArgs::refuse_unknown_arguments"));
868
912
  assert!(generated.contains("refuse_absent_arguments: &crate::generated::banking::safedepositbox::RentArgs::refuse_absent_arguments"));
@@ -870,7 +914,9 @@ mod tests {
870
914
  assert!(generated.contains("if let Some(route) = route { route.require_depth(0)?; }"));
871
915
  assert!(generated.contains("Some(route) => route.aggregate().to_string()"));
872
916
  assert!(generated.contains("if let Some(route) = route { route.require_depth(1)?; }"));
873
- assert!(generated.contains("crate::kernel::decode_entity_arguments(facts_json, &crate::kernel::ArgumentGates {"));
917
+ assert!(generated.contains(
918
+ "crate::kernel::decode_entity_arguments(facts_json, &crate::kernel::ArgumentGates {"
919
+ ));
874
920
  assert!(generated.contains("let element_id = route.entities()[0].clone()"));
875
921
  assert!(generated.contains("VisitAnnotateArgs::from_json(v)?"));
876
922
  assert!(!generated.contains("RentArgs::from_json(facts_json)?"));
@@ -950,7 +996,10 @@ mod tests {
950
996
  chapter_mod: "banking".to_string(),
951
997
  domain_name: "Banking".to_string(),
952
998
  reference_specs: Vec::new(),
953
- identified_by: vec!["branch_code.value".to_string(), "box_number.value".to_string()],
999
+ identified_by: vec![
1000
+ "branch_code.value".to_string(),
1001
+ "box_number.value".to_string(),
1002
+ ],
954
1003
  entities: Vec::new(),
955
1004
  };
956
1005
 
@@ -2,57 +2,13 @@
2
2
  // CargoReservedName vocabularies (lib/hecks/language/bluebook/
3
3
  // vocabulary.bluebook). Do not hand-edit — re-run hecks project_reserved_names.
4
4
 
5
- /// The `RustReservedWord` vocabulary, 49 entries.
5
+ /// The `RustReservedWord` vocabulary, 51 entries.
6
6
  pub const RUST_KEYWORDS: &[&str] = &[
7
- "as",
8
- "break",
9
- "const",
10
- "continue",
11
- "crate",
12
- "dyn",
13
- "else",
14
- "enum",
15
- "extern",
16
- "false",
17
- "fn",
18
- "for",
19
- "if",
20
- "impl",
21
- "in",
22
- "let",
23
- "loop",
24
- "match",
25
- "mod",
26
- "move",
27
- "mut",
28
- "pub",
29
- "ref",
30
- "return",
31
- "self",
32
- "Self",
33
- "static",
34
- "struct",
35
- "super",
36
- "trait",
37
- "true",
38
- "type",
39
- "unsafe",
40
- "use",
41
- "where",
42
- "while",
43
- "abstract",
44
- "become",
45
- "box",
46
- "do",
47
- "final",
48
- "macro",
49
- "override",
50
- "priv",
51
- "typeof",
52
- "unsized",
53
- "virtual",
54
- "yield",
55
- "try",
7
+ "as", "async", "await", "break", "const", "continue", "crate", "dyn", "else", "enum", "extern",
8
+ "false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub",
9
+ "ref", "return", "self", "Self", "static", "struct", "super", "trait", "true", "type",
10
+ "unsafe", "use", "where", "while", "abstract", "become", "box", "do", "final", "macro",
11
+ "override", "priv", "typeof", "unsized", "virtual", "yield", "try",
56
12
  ];
57
13
 
58
14
  /// The `CargoReservedName` vocabulary, 16 entries.
@@ -1,13 +1,55 @@
1
1
  //! Predicates ported from the retired Ruby generator's `mutations.rb` that `types.rs` and `json_codec.rs` share.
2
2
 
3
3
  use crate::json::Json;
4
- use std::collections::HashMap;
4
+
5
+ /// Mirrors Ruby's `puts`: appends "\n" only when the string does not already end in one.
6
+ pub fn puts_str(out: &mut String, s: &str) {
7
+ out.push_str(s);
8
+ if !s.ends_with('\n') {
9
+ out.push('\n');
10
+ }
11
+ }
12
+
13
+ pub fn puts_blank(out: &mut String) {
14
+ out.push('\n');
15
+ }
16
+
17
+ /// The extra `(field, json)` pair a lifecycle contributes to a node's record fields.
18
+ pub fn lifecycle_extra_field(node: &Json) -> Vec<(String, String)> {
19
+ node.get("lifecycle")
20
+ .map_or_else(std::vec::Vec::new, |lifecycle| {
21
+ let field = lifecycle.get("field").and_then(Json::as_str).unwrap_or("");
22
+ let ident = crate::naming::rust_ident_field(field);
23
+ vec![(
24
+ crate::naming::rust_field(field),
25
+ format!("crate::kernel::Json::Str(self.{ident}.clone())"),
26
+ )]
27
+ })
28
+ }
29
+
30
+ /// Ruby's `String#to_i`: parses a leading integer prefix, 0 when there is none.
31
+ pub fn ruby_to_i(s: &str) -> i64 {
32
+ let trimmed = s.trim_start();
33
+ let bytes = trimmed.as_bytes();
34
+ let mut end = 0;
35
+ if end < bytes.len() && (bytes[end] == b'-' || bytes[end] == b'+') {
36
+ end += 1;
37
+ }
38
+ let digits_start = end;
39
+ while end < bytes.len() && bytes[end].is_ascii_digit() {
40
+ end += 1;
41
+ }
42
+ if end == digits_start {
43
+ return 0;
44
+ }
45
+ trimmed[..end].parse().unwrap_or(0)
46
+ }
5
47
 
6
48
  /// Argument names claimed by an append, so an append's element-field argument never bare-name-
7
49
  /// matches an unrelated owner field. Shared by `creates_owner` and `identity_components`.
8
50
  pub fn append_claimed_names(command: &Json) -> std::collections::HashSet<String> {
9
51
  let mut claimed = std::collections::HashSet::new();
10
- for m in command.get("mutations").map(Json::each).unwrap_or(&[]) {
52
+ for m in command.get("mutations").map_or_default(Json::each) {
11
53
  if m.get("op").map(Json::to_s).as_deref() != Some("append") {
12
54
  continue;
13
55
  }
@@ -28,26 +70,32 @@ pub fn append_claimed_names(command: &Json) -> std::collections::HashSet<String>
28
70
  /// A command with `references` acts on an existing record. Otherwise it creates when it supplies,
29
71
  /// via a `:set` mutation or a same-named argument, a required non-list owner field, ignoring
30
72
  /// arguments claimed by an append (`append_claimed_names`).
31
- pub fn creates_owner(aggregate: &Json, command: &Json, _value_objects_by_name: &HashMap<String, &Json>) -> bool {
73
+ pub fn creates_owner(aggregate: &Json, command: &Json) -> bool {
32
74
  if command.get("references").is_some() {
33
75
  return false;
34
76
  }
35
77
 
36
- let attrs = aggregate.get("attributes").map(Json::each).unwrap_or(&[]);
37
- let owner_fields: std::collections::HashSet<String> = attrs.iter().map(|a| crate::attr::name(a).to_string()).collect();
38
- let required_fields: std::collections::HashSet<String> =
39
- attrs.iter().filter(|a| !crate::attr::list(a) && !crate::attr::optional(a)).map(|a| crate::attr::name(a).to_string()).collect();
78
+ let attrs = aggregate.get("attributes").map_or_default(Json::each);
79
+ let owner_fields: std::collections::HashSet<String> = attrs
80
+ .iter()
81
+ .map(|a| crate::attr::name(a).to_string())
82
+ .collect();
83
+ let required_fields: std::collections::HashSet<String> = attrs
84
+ .iter()
85
+ .filter(|a| !crate::attr::list(a) && !crate::attr::optional(a))
86
+ .map(|a| crate::attr::name(a).to_string())
87
+ .collect();
40
88
 
41
89
  let append_claimed = append_claimed_names(command);
42
90
 
43
91
  let mut known_writes: std::collections::HashSet<String> = std::collections::HashSet::new();
44
- for attr in command.get("attributes").map(Json::each).unwrap_or(&[]) {
92
+ for attr in command.get("attributes").map_or_default(Json::each) {
45
93
  let name = crate::attr::name(attr).to_string();
46
94
  if owner_fields.contains(&name) && !append_claimed.contains(&name) {
47
95
  known_writes.insert(name);
48
96
  }
49
97
  }
50
- for m in command.get("mutations").map(Json::each).unwrap_or(&[]) {
98
+ for m in command.get("mutations").map_or_default(Json::each) {
51
99
  if m.get("op").map(Json::to_s).as_deref() != Some("set") {
52
100
  continue;
53
101
  }
@@ -57,37 +105,38 @@ pub fn creates_owner(aggregate: &Json, command: &Json, _value_objects_by_name: &
57
105
  }
58
106
  }
59
107
 
60
- required_fields.iter().any(|field| known_writes.contains(field))
108
+ required_fields
109
+ .iter()
110
+ .any(|field| known_writes.contains(field))
61
111
  }
62
112
 
63
113
  /// Whether a list-typed attribute reads `nil` rather than `[]` in Ruby; see
64
114
  /// `mutations.rb#list_attr_creation_optional?`.
65
- pub fn list_attr_creation_optional(aggregate: &Json, attr_name: &str, value_objects_by_name: &HashMap<String, &Json>) -> bool {
66
- let commands = aggregate.get("commands").map(Json::each).unwrap_or(&[]);
115
+ pub fn list_attr_creation_optional(aggregate: &Json, attr_name: &str) -> bool {
116
+ let commands = aggregate.get("commands").map_or_default(Json::each);
67
117
  commands.iter().any(|command| {
68
- if !creates_owner(aggregate, command, value_objects_by_name) {
118
+ if !creates_owner(aggregate, command) {
69
119
  return false;
70
120
  }
71
- let mutations = command.get("mutations").map(Json::each).unwrap_or(&[]);
121
+ let mutations = command.get("mutations").map_or_default(Json::each);
72
122
  mutations.iter().any(|m| {
73
123
  let op = m.get("op").map(Json::to_s).unwrap_or_default();
74
124
  let target = m.get("target").map(Json::to_s).unwrap_or_default();
75
125
  if op != "set" || target != attr_name {
76
126
  return false;
77
127
  }
78
- let source = match m.get("source") {
79
- Some(s) => s,
80
- None => return false,
128
+ let Some(source) = m.get("source") else {
129
+ return false;
81
130
  };
82
131
  if source.get("kind").map(Json::to_s).unwrap_or_default() != "argument" {
83
132
  return false;
84
133
  }
85
134
  let source_name = source.get("name").map(Json::to_s).unwrap_or_default();
86
- let attrs = command.get("attributes").map(Json::each).unwrap_or(&[]);
87
- match attrs.iter().find(|a| crate::attr::name(a) == source_name) {
88
- Some(source_attr) => crate::attr::optional(source_attr),
89
- None => false,
90
- }
135
+ let attrs = command.get("attributes").map_or_default(Json::each);
136
+ attrs
137
+ .iter()
138
+ .find(|a| crate::attr::name(a) == source_name)
139
+ .is_some_and(crate::attr::optional)
91
140
  })
92
141
  })
93
142
  }