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
@@ -6,7 +6,7 @@ use serde_json::Value;
6
6
 
7
7
  /// The chapter that answers the guest newsletter signup, read from
8
8
  /// `ir.json`'s own `newsletter` key. `None` when nothing provides it.
9
- #[derive(Debug, Clone, PartialEq)]
9
+ #[derive(Debug, Clone, PartialEq, Eq)]
10
10
  pub struct NewsletterProvider {
11
11
  /// Chapter name, e.g. `Newsletter`.
12
12
  pub provider: String,
@@ -58,9 +58,17 @@ fn declared_seconds(fact: &Value, key: &str) -> Option<u64> {
58
58
  }
59
59
 
60
60
  /// The declared seconds for `key`, else `legacy` with a single warning per process.
61
- fn seconds_or_legacy(fact: &Value, capability: &str, key: &str, legacy: u64, warned: &std::sync::Once) -> u64 {
61
+ fn seconds_or_legacy(
62
+ fact: &Value,
63
+ capability: &str,
64
+ key: &str,
65
+ legacy: u64,
66
+ warned: &std::sync::Once,
67
+ ) -> u64 {
62
68
  declared_seconds(fact, key).unwrap_or_else(|| {
63
- warned.call_once(|| eprintln!("{capability} capability declares no {key}; using built-in default"));
69
+ warned.call_once(|| {
70
+ eprintln!("{capability} capability declares no {key}; using built-in default");
71
+ });
64
72
  legacy
65
73
  })
66
74
  }
@@ -68,7 +76,7 @@ fn seconds_or_legacy(fact: &Value, capability: &str, key: &str, legacy: u64, war
68
76
  /// The checkout boundary's windows, read from `ir.json`'s own `checkout` key
69
77
  /// (ADR 0098). Always present: a window the chapter does not declare takes its
70
78
  /// labelled legacy default.
71
- #[derive(Debug, Clone, Copy, PartialEq)]
79
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
72
80
  pub struct CheckoutWindows {
73
81
  /// Seconds either side of now a signed webhook's timestamp may sit.
74
82
  pub webhook_tolerance: i64,
@@ -92,9 +100,15 @@ pub fn checkout_windows(domain_ir: &Value) -> CheckoutWindows {
92
100
  static TOLERANCE_WARNED: std::sync::Once = std::sync::Once::new();
93
101
  static HOLD_WARNED: std::sync::Once = std::sync::Once::new();
94
102
  let fact = domain_ir.get("checkout").unwrap_or(&Value::Null);
95
- let seconds = |key, legacy: i64, warned| seconds_or_legacy(fact, "checkout", key, legacy as u64, warned) as i64;
103
+ let seconds = |key, legacy: i64, warned| {
104
+ seconds_or_legacy(fact, "checkout", key, legacy as u64, warned) as i64
105
+ };
96
106
  CheckoutWindows {
97
- webhook_tolerance: seconds("webhook_tolerance", LEGACY_WEBHOOK_TOLERANCE_SECS, &TOLERANCE_WARNED),
107
+ webhook_tolerance: seconds(
108
+ "webhook_tolerance",
109
+ LEGACY_WEBHOOK_TOLERANCE_SECS,
110
+ &TOLERANCE_WARNED,
111
+ ),
98
112
  session_hold: seconds("session_hold", LEGACY_SESSION_HOLD_SECS, &HOLD_WARNED),
99
113
  }
100
114
  }
@@ -107,9 +121,10 @@ pub fn checkout_windows_binding() -> CheckoutWindows {
107
121
  /// The confirm-link and unsubscribe-link windows of the IR this host loaded:
108
122
  /// `(confirm, unsubscribe)` seconds, legacy defaults without a `newsletter` fact.
109
123
  pub fn newsletter_windows_binding() -> (u64, u64) {
110
- ir().and_then(newsletter_provider)
111
- .map(|p| (p.confirm_window, p.unsubscribe_window))
112
- .unwrap_or((LEGACY_CONFIRM_WINDOW_SECS, LEGACY_UNSUBSCRIBE_WINDOW_SECS))
124
+ ir().and_then(newsletter_provider).map_or(
125
+ (LEGACY_CONFIRM_WINDOW_SECS, LEGACY_UNSUBSCRIBE_WINDOW_SECS),
126
+ |p| (p.confirm_window, p.unsubscribe_window),
127
+ )
113
128
  }
114
129
 
115
130
  /// LEGACY DEFAULT: the states a new subscriber waits in, used while the
@@ -133,14 +148,29 @@ pub const LEGACY_LEFT: [&str; 1] = ["unsubscribed"];
133
148
  /// resolved by the exporter. `None` when the fact omits it.
134
149
  fn declared_states(fact: &Value, key: &str) -> Option<Vec<String>> {
135
150
  let states = fact.get(key)?.as_array()?;
136
- Some(states.iter().filter_map(|s| s.as_str().map(String::from)).collect())
151
+ Some(
152
+ states
153
+ .iter()
154
+ .filter_map(|s| s.as_str().map(String::from))
155
+ .collect(),
156
+ )
137
157
  }
138
158
 
139
159
  /// The declared states for `key`, else `legacy` with a single warning per process.
140
- fn states_or_legacy(fact: &Value, key: &str, legacy: &[&str], warned: &std::sync::Once) -> Vec<String> {
160
+ fn states_or_legacy(
161
+ fact: &Value,
162
+ key: &str,
163
+ legacy: &[&str],
164
+ warned: &std::sync::Once,
165
+ ) -> Vec<String> {
141
166
  declared_states(fact, key).unwrap_or_else(|| {
142
- warned.call_once(|| eprintln!("newsletter capability declares no {key}; using built-in default"));
143
- legacy.iter().map(|s| s.to_string()).collect()
167
+ warned.call_once(|| {
168
+ eprintln!("newsletter capability declares no {key}; using built-in default");
169
+ });
170
+ legacy
171
+ .iter()
172
+ .map(std::string::ToString::to_string)
173
+ .collect()
144
174
  })
145
175
  }
146
176
 
@@ -163,7 +193,9 @@ impl NewsletterProvider {
163
193
  /// The state a subscriber is reported in when none is recorded yet: the
164
194
  /// first awaiting-confirmation state.
165
195
  pub fn initial_status(&self) -> &str {
166
- self.awaiting_confirmation.first().map(String::as_str).unwrap_or("")
196
+ self.awaiting_confirmation
197
+ .first()
198
+ .map_or("", String::as_str)
167
199
  }
168
200
 
169
201
  /// The prefix every subscriber's key in a `dispatch::read` `instances`
@@ -176,7 +208,8 @@ impl NewsletterProvider {
176
208
  /// Whether the domain's own `aggregate`'s `command` declares an argument
177
209
  /// named `field` — so a route only sends an argument the command declares.
178
210
  pub fn command_declares(domain_ir: &Value, aggregate: &str, command: &str, field: &str) -> bool {
179
- let named = |value: &Value, name: &str| value.get("name").and_then(|n| n.as_str()) == Some(name);
211
+ let named =
212
+ |value: &Value, name: &str| value.get("name").and_then(|n| n.as_str()) == Some(name);
180
213
  let Some(aggregates) = domain_ir.get("aggregates").and_then(|a| a.as_array()) else {
181
214
  return false;
182
215
  };
@@ -199,10 +232,32 @@ pub fn newsletter_provider(domain_ir: &Value) -> Option<NewsletterProvider> {
199
232
  static UNSUBSCRIBE_WINDOW_WARNED: std::sync::Once = std::sync::Once::new();
200
233
  let fact = domain_ir.get("newsletter")?;
201
234
  Some(NewsletterProvider {
202
- confirm_window: seconds_or_legacy(fact, "newsletter", "confirm_window", LEGACY_CONFIRM_WINDOW_SECS, &CONFIRM_WINDOW_WARNED),
203
- unsubscribe_window: seconds_or_legacy(fact, "newsletter", "unsubscribe_window", LEGACY_UNSUBSCRIBE_WINDOW_SECS, &UNSUBSCRIBE_WINDOW_WARNED),
204
- awaiting_confirmation: states_or_legacy(fact, "awaiting_confirmation", &LEGACY_AWAITING_CONFIRMATION, &AWAITING_WARNED),
205
- receives_issues: states_or_legacy(fact, "receives_issues", &LEGACY_RECEIVES_ISSUES, &RECEIVES_WARNED),
235
+ confirm_window: seconds_or_legacy(
236
+ fact,
237
+ "newsletter",
238
+ "confirm_window",
239
+ LEGACY_CONFIRM_WINDOW_SECS,
240
+ &CONFIRM_WINDOW_WARNED,
241
+ ),
242
+ unsubscribe_window: seconds_or_legacy(
243
+ fact,
244
+ "newsletter",
245
+ "unsubscribe_window",
246
+ LEGACY_UNSUBSCRIBE_WINDOW_SECS,
247
+ &UNSUBSCRIBE_WINDOW_WARNED,
248
+ ),
249
+ awaiting_confirmation: states_or_legacy(
250
+ fact,
251
+ "awaiting_confirmation",
252
+ &LEGACY_AWAITING_CONFIRMATION,
253
+ &AWAITING_WARNED,
254
+ ),
255
+ receives_issues: states_or_legacy(
256
+ fact,
257
+ "receives_issues",
258
+ &LEGACY_RECEIVES_ISSUES,
259
+ &RECEIVES_WARNED,
260
+ ),
206
261
  left: states_or_legacy(fact, "left", &LEGACY_LEFT, &LEFT_WARNED),
207
262
  provider: fact.get("provider")?.as_str()?.to_string(),
208
263
  subscribe: fact.get("subscribe")?.as_str()?.to_string(),
@@ -215,7 +270,7 @@ pub fn newsletter_provider(domain_ir: &Value) -> Option<NewsletterProvider> {
215
270
 
216
271
  /// The chapter that declares `provides "newsletter_issues"`, read from
217
272
  /// `ir.json`'s own `newsletter_issues` key. Omitted when nothing declares it.
218
- #[derive(Debug, Clone, PartialEq)]
273
+ #[derive(Debug, Clone, PartialEq, Eq)]
219
274
  pub struct NewsletterIssuesProvider {
220
275
  /// Qualified send command, e.g. `Newsletter::Issue.Send`.
221
276
  pub send_issue: String,
@@ -244,10 +299,9 @@ pub fn newsletter_issues_provider(domain_ir: &Value) -> Option<NewsletterIssuesP
244
299
  })
245
300
  }
246
301
 
247
-
248
302
  /// The chapter that takes payments, read from `ir.json`'s own `payments`
249
303
  /// key. `None` when nothing provides it.
250
- #[derive(Debug, Clone, PartialEq)]
304
+ #[derive(Debug, Clone, PartialEq, Eq)]
251
305
  pub struct PaymentsProvider {
252
306
  /// Chapter name, e.g. `Payments`.
253
307
  pub provider: String,
@@ -283,7 +337,8 @@ pub const LEGACY_LAPSE_REASON: &str = "checkout_expired";
283
337
  /// the chapter predates `provides "registrations", registered_at: "Registration.requested_at"`.
284
338
  /// Delete this (and the fallback in `registrations_provider`) once every shipped
285
339
  /// bluebook declares it.
286
- pub const LEGACY_REGISTERED_AT: [&str; 4] = ["created_at", "registered_at", "requested_at", "occurred_at"];
340
+ pub const LEGACY_REGISTERED_AT: [&str; 4] =
341
+ ["created_at", "registered_at", "requested_at", "occurred_at"];
287
342
 
288
343
  /// LEGACY DEFAULT: the seat-holding payment states used while the `payments`
289
344
  /// capability fact carries no `holds_seat` list, because the Payments chapter
@@ -303,7 +358,12 @@ pub const LEGACY_FREES_SEAT: [&str; 1] = ["archived"];
303
358
  /// lifecycle mark, resolved by the exporter. `None` when the fact omits it.
304
359
  fn declared_holds_seat(fact: &Value) -> Option<Vec<String>> {
305
360
  let states = fact.get("holds_seat")?.as_array()?;
306
- Some(states.iter().filter_map(|s| s.as_str().map(String::from)).collect())
361
+ Some(
362
+ states
363
+ .iter()
364
+ .filter_map(|s| s.as_str().map(String::from))
365
+ .collect(),
366
+ )
307
367
  }
308
368
 
309
369
  impl PaymentsProvider {
@@ -319,8 +379,13 @@ pub fn payments_provider(domain_ir: &Value) -> Option<PaymentsProvider> {
319
379
  let aggregate = fact.get("aggregate")?.as_str()?.to_string();
320
380
  let holds_seat = declared_holds_seat(fact).unwrap_or_else(|| {
321
381
  static WARNED: std::sync::Once = std::sync::Once::new();
322
- WARNED.call_once(|| eprintln!("payments capability declares no holds_seat; using built-in default"));
323
- LEGACY_HOLDS_SEAT.iter().map(|s| s.to_string()).collect()
382
+ WARNED.call_once(|| {
383
+ eprintln!("payments capability declares no holds_seat; using built-in default");
384
+ });
385
+ LEGACY_HOLDS_SEAT
386
+ .iter()
387
+ .map(std::string::ToString::to_string)
388
+ .collect()
324
389
  });
325
390
  Some(PaymentsProvider {
326
391
  provider: fact.get("provider")?.as_str()?.to_string(),
@@ -329,17 +394,28 @@ pub fn payments_provider(domain_ir: &Value) -> Option<PaymentsProvider> {
329
394
  failed: fact.get("failed")?.as_str()?.to_string(),
330
395
  aggregate,
331
396
  holds_seat,
332
- lapse_reason: fact.get("lapse_reason").and_then(|v| v.as_str()).filter(|v| !v.is_empty()).map(String::from).unwrap_or_else(|| {
333
- static WARNED: std::sync::Once = std::sync::Once::new();
334
- WARNED.call_once(|| eprintln!("payments capability declares no lapse_reason; using built-in default"));
335
- LEGACY_LAPSE_REASON.to_string()
336
- }),
397
+ lapse_reason: fact
398
+ .get("lapse_reason")
399
+ .and_then(|v| v.as_str())
400
+ .filter(|v| !v.is_empty())
401
+ .map_or_else(
402
+ || {
403
+ static WARNED: std::sync::Once = std::sync::Once::new();
404
+ WARNED.call_once(|| {
405
+ eprintln!(
406
+ "payments capability declares no lapse_reason; using built-in default"
407
+ );
408
+ });
409
+ LEGACY_LAPSE_REASON.to_string()
410
+ },
411
+ String::from,
412
+ ),
337
413
  })
338
414
  }
339
415
 
340
416
  /// The chapter that answers scheduling sessions and taking registrations,
341
417
  /// read from `ir.json`'s own `registrations` key. `None` when nothing provides it.
342
- #[derive(Debug, Clone, PartialEq)]
418
+ #[derive(Debug, Clone, PartialEq, Eq)]
343
419
  pub struct RegistrationsProvider {
344
420
  /// Chapter name, e.g. `Studio`.
345
421
  pub provider: String,
@@ -371,8 +447,14 @@ impl RegistrationsProvider {
371
447
  request: format!("{domain}::Registration.Request"),
372
448
  event_aggregate: format!("{domain}::Event"),
373
449
  registration_aggregate: format!("{domain}::Registration"),
374
- timestamp_keys: LEGACY_REGISTERED_AT.iter().map(|k| k.to_string()).collect(),
375
- frees_seat: LEGACY_FREES_SEAT.iter().map(|s| s.to_string()).collect(),
450
+ timestamp_keys: LEGACY_REGISTERED_AT
451
+ .iter()
452
+ .map(std::string::ToString::to_string)
453
+ .collect(),
454
+ frees_seat: LEGACY_FREES_SEAT
455
+ .iter()
456
+ .map(std::string::ToString::to_string)
457
+ .collect(),
376
458
  }
377
459
  }
378
460
 
@@ -399,15 +481,25 @@ impl RegistrationsProvider {
399
481
  /// mark, resolved by the exporter. `None` when the fact omits it.
400
482
  fn declared_frees_seat(fact: &Value) -> Option<Vec<String>> {
401
483
  let states = fact.get("frees_seat")?.as_array()?;
402
- Some(states.iter().filter_map(|s| s.as_str().map(String::from)).collect())
484
+ Some(
485
+ states
486
+ .iter()
487
+ .filter_map(|s| s.as_str().map(String::from))
488
+ .collect(),
489
+ )
403
490
  }
404
491
 
405
492
  pub fn registrations_provider(domain_ir: &Value) -> Option<RegistrationsProvider> {
406
493
  let fact = domain_ir.get("registrations")?;
407
494
  let frees_seat = declared_frees_seat(fact).unwrap_or_else(|| {
408
495
  static WARNED: std::sync::Once = std::sync::Once::new();
409
- WARNED.call_once(|| eprintln!("registrations capability declares no frees_seat; using built-in default"));
410
- LEGACY_FREES_SEAT.iter().map(|s| s.to_string()).collect()
496
+ WARNED.call_once(|| {
497
+ eprintln!("registrations capability declares no frees_seat; using built-in default");
498
+ });
499
+ LEGACY_FREES_SEAT
500
+ .iter()
501
+ .map(std::string::ToString::to_string)
502
+ .collect()
411
503
  });
412
504
  Some(RegistrationsProvider {
413
505
  provider: fact.get("provider")?.as_str()?.to_string(),
@@ -415,14 +507,18 @@ pub fn registrations_provider(domain_ir: &Value) -> Option<RegistrationsProvider
415
507
  request: fact.get("request")?.as_str()?.to_string(),
416
508
  event_aggregate: fact.get("event_aggregate")?.as_str()?.to_string(),
417
509
  registration_aggregate: fact.get("registration_aggregate")?.as_str()?.to_string(),
418
- timestamp_keys: match fact.get("registered_at").and_then(|v| v.as_str()).filter(|v| !v.is_empty()) {
419
- Some(declared) => vec![declared.to_string()],
420
- None => {
421
- static WARNED: std::sync::Once = std::sync::Once::new();
422
- WARNED.call_once(|| eprintln!("registrations capability declares no registered_at; using built-in default"));
423
- LEGACY_REGISTERED_AT.iter().map(|k| k.to_string()).collect()
424
- }
425
- },
510
+ timestamp_keys: fact
511
+ .get("registered_at")
512
+ .and_then(|v| v.as_str())
513
+ .filter(|v| !v.is_empty())
514
+ .map_or_else(
515
+ || {
516
+ static WARNED: std::sync::Once = std::sync::Once::new();
517
+ WARNED.call_once(|| eprintln!("registrations capability declares no registered_at; using built-in default"));
518
+ LEGACY_REGISTERED_AT.iter().map(std::string::ToString::to_string).collect()
519
+ },
520
+ |declared| vec![declared.to_string()],
521
+ ),
426
522
  frees_seat,
427
523
  })
428
524
  }
@@ -430,7 +526,8 @@ pub fn registrations_provider(domain_ir: &Value) -> Option<RegistrationsProvider
430
526
  /// The registrations binding of the IR this host loaded, or the
431
527
  /// conventional names for `domain` when the IR declares none.
432
528
  pub fn registrations_binding(domain: &str) -> RegistrationsProvider {
433
- ir().and_then(registrations_provider).unwrap_or_else(|| RegistrationsProvider::conventional(domain))
529
+ ir().and_then(registrations_provider)
530
+ .unwrap_or_else(|| RegistrationsProvider::conventional(domain))
434
531
  }
435
532
 
436
533
  /// One command of the payment-processor connection, by the role it plays —
@@ -448,7 +545,7 @@ pub enum ConnectionVerb {
448
545
 
449
546
  /// The chapter that owns the business's payment-processor connection, read
450
547
  /// from `ir.json`'s own `payment_connection` key. `None` when nothing provides it.
451
- #[derive(Debug, Clone, PartialEq)]
548
+ #[derive(Debug, Clone, PartialEq, Eq)]
452
549
  pub struct PaymentConnectionProvider {
453
550
  /// Chapter name, e.g. `Studio`.
454
551
  pub provider: String,
@@ -520,15 +617,18 @@ pub fn payment_connection_provider(domain_ir: &Value) -> Option<PaymentConnectio
520
617
  /// The payment-connection binding of the IR this host loaded, or the
521
618
  /// conventional names for `domain` when the IR declares none.
522
619
  pub fn payment_connection_binding(domain: &str) -> PaymentConnectionProvider {
523
- ir().and_then(payment_connection_provider).unwrap_or_else(|| PaymentConnectionProvider::conventional(domain))
620
+ ir().and_then(payment_connection_provider)
621
+ .unwrap_or_else(|| PaymentConnectionProvider::conventional(domain))
524
622
  }
525
623
 
526
624
  /// The checkout fixture's own payments binding, read from its committed
527
625
  /// `ir.json` — what the checkout routes' tests dispatch against.
528
626
  #[cfg(test)]
529
627
  pub fn fixture_ir() -> Value {
530
- let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../src/generated/checkout_fixture/ir.json");
531
- serde_json::from_str(&std::fs::read_to_string(path).expect("checkout_fixture ir.json")).expect("valid json")
628
+ let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
629
+ .join("../src/generated/checkout_fixture/ir.json");
630
+ serde_json::from_str(&std::fs::read_to_string(path).expect("checkout_fixture ir.json"))
631
+ .expect("valid json")
532
632
  }
533
633
 
534
634
  /// The checkout fixture's own payments binding.
@@ -564,7 +664,7 @@ mod tests {
564
664
  assert!(newsletter_provider(&serde_json::json!({ "name": "Pizzas" })).is_none());
565
665
  }
566
666
 
567
- fn newsletter_ir(marks: serde_json::Value) -> Value {
667
+ fn newsletter_ir(marks: &serde_json::Value) -> Value {
568
668
  let mut fact = serde_json::json!({
569
669
  "provider": "Newsletter",
570
670
  "subscribe": "Newsletter::Subscriber.Subscribe",
@@ -581,7 +681,7 @@ mod tests {
581
681
 
582
682
  #[test]
583
683
  fn newsletter_provider_reads_the_marks_from_the_fact() {
584
- let ir = newsletter_ir(serde_json::json!({
684
+ let ir = newsletter_ir(&serde_json::json!({
585
685
  "awaiting_confirmation": ["invited"],
586
686
  "receives_issues": ["active", "vip"],
587
687
  "left": ["gone"]
@@ -600,7 +700,8 @@ mod tests {
600
700
 
601
701
  #[test]
602
702
  fn newsletter_provider_falls_back_to_the_legacy_defaults_without_marks() {
603
- let provider = newsletter_provider(&newsletter_ir(serde_json::json!({}))).expect("newsletter");
703
+ let provider =
704
+ newsletter_provider(&newsletter_ir(&serde_json::json!({}))).expect("newsletter");
604
705
  assert_eq!(provider.awaiting_confirmation, LEGACY_AWAITING_CONFIRMATION);
605
706
  assert_eq!(provider.receives_issues, LEGACY_RECEIVES_ISSUES);
606
707
  assert_eq!(provider.left, LEGACY_LEFT);
@@ -613,33 +714,68 @@ mod tests {
613
714
 
614
715
  #[test]
615
716
  fn newsletter_provider_reads_the_link_windows_from_the_fact() {
616
- let ir = newsletter_ir(serde_json::json!({ "confirm_window": 3600, "unsubscribe_window": 7200 }));
717
+ let ir = newsletter_ir(
718
+ &serde_json::json!({ "confirm_window": 3600, "unsubscribe_window": 7200 }),
719
+ );
617
720
  let provider = newsletter_provider(&ir).expect("newsletter");
618
- assert_eq!((provider.confirm_window, provider.unsubscribe_window), (3600, 7200));
721
+ assert_eq!(
722
+ (provider.confirm_window, provider.unsubscribe_window),
723
+ (3600, 7200)
724
+ );
619
725
  }
620
726
 
621
727
  #[test]
622
728
  fn newsletter_provider_falls_back_to_the_legacy_windows_without_them() {
623
- let provider = newsletter_provider(&newsletter_ir(serde_json::json!({}))).expect("newsletter");
729
+ let provider =
730
+ newsletter_provider(&newsletter_ir(&serde_json::json!({}))).expect("newsletter");
624
731
  assert_eq!(provider.confirm_window, LEGACY_CONFIRM_WINDOW_SECS);
625
732
  assert_eq!(provider.unsubscribe_window, LEGACY_UNSUBSCRIBE_WINDOW_SECS);
626
- assert_eq!((LEGACY_CONFIRM_WINDOW_SECS, LEGACY_UNSUBSCRIBE_WINDOW_SECS), (1_209_600, 63_072_000));
733
+ assert_eq!(
734
+ (LEGACY_CONFIRM_WINDOW_SECS, LEGACY_UNSUBSCRIBE_WINDOW_SECS),
735
+ (1_209_600, 63_072_000)
736
+ );
627
737
  }
628
738
 
629
739
  #[test]
630
740
  fn checkout_windows_read_the_fact_and_fall_back_per_window() {
631
741
  let declared = serde_json::json!({ "checkout": { "provider": "Checkout", "webhook_tolerance": 120, "session_hold": 2400 } });
632
- assert_eq!(checkout_windows(&declared), CheckoutWindows { webhook_tolerance: 120, session_hold: 2400 });
633
- let partial = serde_json::json!({ "checkout": { "provider": "Checkout", "session_hold": 2400 } });
634
- assert_eq!(checkout_windows(&partial), CheckoutWindows { webhook_tolerance: LEGACY_WEBHOOK_TOLERANCE_SECS, session_hold: 2400 });
742
+ assert_eq!(
743
+ checkout_windows(&declared),
744
+ CheckoutWindows {
745
+ webhook_tolerance: 120,
746
+ session_hold: 2400
747
+ }
748
+ );
749
+ let partial =
750
+ serde_json::json!({ "checkout": { "provider": "Checkout", "session_hold": 2400 } });
751
+ assert_eq!(
752
+ checkout_windows(&partial),
753
+ CheckoutWindows {
754
+ webhook_tolerance: LEGACY_WEBHOOK_TOLERANCE_SECS,
755
+ session_hold: 2400
756
+ }
757
+ );
635
758
  let none = checkout_windows(&serde_json::json!({ "name": "Pizzas" }));
636
- assert_eq!(none, CheckoutWindows { webhook_tolerance: 300, session_hold: 1800 });
759
+ assert_eq!(
760
+ none,
761
+ CheckoutWindows {
762
+ webhook_tolerance: 300,
763
+ session_hold: 1800
764
+ }
765
+ );
637
766
  }
638
767
 
639
768
  #[test]
640
769
  fn a_zero_or_non_numeric_window_is_not_a_declaration() {
641
- let ir = serde_json::json!({ "checkout": { "webhook_tolerance": 0, "session_hold": "soon" } });
642
- assert_eq!(checkout_windows(&ir), CheckoutWindows { webhook_tolerance: 300, session_hold: 1800 });
770
+ let ir =
771
+ serde_json::json!({ "checkout": { "webhook_tolerance": 0, "session_hold": "soon" } });
772
+ assert_eq!(
773
+ checkout_windows(&ir),
774
+ CheckoutWindows {
775
+ webhook_tolerance: 300,
776
+ session_hold: 1800
777
+ }
778
+ );
643
779
  }
644
780
 
645
781
  #[test]
@@ -676,7 +812,10 @@ mod tests {
676
812
  let provider = payments_provider(&ir).expect("should find payments");
677
813
  assert_eq!(provider.provider, "Payments");
678
814
  assert_eq!(provider.initiate, "Payments::Payment.Initiate");
679
- assert_eq!(provider.succeeded, "Payments::Payment.PaymentGateway.Succeeded");
815
+ assert_eq!(
816
+ provider.succeeded,
817
+ "Payments::Payment.PaymentGateway.Succeeded"
818
+ );
680
819
  assert_eq!(provider.failed, "Payments::Payment.PaymentGateway.Failed");
681
820
  assert_eq!(provider.instance_prefix(), "Payments::Payment#");
682
821
  assert!(payments_provider(&serde_json::json!({ "name": "Pizzas" })).is_none());
@@ -697,31 +836,46 @@ mod tests {
697
836
  #[test]
698
837
  fn payments_provider_reads_holds_seat_from_the_fact() {
699
838
  let ir = payments_ir(Some(serde_json::json!(["pending", "paid"])));
700
- assert_eq!(declared_holds_seat(&ir["payments"]), Some(vec!["pending".to_string(), "paid".to_string()]));
701
- assert_eq!(payments_provider(&ir).expect("payments").holds_seat, vec!["pending", "paid"]);
839
+ assert_eq!(
840
+ declared_holds_seat(&ir["payments"]),
841
+ Some(vec!["pending".to_string(), "paid".to_string()])
842
+ );
843
+ assert_eq!(
844
+ payments_provider(&ir).expect("payments").holds_seat,
845
+ vec!["pending", "paid"]
846
+ );
702
847
  }
703
848
 
704
849
  #[test]
705
850
  fn the_checkout_fixture_ir_carries_holds_seat_with_no_fallback() {
706
851
  let ir = fixture_ir();
707
- assert_eq!(declared_holds_seat(&ir["payments"]), Some(vec!["pending".to_string(), "succeeded".to_string()]));
852
+ assert_eq!(
853
+ declared_holds_seat(&ir["payments"]),
854
+ Some(vec!["pending".to_string(), "succeeded".to_string()])
855
+ );
708
856
  assert_eq!(fixture_payments().holds_seat, vec!["pending", "succeeded"]);
709
857
  }
710
858
 
711
859
  #[test]
712
860
  fn payments_provider_falls_back_to_the_legacy_default_without_holds_seat() {
713
861
  assert_eq!(declared_holds_seat(&payments_ir(None)["payments"]), None);
714
- let held = payments_provider(&payments_ir(None)).expect("payments").holds_seat;
862
+ let held = payments_provider(&payments_ir(None))
863
+ .expect("payments")
864
+ .holds_seat;
715
865
  assert_eq!(held, LEGACY_HOLDS_SEAT);
716
866
  }
717
867
 
718
868
  #[test]
719
869
  fn registrations_provider_reads_the_declared_capability() {
720
- let provider = registrations_provider(&fixture_ir()).expect("the checkout fixture provides registrations");
870
+ let provider = registrations_provider(&fixture_ir())
871
+ .expect("the checkout fixture provides registrations");
721
872
  assert_eq!(provider.schedule, "CheckoutFixture::Event.Schedule");
722
873
  assert_eq!(provider.request, "CheckoutFixture::Registration.Request");
723
874
  assert_eq!(provider.event_prefix(), "CheckoutFixture::Event#");
724
- assert_eq!(provider.registration_prefix(), "CheckoutFixture::Registration#");
875
+ assert_eq!(
876
+ provider.registration_prefix(),
877
+ "CheckoutFixture::Registration#"
878
+ );
725
879
  assert_eq!(provider.request_target(), Some(("Registration", "Request")));
726
880
  }
727
881
 
@@ -731,18 +885,29 @@ mod tests {
731
885
  let conventional = RegistrationsProvider {
732
886
  timestamp_keys: declared.timestamp_keys.clone(),
733
887
  frees_seat: declared.frees_seat.clone(),
734
- ..RegistrationsProvider::conventional("CheckoutFixture") };
888
+ ..RegistrationsProvider::conventional("CheckoutFixture")
889
+ };
735
890
  assert_eq!(declared, conventional);
736
891
  }
737
892
 
738
893
  #[test]
739
894
  fn the_registration_timestamp_is_the_declared_attribute_or_the_legacy_guess() {
740
- assert_eq!(registrations_provider(&fixture_ir()).expect("registrations").timestamp_keys, vec!["requested_at"]);
895
+ assert_eq!(
896
+ registrations_provider(&fixture_ir())
897
+ .expect("registrations")
898
+ .timestamp_keys,
899
+ vec!["requested_at"]
900
+ );
741
901
  let undeclared = serde_json::json!({"registrations": {
742
902
  "provider": "B", "schedule": "B::E.S", "request": "B::R.Q",
743
903
  "event_aggregate": "B::E", "registration_aggregate": "B::R"
744
904
  }});
745
- assert_eq!(registrations_provider(&undeclared).expect("registrations").timestamp_keys, LEGACY_REGISTERED_AT);
905
+ assert_eq!(
906
+ registrations_provider(&undeclared)
907
+ .expect("registrations")
908
+ .timestamp_keys,
909
+ LEGACY_REGISTERED_AT
910
+ );
746
911
  }
747
912
 
748
913
  fn registrations_ir(frees_seat: Option<serde_json::Value>) -> Value {
@@ -759,21 +924,42 @@ mod tests {
759
924
  #[test]
760
925
  fn registrations_provider_reads_frees_seat_from_the_fact() {
761
926
  let ir = registrations_ir(Some(serde_json::json!(["cancelled", "archived"])));
762
- assert_eq!(declared_frees_seat(&ir["registrations"]), Some(vec!["cancelled".to_string(), "archived".to_string()]));
763
- assert_eq!(registrations_provider(&ir).expect("registrations").frees_seat, vec!["cancelled", "archived"]);
927
+ assert_eq!(
928
+ declared_frees_seat(&ir["registrations"]),
929
+ Some(vec!["cancelled".to_string(), "archived".to_string()])
930
+ );
931
+ assert_eq!(
932
+ registrations_provider(&ir)
933
+ .expect("registrations")
934
+ .frees_seat,
935
+ vec!["cancelled", "archived"]
936
+ );
764
937
  }
765
938
 
766
939
  #[test]
767
940
  fn the_checkout_fixture_ir_carries_frees_seat_with_no_fallback() {
768
941
  let ir = fixture_ir();
769
- assert_eq!(declared_frees_seat(&ir["registrations"]), Some(vec!["archived".to_string(), "cancelled".to_string()]));
770
- assert_eq!(registrations_provider(&ir).expect("registrations").frees_seat, vec!["archived", "cancelled"]);
942
+ assert_eq!(
943
+ declared_frees_seat(&ir["registrations"]),
944
+ Some(vec!["archived".to_string(), "cancelled".to_string()])
945
+ );
946
+ assert_eq!(
947
+ registrations_provider(&ir)
948
+ .expect("registrations")
949
+ .frees_seat,
950
+ vec!["archived", "cancelled"]
951
+ );
771
952
  }
772
953
 
773
954
  #[test]
774
955
  fn registrations_provider_falls_back_to_the_legacy_default_without_frees_seat() {
775
- assert_eq!(declared_frees_seat(&registrations_ir(None)["registrations"]), None);
776
- let freed = registrations_provider(&registrations_ir(None)).expect("registrations").frees_seat;
956
+ assert_eq!(
957
+ declared_frees_seat(&registrations_ir(None)["registrations"]),
958
+ None
959
+ );
960
+ let freed = registrations_provider(&registrations_ir(None))
961
+ .expect("registrations")
962
+ .frees_seat;
777
963
  assert_eq!(freed, LEGACY_FREES_SEAT);
778
964
  }
779
965
 
@@ -781,9 +967,15 @@ mod tests {
781
967
  fn the_lapse_reason_is_the_declared_word_or_the_legacy_default() {
782
968
  assert_eq!(fixture_payments().lapse_reason, "checkout_expired");
783
969
  let mut ir = payments_ir(None);
784
- assert_eq!(payments_provider(&ir).expect("payments").lapse_reason, LEGACY_LAPSE_REASON);
970
+ assert_eq!(
971
+ payments_provider(&ir).expect("payments").lapse_reason,
972
+ LEGACY_LAPSE_REASON
973
+ );
785
974
  ir["payments"]["lapse_reason"] = serde_json::json!("hold_lapsed");
786
- assert_eq!(payments_provider(&ir).expect("payments").lapse_reason, "hold_lapsed");
975
+ assert_eq!(
976
+ payments_provider(&ir).expect("payments").lapse_reason,
977
+ "hold_lapsed"
978
+ );
787
979
  }
788
980
 
789
981
  #[test]
@@ -803,26 +995,48 @@ mod tests {
803
995
 
804
996
  #[test]
805
997
  fn registrations_provider_is_none_without_the_key() {
806
- assert_eq!(registrations_provider(&serde_json::json!({"name": "Pizzas"})), None);
998
+ assert_eq!(
999
+ registrations_provider(&serde_json::json!({"name": "Pizzas"})),
1000
+ None
1001
+ );
807
1002
  }
808
1003
 
809
1004
  #[test]
810
1005
  fn registrations_binding_falls_back_to_the_conventional_names_when_no_ir_is_loaded() {
811
- assert_eq!(registrations_binding("Shop"), RegistrationsProvider::conventional("Shop"));
1006
+ assert_eq!(
1007
+ registrations_binding("Shop"),
1008
+ RegistrationsProvider::conventional("Shop")
1009
+ );
812
1010
  }
813
1011
 
814
1012
  #[test]
815
1013
  fn payment_connection_provider_reads_the_declared_capability() {
816
- let provider = payment_connection_provider(&fixture_ir()).expect("the checkout fixture provides payment_connection");
817
- assert_eq!(provider.verb(ConnectionVerb::Connect), "CheckoutFixture::PaymentConnection.Connect");
818
- assert_eq!(provider.verb(ConnectionVerb::Enable), "CheckoutFixture::PaymentConnection.EnablePayments");
819
- assert_eq!(provider.verb(ConnectionVerb::Disable), "CheckoutFixture::PaymentConnection.DisablePayments");
820
- assert_eq!(provider.instance_prefix(), "CheckoutFixture::PaymentConnection#");
1014
+ let provider = payment_connection_provider(&fixture_ir())
1015
+ .expect("the checkout fixture provides payment_connection");
1016
+ assert_eq!(
1017
+ provider.verb(ConnectionVerb::Connect),
1018
+ "CheckoutFixture::PaymentConnection.Connect"
1019
+ );
1020
+ assert_eq!(
1021
+ provider.verb(ConnectionVerb::Enable),
1022
+ "CheckoutFixture::PaymentConnection.EnablePayments"
1023
+ );
1024
+ assert_eq!(
1025
+ provider.verb(ConnectionVerb::Disable),
1026
+ "CheckoutFixture::PaymentConnection.DisablePayments"
1027
+ );
1028
+ assert_eq!(
1029
+ provider.instance_prefix(),
1030
+ "CheckoutFixture::PaymentConnection#"
1031
+ );
821
1032
  }
822
1033
 
823
1034
  #[test]
824
1035
  fn the_conventional_payment_connection_names_match_what_the_fixture_declares() {
825
- assert_eq!(payment_connection_provider(&fixture_ir()), Some(PaymentConnectionProvider::conventional("CheckoutFixture")));
1036
+ assert_eq!(
1037
+ payment_connection_provider(&fixture_ir()),
1038
+ Some(PaymentConnectionProvider::conventional("CheckoutFixture"))
1039
+ );
826
1040
  }
827
1041
 
828
1042
  #[test]
@@ -835,14 +1049,23 @@ mod tests {
835
1049
  "disable": "Billing::Link.TurnOff", "aggregate": "Billing::Link"
836
1050
  }});
837
1051
  let provider = payment_connection_provider(&ir).expect("declared");
838
- assert_eq!(provider.verb(ConnectionVerb::Suspend), "Billing::Link.Pause");
839
- assert_eq!(provider.verb(ConnectionVerb::Resume), "Billing::Link.Unpause");
1052
+ assert_eq!(
1053
+ provider.verb(ConnectionVerb::Suspend),
1054
+ "Billing::Link.Pause"
1055
+ );
1056
+ assert_eq!(
1057
+ provider.verb(ConnectionVerb::Resume),
1058
+ "Billing::Link.Unpause"
1059
+ );
840
1060
  assert_eq!(provider.instance_prefix(), "Billing::Link#");
841
1061
  }
842
1062
 
843
1063
  #[test]
844
1064
  fn payment_connection_binding_falls_back_to_the_conventional_names_when_no_ir_is_loaded() {
845
- assert_eq!(payment_connection_binding("Shop"), PaymentConnectionProvider::conventional("Shop"));
1065
+ assert_eq!(
1066
+ payment_connection_binding("Shop"),
1067
+ PaymentConnectionProvider::conventional("Shop")
1068
+ );
846
1069
  }
847
1070
 
848
1071
  #[test]
@@ -856,10 +1079,25 @@ mod tests {
856
1079
  {"name": "Event", "commands": [{"name": "Request", "attributes": [{"name": "slug"}]}]}
857
1080
  ]
858
1081
  });
859
- assert!(command_declares(&ir, "Registration", "Request", "news_signup"));
860
- assert!(!command_declares(&ir, "Registration", "Cancel", "news_signup"));
1082
+ assert!(command_declares(
1083
+ &ir,
1084
+ "Registration",
1085
+ "Request",
1086
+ "news_signup"
1087
+ ));
1088
+ assert!(!command_declares(
1089
+ &ir,
1090
+ "Registration",
1091
+ "Cancel",
1092
+ "news_signup"
1093
+ ));
861
1094
  assert!(!command_declares(&ir, "Event", "Request", "news_signup"));
862
1095
  assert!(!command_declares(&ir, "Registration", "Request", "phone"));
863
- assert!(!command_declares(&serde_json::json!({"name": "Pizzas"}), "Registration", "Request", "news_signup"));
1096
+ assert!(!command_declares(
1097
+ &serde_json::json!({"name": "Pizzas"}),
1098
+ "Registration",
1099
+ "Request",
1100
+ "news_signup"
1101
+ ));
864
1102
  }
865
1103
  }