hecks 3.14.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (427) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/adapters/fake_processor.rb +34 -0
  3. data/examples/pizzas/adapters/payment_signature.rb +66 -0
  4. data/examples/pizzas/adapters/payment_webhook.rb +87 -0
  5. data/examples/pizzas/bluebook/pizzas.behaviors +33 -0
  6. data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
  7. data/examples/pizzas/config.ru +22 -0
  8. data/exe/hecks +1 -1
  9. data/lib/hecks/adapters/driven/prism.rb +0 -2
  10. data/lib/hecks/adapters/driven.rb +0 -2
  11. data/lib/hecks/adapters/driving/json.rb +9 -16
  12. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
  13. data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
  14. data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
  15. data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
  16. data/lib/hecks/bluebook/hecksagon.rb +1 -1
  17. data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
  18. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
  19. data/lib/hecks/bluebook/meta_validator.rb +0 -11
  20. data/lib/hecks/bluebook/translation.rb +18 -10
  21. data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
  22. data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
  23. data/lib/hecks/bluebook/translation_expression.rb +39 -0
  24. data/lib/hecks/bluebook/translation_plan.rb +70 -0
  25. data/lib/hecks/bluebook.rb +2 -0
  26. data/lib/hecks/chapters.rb +3 -3
  27. data/lib/hecks/cli/project_cli/launcher_source.rb +1 -1
  28. data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
  29. data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
  30. data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
  31. data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
  32. data/lib/hecks/composer/adapters/jev.adapter +3 -0
  33. data/lib/hecks/composer/adapters/jev.rb +66 -0
  34. data/lib/hecks/composer/agent.rb +113 -0
  35. data/lib/hecks/composer/assistant.rb +110 -0
  36. data/lib/hecks/composer/behavior_run.rb +53 -0
  37. data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
  38. data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
  39. data/lib/hecks/composer/board.rb +65 -0
  40. data/lib/hecks/composer/board_edit/command_text.rb +39 -0
  41. data/lib/hecks/composer/board_edit/source.rb +84 -0
  42. data/lib/hecks/composer/board_edit.rb +144 -0
  43. data/lib/hecks/composer/check.rb +89 -0
  44. data/lib/hecks/composer/domains.rb +81 -0
  45. data/lib/hecks/composer/jobs.rb +134 -0
  46. data/lib/hecks/composer/layout.rb +37 -0
  47. data/lib/hecks/composer/live.rb +150 -0
  48. data/lib/hecks/composer/outline.rb +48 -0
  49. data/lib/hecks/composer/page.html +989 -0
  50. data/lib/hecks/composer/panels.rb +142 -0
  51. data/lib/hecks/composer/parse.rb +83 -0
  52. data/lib/hecks/composer/plain.rb +28 -0
  53. data/lib/hecks/composer/ports/composer_agent.port +7 -0
  54. data/lib/hecks/composer/session.rb +141 -0
  55. data/lib/hecks/composer/toolbox.rb +166 -0
  56. data/lib/hecks/composer/web.rb +156 -0
  57. data/lib/hecks/composer/workbench.rb +51 -0
  58. data/lib/hecks/composer/workspace.rb +81 -0
  59. data/lib/hecks/composer.rb +17 -0
  60. data/lib/hecks/corpus/adapters.rb +22 -0
  61. data/lib/hecks/corpus/rust.rb +1 -1
  62. data/lib/hecks/corpus/worlds.rb +19 -0
  63. data/lib/hecks/corpus.rb +5 -0
  64. data/lib/hecks/forms/app/commands.rb +2 -3
  65. data/lib/hecks/forms/app/queries.rb +2 -3
  66. data/lib/hecks/forms/app/records.rb +17 -5
  67. data/lib/hecks/forms/app/responses.rb +3 -1
  68. data/lib/hecks/forms/command_form_renderer.rb +42 -16
  69. data/lib/hecks/forms/field_controls.rb +2 -2
  70. data/lib/hecks/forms/field_renderer.rb +1 -1
  71. data/lib/hecks/forms/field_shape.rb +36 -17
  72. data/lib/hecks/forms/index_renderer.rb +2 -1
  73. data/lib/hecks/forms/page.rb +1 -1
  74. data/lib/hecks/forms/params.rb +0 -13
  75. data/lib/hecks/forms/query_form_renderer.rb +7 -6
  76. data/lib/hecks/forms/record_renderer.rb +11 -7
  77. data/lib/hecks/forms/rule_words.rb +100 -0
  78. data/lib/hecks/forms/value_object_shape.rb +2 -1
  79. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
  80. data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
  81. data/lib/hecks/fuzzing/replay/filters.rb +2 -6
  82. data/lib/hecks/gate/stages.yml +25 -0
  83. data/lib/hecks/grammar/evolve/seeds.rb +0 -6
  84. data/lib/hecks/grammar/evolve.rb +0 -3
  85. data/lib/hecks/grammar/translation.bluebook +1 -1
  86. data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
  87. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
  88. data/lib/hecks/hecks/adapters/first_admin.rb +3 -3
  89. data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
  90. data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
  91. data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
  92. data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
  93. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
  94. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
  95. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
  96. data/lib/hecks/hecks/codebase.behaviors +6 -0
  97. data/lib/hecks/hecks/codebase.bluebook +21 -0
  98. data/lib/hecks/hecks/composer.behaviors +68 -0
  99. data/lib/hecks/hecks/context_map.hecksagon +1 -1
  100. data/lib/hecks/hecks/custodian.behaviors +0 -6
  101. data/lib/hecks/hecks/custodian.bluebook +3 -80
  102. data/lib/hecks/hecks/hecks.hecksagon +2 -2
  103. data/lib/hecks/hecks/hecks.world +1 -1
  104. data/lib/hecks/hecks/rehearsal.bluebook +7 -13
  105. data/lib/hecks/kernel_capabilities/templates.rb +4 -4
  106. data/lib/hecks/kernel_capabilities.rb +1 -1
  107. data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
  108. data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
  109. data/lib/hecks/ports/access_control.rb +2 -2
  110. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
  111. data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
  112. data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
  113. data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
  114. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
  115. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
  116. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
  117. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
  118. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
  119. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
  120. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
  121. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
  122. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
  123. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
  124. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
  125. data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
  126. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
  127. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
  128. data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
  129. data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
  130. data/lib/hecks/ports.rb +0 -1
  131. data/lib/hecks/projection_files.rb +3 -1
  132. data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
  133. data/lib/hecks/projections/glossary/entries.rb +10 -4
  134. data/lib/hecks/projections/glossary/groups.rb +42 -0
  135. data/lib/hecks/projections/glossary/html/page.rb +7 -2
  136. data/lib/hecks/projections/glossary/html/parser.rb +1 -1
  137. data/lib/hecks/projections/glossary/html/sections.rb +31 -11
  138. data/lib/hecks/projections/glossary/html.rb +1 -2
  139. data/lib/hecks/projections/glossary/markdown.rb +31 -28
  140. data/lib/hecks/projections/glossary/mermaid.rb +3 -0
  141. data/lib/hecks/projections/glossary/page.css +143 -153
  142. data/lib/hecks/projections/glossary/page.js +63 -3
  143. data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
  144. data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
  145. data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
  146. data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
  147. data/lib/hecks/projections/glossary/sentences.rb +13 -3
  148. data/lib/hecks/projections/glossary/slugs.rb +4 -1
  149. data/lib/hecks/projections/glossary.rb +2 -1
  150. data/lib/hecks/projections/plain_english.rb +97 -0
  151. data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
  152. data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
  153. data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
  154. data/lib/hecks/projections/rust_vocabulary.rb +14 -6
  155. data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
  156. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +7 -2
  157. data/lib/hecks/projections/statements/actions.rb +99 -0
  158. data/lib/hecks/projections/statements/relationships.rb +86 -0
  159. data/lib/hecks/projections/statements.rb +15 -24
  160. data/lib/hecks/projector/exporter/adapters.rb +112 -0
  161. data/lib/hecks/projector/exporter/translations.rb +18 -35
  162. data/lib/hecks/projector/exporter/worlds.rb +103 -0
  163. data/lib/hecks/projector/exporter.rb +5 -1
  164. data/lib/hecks/quality_control/adapters/agent.rb +6 -7
  165. data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
  166. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
  167. data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
  168. data/lib/hecks/runtime/outbox.rb +2 -9
  169. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
  170. data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
  171. data/lib/hecks/runtime/value/unwrapping.rb +60 -0
  172. data/lib/hecks/runtime/value.rb +10 -21
  173. data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
  174. data/lib/hecks/rust_build/wasm.rb +0 -12
  175. data/lib/hecks/rust_build/write_if_changed.rb +12 -12
  176. data/lib/hecks/rust_format.rb +77 -0
  177. data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
  178. data/lib/hecks/tools/fuzz_sweep.rb +1 -3
  179. data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
  180. data/lib/hecks/tools/translation_audit.rb +8 -10
  181. data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
  182. data/lib/hecks/version.rb +1 -1
  183. data/lib/hecks/vocabulary.rb +2 -1
  184. data/lib/hecks.rb +3 -0
  185. data/prebuilt/{1a6f596be124228be93eba1d86dbc9d2933190fa389b284331241cb9f27115a0.marshal → ad68f9e06c8068c2a4176b57abf3083d591d5d8980c2b3e897b97331f965b643.marshal} +0 -0
  186. data/prebuilt/{a883a7e568667aa5ac904c82daff356bdf9e996252a36af6def4a4a43efb967e.marshal → ee22ec8642c4006717ba2b80f46e5efb81de459eef9dfec043e838c0ad4fd720.marshal} +0 -0
  187. data/prebuilt/{verdicts-872993b1aa26d73fe48f28251374b8585fed145d02aab42be0f00de386b6ff80.json → verdicts-b69f5dcbba79c20daa0493476fa7c317004a30aa2bc3d3dc6a722ea70ae2b43b.json} +1 -1
  188. data/rust/Cargo.toml +40 -0
  189. data/rust/build/Cargo.toml +36 -0
  190. data/rust/build/clippy.toml +4 -0
  191. data/rust/build/src/adapters_pass.rs +196 -0
  192. data/rust/build/src/binding_pass.rs +54 -13
  193. data/rust/build/src/build_artifact.rs +23 -5
  194. data/rust/build/src/cargo_sync.rs +69 -26
  195. data/rust/build/src/lineage_pass.rs +101 -87
  196. data/rust/build/src/main.rs +11 -2
  197. data/rust/build/src/pipeline.rs +80 -19
  198. data/rust/build/src/reserved_names.rs +6 -50
  199. data/rust/build/src/resolve.rs +82 -35
  200. data/rust/build/src/rustfmt.rs +100 -0
  201. data/rust/build/src/seams_pass.rs +85 -16
  202. data/rust/build/src/subprocess.rs +30 -6
  203. data/rust/build/src/tmp.rs +5 -2
  204. data/rust/build/src/translation_pass.rs +72 -22
  205. data/rust/build/src/world_pass.rs +109 -0
  206. data/rust/clippy.toml +4 -0
  207. data/rust/codegen/Cargo.toml +36 -0
  208. data/rust/codegen/clippy.toml +4 -0
  209. data/rust/codegen/src/asks.rs +40 -14
  210. data/rust/codegen/src/attr.rs +2 -2
  211. data/rust/codegen/src/bridging.rs +676 -117
  212. data/rust/codegen/src/commands.rs +979 -373
  213. data/rust/codegen/src/constraints.rs +88 -25
  214. data/rust/codegen/src/dependency_planning.rs +128 -34
  215. data/rust/codegen/src/domain_generator.rs +306 -170
  216. data/rust/codegen/src/exemplar.rs +87 -32
  217. data/rust/codegen/src/expr_emitter.rs +54 -6
  218. data/rust/codegen/src/fielded.rs +193 -41
  219. data/rust/codegen/src/hecks_naming.rs +124 -16
  220. data/rust/codegen/src/hecks_naming_tests.rs +81 -0
  221. data/rust/codegen/src/json.rs +80 -44
  222. data/rust/codegen/src/json_codec.rs +585 -149
  223. data/rust/codegen/src/literal.rs +104 -138
  224. data/rust/codegen/src/main.rs +151 -52
  225. data/rust/codegen/src/manifest.rs +76 -14
  226. data/rust/codegen/src/mutations.rs +1212 -239
  227. data/rust/codegen/src/naming.rs +111 -60
  228. data/rust/codegen/src/optional_pass.rs +90 -28
  229. data/rust/codegen/src/ports.rs +5 -4
  230. data/rust/codegen/src/prelude.rs +139 -49
  231. data/rust/codegen/src/presenters.rs +335 -0
  232. data/rust/codegen/src/queries.rs +601 -226
  233. data/rust/codegen/src/reactions.rs +268 -74
  234. data/rust/codegen/src/read_models.rs +574 -229
  235. data/rust/codegen/src/reference_specs.rs +16 -3
  236. data/rust/codegen/src/registry.rs +128 -79
  237. data/rust/codegen/src/reserved_names.rs +6 -50
  238. data/rust/codegen/src/shared.rs +71 -22
  239. data/rust/codegen/src/sidecars.rs +15 -5
  240. data/rust/codegen/src/skip_reason.rs +8 -2
  241. data/rust/codegen/src/types.rs +306 -108
  242. data/rust/host/Cargo.lock +1 -0
  243. data/rust/host/Cargo.toml +40 -4
  244. data/rust/host/HECKS_RELEASE +1 -1
  245. data/rust/host/clippy.toml +4 -0
  246. data/rust/host/src/api.rs +698 -218
  247. data/rust/host/src/auth.rs +491 -214
  248. data/rust/host/src/b64.rs +91 -0
  249. data/rust/host/src/bin/expr_harness.rs +6 -2
  250. data/rust/host/src/bin/lineage_harness.rs +47 -11
  251. data/rust/host/src/bin/mint_harness.rs +70 -37
  252. data/rust/host/src/bin/validate_harness.rs +16 -8
  253. data/rust/host/src/boot.rs +144 -68
  254. data/rust/host/src/boundary_fuzz/api.rs +130 -32
  255. data/rust/host/src/boundary_fuzz/auth.rs +108 -23
  256. data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
  257. data/rust/host/src/boundary_fuzz/ir.rs +164 -12
  258. data/rust/host/src/boundary_fuzz/server.rs +283 -36
  259. data/rust/host/src/boundary_fuzz/web.rs +56 -8
  260. data/rust/host/src/checkout.rs +78 -32
  261. data/rust/host/src/clock.rs +8 -0
  262. data/rust/host/src/commerce_ir.rs +333 -95
  263. data/rust/host/src/crypto.rs +33 -0
  264. data/rust/host/src/dispatch.rs +1037 -331
  265. data/rust/host/src/expr_json.rs +1021 -273
  266. data/rust/host/src/extension.rs +24 -4
  267. data/rust/host/src/field_hints.rs +11 -7
  268. data/rust/host/src/fuzz_support.rs +85 -13
  269. data/rust/host/src/host.rs +100 -0
  270. data/rust/host/src/http.rs +22 -0
  271. data/rust/host/src/ir.rs +326 -16
  272. data/rust/host/src/journal.rs +277 -176
  273. data/rust/host/src/lambda_client.rs +219 -53
  274. data/rust/host/src/lib.rs +16 -4
  275. data/rust/host/src/log.rs +62 -11
  276. data/rust/host/src/mint.rs +1153 -326
  277. data/rust/host/src/needs.rs +185 -44
  278. data/rust/host/src/payments/keystore.rs +197 -45
  279. data/rust/host/src/payments/roles.rs +39 -7
  280. data/rust/host/src/payments/tests.rs +1427 -282
  281. data/rust/host/src/payments.rs +391 -112
  282. data/rust/host/src/platform_signin/attack_tests.rs +5 -5
  283. data/rust/host/src/platform_signin/config.rs +11 -11
  284. data/rust/host/src/platform_signin/cookie.rs +2 -2
  285. data/rust/host/src/platform_signin/extension.rs +2 -1
  286. data/rust/host/src/platform_signin/handler.rs +9 -8
  287. data/rust/host/src/platform_signin/jti.rs +4 -2
  288. data/rust/host/src/platform_signin/jwks.rs +2 -1
  289. data/rust/host/src/platform_signin/jws.rs +2 -37
  290. data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
  291. data/rust/host/src/platform_signin/mode_tests.rs +5 -3
  292. data/rust/host/src/platform_signin/pages.rs +20 -20
  293. data/rust/host/src/platform_signin/session.rs +8 -1
  294. data/rust/host/src/platform_signin/settings.rs +6 -6
  295. data/rust/host/src/platform_signin/strict_json.rs +12 -9
  296. data/rust/host/src/platform_signin/verify.rs +41 -41
  297. data/rust/host/src/platform_signin/wiring_support.rs +13 -18
  298. data/rust/host/src/presentation.rs +207 -63
  299. data/rust/host/src/presentation_write.rs +502 -187
  300. data/rust/host/src/query_step.rs +69 -18
  301. data/rust/host/src/rate_limit.rs +541 -142
  302. data/rust/host/src/reference_transform.rs +77 -25
  303. data/rust/host/src/reference_validate.rs +443 -104
  304. data/rust/host/src/resend.rs +190 -55
  305. data/rust/host/src/secrets.rs +116 -21
  306. data/rust/host/src/server.rs +555 -104
  307. data/rust/host/src/storage_shape.rs +88 -21
  308. data/rust/host/src/test_pg.rs +17 -7
  309. data/rust/host/src/ui_schema.rs +495 -207
  310. data/rust/host/src/wasm_runner.rs +21 -6
  311. data/rust/host/src/web/commerce.rs +101 -23
  312. data/rust/host/src/web/newsletter/cooldown.rs +10 -3
  313. data/rust/host/src/web/newsletter/template.rs +26 -7
  314. data/rust/host/src/web/newsletter/tests.rs +112 -23
  315. data/rust/host/src/web/newsletter.rs +209 -59
  316. data/rust/host/src/web/newsletter_send.rs +251 -79
  317. data/rust/host/src/web/registration_receipt/tests.rs +16 -4
  318. data/rust/host/src/web/registration_receipt.rs +100 -24
  319. data/rust/host/src/web/registrations.rs +1384 -332
  320. data/rust/host/src/web/resend_webhook.rs +138 -80
  321. data/rust/host/src/web.rs +2416 -508
  322. data/rust/lsp/Cargo.toml +36 -0
  323. data/rust/lsp/clippy.toml +4 -0
  324. data/rust/lsp/src/json.rs +29 -22
  325. data/rust/lsp/src/main.rs +81 -31
  326. data/rust/lsp/src/outline.rs +23 -7
  327. data/rust/lsp/src/rpc.rs +17 -4
  328. data/rust/parser/Cargo.toml +36 -0
  329. data/rust/parser/clippy.toml +4 -0
  330. data/rust/parser/src/build/closed_sets.rs +6 -1
  331. data/rust/parser/src/build/identity.rs +4 -3
  332. data/rust/parser/src/build/mod.rs +1 -1
  333. data/rust/parser/src/build/named_pattern.rs +4 -1
  334. data/rust/parser/src/build/naming.rs +2 -80
  335. data/rust/parser/src/build/pattern_subset.rs +6 -8
  336. data/rust/parser/src/build/query_derive.rs +3 -1
  337. data/rust/parser/src/build/query_inference.rs +14 -5
  338. data/rust/parser/src/build/read_model.rs +4 -1
  339. data/rust/parser/src/build/references.rs +4 -3
  340. data/rust/parser/src/canonical.rs +34 -15
  341. data/rust/parser/src/emit.rs +187 -105
  342. data/rust/parser/src/expr/ast_json.rs +134 -42
  343. data/rust/parser/src/expr/evaluator.rs +27 -9
  344. data/rust/parser/src/expr/mod.rs +54 -13
  345. data/rust/parser/src/expr/resolver.rs +299 -92
  346. data/rust/parser/src/ir.rs +35 -3
  347. data/rust/parser/src/keywords.rs +7168 -516
  348. data/rust/parser/src/lex.rs +11 -25
  349. data/rust/parser/src/lib.rs +1 -0
  350. data/rust/parser/src/main.rs +65 -6
  351. data/rust/parser/src/parse/adapter.rs +157 -0
  352. data/rust/parser/src/parse/aggregate.rs +19 -10
  353. data/rust/parser/src/parse/chapter.rs +16 -17
  354. data/rust/parser/src/parse/command.rs +35 -16
  355. data/rust/parser/src/parse/domain_port.rs +4 -7
  356. data/rust/parser/src/parse/entity.rs +21 -11
  357. data/rust/parser/src/parse/hecksagon.rs +10 -4
  358. data/rust/parser/src/parse/lifecycle.rs +9 -5
  359. data/rust/parser/src/parse/mod.rs +22 -91
  360. data/rust/parser/src/parse/needs.rs +21 -12
  361. data/rust/parser/src/parse/policy.rs +2 -2
  362. data/rust/parser/src/parse/process_manager.rs +9 -11
  363. data/rust/parser/src/parse/query.rs +8 -4
  364. data/rust/parser/src/parse/read_model.rs +57 -16
  365. data/rust/parser/src/parse/value_object.rs +7 -2
  366. data/rust/parser/src/parse/world.rs +322 -0
  367. data/rust/parser/src/ruby_value.rs +7 -6
  368. data/rust/src/exemplar/json.rs +4 -4
  369. data/rust/src/exemplar/mutations.rs +18 -0
  370. data/rust/src/exemplar/read_models.rs +1 -10
  371. data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
  372. data/rust/src/kernel/attribute_shapes/list.rs +2 -2
  373. data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
  374. data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
  375. data/rust/src/kernel/cli.rs +244 -114
  376. data/rust/src/kernel/dispatch.rs +323 -115
  377. data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
  378. data/rust/src/kernel/expr.rs +203 -86
  379. data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
  380. data/rust/src/kernel/expression_operators/comparison.rs +87 -2
  381. data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
  382. data/rust/src/kernel/expression_operators/logical.rs +9 -3
  383. data/rust/src/kernel/expression_operators/membership.rs +3 -1
  384. data/rust/src/kernel/expression_operators/mod.rs +34 -21
  385. data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
  386. data/rust/src/kernel/expression_operators/positional.rs +84 -30
  387. data/rust/src/kernel/expression_operators/presence.rs +52 -13
  388. data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
  389. data/rust/src/kernel/expression_operators/sized.rs +24 -8
  390. data/rust/src/kernel/expression_operators/text.rs +120 -27
  391. data/rust/src/kernel/expression_operators/to_string.rs +10 -4
  392. data/rust/src/kernel/json.rs +244 -148
  393. data/rust/src/kernel/mod.rs +47 -33
  394. data/rust/src/kernel/named_query.rs +63 -15
  395. data/rust/src/kernel/needs.rs +112 -25
  396. data/rust/src/kernel/orchestrate.rs +980 -581
  397. data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
  398. data/rust/src/kernel/pattern.rs +107 -34
  399. data/rust/src/kernel/present.rs +261 -0
  400. data/rust/src/kernel/query_comparators.rs +118 -41
  401. data/rust/src/kernel/query_ordering.rs +13 -5
  402. data/rust/src/kernel/read_model.rs +479 -181
  403. data/rust/src/kernel/reference_lookup.rs +122 -26
  404. data/rust/src/kernel/repository.rs +293 -66
  405. data/rust/src/kernel/routing.rs +58 -42
  406. data/rust/src/kernel/ruby_float.rs +139 -0
  407. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
  408. data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
  409. data/rust/src/kernel/vocab/field_hint.rs +19 -25
  410. data/rust/src/kernel/vocab/mod.rs +6 -6
  411. data/rust/src/kernel/vocab/query_comparator.rs +23 -24
  412. data/rust/src/kernel/vocab/refusal_template.rs +501 -297
  413. data/rust/src/lib.rs +50 -0
  414. data/rust/src/main.rs +4 -1
  415. data/rust/web/Cargo.toml +36 -0
  416. data/rust/web/clippy.toml +4 -0
  417. metadata +85 -15
  418. data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
  419. data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
  420. data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
  421. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
  422. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
  423. data/lib/hecks/ports/authentication.port +0 -6
  424. data/lib/hecks/ports/authentication.rb +0 -67
  425. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
  426. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
  427. data/rust/host/src/approval.rs +0 -652
@@ -17,9 +17,9 @@ pub trait LambdaInvoker: Send + Sync {
17
17
  }
18
18
 
19
19
  // Mirrors Ruby's `"hecks-#{domain.downcase}"` — deployed stack names depend on
20
- // this exact casing.
20
+ // this exact casing, down to a word-final capital sigma, so it lowercases as Ruby does.
21
21
  pub fn function_name_for(domain: &str) -> String {
22
- format!("hecks-{}", domain.to_lowercase())
22
+ format!("hecks-{}", crate::hecks_naming::ruby_downcase(domain))
23
23
  }
24
24
 
25
25
  // Mirrors `PolicyInterpreter#deliver`'s record shape, minus `on:` — this crate
@@ -47,11 +47,29 @@ impl CrossDomainDeliveryRecord {
47
47
 
48
48
  // A target-side refusal returns `delivered: false`; a genuine invoke fault
49
49
  // (`function_error`, or a hard `Err` from `invoke`) returns `Err` instead.
50
- pub async fn deliver<L: LambdaInvoker + ?Sized>(invoker: &L, reaction: &Value) -> anyhow::Result<CrossDomainDeliveryRecord> {
51
- let policy = reaction.get("policy").and_then(Value::as_str).unwrap_or_default().to_string();
52
- let target_domain = reaction.get("target_domain").and_then(Value::as_str).unwrap_or_default().to_string();
53
- let target_verb = reaction.get("target_verb").and_then(Value::as_str).unwrap_or_default().to_string();
54
- let payload = reaction.get("payload").cloned().unwrap_or_else(|| serde_json::json!({}));
50
+ pub async fn deliver<L: LambdaInvoker + ?Sized>(
51
+ invoker: &L,
52
+ reaction: &Value,
53
+ ) -> anyhow::Result<CrossDomainDeliveryRecord> {
54
+ let policy = reaction
55
+ .get("policy")
56
+ .and_then(Value::as_str)
57
+ .unwrap_or_default()
58
+ .to_string();
59
+ let target_domain = reaction
60
+ .get("target_domain")
61
+ .and_then(Value::as_str)
62
+ .unwrap_or_default()
63
+ .to_string();
64
+ let target_verb = reaction
65
+ .get("target_verb")
66
+ .and_then(Value::as_str)
67
+ .unwrap_or_default()
68
+ .to_string();
69
+ let payload = reaction
70
+ .get("payload")
71
+ .cloned()
72
+ .unwrap_or_else(|| serde_json::json!({}));
55
73
 
56
74
  let function_name = function_name_for(&target_domain);
57
75
  // No `"role"` — a policy reaction is system-triggered, the same reasoning
@@ -61,18 +79,32 @@ pub async fn deliver<L: LambdaInvoker + ?Sized>(invoker: &L, reaction: &Value) -
61
79
  let outcome = invoker.invoke(&function_name, &request_payload).await?;
62
80
 
63
81
  if outcome.function_error {
64
- let message = outcome.body.get("errorMessage").and_then(Value::as_str).map(str::to_string).unwrap_or_else(|| outcome.body.to_string());
65
- anyhow::bail!("Lambda {function_name} ({}): {message}", target_verb);
82
+ let message = outcome
83
+ .body
84
+ .get("errorMessage")
85
+ .and_then(Value::as_str)
86
+ .map_or_else(|| outcome.body.to_string(), str::to_string);
87
+ anyhow::bail!("Lambda {function_name} ({target_verb}): {message}");
66
88
  }
67
89
 
68
90
  let target_side_refusal = outcome
69
91
  .body
70
92
  .get("refusals")
71
93
  .and_then(Value::as_array)
72
- .and_then(|refusals| refusals.iter().find(|r| r.get("verb").and_then(Value::as_str) == Some(target_verb.as_str())));
94
+ .and_then(|refusals| {
95
+ refusals
96
+ .iter()
97
+ .find(|r| r.get("verb").and_then(Value::as_str) == Some(target_verb.as_str()))
98
+ });
73
99
 
74
100
  let Some(refusal) = target_side_refusal else {
75
- return Ok(CrossDomainDeliveryRecord { policy, target_domain, target_verb, delivered: true, reason: None });
101
+ return Ok(CrossDomainDeliveryRecord {
102
+ policy,
103
+ target_domain,
104
+ target_verb,
105
+ delivered: true,
106
+ reason: None,
107
+ });
76
108
  };
77
109
 
78
110
  Ok(CrossDomainDeliveryRecord {
@@ -80,7 +112,10 @@ pub async fn deliver<L: LambdaInvoker + ?Sized>(invoker: &L, reaction: &Value) -
80
112
  target_domain,
81
113
  target_verb,
82
114
  delivered: false,
83
- reason: refusal.get("error").and_then(Value::as_str).map(String::from),
115
+ reason: refusal
116
+ .get("error")
117
+ .and_then(Value::as_str)
118
+ .map(String::from),
84
119
  })
85
120
  }
86
121
 
@@ -102,6 +137,8 @@ pub struct DeliveryFailure {
102
137
 
103
138
  // Retries only a genuine invoke fault (`Err`); a clean `Ok(delivered: false)`
104
139
  // refusal is a business outcome and is never retried.
140
+ // `DeliveryFailure` carries the whole reaction for the dead letter, so its size is intended.
141
+ #[allow(clippy::result_large_err)]
105
142
  pub async fn deliver_with_retry<L: LambdaInvoker + ?Sized>(
106
143
  invoker: &L,
107
144
  reaction: &Value,
@@ -122,13 +159,28 @@ pub async fn deliver_with_retry<L: LambdaInvoker + ?Sized>(
122
159
  }
123
160
 
124
161
  Err(DeliveryFailure {
125
- policy: reaction.get("policy").and_then(Value::as_str).unwrap_or_default().to_string(),
126
- target_domain: reaction.get("target_domain").and_then(Value::as_str).unwrap_or_default().to_string(),
127
- target_verb: reaction.get("target_verb").and_then(Value::as_str).unwrap_or_default().to_string(),
128
- payload: reaction.get("payload").cloned().unwrap_or_else(|| serde_json::json!({})),
129
- // Safe: this arm only runs after the loop above executed at least once,
130
- // and every iteration's `Err` arm sets `last_error` before falling through.
131
- error: last_error.unwrap(),
162
+ policy: reaction
163
+ .get("policy")
164
+ .and_then(Value::as_str)
165
+ .unwrap_or_default()
166
+ .to_string(),
167
+ target_domain: reaction
168
+ .get("target_domain")
169
+ .and_then(Value::as_str)
170
+ .unwrap_or_default()
171
+ .to_string(),
172
+ target_verb: reaction
173
+ .get("target_verb")
174
+ .and_then(Value::as_str)
175
+ .unwrap_or_default()
176
+ .to_string(),
177
+ payload: reaction
178
+ .get("payload")
179
+ .cloned()
180
+ .unwrap_or_else(|| serde_json::json!({})),
181
+ // The loop runs at least once and every `Err` arm sets `last_error`, so the fallback is
182
+ // unreachable; it exists only so a zero attempt budget still reports a failure.
183
+ error: last_error.unwrap_or_else(|| anyhow::anyhow!("no delivery attempt ran")),
132
184
  attempts: MAX_DELIVERY_ATTEMPTS,
133
185
  })
134
186
  }
@@ -144,10 +196,17 @@ impl AwsLambdaInvoker {
144
196
  // actually wires in `ring` (see Cargo.toml's note on this dependency).
145
197
  pub async fn from_env() -> Self {
146
198
  let http_client = aws_smithy_http_client::Builder::new()
147
- .tls_provider(aws_smithy_http_client::tls::Provider::Rustls(aws_smithy_http_client::tls::rustls_provider::CryptoMode::Ring))
199
+ .tls_provider(aws_smithy_http_client::tls::Provider::Rustls(
200
+ aws_smithy_http_client::tls::rustls_provider::CryptoMode::Ring,
201
+ ))
148
202
  .build_https();
149
- let config = aws_config::defaults(aws_config::BehaviorVersion::latest()).http_client(http_client).load().await;
150
- Self { client: aws_sdk_lambda::Client::new(&config) }
203
+ let config = aws_config::defaults(aws_config::BehaviorVersion::latest())
204
+ .http_client(http_client)
205
+ .load()
206
+ .await;
207
+ Self {
208
+ client: aws_sdk_lambda::Client::new(&config),
209
+ }
151
210
  }
152
211
  }
153
212
 
@@ -177,12 +236,18 @@ impl LambdaInvoker for AwsLambdaInvoker {
177
236
  .map_err(|e| anyhow::anyhow!("invoking Lambda {function_name}: {e:?}"))?;
178
237
 
179
238
  let function_error = response.function_error().is_some();
180
- let bytes = response.payload().map(|blob| blob.as_ref().to_vec()).unwrap_or_default();
239
+ let bytes = response
240
+ .payload()
241
+ .map(|blob| blob.as_ref().to_vec())
242
+ .unwrap_or_default();
181
243
  // A non-JSON/empty body reads as `{}` rather than failing the call;
182
244
  // `function_error` (checked above) is what actually signals a fault.
183
245
  let body: Value = serde_json::from_slice(&bytes).unwrap_or_else(|_| serde_json::json!({}));
184
246
 
185
- Ok(InvokeOutcome { body, function_error })
247
+ Ok(InvokeOutcome {
248
+ body,
249
+ function_error,
250
+ })
186
251
  }
187
252
  }
188
253
 
@@ -200,6 +265,30 @@ mod tests {
200
265
  assert_eq!(function_name_for("Banking"), "hecks-banking");
201
266
  }
202
267
 
268
+ // spec/fixtures/stack_name_vectors.json is asserted by spec/deploy_stack_names_spec.rb against
269
+ // all four Ruby copies of the formula, so a drift in any of them fails a test.
270
+ fn stack_name_vectors() -> Value {
271
+ serde_json::from_str(include_str!(
272
+ "../../../spec/fixtures/stack_name_vectors.json"
273
+ ))
274
+ .unwrap()
275
+ }
276
+
277
+ #[test]
278
+ fn function_name_matches_every_shared_stack_name_vector() {
279
+ let vectors = stack_name_vectors();
280
+ let rows = vectors["rows"].as_array().unwrap();
281
+ assert!(!rows.is_empty());
282
+ for row in rows {
283
+ let domain = row["domain"].as_str().unwrap();
284
+ assert_eq!(
285
+ function_name_for(domain),
286
+ row["name"].as_str().unwrap(),
287
+ "{domain}"
288
+ );
289
+ }
290
+ }
291
+
203
292
  // Records each call and answers with whatever canned outcome the test configured.
204
293
  struct MockLambdaInvoker {
205
294
  response: Result<InvokeOutcome, String>,
@@ -208,20 +297,39 @@ mod tests {
208
297
 
209
298
  impl MockLambdaInvoker {
210
299
  fn answering(body: Value, function_error: bool) -> Self {
211
- Self { response: Ok(InvokeOutcome { body, function_error }), calls: Mutex::new(Vec::new()) }
300
+ Self {
301
+ response: Ok(InvokeOutcome {
302
+ body,
303
+ function_error,
304
+ }),
305
+ calls: Mutex::new(Vec::new()),
306
+ }
212
307
  }
213
308
 
214
309
  fn failing(message: &str) -> Self {
215
- Self { response: Err(message.to_string()), calls: Mutex::new(Vec::new()) }
310
+ Self {
311
+ response: Err(message.to_string()),
312
+ calls: Mutex::new(Vec::new()),
313
+ }
216
314
  }
217
315
  }
218
316
 
219
317
  #[async_trait::async_trait]
220
318
  impl LambdaInvoker for MockLambdaInvoker {
221
- async fn invoke(&self, function_name: &str, payload: &str) -> anyhow::Result<InvokeOutcome> {
222
- self.calls.lock().unwrap().push((function_name.to_string(), payload.to_string()));
319
+ async fn invoke(
320
+ &self,
321
+ function_name: &str,
322
+ payload: &str,
323
+ ) -> anyhow::Result<InvokeOutcome> {
324
+ self.calls
325
+ .lock()
326
+ .unwrap()
327
+ .push((function_name.to_string(), payload.to_string()));
223
328
  match &self.response {
224
- Ok(outcome) => Ok(InvokeOutcome { body: outcome.body.clone(), function_error: outcome.function_error }),
329
+ Ok(outcome) => Ok(InvokeOutcome {
330
+ body: outcome.body.clone(),
331
+ function_error: outcome.function_error,
332
+ }),
225
333
  Err(message) => anyhow::bail!("{message}"),
226
334
  }
227
335
  }
@@ -240,7 +348,12 @@ mod tests {
240
348
  async fn delivers_and_invokes_the_computed_function_name_with_the_verb_args_payload_shape() {
241
349
  let invoker = MockLambdaInvoker::answering(serde_json::json!({ "refusals": [] }), false);
242
350
 
243
- let record = deliver(&invoker, &reaction("Compliance", "Compliance::AccountFreezeReview.Open")).await.unwrap();
351
+ let record = deliver(
352
+ &invoker,
353
+ &reaction("Compliance", "Compliance::AccountFreezeReview.Open"),
354
+ )
355
+ .await
356
+ .unwrap();
244
357
 
245
358
  assert!(record.delivered);
246
359
  assert_eq!(record.reason, None);
@@ -262,7 +375,12 @@ mod tests {
262
375
  false,
263
376
  );
264
377
 
265
- let record = deliver(&invoker, &reaction("Compliance", "Compliance::AccountFreezeReview.Open")).await.unwrap();
378
+ let record = deliver(
379
+ &invoker,
380
+ &reaction("Compliance", "Compliance::AccountFreezeReview.Open"),
381
+ )
382
+ .await
383
+ .unwrap();
266
384
 
267
385
  assert!(!record.delivered);
268
386
  assert_eq!(record.reason.as_deref(), Some("already under review"));
@@ -277,7 +395,12 @@ mod tests {
277
395
  false,
278
396
  );
279
397
 
280
- let record = deliver(&invoker, &reaction("Compliance", "Compliance::AccountFreezeReview.Open")).await.unwrap();
398
+ let record = deliver(
399
+ &invoker,
400
+ &reaction("Compliance", "Compliance::AccountFreezeReview.Open"),
401
+ )
402
+ .await
403
+ .unwrap();
281
404
 
282
405
  assert!(record.delivered);
283
406
  }
@@ -286,11 +409,21 @@ mod tests {
286
409
  async fn a_function_error_response_is_a_hard_failure_not_a_delivery_record() {
287
410
  // Mirrors Ruby's `raise WiringError` on `function_error` — not a domain
288
411
  // refusal, so it propagates rather than becoming a `delivered: false` record.
289
- let invoker = MockLambdaInvoker::answering(serde_json::json!({ "errorMessage": "unhandled panic" }), true);
412
+ let invoker = MockLambdaInvoker::answering(
413
+ serde_json::json!({ "errorMessage": "unhandled panic" }),
414
+ true,
415
+ );
290
416
 
291
- let outcome = deliver(&invoker, &reaction("Compliance", "Compliance::AccountFreezeReview.Open")).await;
417
+ let outcome = deliver(
418
+ &invoker,
419
+ &reaction("Compliance", "Compliance::AccountFreezeReview.Open"),
420
+ )
421
+ .await;
292
422
 
293
- assert!(outcome.is_err(), "a function_error response must propagate as Err, not a delivered:false record");
423
+ assert!(
424
+ outcome.is_err(),
425
+ "a function_error response must propagate as Err, not a delivered:false record"
426
+ );
294
427
  }
295
428
 
296
429
  #[tokio::test]
@@ -313,20 +446,31 @@ mod tests {
313
446
 
314
447
  impl FlakyLambdaInvoker {
315
448
  fn failing_then_succeeding(fail_count: u32) -> Self {
316
- Self { fail_count: std::sync::atomic::AtomicU32::new(fail_count), calls: Mutex::new(Vec::new()) }
449
+ Self {
450
+ fail_count: std::sync::atomic::AtomicU32::new(fail_count),
451
+ calls: Mutex::new(Vec::new()),
452
+ }
317
453
  }
318
454
  }
319
455
 
320
456
  #[async_trait::async_trait]
321
457
  impl LambdaInvoker for FlakyLambdaInvoker {
322
- async fn invoke(&self, function_name: &str, _payload: &str) -> anyhow::Result<InvokeOutcome> {
458
+ async fn invoke(
459
+ &self,
460
+ function_name: &str,
461
+ _payload: &str,
462
+ ) -> anyhow::Result<InvokeOutcome> {
323
463
  self.calls.lock().unwrap().push(function_name.to_string());
324
464
  let remaining = self.fail_count.load(std::sync::atomic::Ordering::SeqCst);
325
465
  if remaining > 0 {
326
- self.fail_count.store(remaining - 1, std::sync::atomic::Ordering::SeqCst);
466
+ self.fail_count
467
+ .store(remaining - 1, std::sync::atomic::Ordering::SeqCst);
327
468
  anyhow::bail!("ThrottlingException: rate exceeded");
328
469
  }
329
- Ok(InvokeOutcome { body: serde_json::json!({ "refusals": [] }), function_error: false })
470
+ Ok(InvokeOutcome {
471
+ body: serde_json::json!({ "refusals": [] }),
472
+ function_error: false,
473
+ })
330
474
  }
331
475
  }
332
476
 
@@ -334,12 +478,19 @@ mod tests {
334
478
  async fn deliver_with_retry_rides_out_a_transient_fault_that_clears_before_attempts_run_out() {
335
479
  let invoker = FlakyLambdaInvoker::failing_then_succeeding(MAX_DELIVERY_ATTEMPTS - 1);
336
480
 
337
- let record = deliver_with_retry(&invoker, &reaction("Compliance", "Compliance::AccountFreezeReview.Open"))
338
- .await
339
- .expect("should succeed once the fault clears, within MAX_DELIVERY_ATTEMPTS");
481
+ let record = deliver_with_retry(
482
+ &invoker,
483
+ &reaction("Compliance", "Compliance::AccountFreezeReview.Open"),
484
+ )
485
+ .await
486
+ .expect("should succeed once the fault clears, within MAX_DELIVERY_ATTEMPTS");
340
487
 
341
488
  assert!(record.delivered);
342
- assert_eq!(invoker.calls.lock().unwrap().len(), MAX_DELIVERY_ATTEMPTS as usize, "should have retried exactly up to the successful attempt");
489
+ assert_eq!(
490
+ invoker.calls.lock().unwrap().len(),
491
+ MAX_DELIVERY_ATTEMPTS as usize,
492
+ "should have retried exactly up to the successful attempt"
493
+ );
343
494
  }
344
495
 
345
496
  #[tokio::test]
@@ -351,28 +502,43 @@ mod tests {
351
502
  false,
352
503
  );
353
504
 
354
- let record = deliver_with_retry(&invoker, &reaction("Compliance", "Compliance::AccountFreezeReview.Open"))
355
- .await
356
- .expect("a domain-side refusal is Ok, not a DeliveryFailure");
505
+ let record = deliver_with_retry(
506
+ &invoker,
507
+ &reaction("Compliance", "Compliance::AccountFreezeReview.Open"),
508
+ )
509
+ .await
510
+ .expect("a domain-side refusal is Ok, not a DeliveryFailure");
357
511
 
358
512
  assert!(!record.delivered);
359
- assert_eq!(invoker.calls.lock().unwrap().len(), 1, "a clean refusal should never be retried");
513
+ assert_eq!(
514
+ invoker.calls.lock().unwrap().len(),
515
+ 1,
516
+ "a clean refusal should never be retried"
517
+ );
360
518
  }
361
519
 
362
520
  #[tokio::test]
363
- async fn deliver_with_retry_gives_up_after_max_attempts_and_carries_everything_a_dead_letter_needs() {
521
+ async fn deliver_with_retry_gives_up_after_max_attempts_and_carries_everything_a_dead_letter_needs(
522
+ ) {
364
523
  let invoker = MockLambdaInvoker::failing("ResourceNotFoundException: function not found");
365
524
 
366
- let failure = deliver_with_retry(&invoker, &reaction("Notifications", "Notifications.Send"))
367
- .await
368
- .expect_err("a persistent fault should exhaust every attempt and fail");
525
+ let failure =
526
+ deliver_with_retry(&invoker, &reaction("Notifications", "Notifications.Send"))
527
+ .await
528
+ .expect_err("a persistent fault should exhaust every attempt and fail");
369
529
 
370
530
  assert_eq!(failure.policy, "ReviewOnFreeze");
371
531
  assert_eq!(failure.target_domain, "Notifications");
372
532
  assert_eq!(failure.target_verb, "Notifications.Send");
373
- assert_eq!(failure.payload, serde_json::json!({ "number": { "value": "acct-1" } }));
533
+ assert_eq!(
534
+ failure.payload,
535
+ serde_json::json!({ "number": { "value": "acct-1" } })
536
+ );
374
537
  assert_eq!(failure.attempts, MAX_DELIVERY_ATTEMPTS);
375
538
  assert!(format!("{:#}", failure.error).contains("ResourceNotFoundException"));
376
- assert_eq!(invoker.calls.lock().unwrap().len(), MAX_DELIVERY_ATTEMPTS as usize);
539
+ assert_eq!(
540
+ invoker.calls.lock().unwrap().len(),
541
+ MAX_DELIVERY_ATTEMPTS as usize
542
+ );
377
543
  }
378
544
  }
data/rust/host/src/lib.rs CHANGED
@@ -2,20 +2,32 @@
2
2
  //! Commerce (payments, checkout, newsletter, registrations, mail) plugs in through [`extension::HostExtension`].
3
3
 
4
4
  pub mod api;
5
- pub mod approval;
6
5
  pub mod auth;
6
+ mod b64;
7
7
  pub mod boot;
8
8
  pub mod checkout;
9
+ mod clock;
9
10
  pub mod commerce_ir;
11
+ mod crypto;
10
12
  pub mod dispatch;
11
13
  pub mod expr_json;
14
+ #[cfg(test)]
15
+ #[path = "boundary_fuzz/expr_json.rs"]
16
+ pub mod expr_json_fuzz;
12
17
  pub mod extension;
18
+ mod http;
19
+ // Generated: its static regexes are literals that compile, and the generator always emits
20
+ // `r#"…"#`, so the lints below are allowed here.
21
+ #[allow(clippy::expect_used, clippy::needless_raw_string_hashes)]
13
22
  pub mod field_hints;
14
23
  #[cfg(test)]
15
24
  pub mod fuzz_support;
16
- #[cfg(test)]
17
- #[path = "boundary_fuzz/expr_json.rs"]
18
- pub mod expr_json_fuzz;
25
+ pub mod host;
26
+ // The one `Hecks::Naming` port, compiled from `hecks-codegen`'s source: the host and the codegen
27
+ // are independent Cargo projects, so the file is shared by path rather than by dependency.
28
+ #[allow(dead_code)]
29
+ #[path = "../../codegen/src/hecks_naming.rs"]
30
+ pub mod hecks_naming;
19
31
  pub mod ir;
20
32
  pub mod journal;
21
33
  pub mod lambda_client;
data/rust/host/src/log.rs CHANGED
@@ -29,8 +29,16 @@ pub fn phase(name: &'static str) -> Phase {
29
29
 
30
30
  /// Starts a boot phase and logs its `start` line.
31
31
  pub fn phase_with(name: &'static str, fields: Value) -> Phase {
32
- emit("info", "boot_phase", phase_fields(name, "start", &fields, None));
33
- Phase { name, fields, started: std::time::Instant::now() }
32
+ emit(
33
+ "info",
34
+ "boot_phase",
35
+ phase_fields(name, "start", &fields, None),
36
+ );
37
+ Phase {
38
+ name,
39
+ fields,
40
+ started: std::time::Instant::now(),
41
+ }
34
42
  }
35
43
 
36
44
  impl Phase {
@@ -46,7 +54,11 @@ impl Phase {
46
54
  if let (Value::Object(base), Value::Object(extra)) = (&mut fields, outcome) {
47
55
  base.extend(extra);
48
56
  }
49
- emit("info", "boot_phase", phase_fields(self.name, "end", &fields, Some(elapsed_ms)));
57
+ emit(
58
+ "info",
59
+ "boot_phase",
60
+ phase_fields(self.name, "end", &fields, Some(elapsed_ms)),
61
+ );
50
62
  elapsed_ms
51
63
  }
52
64
  }
@@ -113,22 +125,61 @@ mod tests {
113
125
 
114
126
  #[test]
115
127
  fn a_phase_line_names_the_phase_and_an_end_line_carries_elapsed_ms() {
116
- let start = render("info", "boot_phase", phase_fields("audit_aggregate", "start", &json!({"aggregate": "registration"}), None));
128
+ let start = render(
129
+ "info",
130
+ "boot_phase",
131
+ phase_fields(
132
+ "audit_aggregate",
133
+ "start",
134
+ &json!({"aggregate": "registration"}),
135
+ None,
136
+ ),
137
+ );
117
138
  let parsed: Value = serde_json::from_str(&start).unwrap();
118
139
  assert_eq!(parsed["msg"], "boot_phase");
119
- assert_eq!((parsed["phase"].as_str(), parsed["event"].as_str()), (Some("audit_aggregate"), Some("start")));
140
+ assert_eq!(
141
+ (parsed["phase"].as_str(), parsed["event"].as_str()),
142
+ (Some("audit_aggregate"), Some("start"))
143
+ );
120
144
  assert_eq!(parsed["aggregate"], "registration");
121
- assert!(parsed.get("elapsed_ms").is_none(), "a start line has no duration yet");
122
-
123
- let end = render("info", "boot_phase", phase_fields("audit_aggregate", "end", &json!({"aggregate": "registration"}), Some(42)));
145
+ assert!(
146
+ parsed.get("elapsed_ms").is_none(),
147
+ "a start line has no duration yet"
148
+ );
149
+
150
+ let end = render(
151
+ "info",
152
+ "boot_phase",
153
+ phase_fields(
154
+ "audit_aggregate",
155
+ "end",
156
+ &json!({"aggregate": "registration"}),
157
+ Some(42),
158
+ ),
159
+ );
124
160
  let parsed: Value = serde_json::from_str(&end).unwrap();
125
- assert_eq!((parsed["event"].as_str(), parsed["elapsed_ms"].as_u64()), (Some("end"), Some(42)));
161
+ assert_eq!(
162
+ (parsed["event"].as_str(), parsed["elapsed_ms"].as_u64()),
163
+ (Some("end"), Some(42))
164
+ );
126
165
  }
127
166
 
128
167
  #[test]
129
168
  fn a_phase_field_cannot_overwrite_the_phase_or_event() {
130
- let fields = phase_fields("connect", "start", &json!({"phase": "other", "event": "end", "era": 7}), None);
131
- assert_eq!((fields["phase"].as_str(), fields["event"].as_str(), fields["era"].as_u64()), (Some("connect"), Some("start"), Some(7)));
169
+ let fields = phase_fields(
170
+ "connect",
171
+ "start",
172
+ &json!({"phase": "other", "event": "end", "era": 7}),
173
+ None,
174
+ );
175
+ assert_eq!(
176
+ (
177
+ fields["phase"].as_str(),
178
+ fields["event"].as_str(),
179
+ fields["era"].as_u64()
180
+ ),
181
+ (Some("connect"), Some("start"), Some(7))
182
+ );
132
183
  }
133
184
 
134
185
  #[test]