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
@@ -123,6 +123,8 @@ pub fn entity_at_path_mut<'a>(
123
123
  entities: &'a mut [ir::Entity],
124
124
  path: &[usize],
125
125
  ) -> &'a mut ir::Entity {
126
+ // reason: documented under `# Panics`; paths are built internally, never from input.
127
+ #[allow(clippy::expect_used)]
126
128
  let (first, rest) = path
127
129
  .split_first()
128
130
  .expect("entity_path must have at least one segment");
@@ -140,6 +142,7 @@ pub fn entity_at_path_mut<'a>(
140
142
  /// type-form `identified_by` resolves against it. `entity_named_givens` is one pool shared by
141
143
  /// every piece under the root aggregate; a sibling's command reads it back through
142
144
  /// `command::try_reference_named_given`.
145
+ #[allow(clippy::too_many_arguments)] // established signature
143
146
  pub fn parse_body(
144
147
  file: &str,
145
148
  lines: &[SourceLine],
@@ -175,12 +178,9 @@ pub fn parse_body(
175
178
  loop {
176
179
  // Peeked before `next_line`: the grammar row still requires a block for `given`, so a
177
180
  // bare reference must be consumed here.
178
- if let Some(outcome) = try_reference_named_chapter_entity_given(
179
- file,
180
- lines,
181
- pos,
182
- chapter_entity_named_givens,
183
- )? {
181
+ if let Some(outcome) =
182
+ try_reference_named_chapter_entity_given(file, lines, pos, chapter_entity_named_givens)?
183
+ {
184
184
  match outcome {
185
185
  ChapterEntityGivenLookup::Resolved(given) => {
186
186
  entity.preconditions.push(given.clone());
@@ -230,7 +230,7 @@ pub fn parse_body(
230
230
  "description",
231
231
  &gated.args,
232
232
  1,
233
- )?)
233
+ )?);
234
234
  }
235
235
  "attribute" => entity
236
236
  .attributes
@@ -359,9 +359,13 @@ pub fn parse_body(
359
359
  }
360
360
  // Chapter-wide pool, keyed by "Aggregate.Entity" plus description.
361
361
  let owner = format!("{aggregate_name}.{name}");
362
- if !chapter_entity_named_givens.iter().any(|(candidate_owner, g)| {
363
- candidate_owner == &owner && g.description.as_deref() == Some(description.as_str())
364
- }) {
362
+ if !chapter_entity_named_givens
363
+ .iter()
364
+ .any(|(candidate_owner, g)| {
365
+ candidate_owner == &owner
366
+ && g.description.as_deref() == Some(description.as_str())
367
+ })
368
+ {
365
369
  chapter_entity_named_givens.push((owner, built));
366
370
  }
367
371
  }
@@ -517,7 +521,13 @@ pub fn parse_body(
517
521
  }
518
522
  }
519
523
 
520
- lifecycle::seal_commands(file, *pos, &entity.name, entity.lifecycle.as_ref(), &entity.commands)?;
524
+ lifecycle::seal_commands(
525
+ file,
526
+ *pos,
527
+ &entity.name,
528
+ entity.lifecycle.as_ref(),
529
+ &entity.commands,
530
+ )?;
521
531
 
522
532
  Ok((
523
533
  entity,
@@ -32,7 +32,7 @@ pub fn apply(
32
32
  ) -> ParseResult<()> {
33
33
  loop {
34
34
  let line = *lines.get(*pos).ok_or_else(|| {
35
- let last = lines.last().map(|l| l.number).unwrap_or(0);
35
+ let last = lines.last().map_or(0, |l| l.number);
36
36
  Diagnostic::new(
37
37
  file,
38
38
  last,
@@ -115,8 +115,13 @@ pub fn apply(
115
115
  // fact collected for `resolve`, absent from `ir.json`. Only `from: :vendor`
116
116
  // is admitted as a source, and anything else is refused.
117
117
  "attaches" => {
118
- let name =
119
- super::positional_text(file, gated.line.number, "attaches", &gated.args, 1)?;
118
+ let name = super::positional_text(
119
+ file,
120
+ gated.line.number,
121
+ "attaches",
122
+ &gated.args,
123
+ 1,
124
+ )?;
120
125
  match super::named_symbol(&gated.args, "from").as_deref() {
121
126
  None => gem_chapter_names.push(name),
122
127
  Some("vendor") => vendored_bluebook_names.push(name),
@@ -149,6 +154,7 @@ pub fn apply(
149
154
 
150
155
  /// `<Domain>::<Aggregate>.<verb>`: `port` attaches a port to the named aggregate; any other
151
156
  /// verb is a generic adapter-bind, shape-matched and dropped.
157
+ #[allow(clippy::too_many_arguments)] // established signature
152
158
  fn apply_aggregate_qualified(
153
159
  file: &str,
154
160
  lines: &[SourceLine],
@@ -216,7 +222,7 @@ fn skip_dropped_body(file: &str, lines: &[SourceLine], pos: &mut usize) -> Parse
216
222
  let mut depth = 1;
217
223
  while depth > 0 {
218
224
  let line = *lines.get(*pos).ok_or_else(|| {
219
- let last = lines.last().map(|l| l.number).unwrap_or(0);
225
+ let last = lines.last().map_or(0, |l| l.number);
220
226
  Diagnostic::new(
221
227
  file,
222
228
  last,
@@ -41,10 +41,10 @@ pub fn parse_body(
41
41
  let (command_raw, to_state_raw) = super::split_top_level_rocket(pair_text);
42
42
  let command = text_value(command_raw);
43
43
  let to_state = text_value(to_state_raw);
44
- let from_states = match super::named_raw(&gated.args, "from") {
45
- None => vec![None],
46
- Some(raw) => from_values(raw).into_iter().map(Some).collect(),
47
- };
44
+ let from_states = super::named_raw(&gated.args, "from").map_or_else(
45
+ || vec![None],
46
+ |raw| from_values(raw).into_iter().map(Some).collect(),
47
+ );
48
48
 
49
49
  for from_state in from_states {
50
50
  lifecycle.transitions.push(ir::StateTransitionRow {
@@ -117,7 +117,11 @@ fn is_lowercase_word(name: &str) -> bool {
117
117
 
118
118
  // Refuses a mark naming a state that is neither the default nor a transition target. Wording
119
119
  // matches Ruby's.
120
- fn refuse_unknown_mark_states(file: &str, line: usize, lifecycle: &ir::Lifecycle) -> ParseResult<()> {
120
+ fn refuse_unknown_mark_states(
121
+ file: &str,
122
+ line: usize,
123
+ lifecycle: &ir::Lifecycle,
124
+ ) -> ParseResult<()> {
121
125
  let mut known: Vec<&str> = vec![lifecycle.default.as_str()];
122
126
  for row in &lifecycle.transitions {
123
127
  if !known.contains(&row.to_state.as_str()) {
@@ -1,6 +1,7 @@
1
1
  //! Word, body, and argument gates, plus the recursive body walker.
2
2
  //! Per-construct modules build IR; a word they do not build fails with `not_yet_implemented`.
3
3
 
4
+ pub mod adapter;
4
5
  pub mod aggregate;
5
6
  pub mod chapter;
6
7
  pub mod command;
@@ -15,6 +16,7 @@ pub mod process_manager;
15
16
  pub mod query;
16
17
  pub mod read_model;
17
18
  pub mod value_object;
19
+ pub mod world;
18
20
 
19
21
  use crate::diag::{Diagnostic, ParseResult};
20
22
  use crate::ir;
@@ -176,11 +178,7 @@ pub(crate) fn classify_lexical_kind(token: &str) -> &'static str {
176
178
  if t.starts_with('{') && t.ends_with('}') {
177
179
  return "pairs";
178
180
  }
179
- if t.chars()
180
- .next()
181
- .map(|c| c.is_ascii_uppercase())
182
- .unwrap_or(false)
183
- {
181
+ if t.chars().next().is_some_and(|c| c.is_ascii_uppercase()) {
184
182
  return "constant";
185
183
  }
186
184
  if type_context_call_word(t).is_some() {
@@ -564,12 +562,7 @@ pub(crate) fn split_top_level_rocket(segment: &str) -> (&str, &str) {
564
562
 
565
563
  /// The field name of a pair key, a bare or quoted Symbol literal (`:status`, `:"a.b"`).
566
564
  /// Any other key is refused; pair keys are always symbols.
567
- fn rocket_key_name<'a>(
568
- file: &str,
569
- word: &str,
570
- key_text: &'a str,
571
- line: usize,
572
- ) -> ParseResult<String> {
565
+ fn rocket_key_name(file: &str, word: &str, key_text: &str, line: usize) -> ParseResult<String> {
573
566
  let Some(rest) = key_text.strip_prefix(':') else {
574
567
  return Err(Diagnostic::new(file, line, format!("'{word}'s key '{key_text}' is not a symbol — a pair's key is always :name or :\"a.dotted.path\"")));
575
568
  };
@@ -677,7 +670,7 @@ pub(crate) fn next_line<'src>(
677
670
  context: &'static str,
678
671
  ) -> ParseResult<Option<GatedLine<'src>>> {
679
672
  let line = *lines.get(*pos).ok_or_else(|| {
680
- let last = lines.last().map(|l| l.number).unwrap_or(0);
673
+ let last = lines.last().map_or(0, |l| l.number);
681
674
  Diagnostic::new(
682
675
  file,
683
676
  last,
@@ -807,7 +800,7 @@ pub(crate) fn positional_constant<'a>(
807
800
  args: &'a ArgumentGateResult,
808
801
  at: usize,
809
802
  ) -> ParseResult<&'a str> {
810
- positional_raw(file, line, word, args, at).map(|s| s.trim())
803
+ positional_raw(file, line, word, args, at).map(str::trim)
811
804
  }
812
805
 
813
806
  /// A command reference: bare constant (`trigger Account::Debit`) or quoted (`"Account.Debit"`).
@@ -847,7 +840,7 @@ pub(crate) fn named_raw<'a>(args: &'a ArgumentGateResult, name: &str) -> Option<
847
840
 
848
841
  /// A named constant argument (`to: Payment`): raw text, trimmed; resolved downstream.
849
842
  pub(crate) fn named_constant<'a>(args: &'a ArgumentGateResult, name: &str) -> Option<&'a str> {
850
- named_raw(args, name).map(|s| s.trim())
843
+ named_raw(args, name).map(str::trim)
851
844
  }
852
845
 
853
846
  /// A named symbol argument without its colon (`as: :name`); bare or quoted.
@@ -865,9 +858,7 @@ pub(crate) fn named_text(args: &ArgumentGateResult, name: &str) -> Option<String
865
858
 
866
859
  /// A named flag (`true`/`false`) argument — `optional: true`.
867
860
  pub(crate) fn named_flag(args: &ArgumentGateResult, name: &str) -> bool {
868
- named_raw(args, name)
869
- .map(|raw| raw.trim() == "true")
870
- .unwrap_or(false)
861
+ named_raw(args, name).is_some_and(|raw| raw.trim() == "true")
871
862
  }
872
863
 
873
864
  /// Raw text of a `source`-shaped body written as `{ ... }` or `do ... end`.
@@ -1036,6 +1027,7 @@ pub(crate) enum PendingIdentity {
1036
1027
  /// Parses `identified_by` in its three forms: a value object type (`PizzaName, as: :name`), one or
1037
1028
  /// more field symbols (`:a, :b`), or a `{ ... }`/`do ... end` block declaring an inline value
1038
1029
  /// object named `inline_type_name`.
1030
+ #[allow(clippy::too_many_arguments)] // established signature
1039
1031
  pub(crate) fn parse_identified_by(
1040
1032
  file: &str,
1041
1033
  lines: &[SourceLine],
@@ -1072,9 +1064,10 @@ pub(crate) fn parse_identified_by(
1072
1064
  }
1073
1065
  // An identity block's own `one_of(...)` sets are not installed (its builder is not
1074
1066
  // the aggregate's `value_object` verb).
1075
- let (value_object, _) = parse_nested_body(file, lines, pos, opener, line, |f, l, p| {
1076
- value_object::parse_body(f, l, p, inline_type_name, owner_value_objects)
1077
- })?;
1067
+ let (value_object, _) =
1068
+ parse_nested_body(file, lines, pos, opener, line, |f, l, p| {
1069
+ value_object::parse_body(f, l, p, inline_type_name, owner_value_objects)
1070
+ })?;
1078
1071
  if value_object.attributes.is_empty() {
1079
1072
  return Err(Diagnostic::new(
1080
1073
  file,
@@ -1138,7 +1131,11 @@ pub(crate) fn build_attribute(
1138
1131
  let pattern = match named_symbol(args, "matches") {
1139
1132
  Some(given) => {
1140
1133
  if named_text(args, "pattern").is_some() {
1141
- return Err(Diagnostic::new(file, line, "matches: and pattern: both state the pattern".to_string()));
1134
+ return Err(Diagnostic::new(
1135
+ file,
1136
+ line,
1137
+ "matches: and pattern: both state the pattern".to_string(),
1138
+ ));
1142
1139
  }
1143
1140
  match crate::build::named_pattern::source(&given) {
1144
1141
  Some(source) => Some(source.to_string()),
@@ -1199,6 +1196,8 @@ pub(crate) fn resolve_type_expression(
1199
1196
  let trimmed = text.trim();
1200
1197
  match type_context_call_word(trimmed) {
1201
1198
  Some("list_of") => {
1199
+ // reason: `type_context_call_word` already confirmed a '(' in `trimmed`.
1200
+ #[allow(clippy::expect_used)]
1202
1201
  let open = trimmed
1203
1202
  .find('(')
1204
1203
  .expect("type_context_call_word already confirmed a '('");
@@ -1213,6 +1212,8 @@ pub(crate) fn resolve_type_expression(
1213
1212
  Ok((inner.to_string(), true, None))
1214
1213
  }
1215
1214
  Some("one_of") => {
1215
+ // reason: `type_context_call_word` already confirmed a '(' in `trimmed`.
1216
+ #[allow(clippy::expect_used)]
1216
1217
  let open = trimmed
1217
1218
  .find('(')
1218
1219
  .expect("type_context_call_word already confirmed a '('");
@@ -1251,73 +1252,3 @@ pub(crate) fn resolve_type_expression(
1251
1252
  None => Ok((trimmed.to_string(), false, None)),
1252
1253
  }
1253
1254
  }
1254
-
1255
- /// Walks lines inside an open `context` up to and including the matching `end`, gating each one.
1256
- /// The first gate failure or not-yet-implemented construct, at any depth, aborts the walk.
1257
- pub fn walk_body(
1258
- file: &str,
1259
- lines: &[SourceLine],
1260
- pos: &mut usize,
1261
- context: &'static str,
1262
- ) -> ParseResult<()> {
1263
- loop {
1264
- let line = *lines.get(*pos).ok_or_else(|| {
1265
- let last = lines.last().map(|l| l.number).unwrap_or(0);
1266
- Diagnostic::new(
1267
- file,
1268
- last,
1269
- format!("unexpected end of file — still inside {context}"),
1270
- )
1271
- })?;
1272
-
1273
- let shape = lex::classify(file, &line)?;
1274
- *pos += 1;
1275
-
1276
- match shape {
1277
- LineShape::End => return Ok(()),
1278
- LineShape::Call(call) => handle_call(file, lines, pos, context, &line, call)?,
1279
- }
1280
- }
1281
- }
1282
-
1283
- fn handle_call(
1284
- file: &str,
1285
- lines: &[SourceLine],
1286
- pos: &mut usize,
1287
- context: &'static str,
1288
- line: &SourceLine,
1289
- call: Call,
1290
- ) -> ParseResult<()> {
1291
- let candidates = word_gate(file, &call.word, context, line.number)?;
1292
- let row = body_gate(file, &candidates, &call.opener, line.number, &call.word)?;
1293
- argument_gate(file, &call.word, context, &call.args, line.number)?;
1294
-
1295
- match &call.opener {
1296
- Opener::DoBlock { .. } => {
1297
- if row.inner.is_empty() {
1298
- return Err(dispatch_stub(context, file, line.number, &call.word));
1299
- }
1300
- let inner_context = keywords::CONTEXTS
1301
- .iter()
1302
- .find(|c| **c == row.inner)
1303
- .copied()
1304
- .ok_or_else(|| {
1305
- Diagnostic::new(
1306
- file,
1307
- line.number,
1308
- format!(
1309
- "'{}' opens an undeclared context '{}'",
1310
- call.word, row.inner
1311
- ),
1312
- )
1313
- })?;
1314
- walk_body(file, lines, pos, inner_context)?;
1315
- // The nested body gated cleanly but builds nothing yet, so the failure is reported at
1316
- // the construct just entered rather than the outer `context`.
1317
- Err(dispatch_stub(inner_context, file, line.number, &call.word))
1318
- }
1319
- Opener::BraceBlock { .. } | Opener::None => {
1320
- Err(dispatch_stub(context, file, line.number, &call.word))
1321
- }
1322
- }
1323
- }
@@ -22,7 +22,10 @@ pub fn declare(
22
22
  ) -> ParseResult<()> {
23
23
  let fact = super::positional_symbol(file, line, "needs", args, 1)?;
24
24
  if !NEEDABLE_FACTS.contains(&fact.as_str()) {
25
- let supplied: Vec<String> = NEEDABLE_FACTS.iter().map(|known| format!(":{known}")).collect();
25
+ let supplied: Vec<String> = NEEDABLE_FACTS
26
+ .iter()
27
+ .map(|known| format!(":{known}"))
28
+ .collect();
26
29
  return Err(Diagnostic::new(
27
30
  file,
28
31
  line,
@@ -33,7 +36,11 @@ pub fn declare(
33
36
  ));
34
37
  }
35
38
  if needs.contains(&fact) {
36
- return Err(Diagnostic::new(file, line, format!("{owner} declares needs :{fact} twice")));
39
+ return Err(Diagnostic::new(
40
+ file,
41
+ line,
42
+ format!("{owner} declares needs :{fact} twice"),
43
+ ));
37
44
  }
38
45
  needs.push(fact);
39
46
  Ok(())
@@ -47,14 +54,16 @@ pub fn refuse_undeclared(
47
54
  needs: &[String],
48
55
  attributes: &[ir::Attribute],
49
56
  ) -> ParseResult<()> {
50
- match needs.iter().find(|fact| !attributes.iter().any(|a| &a.name == *fact)) {
51
- Some(fact) => Err(Diagnostic::new(
52
- file,
53
- line,
54
- format!(
55
- "{owner} needs :{fact} but declares no attribute :{fact} for the runtime to fill — add `attribute :{fact}, <type>`"
56
- ),
57
- )),
58
- None => Ok(()),
59
- }
57
+ needs
58
+ .iter()
59
+ .find(|fact| !attributes.iter().any(|a| &a.name == *fact))
60
+ .map_or(Ok(()), |fact| {
61
+ Err(Diagnostic::new(
62
+ file,
63
+ line,
64
+ format!(
65
+ "{owner} needs :{fact} but declares no attribute :{fact} for the runtime to fill — add `attribute :{fact}, <type>`"
66
+ ),
67
+ ))
68
+ })
60
69
  }
@@ -36,7 +36,7 @@ pub fn parse_body(
36
36
  "on",
37
37
  &gated.args,
38
38
  1,
39
- )?)
39
+ )?);
40
40
  }
41
41
  "trigger" => {
42
42
  policy.trigger_command = Some(super::positional_command_ref(
@@ -82,7 +82,7 @@ pub fn parse_body(
82
82
  "for_each",
83
83
  &gated.args,
84
84
  1,
85
- )?)
85
+ )?);
86
86
  }
87
87
  _ => {
88
88
  return Err(super::not_built_yet(
@@ -34,7 +34,7 @@ pub fn parse_body(
34
34
  // A dotted symbol (`:"reference.value"`), which only `positional_symbol` can spell.
35
35
  "correlates_by" => {
36
36
  pm.correlates_by =
37
- super::positional_symbol(file, line, "correlates_by", &gated.args, 1)?
37
+ super::positional_symbol(file, line, "correlates_by", &gated.args, 1)?;
38
38
  }
39
39
  // Event references keep only the bare final segment (`Naming.event_name_ref`);
40
40
  // `SagaInterpreter` matches them against a bare `event.name`.
@@ -45,7 +45,7 @@ pub fn parse_body(
45
45
  "starts_on",
46
46
  &gated.args,
47
47
  1,
48
- )?)
48
+ )?);
49
49
  }
50
50
  "ends_on" => {
51
51
  pm.ends_on = Some(super::positional_event_name_ref(
@@ -54,17 +54,15 @@ pub fn parse_body(
54
54
  "ends_on",
55
55
  &gated.args,
56
56
  1,
57
- )?)
57
+ )?);
58
58
  }
59
59
  "transition" => {
60
60
  for handler in parse_transition(file, lines, pos, line, &gated)? {
61
61
  // Two legs on one (event, state) pair would be picked by declaration order;
62
62
  // mirrors `ProcessManagerBuilder#refuse_ambiguous_legs!`.
63
- if let Some(earlier) = pm
64
- .handlers
65
- .iter()
66
- .find(|h| h.event_type == handler.event_type && h.from_state == handler.from_state)
67
- {
63
+ if let Some(earlier) = pm.handlers.iter().find(|h| {
64
+ h.event_type == handler.event_type && h.from_state == handler.from_state
65
+ }) {
68
66
  return Err(Diagnostic::new(
69
67
  file,
70
68
  line,
@@ -208,9 +206,9 @@ fn parse_dispatches(
208
206
  Opener::DoBlock { .. } => {
209
207
  parse_compensates_block(file, lines, pos)?.map(Box::new)
210
208
  }
211
- Opener::BraceBlock { .. } => unreachable!(
212
- "body_gate never admits a BraceBlock for 'dispatch'/Handler"
213
- ),
209
+ Opener::BraceBlock { .. } => {
210
+ unreachable!("body_gate never admits a BraceBlock for 'dispatch'/Handler")
211
+ }
214
212
  };
215
213
  dispatches.push(ir::DispatchSpec {
216
214
  command_name,
@@ -39,7 +39,7 @@ pub fn parse_body(
39
39
  "description",
40
40
  &gated.args,
41
41
  1,
42
- )?)
42
+ )?);
43
43
  }
44
44
  "attribute" => query
45
45
  .attributes
@@ -48,7 +48,7 @@ pub fn parse_body(
48
48
  query_derive::refuse_on_target(file, line, "where", &gated.args.named)?;
49
49
  query
50
50
  .wheres
51
- .extend(query_derive::where_clauses(&gated.args.named, None))
51
+ .extend(query_derive::where_clauses(&gated.args.named, None));
52
52
  }
53
53
  "order_by" => {
54
54
  // The gate already refuses an undeclared `on:`; only `where` needs a check.
@@ -57,7 +57,11 @@ pub fn parse_body(
57
57
  Some((_, text)) => text.trim().trim_start_matches(':').to_string(),
58
58
  None => "asc".to_string(),
59
59
  };
60
- query.order_by = Some(ir::OrderBy { field, direction, target: None });
60
+ query.order_by = Some(ir::OrderBy {
61
+ field,
62
+ direction,
63
+ target: None,
64
+ });
61
65
  }
62
66
  // `positional_constant` only fetches raw text; the gate already checked it is numeric.
63
67
  "limit" => {
@@ -87,7 +91,7 @@ pub fn parse_body(
87
91
  }
88
92
  "needs" => super::needs::declare(file, line, name, &gated.args, &mut query.needs)?,
89
93
  word if OPTION_WORDS.contains(&word) => {
90
- query_options::apply(file, line, word, &gated.args, &mut query.options)?
94
+ query_options::apply(file, line, word, &gated.args, &mut query.options)?;
91
95
  }
92
96
  _ => {
93
97
  return Err(super::not_built_yet(
@@ -41,10 +41,7 @@ pub fn parse_body(
41
41
  let mut options = ir::QueryOptions::default();
42
42
  let mut last_line = 0usize;
43
43
 
44
- loop {
45
- let Some(gated) = super::next_line(file, lines, pos, "ReadModel")? else {
46
- break;
47
- };
44
+ while let Some(gated) = super::next_line(file, lines, pos, "ReadModel")? {
48
45
  last_line = gated.line.number;
49
46
 
50
47
  match gated.row.word {
@@ -55,7 +52,7 @@ pub fn parse_body(
55
52
  "description",
56
53
  &gated.args,
57
54
  1,
58
- )?)
55
+ )?);
59
56
  }
60
57
  // Ruby refuses a second `reference_to`; that seal is out of scope for this parser.
61
58
  "reference_to" => {
@@ -93,19 +90,43 @@ pub fn parse_body(
93
90
  "median",
94
91
  &gated.args,
95
92
  1,
96
- )?)
93
+ )?);
97
94
  }
98
95
  "sum" => {
99
- sum_field = Some(super::positional_symbol(file, last_line, "sum", &gated.args, 1)?)
96
+ sum_field = Some(super::positional_symbol(
97
+ file,
98
+ last_line,
99
+ "sum",
100
+ &gated.args,
101
+ 1,
102
+ )?);
100
103
  }
101
104
  "avg" => {
102
- avg_field = Some(super::positional_symbol(file, last_line, "avg", &gated.args, 1)?)
105
+ avg_field = Some(super::positional_symbol(
106
+ file,
107
+ last_line,
108
+ "avg",
109
+ &gated.args,
110
+ 1,
111
+ )?);
103
112
  }
104
113
  "min" => {
105
- min_field = Some(super::positional_symbol(file, last_line, "min", &gated.args, 1)?)
114
+ min_field = Some(super::positional_symbol(
115
+ file,
116
+ last_line,
117
+ "min",
118
+ &gated.args,
119
+ 1,
120
+ )?);
106
121
  }
107
122
  "max" => {
108
- max_field = Some(super::positional_symbol(file, last_line, "max", &gated.args, 1)?)
123
+ max_field = Some(super::positional_symbol(
124
+ file,
125
+ last_line,
126
+ "max",
127
+ &gated.args,
128
+ 1,
129
+ )?);
109
130
  }
110
131
  "percentile" => {
111
132
  percentile_field = Some(super::positional_symbol(
@@ -116,14 +137,30 @@ pub fn parse_body(
116
137
  1,
117
138
  )?);
118
139
  percentile_at = Some(super::named_text(&gated.args, "at").ok_or_else(|| {
119
- Diagnostic::new(file, last_line, "'percentile' needs an at: argument".to_string())
140
+ Diagnostic::new(
141
+ file,
142
+ last_line,
143
+ "'percentile' needs an at: argument".to_string(),
144
+ )
120
145
  })?);
121
146
  }
122
147
  "any" => {
123
- any_field = Some(super::positional_symbol(file, last_line, "any", &gated.args, 1)?)
148
+ any_field = Some(super::positional_symbol(
149
+ file,
150
+ last_line,
151
+ "any",
152
+ &gated.args,
153
+ 1,
154
+ )?);
124
155
  }
125
156
  "all" => {
126
- all_field = Some(super::positional_symbol(file, last_line, "all", &gated.args, 1)?)
157
+ all_field = Some(super::positional_symbol(
158
+ file,
159
+ last_line,
160
+ "all",
161
+ &gated.args,
162
+ 1,
163
+ )?);
127
164
  }
128
165
  "where" => {
129
166
  // ADR 0055: `on:` is a recognized named argument for `where` in ReadModel
@@ -138,7 +175,7 @@ pub fn parse_body(
138
175
  .filter(|(name, _)| name != "on")
139
176
  .cloned()
140
177
  .collect();
141
- wheres.extend(query_derive::where_clauses(&field_pairs, target.as_deref()))
178
+ wheres.extend(query_derive::where_clauses(&field_pairs, target.as_deref()));
142
179
  }
143
180
  "order_by" => {
144
181
  let field = super::positional_symbol(file, last_line, "order_by", &gated.args, 1)?;
@@ -147,7 +184,11 @@ pub fn parse_body(
147
184
  None => "asc".to_string(),
148
185
  };
149
186
  let target = super::named_constant(&gated.args, "on").map(naming::demodulise);
150
- order_by = Some(ir::OrderBy { field, direction, target });
187
+ order_by = Some(ir::OrderBy {
188
+ field,
189
+ direction,
190
+ target,
191
+ });
151
192
  }
152
193
  "limit" => {
153
194
  let raw = super::positional_constant(file, last_line, "limit", &gated.args, 1)?;
@@ -158,7 +199,7 @@ pub fn parse_body(
158
199
  });
159
200
  }
160
201
  word if OPTION_WORDS.contains(&word) => {
161
- query_options::apply(file, last_line, word, &gated.args, &mut options)?
202
+ query_options::apply(file, last_line, word, &gated.args, &mut options)?;
162
203
  }
163
204
  _ => {
164
205
  return Err(super::not_built_yet(
@@ -98,7 +98,8 @@ pub fn parse_body(
98
98
  // An inline type-position `one_of(...)` set is returned beside the value object (see
99
99
  // build/closed_sets.rs); `ValueObject` has no nested-value-objects field.
100
100
  "attribute" => {
101
- let (attribute, one_of, closed_set) = build_value_object_attribute(file, &gated.args)?;
101
+ let (attribute, one_of, closed_set) =
102
+ build_value_object_attribute(file, &gated.args)?;
102
103
  synthesized.extend(closed_set);
103
104
  if let Some(values) = one_of {
104
105
  let vo_name = vo.name.clone();
@@ -140,7 +141,11 @@ pub fn parse_body(
140
141
  fn build_value_object_attribute(
141
142
  file: &str,
142
143
  args: &super::ArgumentGateResult,
143
- ) -> ParseResult<(ir::Attribute, Option<Vec<ruby_value::Value>>, Option<ir::ValueObject>)> {
144
+ ) -> ParseResult<(
145
+ ir::Attribute,
146
+ Option<Vec<ruby_value::Value>>,
147
+ Option<ir::ValueObject>,
148
+ )> {
144
149
  let (attribute, closed_set) = super::build_attribute(file, 0, "attribute", args)?;
145
150
  let one_of = super::named_raw(args, "one_of").map(|raw| {
146
151
  let trimmed = raw.trim();