hecks 3.14.0 → 4.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 (419) 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/lib/hecks/adapters/driven/prism.rb +0 -2
  9. data/lib/hecks/adapters/driven.rb +0 -2
  10. data/lib/hecks/adapters/driving/json.rb +1 -7
  11. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
  12. data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
  13. data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
  14. data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
  15. data/lib/hecks/bluebook/hecksagon.rb +1 -1
  16. data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
  17. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
  18. data/lib/hecks/bluebook/meta_validator.rb +0 -11
  19. data/lib/hecks/bluebook/translation.rb +18 -10
  20. data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
  21. data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
  22. data/lib/hecks/bluebook/translation_expression.rb +39 -0
  23. data/lib/hecks/bluebook/translation_plan.rb +70 -0
  24. data/lib/hecks/bluebook.rb +2 -0
  25. data/lib/hecks/chapters.rb +3 -3
  26. data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
  27. data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
  28. data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
  29. data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
  30. data/lib/hecks/composer/adapters/jev.adapter +3 -0
  31. data/lib/hecks/composer/adapters/jev.rb +66 -0
  32. data/lib/hecks/composer/agent.rb +113 -0
  33. data/lib/hecks/composer/assistant.rb +110 -0
  34. data/lib/hecks/composer/behavior_run.rb +53 -0
  35. data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
  36. data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
  37. data/lib/hecks/composer/board.rb +65 -0
  38. data/lib/hecks/composer/board_edit/command_text.rb +39 -0
  39. data/lib/hecks/composer/board_edit/source.rb +84 -0
  40. data/lib/hecks/composer/board_edit.rb +144 -0
  41. data/lib/hecks/composer/check.rb +89 -0
  42. data/lib/hecks/composer/domains.rb +81 -0
  43. data/lib/hecks/composer/jobs.rb +134 -0
  44. data/lib/hecks/composer/layout.rb +37 -0
  45. data/lib/hecks/composer/live.rb +150 -0
  46. data/lib/hecks/composer/outline.rb +48 -0
  47. data/lib/hecks/composer/page.html +989 -0
  48. data/lib/hecks/composer/panels.rb +142 -0
  49. data/lib/hecks/composer/parse.rb +83 -0
  50. data/lib/hecks/composer/plain.rb +28 -0
  51. data/lib/hecks/composer/ports/composer_agent.port +7 -0
  52. data/lib/hecks/composer/session.rb +141 -0
  53. data/lib/hecks/composer/toolbox.rb +166 -0
  54. data/lib/hecks/composer/web.rb +156 -0
  55. data/lib/hecks/composer/workbench.rb +51 -0
  56. data/lib/hecks/composer/workspace.rb +81 -0
  57. data/lib/hecks/composer.rb +17 -0
  58. data/lib/hecks/corpus/adapters.rb +22 -0
  59. data/lib/hecks/corpus/rust.rb +1 -1
  60. data/lib/hecks/corpus/worlds.rb +19 -0
  61. data/lib/hecks/corpus.rb +5 -0
  62. data/lib/hecks/forms/app/commands.rb +2 -3
  63. data/lib/hecks/forms/app/queries.rb +2 -3
  64. data/lib/hecks/forms/app/records.rb +17 -5
  65. data/lib/hecks/forms/command_form_renderer.rb +42 -16
  66. data/lib/hecks/forms/field_controls.rb +2 -2
  67. data/lib/hecks/forms/field_renderer.rb +1 -1
  68. data/lib/hecks/forms/field_shape.rb +36 -17
  69. data/lib/hecks/forms/index_renderer.rb +2 -1
  70. data/lib/hecks/forms/page.rb +1 -1
  71. data/lib/hecks/forms/params.rb +0 -13
  72. data/lib/hecks/forms/query_form_renderer.rb +7 -6
  73. data/lib/hecks/forms/record_renderer.rb +11 -7
  74. data/lib/hecks/forms/rule_words.rb +100 -0
  75. data/lib/hecks/forms/value_object_shape.rb +2 -1
  76. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
  77. data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
  78. data/lib/hecks/fuzzing/replay/filters.rb +2 -6
  79. data/lib/hecks/gate/stages.yml +25 -0
  80. data/lib/hecks/grammar/evolve/seeds.rb +0 -6
  81. data/lib/hecks/grammar/evolve.rb +0 -3
  82. data/lib/hecks/grammar/translation.bluebook +1 -1
  83. data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
  84. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
  85. data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
  86. data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
  87. data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
  88. data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
  89. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
  90. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
  91. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
  92. data/lib/hecks/hecks/codebase.behaviors +6 -0
  93. data/lib/hecks/hecks/codebase.bluebook +21 -0
  94. data/lib/hecks/hecks/composer.behaviors +68 -0
  95. data/lib/hecks/hecks/context_map.hecksagon +1 -1
  96. data/lib/hecks/hecks/custodian.behaviors +0 -6
  97. data/lib/hecks/hecks/custodian.bluebook +3 -80
  98. data/lib/hecks/hecks/hecks.hecksagon +2 -2
  99. data/lib/hecks/hecks/hecks.world +1 -1
  100. data/lib/hecks/hecks/rehearsal.bluebook +7 -13
  101. data/lib/hecks/kernel_capabilities/templates.rb +4 -4
  102. data/lib/hecks/kernel_capabilities.rb +1 -1
  103. data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
  104. data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
  105. data/lib/hecks/ports/access_control.rb +2 -2
  106. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
  107. data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
  108. data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
  109. data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
  110. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
  111. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
  112. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
  113. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
  114. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
  115. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
  116. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
  117. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
  118. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
  119. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
  120. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
  121. data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
  122. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
  123. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
  124. data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
  125. data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
  126. data/lib/hecks/ports.rb +0 -1
  127. data/lib/hecks/projection_files.rb +3 -1
  128. data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
  129. data/lib/hecks/projections/glossary/entries.rb +10 -4
  130. data/lib/hecks/projections/glossary/groups.rb +42 -0
  131. data/lib/hecks/projections/glossary/html/page.rb +7 -2
  132. data/lib/hecks/projections/glossary/html/parser.rb +1 -1
  133. data/lib/hecks/projections/glossary/html/sections.rb +31 -11
  134. data/lib/hecks/projections/glossary/html.rb +1 -2
  135. data/lib/hecks/projections/glossary/markdown.rb +31 -28
  136. data/lib/hecks/projections/glossary/mermaid.rb +3 -0
  137. data/lib/hecks/projections/glossary/page.css +143 -153
  138. data/lib/hecks/projections/glossary/page.js +63 -3
  139. data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
  140. data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
  141. data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
  142. data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
  143. data/lib/hecks/projections/glossary/sentences.rb +13 -3
  144. data/lib/hecks/projections/glossary/slugs.rb +4 -1
  145. data/lib/hecks/projections/glossary.rb +2 -1
  146. data/lib/hecks/projections/plain_english.rb +97 -0
  147. data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
  148. data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
  149. data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
  150. data/lib/hecks/projections/rust_vocabulary.rb +14 -6
  151. data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
  152. data/lib/hecks/projections/statements/actions.rb +99 -0
  153. data/lib/hecks/projections/statements/relationships.rb +86 -0
  154. data/lib/hecks/projections/statements.rb +15 -24
  155. data/lib/hecks/projector/exporter/adapters.rb +112 -0
  156. data/lib/hecks/projector/exporter/translations.rb +18 -35
  157. data/lib/hecks/projector/exporter/worlds.rb +103 -0
  158. data/lib/hecks/projector/exporter.rb +5 -1
  159. data/lib/hecks/quality_control/adapters/agent.rb +6 -7
  160. data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
  161. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
  162. data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
  163. data/lib/hecks/runtime/outbox.rb +2 -9
  164. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
  165. data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
  166. data/lib/hecks/runtime/value.rb +13 -0
  167. data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
  168. data/lib/hecks/rust_build/wasm.rb +0 -12
  169. data/lib/hecks/rust_build/write_if_changed.rb +12 -12
  170. data/lib/hecks/rust_format.rb +77 -0
  171. data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
  172. data/lib/hecks/tools/fuzz_sweep.rb +1 -3
  173. data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
  174. data/lib/hecks/tools/translation_audit.rb +8 -10
  175. data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
  176. data/lib/hecks/version.rb +1 -1
  177. data/lib/hecks/vocabulary.rb +2 -1
  178. data/lib/hecks.rb +3 -0
  179. data/prebuilt/{a883a7e568667aa5ac904c82daff356bdf9e996252a36af6def4a4a43efb967e.marshal → 02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal} +0 -0
  180. data/prebuilt/{1a6f596be124228be93eba1d86dbc9d2933190fa389b284331241cb9f27115a0.marshal → bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal} +0 -0
  181. data/prebuilt/{verdicts-872993b1aa26d73fe48f28251374b8585fed145d02aab42be0f00de386b6ff80.json → verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json} +1 -1
  182. data/rust/Cargo.toml +40 -0
  183. data/rust/build/Cargo.toml +36 -0
  184. data/rust/build/clippy.toml +4 -0
  185. data/rust/build/src/adapters_pass.rs +196 -0
  186. data/rust/build/src/binding_pass.rs +54 -13
  187. data/rust/build/src/build_artifact.rs +23 -5
  188. data/rust/build/src/cargo_sync.rs +69 -26
  189. data/rust/build/src/lineage_pass.rs +101 -87
  190. data/rust/build/src/main.rs +11 -2
  191. data/rust/build/src/pipeline.rs +80 -19
  192. data/rust/build/src/reserved_names.rs +6 -50
  193. data/rust/build/src/resolve.rs +82 -35
  194. data/rust/build/src/rustfmt.rs +100 -0
  195. data/rust/build/src/seams_pass.rs +85 -16
  196. data/rust/build/src/subprocess.rs +30 -6
  197. data/rust/build/src/tmp.rs +5 -2
  198. data/rust/build/src/translation_pass.rs +72 -22
  199. data/rust/build/src/world_pass.rs +109 -0
  200. data/rust/clippy.toml +4 -0
  201. data/rust/codegen/Cargo.toml +36 -0
  202. data/rust/codegen/clippy.toml +4 -0
  203. data/rust/codegen/src/asks.rs +40 -14
  204. data/rust/codegen/src/attr.rs +2 -2
  205. data/rust/codegen/src/bridging.rs +676 -117
  206. data/rust/codegen/src/commands.rs +979 -373
  207. data/rust/codegen/src/constraints.rs +88 -25
  208. data/rust/codegen/src/dependency_planning.rs +128 -34
  209. data/rust/codegen/src/domain_generator.rs +300 -170
  210. data/rust/codegen/src/exemplar.rs +87 -32
  211. data/rust/codegen/src/expr_emitter.rs +54 -6
  212. data/rust/codegen/src/fielded.rs +193 -41
  213. data/rust/codegen/src/hecks_naming.rs +124 -16
  214. data/rust/codegen/src/hecks_naming_tests.rs +81 -0
  215. data/rust/codegen/src/json.rs +80 -44
  216. data/rust/codegen/src/json_codec.rs +585 -149
  217. data/rust/codegen/src/literal.rs +104 -138
  218. data/rust/codegen/src/main.rs +145 -52
  219. data/rust/codegen/src/manifest.rs +76 -14
  220. data/rust/codegen/src/mutations.rs +1212 -239
  221. data/rust/codegen/src/naming.rs +111 -60
  222. data/rust/codegen/src/optional_pass.rs +90 -28
  223. data/rust/codegen/src/ports.rs +5 -4
  224. data/rust/codegen/src/prelude.rs +139 -49
  225. data/rust/codegen/src/queries.rs +601 -226
  226. data/rust/codegen/src/reactions.rs +268 -74
  227. data/rust/codegen/src/read_models.rs +606 -205
  228. data/rust/codegen/src/reference_specs.rs +16 -3
  229. data/rust/codegen/src/registry.rs +128 -79
  230. data/rust/codegen/src/reserved_names.rs +6 -50
  231. data/rust/codegen/src/shared.rs +71 -22
  232. data/rust/codegen/src/sidecars.rs +15 -5
  233. data/rust/codegen/src/skip_reason.rs +8 -2
  234. data/rust/codegen/src/types.rs +306 -108
  235. data/rust/host/Cargo.lock +1 -0
  236. data/rust/host/Cargo.toml +40 -4
  237. data/rust/host/HECKS_RELEASE +1 -1
  238. data/rust/host/clippy.toml +4 -0
  239. data/rust/host/src/api.rs +698 -218
  240. data/rust/host/src/auth.rs +491 -214
  241. data/rust/host/src/b64.rs +91 -0
  242. data/rust/host/src/bin/expr_harness.rs +6 -2
  243. data/rust/host/src/bin/lineage_harness.rs +47 -11
  244. data/rust/host/src/bin/mint_harness.rs +70 -37
  245. data/rust/host/src/bin/validate_harness.rs +16 -8
  246. data/rust/host/src/boot.rs +144 -68
  247. data/rust/host/src/boundary_fuzz/api.rs +130 -32
  248. data/rust/host/src/boundary_fuzz/auth.rs +108 -23
  249. data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
  250. data/rust/host/src/boundary_fuzz/ir.rs +164 -12
  251. data/rust/host/src/boundary_fuzz/server.rs +283 -36
  252. data/rust/host/src/boundary_fuzz/web.rs +56 -8
  253. data/rust/host/src/checkout.rs +78 -32
  254. data/rust/host/src/clock.rs +8 -0
  255. data/rust/host/src/commerce_ir.rs +333 -95
  256. data/rust/host/src/crypto.rs +33 -0
  257. data/rust/host/src/dispatch.rs +990 -331
  258. data/rust/host/src/expr_json.rs +1021 -273
  259. data/rust/host/src/extension.rs +24 -4
  260. data/rust/host/src/field_hints.rs +11 -7
  261. data/rust/host/src/fuzz_support.rs +85 -13
  262. data/rust/host/src/host.rs +100 -0
  263. data/rust/host/src/http.rs +22 -0
  264. data/rust/host/src/ir.rs +326 -16
  265. data/rust/host/src/journal.rs +277 -176
  266. data/rust/host/src/lambda_client.rs +219 -53
  267. data/rust/host/src/lib.rs +16 -4
  268. data/rust/host/src/log.rs +62 -11
  269. data/rust/host/src/mint.rs +1153 -326
  270. data/rust/host/src/needs.rs +185 -44
  271. data/rust/host/src/payments/keystore.rs +197 -45
  272. data/rust/host/src/payments/roles.rs +39 -7
  273. data/rust/host/src/payments/tests.rs +1427 -282
  274. data/rust/host/src/payments.rs +391 -112
  275. data/rust/host/src/platform_signin/attack_tests.rs +5 -5
  276. data/rust/host/src/platform_signin/config.rs +11 -11
  277. data/rust/host/src/platform_signin/cookie.rs +2 -2
  278. data/rust/host/src/platform_signin/extension.rs +2 -1
  279. data/rust/host/src/platform_signin/handler.rs +9 -8
  280. data/rust/host/src/platform_signin/jti.rs +4 -2
  281. data/rust/host/src/platform_signin/jwks.rs +2 -1
  282. data/rust/host/src/platform_signin/jws.rs +2 -37
  283. data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
  284. data/rust/host/src/platform_signin/mode_tests.rs +5 -3
  285. data/rust/host/src/platform_signin/pages.rs +20 -20
  286. data/rust/host/src/platform_signin/session.rs +8 -1
  287. data/rust/host/src/platform_signin/settings.rs +6 -6
  288. data/rust/host/src/platform_signin/strict_json.rs +12 -9
  289. data/rust/host/src/platform_signin/verify.rs +41 -41
  290. data/rust/host/src/platform_signin/wiring_support.rs +13 -18
  291. data/rust/host/src/presentation.rs +207 -63
  292. data/rust/host/src/presentation_write.rs +502 -187
  293. data/rust/host/src/query_step.rs +69 -18
  294. data/rust/host/src/rate_limit.rs +541 -142
  295. data/rust/host/src/reference_transform.rs +77 -25
  296. data/rust/host/src/reference_validate.rs +443 -104
  297. data/rust/host/src/resend.rs +190 -55
  298. data/rust/host/src/secrets.rs +116 -21
  299. data/rust/host/src/server.rs +483 -104
  300. data/rust/host/src/storage_shape.rs +88 -21
  301. data/rust/host/src/test_pg.rs +17 -7
  302. data/rust/host/src/ui_schema.rs +495 -207
  303. data/rust/host/src/wasm_runner.rs +21 -6
  304. data/rust/host/src/web/commerce.rs +101 -23
  305. data/rust/host/src/web/newsletter/cooldown.rs +10 -3
  306. data/rust/host/src/web/newsletter/template.rs +26 -7
  307. data/rust/host/src/web/newsletter/tests.rs +112 -23
  308. data/rust/host/src/web/newsletter.rs +209 -59
  309. data/rust/host/src/web/newsletter_send.rs +251 -79
  310. data/rust/host/src/web/registration_receipt/tests.rs +16 -4
  311. data/rust/host/src/web/registration_receipt.rs +100 -24
  312. data/rust/host/src/web/registrations.rs +1384 -332
  313. data/rust/host/src/web/resend_webhook.rs +138 -80
  314. data/rust/host/src/web.rs +2416 -508
  315. data/rust/lsp/Cargo.toml +36 -0
  316. data/rust/lsp/clippy.toml +4 -0
  317. data/rust/lsp/src/json.rs +29 -22
  318. data/rust/lsp/src/main.rs +81 -31
  319. data/rust/lsp/src/outline.rs +23 -7
  320. data/rust/lsp/src/rpc.rs +17 -4
  321. data/rust/parser/Cargo.toml +36 -0
  322. data/rust/parser/clippy.toml +4 -0
  323. data/rust/parser/src/build/closed_sets.rs +6 -1
  324. data/rust/parser/src/build/identity.rs +4 -3
  325. data/rust/parser/src/build/mod.rs +1 -1
  326. data/rust/parser/src/build/named_pattern.rs +4 -1
  327. data/rust/parser/src/build/naming.rs +2 -80
  328. data/rust/parser/src/build/pattern_subset.rs +6 -8
  329. data/rust/parser/src/build/query_derive.rs +3 -1
  330. data/rust/parser/src/build/query_inference.rs +14 -5
  331. data/rust/parser/src/build/read_model.rs +4 -1
  332. data/rust/parser/src/build/references.rs +4 -3
  333. data/rust/parser/src/canonical.rs +34 -15
  334. data/rust/parser/src/emit.rs +187 -105
  335. data/rust/parser/src/expr/ast_json.rs +134 -42
  336. data/rust/parser/src/expr/evaluator.rs +27 -9
  337. data/rust/parser/src/expr/mod.rs +54 -13
  338. data/rust/parser/src/expr/resolver.rs +299 -92
  339. data/rust/parser/src/ir.rs +35 -3
  340. data/rust/parser/src/keywords.rs +7168 -516
  341. data/rust/parser/src/lex.rs +11 -25
  342. data/rust/parser/src/lib.rs +1 -0
  343. data/rust/parser/src/main.rs +65 -6
  344. data/rust/parser/src/parse/adapter.rs +157 -0
  345. data/rust/parser/src/parse/aggregate.rs +19 -10
  346. data/rust/parser/src/parse/chapter.rs +16 -17
  347. data/rust/parser/src/parse/command.rs +35 -16
  348. data/rust/parser/src/parse/domain_port.rs +4 -7
  349. data/rust/parser/src/parse/entity.rs +21 -11
  350. data/rust/parser/src/parse/hecksagon.rs +10 -4
  351. data/rust/parser/src/parse/lifecycle.rs +9 -5
  352. data/rust/parser/src/parse/mod.rs +22 -91
  353. data/rust/parser/src/parse/needs.rs +21 -12
  354. data/rust/parser/src/parse/policy.rs +2 -2
  355. data/rust/parser/src/parse/process_manager.rs +9 -11
  356. data/rust/parser/src/parse/query.rs +8 -4
  357. data/rust/parser/src/parse/read_model.rs +57 -16
  358. data/rust/parser/src/parse/value_object.rs +7 -2
  359. data/rust/parser/src/parse/world.rs +322 -0
  360. data/rust/parser/src/ruby_value.rs +7 -6
  361. data/rust/src/exemplar/json.rs +4 -4
  362. data/rust/src/exemplar/mutations.rs +18 -0
  363. data/rust/src/exemplar/read_models.rs +1 -10
  364. data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
  365. data/rust/src/kernel/attribute_shapes/list.rs +2 -2
  366. data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
  367. data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
  368. data/rust/src/kernel/cli.rs +229 -112
  369. data/rust/src/kernel/dispatch.rs +323 -115
  370. data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
  371. data/rust/src/kernel/expr.rs +203 -86
  372. data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
  373. data/rust/src/kernel/expression_operators/comparison.rs +87 -2
  374. data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
  375. data/rust/src/kernel/expression_operators/logical.rs +9 -3
  376. data/rust/src/kernel/expression_operators/membership.rs +3 -1
  377. data/rust/src/kernel/expression_operators/mod.rs +34 -21
  378. data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
  379. data/rust/src/kernel/expression_operators/positional.rs +84 -30
  380. data/rust/src/kernel/expression_operators/presence.rs +52 -13
  381. data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
  382. data/rust/src/kernel/expression_operators/sized.rs +24 -8
  383. data/rust/src/kernel/expression_operators/text.rs +120 -27
  384. data/rust/src/kernel/expression_operators/to_string.rs +10 -4
  385. data/rust/src/kernel/json.rs +230 -148
  386. data/rust/src/kernel/mod.rs +46 -33
  387. data/rust/src/kernel/named_query.rs +63 -15
  388. data/rust/src/kernel/needs.rs +112 -25
  389. data/rust/src/kernel/orchestrate.rs +980 -581
  390. data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
  391. data/rust/src/kernel/pattern.rs +107 -34
  392. data/rust/src/kernel/query_comparators.rs +118 -41
  393. data/rust/src/kernel/query_ordering.rs +13 -5
  394. data/rust/src/kernel/read_model.rs +479 -181
  395. data/rust/src/kernel/reference_lookup.rs +122 -26
  396. data/rust/src/kernel/repository.rs +293 -66
  397. data/rust/src/kernel/routing.rs +58 -42
  398. data/rust/src/kernel/ruby_float.rs +139 -0
  399. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
  400. data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
  401. data/rust/src/kernel/vocab/field_hint.rs +19 -25
  402. data/rust/src/kernel/vocab/mod.rs +6 -6
  403. data/rust/src/kernel/vocab/query_comparator.rs +23 -24
  404. data/rust/src/kernel/vocab/refusal_template.rs +501 -297
  405. data/rust/src/lib.rs +50 -0
  406. data/rust/src/main.rs +4 -1
  407. data/rust/web/Cargo.toml +36 -0
  408. data/rust/web/clippy.toml +4 -0
  409. metadata +82 -15
  410. data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
  411. data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
  412. data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
  413. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
  414. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
  415. data/lib/hecks/ports/authentication.port +0 -6
  416. data/lib/hecks/ports/authentication.rb +0 -67
  417. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
  418. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
  419. data/rust/host/src/approval.rs +0 -652
@@ -1,10 +1,12 @@
1
1
  //! Generic dispatch loops over the declared `AggregateStep`/`EntityStep` order.
2
2
  //! Argument gates run in `decode_*_arguments`, before the router resolves identity.
3
3
 
4
- use super::expr::{interpret, EvalContext, Expr, Field, Fielded, NoFields, StateFirst, Value, WithOld, WithParent};
4
+ use super::expr::{
5
+ interpret, EvalContext, Expr, Field, Fielded, NoFields, StateFirst, Value, WithOld, WithParent,
6
+ };
5
7
  use super::refusal_wording::{
6
- AlreadyExistsCreatingDuplicateArgs, LifecycleRefusedTransitionBlockedArgs, NotFoundCreatingNoIdentityArgs, NotFoundEntityElementMissingArgs,
7
- NotFoundRecordMissingArgs,
8
+ AlreadyExistsCreatingDuplicateArgs, LifecycleRefusedTransitionBlockedArgs,
9
+ NotFoundCreatingNoIdentityArgs, NotFoundEntityElementMissingArgs, NotFoundRecordMissingArgs,
8
10
  };
9
11
  use super::vocab::{AggregateStep, EntityStep};
10
12
  use super::{Event, Json, MutationRecord, Refusal, Repository, SetProjectedField, ToJson};
@@ -34,7 +36,7 @@ pub struct EntityInvariants {
34
36
  pub name: &'static str,
35
37
  pub list_field: &'static str,
36
38
  pub specs: Vec<InvariantSpec>,
37
- pub nested: Vec<EntityInvariants>,
39
+ pub nested: Vec<Self>,
38
40
  }
39
41
 
40
42
  pub struct InvariantSet {
@@ -42,11 +44,21 @@ pub struct InvariantSet {
42
44
  pub entities: Vec<EntityInvariants>,
43
45
  }
44
46
 
45
- pub fn enforce_invariants(record: &dyn Fielded, aggregate_name: &str, set: &InvariantSet) -> Result<(), Refusal> {
47
+ pub fn enforce_invariants(
48
+ record: &dyn Fielded,
49
+ aggregate_name: &str,
50
+ set: &InvariantSet,
51
+ ) -> Result<(), Refusal> {
46
52
  for rule in &set.aggregate {
47
- let ctx = EvalContext { args: &NoFields, instance: record };
53
+ let ctx = EvalContext {
54
+ args: &NoFields,
55
+ instance: record,
56
+ };
48
57
  if !interpret(&rule.expr, &ctx)?.truthy() {
49
- return Err(Refusal::InvariantViolation(format!("{aggregate_name} refused — {}", rule.description)));
58
+ return Err(Refusal::InvariantViolation(format!(
59
+ "{aggregate_name} refused — {}",
60
+ rule.description
61
+ )));
50
62
  }
51
63
  }
52
64
  for entity in &set.entities {
@@ -55,15 +67,31 @@ pub fn enforce_invariants(record: &dyn Fielded, aggregate_name: &str, set: &Inva
55
67
  Ok(())
56
68
  }
57
69
 
58
- fn enforce_entity_invariants(owner: &dyn Fielded, entity: &EntityInvariants) -> Result<(), Refusal> {
59
- let Some(elements) = owner.items(entity.list_field) else { return Ok(()) };
70
+ fn enforce_entity_invariants(
71
+ owner: &dyn Fielded,
72
+ entity: &EntityInvariants,
73
+ ) -> Result<(), Refusal> {
74
+ let Some(elements) = owner.items(entity.list_field) else {
75
+ return Ok(());
76
+ };
60
77
  for element in elements {
61
- let Field::Nested(element) = element else { continue };
62
- let with_parent = WithParent { args: &NoFields, parent: owner };
78
+ let Field::Nested(element) = element else {
79
+ continue;
80
+ };
81
+ let with_parent = WithParent {
82
+ args: &NoFields,
83
+ parent: owner,
84
+ };
63
85
  for rule in &entity.specs {
64
- let ctx = EvalContext { args: &with_parent, instance: element };
86
+ let ctx = EvalContext {
87
+ args: &with_parent,
88
+ instance: element,
89
+ };
65
90
  if !interpret(&rule.expr, &ctx)?.truthy() {
66
- return Err(Refusal::InvariantViolation(format!("{} refused — {}", entity.name, rule.description)));
91
+ return Err(Refusal::InvariantViolation(format!(
92
+ "{} refused — {}",
93
+ entity.name, rule.description
94
+ )));
67
95
  }
68
96
  }
69
97
  for nested in &entity.nested {
@@ -91,12 +119,17 @@ pub enum Hydrate<'a, T> {
91
119
  ///
92
120
  /// `state_independent` defers the `AlreadyExists` check to just before `repo.save`, for
93
121
  /// commands whose givens, ensures and mutations never read the aggregate's own state.
94
- Create { id: String, build: Box<dyn FnOnce() -> T + 'a>, state_independent: bool },
122
+ Create {
123
+ id: String,
124
+ build: Box<dyn FnOnce() -> T + 'a>,
125
+ state_independent: bool,
126
+ },
95
127
  /// An acting command: the identity already names a record.
96
128
  Act { id: String },
97
129
  }
98
130
 
99
- #[allow(clippy::too_many_arguments)]
131
+ // reason: the generated callers own `transition`/`payload` and pass them by value.
132
+ #[allow(clippy::too_many_arguments, clippy::needless_pass_by_value)]
100
133
  pub fn dispatch<'a, T, R>(
101
134
  repo: &mut R,
102
135
  hydrate: Hydrate<'a, T>,
@@ -152,7 +185,7 @@ where
152
185
  | AggregateStep::RefuseRoleMismatch
153
186
  | AggregateStep::ResolveReferences => {}
154
187
  AggregateStep::Hydrate => {
155
- let hydrate = hydrate.take().expect(ONCE);
188
+ let hydrate = ordered(hydrate.take(), ONCE)?;
156
189
  hydrated = Some(hydrate_record(
157
190
  repo,
158
191
  hydrate,
@@ -163,21 +196,32 @@ where
163
196
  )?);
164
197
  }
165
198
  AggregateStep::EnforceGivens => {
166
- let (id, record) = hydrated.as_ref().expect(HYDRATED);
167
- enforce_givens(record, args, givens, command_name, aggregate_qualified_name, id)?;
199
+ let (id, record) = ordered(hydrated.as_ref(), HYDRATED)?;
200
+ enforce_givens(
201
+ record,
202
+ args,
203
+ givens,
204
+ command_name,
205
+ aggregate_qualified_name,
206
+ id,
207
+ )?;
168
208
  }
169
209
  AggregateStep::AdmissibleTransition => {
170
- let (_, record) = hydrated.as_ref().expect(HYDRATED);
210
+ let (_, record) = ordered(hydrated.as_ref(), HYDRATED)?;
171
211
  admissible_transition(record, transition.as_ref(), command_name)?;
172
212
  }
173
213
  // Folded into `Hydrate::Create.build`: givens already saw the built record.
174
214
  AggregateStep::AssignCreationAttributes => {}
175
215
  AggregateStep::ApplyMutations => {
176
- let (_, record) = hydrated.as_mut().expect(HYDRATED);
216
+ let (_, record) = ordered(hydrated.as_mut(), HYDRATED)?;
177
217
  // The state as the givens saw it — `old` inside an `ensures`,
178
218
  // `old` for `ensures`: the state before the mutation, cloned only if needed.
179
- old_snapshot = if ensures.is_empty() { None } else { Some(record.clone()) };
180
- (apply_mutations.take().expect(ONCE))(record)?;
219
+ old_snapshot = if ensures.is_empty() {
220
+ None
221
+ } else {
222
+ Some(record.clone())
223
+ };
224
+ (ordered(apply_mutations.take(), ONCE)?)(record)?;
181
225
  }
182
226
  // Written by the generated `apply_mutations` closure as its last line.
183
227
  AggregateStep::AdvanceLifecycle => {}
@@ -185,21 +229,21 @@ where
185
229
  // `apply_entity_command` on the record in hand.
186
230
  AggregateStep::DelegateToEntity => {}
187
231
  AggregateStep::EnforceEnsures => {
188
- let (_, record) = hydrated.as_ref().expect(HYDRATED);
232
+ let (_, record) = ordered(hydrated.as_ref(), HYDRATED)?;
189
233
  if let Some(old) = &old_snapshot {
190
234
  enforce_ensures(record, old, args, ensures, command_name)?;
191
235
  }
192
236
  }
193
237
  AggregateStep::EnforceInvariants => {
194
- let (_, record) = hydrated.as_ref().expect(HYDRATED);
238
+ let (_, record) = ordered(hydrated.as_ref(), HYDRATED)?;
195
239
  enforce_invariants(record, aggregate_name, invariants)?;
196
240
  }
197
241
  AggregateStep::Save => {
198
- let (id, record) = hydrated.as_mut().expect(HYDRATED);
242
+ let (id, record) = ordered(hydrated.as_mut(), HYDRATED)?;
199
243
  // Tenant boundary first: it precedes `seed_projections` and the write.
200
- tenant_boundary_check.take().expect(ONCE)?;
244
+ ordered(tenant_boundary_check.take(), ONCE)??;
201
245
 
202
- for (field, value) in seed_projections.take().expect(ONCE) {
246
+ for (field, value) in ordered(seed_projections.take(), ONCE)? {
203
247
  record.set_projected_field(field, value);
204
248
  }
205
249
 
@@ -220,13 +264,13 @@ where
220
264
  persist(repo, id, record, aggregate_qualified_name, mutations);
221
265
  }
222
266
  AggregateStep::Emit => {
223
- let (id, _) = hydrated.as_ref().expect(HYDRATED);
267
+ let (id, _) = ordered(hydrated.as_ref(), HYDRATED)?;
224
268
  events = emitted(emits, aggregate_qualified_name, id, &payload);
225
269
  }
226
270
  }
227
271
  }
228
272
 
229
- let (_, record) = hydrated.expect(HYDRATED);
273
+ let (_, record) = ordered(hydrated, HYDRATED)?;
230
274
  Ok((record, events))
231
275
  }
232
276
 
@@ -245,13 +289,21 @@ where
245
289
  R: Repository<T>,
246
290
  {
247
291
  match hydrate {
248
- Hydrate::Create { id, build, state_independent } => {
292
+ Hydrate::Create {
293
+ id,
294
+ build,
295
+ state_independent,
296
+ } => {
249
297
  // A blank identity names nothing; refuse before any lookup rather than mint a
250
298
  // record keyed by "".
251
299
  if id.is_empty() {
252
300
  return Err(Refusal::NotFound(
253
- NotFoundCreatingNoIdentityArgs { command: command_name, aggregate: aggregate_name, identity: identity_reading }
254
- .render_args(),
301
+ NotFoundCreatingNoIdentityArgs {
302
+ command: command_name,
303
+ aggregate: aggregate_name,
304
+ identity: identity_reading,
305
+ }
306
+ .render_args(),
255
307
  ));
256
308
  }
257
309
  // State-independent creates defer this check to `Save`.
@@ -274,8 +326,12 @@ where
274
326
  // Shared with `dispatch_entity`'s parent lookup: both raise `record_missing`.
275
327
  let record = repo.find(&id).ok_or_else(|| {
276
328
  Refusal::NotFound(
277
- NotFoundRecordMissingArgs { aggregate: aggregate_name, identity: identity_reading, offered: &format!("{id:?}") }
278
- .render_args(),
329
+ NotFoundRecordMissingArgs {
330
+ aggregate: aggregate_name,
331
+ identity: identity_reading,
332
+ offered: &format!("{id:?}"),
333
+ }
334
+ .render_args(),
279
335
  )
280
336
  })?;
281
337
  Ok((id, record))
@@ -293,7 +349,10 @@ fn enforce_givens(
293
349
  id: &str,
294
350
  ) -> Result<(), Refusal> {
295
351
  for given in givens {
296
- let ctx = EvalContext { args, instance: record };
352
+ let ctx = EvalContext {
353
+ args,
354
+ instance: record,
355
+ };
297
356
  if !interpret(&given.expr, &ctx)?.truthy() {
298
357
  // The `corrects` wording interpolates the record id, so it is built here.
299
358
  if let Some(event_name) = given.corrects_event {
@@ -302,7 +361,10 @@ fn enforce_givens(
302
361
  "{command_name} refused — corrects {event_name}, but {aggregate_qualified_name} #{id} has never emitted it"
303
362
  )));
304
363
  }
305
- return Err(Refusal::GivenNotMet(format!("{command_name} refused — {}", given.description)));
364
+ return Err(Refusal::GivenNotMet(format!(
365
+ "{command_name} refused — {}",
366
+ given.description
367
+ )));
306
368
  }
307
369
  }
308
370
  Ok(())
@@ -310,8 +372,14 @@ fn enforce_givens(
310
372
 
311
373
  /// `admissible_transition` — shared by the aggregate record and an entity
312
374
  /// element, which check it identically.
313
- fn admissible_transition(instance: &dyn Fielded, transition: Option<&TransitionCheck>, command_name: &str) -> Result<(), Refusal> {
314
- let Some(check) = transition else { return Ok(()) };
375
+ fn admissible_transition(
376
+ instance: &dyn Fielded,
377
+ transition: Option<&TransitionCheck>,
378
+ command_name: &str,
379
+ ) -> Result<(), Refusal> {
380
+ let Some(check) = transition else {
381
+ return Ok(());
382
+ };
315
383
  match instance.field(check.field) {
316
384
  Some(Field::Value(Value::Str(current))) => {
317
385
  if !check.from_states.contains(&current.as_str()) {
@@ -348,19 +416,33 @@ fn enforce_ensures(
348
416
  ) -> Result<(), Refusal> {
349
417
  // C2.3 — the settled state first; see `StateFirst`.
350
418
  let state_first = StateFirst { args, settled };
351
- let with_old = WithOld { args: &state_first, old };
419
+ let with_old = WithOld {
420
+ args: &state_first,
421
+ old,
422
+ };
352
423
  for rule in ensures {
353
- let ctx = EvalContext { args: &with_old, instance: settled };
424
+ let ctx = EvalContext {
425
+ args: &with_old,
426
+ instance: settled,
427
+ };
354
428
  if !interpret(&rule.expr, &ctx)?.truthy() {
355
- return Err(Refusal::EnsuresNotMet(format!("{command_name} refused — {}", rule.description)));
429
+ return Err(Refusal::EnsuresNotMet(format!(
430
+ "{command_name} refused — {}",
431
+ rule.description
432
+ )));
356
433
  }
357
434
  }
358
435
  Ok(())
359
436
  }
360
437
 
361
438
  /// The write half of `save`: the repository write and its mutation record.
362
- fn persist<T, R>(repo: &mut R, id: &str, record: &T, aggregate_qualified_name: &str, mutations: &mut Vec<MutationRecord>)
363
- where
439
+ fn persist<T, R>(
440
+ repo: &mut R,
441
+ id: &str,
442
+ record: &T,
443
+ aggregate_qualified_name: &str,
444
+ mutations: &mut Vec<MutationRecord>,
445
+ ) where
364
446
  T: Clone + ToJson,
365
447
  R: Repository<T>,
366
448
  {
@@ -374,7 +456,12 @@ where
374
456
  }
375
457
 
376
458
  /// `emit` — the declared events, in declared order.
377
- fn emitted(emits: &[&'static str], aggregate_qualified_name: &str, id: &str, payload: &Json) -> Vec<Event> {
459
+ fn emitted(
460
+ emits: &[&'static str],
461
+ aggregate_qualified_name: &str,
462
+ id: &str,
463
+ payload: &Json,
464
+ ) -> Vec<Event> {
378
465
  emits
379
466
  .iter()
380
467
  .map(|name| Event {
@@ -475,7 +562,8 @@ struct ElementHalf<'a, 's, T, E, GetList, GetListMut, Matches, Apply> {
475
562
  old_snapshot: Option<E>,
476
563
  }
477
564
 
478
- impl<'a, 's, T, E, GetList, GetListMut, Matches, Apply> ElementHalf<'a, 's, T, E, GetList, GetListMut, Matches, Apply>
565
+ impl<T, E, GetList, GetListMut, Matches, Apply>
566
+ ElementHalf<'_, '_, T, E, GetList, GetListMut, Matches, Apply>
479
567
  where
480
568
  T: Fielded + Clone,
481
569
  E: Fielded + Clone,
@@ -497,12 +585,17 @@ where
497
585
  | EntityStep::ResolveReferences => Ok(()),
498
586
  // The parent half: `dispatch_entity`'s own arms, or the delegating aggregate's
499
587
  // `dispatch`.
500
- EntityStep::HydrateParent | EntityStep::EnforceInvariants | EntityStep::Save | EntityStep::Emit => Ok(()),
588
+ EntityStep::HydrateParent
589
+ | EntityStep::EnforceInvariants
590
+ | EntityStep::Save
591
+ | EntityStep::Emit => Ok(()),
501
592
  EntityStep::LocateElement => self.locate_element(record),
502
593
  EntityStep::EnforceGivens => self.enforce_givens(),
503
- EntityStep::AdmissibleTransition => {
504
- admissible_transition(self.element.as_ref().expect(LOCATED), self.transition.as_ref(), self.command_name)
505
- }
594
+ EntityStep::AdmissibleTransition => admissible_transition(
595
+ ordered(self.element.as_ref(), LOCATED)?,
596
+ self.transition.as_ref(),
597
+ self.command_name,
598
+ ),
506
599
  EntityStep::ApplyMutations => self.apply_mutations(record),
507
600
  // Written by the generated `apply_mutations` closure (the entity's own lifecycle).
508
601
  EntityStep::AdvanceLifecycle => Ok(()),
@@ -511,18 +604,21 @@ where
511
604
  }
512
605
 
513
606
  fn locate_element(&mut self, record: &T) -> Result<(), Refusal> {
514
- let position = (self.get_list)(record).iter().position(|el| (self.matches)(el)).ok_or_else(|| {
515
- Refusal::NotFound(
516
- NotFoundEntityElementMissingArgs {
517
- entity: self.entity_name,
518
- identity: self.entity_identity_reading,
519
- wants: self.wants,
520
- aggregate: self.aggregate_name,
521
- parent_id: &format!("{:?}", self.parent_id),
522
- }
523
- .render_args(),
524
- )
525
- })?;
607
+ let position = (self.get_list)(record)
608
+ .iter()
609
+ .position(|el| (self.matches)(el))
610
+ .ok_or_else(|| {
611
+ Refusal::NotFound(
612
+ NotFoundEntityElementMissingArgs {
613
+ entity: self.entity_name,
614
+ identity: self.entity_identity_reading,
615
+ wants: self.wants,
616
+ aggregate: self.aggregate_name,
617
+ parent_id: &format!("{:?}", self.parent_id),
618
+ }
619
+ .render_args(),
620
+ )
621
+ })?;
526
622
  self.element = Some((self.get_list)(record)[position].clone());
527
623
  self.position = Some(position);
528
624
  self.parent_before = Some(record.clone());
@@ -530,15 +626,20 @@ where
530
626
  }
531
627
 
532
628
  fn enforce_givens(&self) -> Result<(), Refusal> {
533
- let parent_before = self.parent_before.as_ref().expect(LOCATED);
534
- let element = self.element.as_ref().expect(LOCATED);
629
+ let parent_before = ordered(self.parent_before.as_ref(), LOCATED)?;
630
+ let element = ordered(self.element.as_ref(), LOCATED)?;
535
631
 
536
632
  // Entity-level `corrects` is asked of the parent record and root aggregate, since an
537
633
  // entity has no event stream. It runs after the element lookup, before declared givens,
538
634
  // and proves the parent emitted the event, not that this element did.
539
635
  for given in self.givens {
540
- let Some(event_name) = given.corrects_event else { continue };
541
- let ctx = EvalContext { args: self.args, instance: parent_before };
636
+ let Some(event_name) = given.corrects_event else {
637
+ continue;
638
+ };
639
+ let ctx = EvalContext {
640
+ args: self.args,
641
+ instance: parent_before,
642
+ };
542
643
  if !interpret(&given.expr, &ctx)?.truthy() {
543
644
  return Err(Refusal::NothingToCorrect(format!(
544
645
  "{} refused — corrects {event_name}, but {} #{} has never emitted it",
@@ -547,42 +648,69 @@ where
547
648
  }
548
649
  }
549
650
 
550
- let with_parent = WithParent { args: self.args, parent: parent_before };
551
- let given_args: &dyn Fielded = if self.parent_in_args { &with_parent } else { self.args };
651
+ let with_parent = WithParent {
652
+ args: self.args,
653
+ parent: parent_before,
654
+ };
655
+ let given_args: &dyn Fielded = if self.parent_in_args {
656
+ &with_parent
657
+ } else {
658
+ self.args
659
+ };
552
660
  for given in self.givens {
553
661
  // Handled above; the `corrects` flag field lives on the parent, not the element.
554
662
  if given.corrects_event.is_some() {
555
663
  continue;
556
664
  }
557
- let ctx = EvalContext { args: given_args, instance: element };
665
+ let ctx = EvalContext {
666
+ args: given_args,
667
+ instance: element,
668
+ };
558
669
  if !interpret(&given.expr, &ctx)?.truthy() {
559
- return Err(Refusal::GivenNotMet(format!("{} refused — {}", self.command_name, given.description)));
670
+ return Err(Refusal::GivenNotMet(format!(
671
+ "{} refused — {}",
672
+ self.command_name, given.description
673
+ )));
560
674
  }
561
675
  }
562
676
  Ok(())
563
677
  }
564
678
 
565
679
  fn apply_mutations(&mut self, record: &mut T) -> Result<(), Refusal> {
566
- let position = self.position.expect(LOCATED);
567
- let mut element = self.element.take().expect(LOCATED);
568
- self.old_snapshot = if self.ensures.is_empty() { None } else { Some(element.clone()) };
569
- (self.apply_mutations.take().expect(ONCE))(&mut element)?;
570
- (self.get_list_mut.take().expect(ONCE))(record)[position] = element;
680
+ let position = ordered(self.position, LOCATED)?;
681
+ let mut element = ordered(self.element.take(), LOCATED)?;
682
+ self.old_snapshot = if self.ensures.is_empty() {
683
+ None
684
+ } else {
685
+ Some(element.clone())
686
+ };
687
+ (ordered(self.apply_mutations.take(), ONCE)?)(&mut element)?;
688
+ (ordered(self.get_list_mut.take(), ONCE)?)(record)[position] = element;
571
689
  Ok(())
572
690
  }
573
691
 
574
692
  fn enforce_ensures(&self, record: &T) -> Result<(), Refusal> {
575
- let Some(old) = &self.old_snapshot else { return Ok(()) };
576
- let position = self.position.expect(LOCATED);
693
+ let Some(old) = &self.old_snapshot else {
694
+ return Ok(());
695
+ };
696
+ let position = ordered(self.position, LOCATED)?;
577
697
  let parent_after = record.clone();
578
- let with_parent = WithParent { args: self.args, parent: &parent_after };
579
- let ensures_args: &dyn Fielded = if self.parent_in_args { &with_parent } else { self.args };
698
+ let with_parent = WithParent {
699
+ args: self.args,
700
+ parent: &parent_after,
701
+ };
702
+ let ensures_args: &dyn Fielded = if self.parent_in_args {
703
+ &with_parent
704
+ } else {
705
+ self.args
706
+ };
580
707
  let settled = &(self.get_list)(record)[position];
581
708
  enforce_ensures(settled, old, ensures_args, self.ensures, self.command_name)
582
709
  }
583
710
  }
584
711
 
585
- #[allow(clippy::too_many_arguments)]
712
+ // reason: the generated callers own `payload` and pass it by value.
713
+ #[allow(clippy::too_many_arguments, clippy::needless_pass_by_value)]
586
714
  pub fn dispatch_entity<'a, T, E, R>(
587
715
  repo: &mut R,
588
716
  parent_id: &str,
@@ -670,14 +798,18 @@ where
670
798
  | EntityStep::ApplyMutations
671
799
  | EntityStep::AdvanceLifecycle
672
800
  | EntityStep::EnforceEnsures => {
673
- element.run(step, hydrated.as_mut().expect(HYDRATED))?;
801
+ element.run(step, ordered(hydrated.as_mut(), HYDRATED)?)?;
674
802
  }
675
803
  EntityStep::EnforceInvariants => {
676
- enforce_invariants(hydrated.as_ref().expect(HYDRATED), aggregate_name, invariants)?;
804
+ enforce_invariants(
805
+ ordered(hydrated.as_ref(), HYDRATED)?,
806
+ aggregate_name,
807
+ invariants,
808
+ )?;
677
809
  }
678
810
  EntityStep::Save => {
679
- let record = hydrated.as_mut().expect(HYDRATED);
680
- for (field, value) in seed_projections.take().expect(ONCE) {
811
+ let record = ordered(hydrated.as_mut(), HYDRATED)?;
812
+ for (field, value) in ordered(seed_projections.take(), ONCE)? {
681
813
  record.set_projected_field(field, value);
682
814
  }
683
815
  persist(repo, parent_id, record, aggregate_qualified_name, mutations);
@@ -688,7 +820,13 @@ where
688
820
  }
689
821
  }
690
822
 
691
- Ok((hydrated.expect(HYDRATED), events))
823
+ Ok((ordered(hydrated, HYDRATED)?, events))
824
+ }
825
+
826
+ /// Turns a slot the declared step order guarantees filled into a value, or a `Fault` naming the
827
+ /// order that was broken; the const assertions below make the `Err` unreachable.
828
+ fn ordered<V>(slot: Option<V>, because: &str) -> Result<V, Refusal> {
829
+ slot.ok_or_else(|| Refusal::Fault(because.to_string()))
692
830
  }
693
831
 
694
832
  const HYDRATED: &str = "the declared order hydrates before any step that reads the record (const-asserted in this file)";
@@ -732,7 +870,9 @@ impl<A> ArgumentGates<'_, A> {
732
870
  Ok(())
733
871
  }
734
872
  ArgumentGate::RefuseRoleMismatch => (self.refuse_role_mismatch)(),
735
- ArgumentGate::ResolveReferences => (self.resolve_references)(args.as_ref().expect(NORMALIZED)),
873
+ ArgumentGate::ResolveReferences => {
874
+ (self.resolve_references)(ordered(args.as_ref(), NORMALIZED)?)
875
+ }
736
876
  }
737
877
  }
738
878
  }
@@ -740,7 +880,10 @@ impl<A> ArgumentGates<'_, A> {
740
880
  /// `AggregateStep::ORDER`'s argument gates over one aggregate command's facts:
741
881
  /// the normalized `<Args>`, or the first refusal in declared order. Every
742
882
  /// other step is `dispatch`'s, entered once the router has resolved identity.
743
- pub fn decode_aggregate_arguments<A>(facts: &Json, gates: &ArgumentGates<'_, A>) -> Result<A, Refusal> {
883
+ pub fn decode_aggregate_arguments<A>(
884
+ facts: &Json,
885
+ gates: &ArgumentGates<'_, A>,
886
+ ) -> Result<A, Refusal> {
744
887
  let mut args = None;
745
888
  for step in AggregateStep::ORDER {
746
889
  #[deny(clippy::wildcard_enum_match_arm)]
@@ -765,12 +908,15 @@ pub fn decode_aggregate_arguments<A>(facts: &Json, gates: &ArgumentGates<'_, A>)
765
908
  };
766
909
  gates.run(gate, facts, &mut args)?;
767
910
  }
768
- Ok(args.expect(NORMALIZED))
911
+ ordered(args, NORMALIZED)
769
912
  }
770
913
 
771
914
  /// `EntityStep::ORDER`'s argument gates over one entity command's facts — see
772
915
  /// `decode_aggregate_arguments`. Every other step is `dispatch_entity`'s.
773
- pub fn decode_entity_arguments<A>(facts: &Json, gates: &ArgumentGates<'_, A>) -> Result<A, Refusal> {
916
+ pub fn decode_entity_arguments<A>(
917
+ facts: &Json,
918
+ gates: &ArgumentGates<'_, A>,
919
+ ) -> Result<A, Refusal> {
774
920
  let mut args = None;
775
921
  for step in EntityStep::ORDER {
776
922
  #[deny(clippy::wildcard_enum_match_arm)]
@@ -794,10 +940,11 @@ pub fn decode_entity_arguments<A>(facts: &Json, gates: &ArgumentGates<'_, A>) ->
794
940
  };
795
941
  gates.run(gate, facts, &mut args)?;
796
942
  }
797
- Ok(args.expect(NORMALIZED))
943
+ ordered(args, NORMALIZED)
798
944
  }
799
945
 
800
946
  /// A step's index in `AggregateStep::ORDER`, usable in a const context.
947
+ #[allow(clippy::panic)] // reason: evaluated at compile time; a missing step fails the build.
801
948
  const fn aggregate_position(step: AggregateStep) -> usize {
802
949
  let mut index = 0;
803
950
  while index < AggregateStep::ORDER.len() {
@@ -810,6 +957,7 @@ const fn aggregate_position(step: AggregateStep) -> usize {
810
957
  }
811
958
 
812
959
  /// A step's index in `EntityStep::ORDER`, usable in a const context.
960
+ #[allow(clippy::panic)] // reason: evaluated at compile time; a missing step fails the build.
813
961
  const fn entity_position(step: EntityStep) -> usize {
814
962
  let mut index = 0;
815
963
  while index < EntityStep::ORDER.len() {
@@ -825,6 +973,7 @@ const fn entity_position(step: EntityStep) -> usize {
825
973
  // vocabulary.bluebook fails the build instead of panicking at dispatch time.
826
974
  const _: () = {
827
975
  use AggregateStep as A;
976
+ use EntityStep as E;
828
977
  let hydrate = aggregate_position(A::Hydrate);
829
978
  assert!(hydrate < aggregate_position(A::EnforceGivens));
830
979
  assert!(hydrate < aggregate_position(A::AdmissibleTransition));
@@ -847,7 +996,6 @@ const _: () = {
847
996
  assert!(aggregate_position(A::ResolveReferences) < hydrate);
848
997
  assert!(aggregate_position(A::NormalizeArgs) < aggregate_position(A::ResolveReferences));
849
998
 
850
- use EntityStep as E;
851
999
  let hydrate_parent = entity_position(E::HydrateParent);
852
1000
  assert!(hydrate_parent < entity_position(E::LocateElement));
853
1001
  assert!(hydrate_parent < entity_position(E::EnforceInvariants));
@@ -914,12 +1062,18 @@ pub const fn aggregate_step_site(step: AggregateStep) -> StepSite {
914
1062
  A::EnforceGivens => StepSite::Kernel,
915
1063
  A::AdmissibleTransition => StepSite::Kernel,
916
1064
  // `Hydrate::Create.build`.
917
- A::AssignCreationAttributes => StepSite::FoldedInto(AggregateStepOrEntityStep::Aggregate(A::Hydrate)),
1065
+ A::AssignCreationAttributes => {
1066
+ StepSite::FoldedInto(AggregateStepOrEntityStep::Aggregate(A::Hydrate))
1067
+ }
918
1068
  A::ApplyMutations => StepSite::Kernel,
919
1069
  // The generated `apply_mutations` closure's last line.
920
- A::AdvanceLifecycle => StepSite::FoldedInto(AggregateStepOrEntityStep::Aggregate(A::ApplyMutations)),
1070
+ A::AdvanceLifecycle => {
1071
+ StepSite::FoldedInto(AggregateStepOrEntityStep::Aggregate(A::ApplyMutations))
1072
+ }
921
1073
  // The generated `apply_mutations` closure → `apply_entity_command`.
922
- A::DelegateToEntity => StepSite::FoldedInto(AggregateStepOrEntityStep::Aggregate(A::ApplyMutations)),
1074
+ A::DelegateToEntity => {
1075
+ StepSite::FoldedInto(AggregateStepOrEntityStep::Aggregate(A::ApplyMutations))
1076
+ }
923
1077
  A::EnforceEnsures => StepSite::Kernel,
924
1078
  A::EnforceInvariants => StepSite::Kernel,
925
1079
  A::Save => StepSite::Kernel,
@@ -944,7 +1098,9 @@ pub const fn entity_step_site(step: EntityStep) -> StepSite {
944
1098
  E::EnforceGivens => StepSite::Kernel,
945
1099
  E::AdmissibleTransition => StepSite::Kernel,
946
1100
  E::ApplyMutations => StepSite::Kernel,
947
- E::AdvanceLifecycle => StepSite::FoldedInto(AggregateStepOrEntityStep::Entity(E::ApplyMutations)),
1101
+ E::AdvanceLifecycle => {
1102
+ StepSite::FoldedInto(AggregateStepOrEntityStep::Entity(E::ApplyMutations))
1103
+ }
948
1104
  E::EnforceEnsures => StepSite::Kernel,
949
1105
  E::EnforceInvariants => StepSite::Kernel,
950
1106
  E::Save => StepSite::Kernel,
@@ -980,18 +1136,33 @@ mod step_order_tests {
980
1136
 
981
1137
  #[test]
982
1138
  fn the_kernel_performs_exactly_these_aggregate_steps() {
983
- let kernel: Vec<&str> =
984
- AggregateStep::ORDER.iter().filter(|s| aggregate_step_site(**s) == StepSite::Kernel).map(|s| s.step()).collect();
1139
+ let kernel: Vec<&str> = AggregateStep::ORDER
1140
+ .iter()
1141
+ .filter(|s| aggregate_step_site(**s) == StepSite::Kernel)
1142
+ .map(AggregateStep::step)
1143
+ .collect();
985
1144
  assert_eq!(
986
1145
  kernel,
987
- ["hydrate", "enforce_givens", "admissible_transition", "apply_mutations", "enforce_ensures", "enforce_invariants", "save", "emit"]
1146
+ [
1147
+ "hydrate",
1148
+ "enforce_givens",
1149
+ "admissible_transition",
1150
+ "apply_mutations",
1151
+ "enforce_ensures",
1152
+ "enforce_invariants",
1153
+ "save",
1154
+ "emit"
1155
+ ]
988
1156
  );
989
1157
  }
990
1158
 
991
1159
  #[test]
992
1160
  fn the_kernel_performs_exactly_these_entity_steps() {
993
- let kernel: Vec<&str> =
994
- EntityStep::ORDER.iter().filter(|s| entity_step_site(**s) == StepSite::Kernel).map(|s| s.step()).collect();
1161
+ let kernel: Vec<&str> = EntityStep::ORDER
1162
+ .iter()
1163
+ .filter(|s| entity_step_site(**s) == StepSite::Kernel)
1164
+ .map(EntityStep::step)
1165
+ .collect();
995
1166
  assert_eq!(
996
1167
  kernel,
997
1168
  [
@@ -1012,16 +1183,30 @@ mod step_order_tests {
1012
1183
  fn argument_gates_all_precede_the_first_kernel_step() {
1013
1184
  // The router resolves identity between the two loops, so every
1014
1185
  // argument gate sits ahead of the first kernel step.
1015
- let first_kernel = AggregateStep::ORDER.iter().position(|s| aggregate_step_site(*s) == StepSite::Kernel).unwrap();
1186
+ let first_kernel = AggregateStep::ORDER
1187
+ .iter()
1188
+ .position(|s| aggregate_step_site(*s) == StepSite::Kernel)
1189
+ .unwrap();
1016
1190
  for step in AggregateStep::ORDER {
1017
1191
  if aggregate_step_site(step) == StepSite::ArgumentGate {
1018
- assert!(step.position() < first_kernel, "{} is an argument gate but is declared after hydrate", step.step());
1192
+ assert!(
1193
+ step.position() < first_kernel,
1194
+ "{} is an argument gate but is declared after hydrate",
1195
+ step.step()
1196
+ );
1019
1197
  }
1020
1198
  }
1021
- let first_kernel = EntityStep::ORDER.iter().position(|s| entity_step_site(*s) == StepSite::Kernel).unwrap();
1199
+ let first_kernel = EntityStep::ORDER
1200
+ .iter()
1201
+ .position(|s| entity_step_site(*s) == StepSite::Kernel)
1202
+ .unwrap();
1022
1203
  for step in EntityStep::ORDER {
1023
1204
  if entity_step_site(step) == StepSite::ArgumentGate {
1024
- assert!(step.position() < first_kernel, "{} is an argument gate but is declared after hydrate_parent", step.step());
1205
+ assert!(
1206
+ step.position() < first_kernel,
1207
+ "{} is an argument gate but is declared after hydrate_parent",
1208
+ step.step()
1209
+ );
1025
1210
  }
1026
1211
  }
1027
1212
  }
@@ -1029,7 +1214,10 @@ mod step_order_tests {
1029
1214
  use std::cell::RefCell;
1030
1215
 
1031
1216
  /// Hooks that record which step ran, refusing at the steps named in `refuse`.
1032
- fn recorded<R>(refuse: &[&'static str], body: impl FnOnce(&ArgumentGates<'_, i64>) -> R) -> (R, Vec<&'static str>) {
1217
+ fn recorded<R>(
1218
+ refuse: &[&'static str],
1219
+ body: impl FnOnce(&ArgumentGates<'_, i64>) -> R,
1220
+ ) -> (R, Vec<&'static str>) {
1033
1221
  let ran = RefCell::new(Vec::new());
1034
1222
  let hit = |step: &'static str| -> Result<(), Refusal> {
1035
1223
  ran.borrow_mut().push(step);
@@ -1045,7 +1233,10 @@ mod step_order_tests {
1045
1233
  let normalize = |_: &Json| hit("normalize_args").map(|()| 7);
1046
1234
  let role = || hit("refuse_role_mismatch");
1047
1235
  let references = |args: &i64| {
1048
- assert_eq!(*args, 7, "resolve_references reads what normalize_args produced");
1236
+ assert_eq!(
1237
+ *args, 7,
1238
+ "resolve_references reads what normalize_args produced"
1239
+ );
1049
1240
  hit("resolve_references")
1050
1241
  };
1051
1242
  let gates = ArgumentGates {
@@ -1065,29 +1256,46 @@ mod step_order_tests {
1065
1256
  let facts = Json::Object(Vec::new());
1066
1257
  let (result, ran) = recorded(&[], |gates| decode_aggregate_arguments(&facts, gates));
1067
1258
  assert_eq!(result.unwrap(), 7);
1068
- let declared: Vec<&str> =
1069
- AggregateStep::ORDER.iter().filter(|s| aggregate_step_site(**s) == StepSite::ArgumentGate).map(|s| s.step()).collect();
1259
+ let declared: Vec<&str> = AggregateStep::ORDER
1260
+ .iter()
1261
+ .filter(|s| aggregate_step_site(**s) == StepSite::ArgumentGate)
1262
+ .map(AggregateStep::step)
1263
+ .collect();
1070
1264
  assert_eq!(ran, declared);
1071
1265
 
1072
1266
  let (result, ran) = recorded(&[], |gates| decode_entity_arguments(&facts, gates));
1073
1267
  assert_eq!(result.unwrap(), 7);
1074
- let declared: Vec<&str> =
1075
- EntityStep::ORDER.iter().filter(|s| entity_step_site(**s) == StepSite::ArgumentGate).map(|s| s.step()).collect();
1268
+ let declared: Vec<&str> = EntityStep::ORDER
1269
+ .iter()
1270
+ .filter(|s| entity_step_site(**s) == StepSite::ArgumentGate)
1271
+ .map(EntityStep::step)
1272
+ .collect();
1076
1273
  assert_eq!(ran, declared);
1077
1274
  }
1078
1275
 
1079
1276
  #[test]
1080
1277
  fn the_earliest_declared_violated_gate_wins_and_later_hooks_never_run() {
1081
1278
  let facts = Json::Object(Vec::new());
1082
- let gates: Vec<&'static str> =
1083
- AggregateStep::ORDER.iter().filter(|s| aggregate_step_site(**s) == StepSite::ArgumentGate).map(|s| s.step()).collect();
1279
+ let gates: Vec<&'static str> = AggregateStep::ORDER
1280
+ .iter()
1281
+ .filter(|s| aggregate_step_site(**s) == StepSite::ArgumentGate)
1282
+ .map(AggregateStep::step)
1283
+ .collect();
1084
1284
  for (i, earlier) in gates.iter().enumerate() {
1085
1285
  for later in &gates[i + 1..] {
1086
- let (result, ran) = recorded(&[earlier, later], |g| decode_aggregate_arguments(&facts, g));
1087
- assert_eq!(result.unwrap_err().to_string(), Refusal::TypeMismatch(earlier.to_string()).to_string());
1286
+ let (result, ran) =
1287
+ recorded(&[earlier, later], |g| decode_aggregate_arguments(&facts, g));
1288
+ assert_eq!(
1289
+ result.unwrap_err().to_string(),
1290
+ Refusal::TypeMismatch(earlier.to_string()).to_string()
1291
+ );
1088
1292
  assert_eq!(ran.last(), Some(earlier));
1089
- let (result, _) = recorded(&[earlier, later], |g| decode_entity_arguments(&facts, g));
1090
- assert_eq!(result.unwrap_err().to_string(), Refusal::TypeMismatch(earlier.to_string()).to_string());
1293
+ let (result, _) =
1294
+ recorded(&[earlier, later], |g| decode_entity_arguments(&facts, g));
1295
+ assert_eq!(
1296
+ result.unwrap_err().to_string(),
1297
+ Refusal::TypeMismatch(earlier.to_string()).to_string()
1298
+ );
1091
1299
  }
1092
1300
  }
1093
1301
  }