hecks 3.14.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/adapters/fake_processor.rb +34 -0
  3. data/examples/pizzas/adapters/payment_signature.rb +66 -0
  4. data/examples/pizzas/adapters/payment_webhook.rb +87 -0
  5. data/examples/pizzas/bluebook/pizzas.behaviors +33 -0
  6. data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
  7. data/examples/pizzas/config.ru +22 -0
  8. data/lib/hecks/adapters/driven/prism.rb +0 -2
  9. data/lib/hecks/adapters/driven.rb +0 -2
  10. data/lib/hecks/adapters/driving/json.rb +1 -7
  11. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
  12. data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
  13. data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
  14. data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
  15. data/lib/hecks/bluebook/hecksagon.rb +1 -1
  16. data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
  17. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
  18. data/lib/hecks/bluebook/meta_validator.rb +0 -11
  19. data/lib/hecks/bluebook/translation.rb +18 -10
  20. data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
  21. data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
  22. data/lib/hecks/bluebook/translation_expression.rb +39 -0
  23. data/lib/hecks/bluebook/translation_plan.rb +70 -0
  24. data/lib/hecks/bluebook.rb +2 -0
  25. data/lib/hecks/chapters.rb +3 -3
  26. data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
  27. data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
  28. data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
  29. data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
  30. data/lib/hecks/composer/adapters/jev.adapter +3 -0
  31. data/lib/hecks/composer/adapters/jev.rb +66 -0
  32. data/lib/hecks/composer/agent.rb +113 -0
  33. data/lib/hecks/composer/assistant.rb +110 -0
  34. data/lib/hecks/composer/behavior_run.rb +53 -0
  35. data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
  36. data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
  37. data/lib/hecks/composer/board.rb +65 -0
  38. data/lib/hecks/composer/board_edit/command_text.rb +39 -0
  39. data/lib/hecks/composer/board_edit/source.rb +84 -0
  40. data/lib/hecks/composer/board_edit.rb +144 -0
  41. data/lib/hecks/composer/check.rb +89 -0
  42. data/lib/hecks/composer/domains.rb +81 -0
  43. data/lib/hecks/composer/jobs.rb +134 -0
  44. data/lib/hecks/composer/layout.rb +37 -0
  45. data/lib/hecks/composer/live.rb +150 -0
  46. data/lib/hecks/composer/outline.rb +48 -0
  47. data/lib/hecks/composer/page.html +989 -0
  48. data/lib/hecks/composer/panels.rb +142 -0
  49. data/lib/hecks/composer/parse.rb +83 -0
  50. data/lib/hecks/composer/plain.rb +28 -0
  51. data/lib/hecks/composer/ports/composer_agent.port +7 -0
  52. data/lib/hecks/composer/session.rb +141 -0
  53. data/lib/hecks/composer/toolbox.rb +166 -0
  54. data/lib/hecks/composer/web.rb +156 -0
  55. data/lib/hecks/composer/workbench.rb +51 -0
  56. data/lib/hecks/composer/workspace.rb +81 -0
  57. data/lib/hecks/composer.rb +17 -0
  58. data/lib/hecks/corpus/adapters.rb +22 -0
  59. data/lib/hecks/corpus/rust.rb +1 -1
  60. data/lib/hecks/corpus/worlds.rb +19 -0
  61. data/lib/hecks/corpus.rb +5 -0
  62. data/lib/hecks/forms/app/commands.rb +2 -3
  63. data/lib/hecks/forms/app/queries.rb +2 -3
  64. data/lib/hecks/forms/app/records.rb +17 -5
  65. data/lib/hecks/forms/command_form_renderer.rb +42 -16
  66. data/lib/hecks/forms/field_controls.rb +2 -2
  67. data/lib/hecks/forms/field_renderer.rb +1 -1
  68. data/lib/hecks/forms/field_shape.rb +36 -17
  69. data/lib/hecks/forms/index_renderer.rb +2 -1
  70. data/lib/hecks/forms/page.rb +1 -1
  71. data/lib/hecks/forms/params.rb +0 -13
  72. data/lib/hecks/forms/query_form_renderer.rb +7 -6
  73. data/lib/hecks/forms/record_renderer.rb +11 -7
  74. data/lib/hecks/forms/rule_words.rb +100 -0
  75. data/lib/hecks/forms/value_object_shape.rb +2 -1
  76. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
  77. data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
  78. data/lib/hecks/fuzzing/replay/filters.rb +2 -6
  79. data/lib/hecks/gate/stages.yml +25 -0
  80. data/lib/hecks/grammar/evolve/seeds.rb +0 -6
  81. data/lib/hecks/grammar/evolve.rb +0 -3
  82. data/lib/hecks/grammar/translation.bluebook +1 -1
  83. data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
  84. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
  85. data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
  86. data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
  87. data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
  88. data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
  89. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
  90. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
  91. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
  92. data/lib/hecks/hecks/codebase.behaviors +6 -0
  93. data/lib/hecks/hecks/codebase.bluebook +21 -0
  94. data/lib/hecks/hecks/composer.behaviors +68 -0
  95. data/lib/hecks/hecks/context_map.hecksagon +1 -1
  96. data/lib/hecks/hecks/custodian.behaviors +0 -6
  97. data/lib/hecks/hecks/custodian.bluebook +3 -80
  98. data/lib/hecks/hecks/hecks.hecksagon +2 -2
  99. data/lib/hecks/hecks/hecks.world +1 -1
  100. data/lib/hecks/hecks/rehearsal.bluebook +7 -13
  101. data/lib/hecks/kernel_capabilities/templates.rb +4 -4
  102. data/lib/hecks/kernel_capabilities.rb +1 -1
  103. data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
  104. data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
  105. data/lib/hecks/ports/access_control.rb +2 -2
  106. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
  107. data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
  108. data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
  109. data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
  110. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
  111. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
  112. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
  113. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
  114. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
  115. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
  116. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
  117. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
  118. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
  119. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
  120. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
  121. data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
  122. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
  123. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
  124. data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
  125. data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
  126. data/lib/hecks/ports.rb +0 -1
  127. data/lib/hecks/projection_files.rb +3 -1
  128. data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
  129. data/lib/hecks/projections/glossary/entries.rb +10 -4
  130. data/lib/hecks/projections/glossary/groups.rb +42 -0
  131. data/lib/hecks/projections/glossary/html/page.rb +7 -2
  132. data/lib/hecks/projections/glossary/html/parser.rb +1 -1
  133. data/lib/hecks/projections/glossary/html/sections.rb +31 -11
  134. data/lib/hecks/projections/glossary/html.rb +1 -2
  135. data/lib/hecks/projections/glossary/markdown.rb +31 -28
  136. data/lib/hecks/projections/glossary/mermaid.rb +3 -0
  137. data/lib/hecks/projections/glossary/page.css +143 -153
  138. data/lib/hecks/projections/glossary/page.js +63 -3
  139. data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
  140. data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
  141. data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
  142. data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
  143. data/lib/hecks/projections/glossary/sentences.rb +13 -3
  144. data/lib/hecks/projections/glossary/slugs.rb +4 -1
  145. data/lib/hecks/projections/glossary.rb +2 -1
  146. data/lib/hecks/projections/plain_english.rb +97 -0
  147. data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
  148. data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
  149. data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
  150. data/lib/hecks/projections/rust_vocabulary.rb +14 -6
  151. data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
  152. data/lib/hecks/projections/statements/actions.rb +99 -0
  153. data/lib/hecks/projections/statements/relationships.rb +86 -0
  154. data/lib/hecks/projections/statements.rb +15 -24
  155. data/lib/hecks/projector/exporter/adapters.rb +112 -0
  156. data/lib/hecks/projector/exporter/translations.rb +18 -35
  157. data/lib/hecks/projector/exporter/worlds.rb +103 -0
  158. data/lib/hecks/projector/exporter.rb +5 -1
  159. data/lib/hecks/quality_control/adapters/agent.rb +6 -7
  160. data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
  161. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
  162. data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
  163. data/lib/hecks/runtime/outbox.rb +2 -9
  164. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
  165. data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
  166. data/lib/hecks/runtime/value.rb +13 -0
  167. data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
  168. data/lib/hecks/rust_build/wasm.rb +0 -12
  169. data/lib/hecks/rust_build/write_if_changed.rb +12 -12
  170. data/lib/hecks/rust_format.rb +77 -0
  171. data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
  172. data/lib/hecks/tools/fuzz_sweep.rb +1 -3
  173. data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
  174. data/lib/hecks/tools/translation_audit.rb +8 -10
  175. data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
  176. data/lib/hecks/version.rb +1 -1
  177. data/lib/hecks/vocabulary.rb +2 -1
  178. data/lib/hecks.rb +3 -0
  179. data/prebuilt/{a883a7e568667aa5ac904c82daff356bdf9e996252a36af6def4a4a43efb967e.marshal → 02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal} +0 -0
  180. data/prebuilt/{1a6f596be124228be93eba1d86dbc9d2933190fa389b284331241cb9f27115a0.marshal → bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal} +0 -0
  181. data/prebuilt/{verdicts-872993b1aa26d73fe48f28251374b8585fed145d02aab42be0f00de386b6ff80.json → verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json} +1 -1
  182. data/rust/Cargo.toml +40 -0
  183. data/rust/build/Cargo.toml +36 -0
  184. data/rust/build/clippy.toml +4 -0
  185. data/rust/build/src/adapters_pass.rs +196 -0
  186. data/rust/build/src/binding_pass.rs +54 -13
  187. data/rust/build/src/build_artifact.rs +23 -5
  188. data/rust/build/src/cargo_sync.rs +69 -26
  189. data/rust/build/src/lineage_pass.rs +101 -87
  190. data/rust/build/src/main.rs +11 -2
  191. data/rust/build/src/pipeline.rs +80 -19
  192. data/rust/build/src/reserved_names.rs +6 -50
  193. data/rust/build/src/resolve.rs +82 -35
  194. data/rust/build/src/rustfmt.rs +100 -0
  195. data/rust/build/src/seams_pass.rs +85 -16
  196. data/rust/build/src/subprocess.rs +30 -6
  197. data/rust/build/src/tmp.rs +5 -2
  198. data/rust/build/src/translation_pass.rs +72 -22
  199. data/rust/build/src/world_pass.rs +109 -0
  200. data/rust/clippy.toml +4 -0
  201. data/rust/codegen/Cargo.toml +36 -0
  202. data/rust/codegen/clippy.toml +4 -0
  203. data/rust/codegen/src/asks.rs +40 -14
  204. data/rust/codegen/src/attr.rs +2 -2
  205. data/rust/codegen/src/bridging.rs +676 -117
  206. data/rust/codegen/src/commands.rs +979 -373
  207. data/rust/codegen/src/constraints.rs +88 -25
  208. data/rust/codegen/src/dependency_planning.rs +128 -34
  209. data/rust/codegen/src/domain_generator.rs +300 -170
  210. data/rust/codegen/src/exemplar.rs +87 -32
  211. data/rust/codegen/src/expr_emitter.rs +54 -6
  212. data/rust/codegen/src/fielded.rs +193 -41
  213. data/rust/codegen/src/hecks_naming.rs +124 -16
  214. data/rust/codegen/src/hecks_naming_tests.rs +81 -0
  215. data/rust/codegen/src/json.rs +80 -44
  216. data/rust/codegen/src/json_codec.rs +585 -149
  217. data/rust/codegen/src/literal.rs +104 -138
  218. data/rust/codegen/src/main.rs +145 -52
  219. data/rust/codegen/src/manifest.rs +76 -14
  220. data/rust/codegen/src/mutations.rs +1212 -239
  221. data/rust/codegen/src/naming.rs +111 -60
  222. data/rust/codegen/src/optional_pass.rs +90 -28
  223. data/rust/codegen/src/ports.rs +5 -4
  224. data/rust/codegen/src/prelude.rs +139 -49
  225. data/rust/codegen/src/queries.rs +601 -226
  226. data/rust/codegen/src/reactions.rs +268 -74
  227. data/rust/codegen/src/read_models.rs +606 -205
  228. data/rust/codegen/src/reference_specs.rs +16 -3
  229. data/rust/codegen/src/registry.rs +128 -79
  230. data/rust/codegen/src/reserved_names.rs +6 -50
  231. data/rust/codegen/src/shared.rs +71 -22
  232. data/rust/codegen/src/sidecars.rs +15 -5
  233. data/rust/codegen/src/skip_reason.rs +8 -2
  234. data/rust/codegen/src/types.rs +306 -108
  235. data/rust/host/Cargo.lock +1 -0
  236. data/rust/host/Cargo.toml +40 -4
  237. data/rust/host/HECKS_RELEASE +1 -1
  238. data/rust/host/clippy.toml +4 -0
  239. data/rust/host/src/api.rs +698 -218
  240. data/rust/host/src/auth.rs +491 -214
  241. data/rust/host/src/b64.rs +91 -0
  242. data/rust/host/src/bin/expr_harness.rs +6 -2
  243. data/rust/host/src/bin/lineage_harness.rs +47 -11
  244. data/rust/host/src/bin/mint_harness.rs +70 -37
  245. data/rust/host/src/bin/validate_harness.rs +16 -8
  246. data/rust/host/src/boot.rs +144 -68
  247. data/rust/host/src/boundary_fuzz/api.rs +130 -32
  248. data/rust/host/src/boundary_fuzz/auth.rs +108 -23
  249. data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
  250. data/rust/host/src/boundary_fuzz/ir.rs +164 -12
  251. data/rust/host/src/boundary_fuzz/server.rs +283 -36
  252. data/rust/host/src/boundary_fuzz/web.rs +56 -8
  253. data/rust/host/src/checkout.rs +78 -32
  254. data/rust/host/src/clock.rs +8 -0
  255. data/rust/host/src/commerce_ir.rs +333 -95
  256. data/rust/host/src/crypto.rs +33 -0
  257. data/rust/host/src/dispatch.rs +990 -331
  258. data/rust/host/src/expr_json.rs +1021 -273
  259. data/rust/host/src/extension.rs +24 -4
  260. data/rust/host/src/field_hints.rs +11 -7
  261. data/rust/host/src/fuzz_support.rs +85 -13
  262. data/rust/host/src/host.rs +100 -0
  263. data/rust/host/src/http.rs +22 -0
  264. data/rust/host/src/ir.rs +326 -16
  265. data/rust/host/src/journal.rs +277 -176
  266. data/rust/host/src/lambda_client.rs +219 -53
  267. data/rust/host/src/lib.rs +16 -4
  268. data/rust/host/src/log.rs +62 -11
  269. data/rust/host/src/mint.rs +1153 -326
  270. data/rust/host/src/needs.rs +185 -44
  271. data/rust/host/src/payments/keystore.rs +197 -45
  272. data/rust/host/src/payments/roles.rs +39 -7
  273. data/rust/host/src/payments/tests.rs +1427 -282
  274. data/rust/host/src/payments.rs +391 -112
  275. data/rust/host/src/platform_signin/attack_tests.rs +5 -5
  276. data/rust/host/src/platform_signin/config.rs +11 -11
  277. data/rust/host/src/platform_signin/cookie.rs +2 -2
  278. data/rust/host/src/platform_signin/extension.rs +2 -1
  279. data/rust/host/src/platform_signin/handler.rs +9 -8
  280. data/rust/host/src/platform_signin/jti.rs +4 -2
  281. data/rust/host/src/platform_signin/jwks.rs +2 -1
  282. data/rust/host/src/platform_signin/jws.rs +2 -37
  283. data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
  284. data/rust/host/src/platform_signin/mode_tests.rs +5 -3
  285. data/rust/host/src/platform_signin/pages.rs +20 -20
  286. data/rust/host/src/platform_signin/session.rs +8 -1
  287. data/rust/host/src/platform_signin/settings.rs +6 -6
  288. data/rust/host/src/platform_signin/strict_json.rs +12 -9
  289. data/rust/host/src/platform_signin/verify.rs +41 -41
  290. data/rust/host/src/platform_signin/wiring_support.rs +13 -18
  291. data/rust/host/src/presentation.rs +207 -63
  292. data/rust/host/src/presentation_write.rs +502 -187
  293. data/rust/host/src/query_step.rs +69 -18
  294. data/rust/host/src/rate_limit.rs +541 -142
  295. data/rust/host/src/reference_transform.rs +77 -25
  296. data/rust/host/src/reference_validate.rs +443 -104
  297. data/rust/host/src/resend.rs +190 -55
  298. data/rust/host/src/secrets.rs +116 -21
  299. data/rust/host/src/server.rs +483 -104
  300. data/rust/host/src/storage_shape.rs +88 -21
  301. data/rust/host/src/test_pg.rs +17 -7
  302. data/rust/host/src/ui_schema.rs +495 -207
  303. data/rust/host/src/wasm_runner.rs +21 -6
  304. data/rust/host/src/web/commerce.rs +101 -23
  305. data/rust/host/src/web/newsletter/cooldown.rs +10 -3
  306. data/rust/host/src/web/newsletter/template.rs +26 -7
  307. data/rust/host/src/web/newsletter/tests.rs +112 -23
  308. data/rust/host/src/web/newsletter.rs +209 -59
  309. data/rust/host/src/web/newsletter_send.rs +251 -79
  310. data/rust/host/src/web/registration_receipt/tests.rs +16 -4
  311. data/rust/host/src/web/registration_receipt.rs +100 -24
  312. data/rust/host/src/web/registrations.rs +1384 -332
  313. data/rust/host/src/web/resend_webhook.rs +138 -80
  314. data/rust/host/src/web.rs +2416 -508
  315. data/rust/lsp/Cargo.toml +36 -0
  316. data/rust/lsp/clippy.toml +4 -0
  317. data/rust/lsp/src/json.rs +29 -22
  318. data/rust/lsp/src/main.rs +81 -31
  319. data/rust/lsp/src/outline.rs +23 -7
  320. data/rust/lsp/src/rpc.rs +17 -4
  321. data/rust/parser/Cargo.toml +36 -0
  322. data/rust/parser/clippy.toml +4 -0
  323. data/rust/parser/src/build/closed_sets.rs +6 -1
  324. data/rust/parser/src/build/identity.rs +4 -3
  325. data/rust/parser/src/build/mod.rs +1 -1
  326. data/rust/parser/src/build/named_pattern.rs +4 -1
  327. data/rust/parser/src/build/naming.rs +2 -80
  328. data/rust/parser/src/build/pattern_subset.rs +6 -8
  329. data/rust/parser/src/build/query_derive.rs +3 -1
  330. data/rust/parser/src/build/query_inference.rs +14 -5
  331. data/rust/parser/src/build/read_model.rs +4 -1
  332. data/rust/parser/src/build/references.rs +4 -3
  333. data/rust/parser/src/canonical.rs +34 -15
  334. data/rust/parser/src/emit.rs +187 -105
  335. data/rust/parser/src/expr/ast_json.rs +134 -42
  336. data/rust/parser/src/expr/evaluator.rs +27 -9
  337. data/rust/parser/src/expr/mod.rs +54 -13
  338. data/rust/parser/src/expr/resolver.rs +299 -92
  339. data/rust/parser/src/ir.rs +35 -3
  340. data/rust/parser/src/keywords.rs +7168 -516
  341. data/rust/parser/src/lex.rs +11 -25
  342. data/rust/parser/src/lib.rs +1 -0
  343. data/rust/parser/src/main.rs +65 -6
  344. data/rust/parser/src/parse/adapter.rs +157 -0
  345. data/rust/parser/src/parse/aggregate.rs +19 -10
  346. data/rust/parser/src/parse/chapter.rs +16 -17
  347. data/rust/parser/src/parse/command.rs +35 -16
  348. data/rust/parser/src/parse/domain_port.rs +4 -7
  349. data/rust/parser/src/parse/entity.rs +21 -11
  350. data/rust/parser/src/parse/hecksagon.rs +10 -4
  351. data/rust/parser/src/parse/lifecycle.rs +9 -5
  352. data/rust/parser/src/parse/mod.rs +22 -91
  353. data/rust/parser/src/parse/needs.rs +21 -12
  354. data/rust/parser/src/parse/policy.rs +2 -2
  355. data/rust/parser/src/parse/process_manager.rs +9 -11
  356. data/rust/parser/src/parse/query.rs +8 -4
  357. data/rust/parser/src/parse/read_model.rs +57 -16
  358. data/rust/parser/src/parse/value_object.rs +7 -2
  359. data/rust/parser/src/parse/world.rs +322 -0
  360. data/rust/parser/src/ruby_value.rs +7 -6
  361. data/rust/src/exemplar/json.rs +4 -4
  362. data/rust/src/exemplar/mutations.rs +18 -0
  363. data/rust/src/exemplar/read_models.rs +1 -10
  364. data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
  365. data/rust/src/kernel/attribute_shapes/list.rs +2 -2
  366. data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
  367. data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
  368. data/rust/src/kernel/cli.rs +229 -112
  369. data/rust/src/kernel/dispatch.rs +323 -115
  370. data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
  371. data/rust/src/kernel/expr.rs +203 -86
  372. data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
  373. data/rust/src/kernel/expression_operators/comparison.rs +87 -2
  374. data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
  375. data/rust/src/kernel/expression_operators/logical.rs +9 -3
  376. data/rust/src/kernel/expression_operators/membership.rs +3 -1
  377. data/rust/src/kernel/expression_operators/mod.rs +34 -21
  378. data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
  379. data/rust/src/kernel/expression_operators/positional.rs +84 -30
  380. data/rust/src/kernel/expression_operators/presence.rs +52 -13
  381. data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
  382. data/rust/src/kernel/expression_operators/sized.rs +24 -8
  383. data/rust/src/kernel/expression_operators/text.rs +120 -27
  384. data/rust/src/kernel/expression_operators/to_string.rs +10 -4
  385. data/rust/src/kernel/json.rs +230 -148
  386. data/rust/src/kernel/mod.rs +46 -33
  387. data/rust/src/kernel/named_query.rs +63 -15
  388. data/rust/src/kernel/needs.rs +112 -25
  389. data/rust/src/kernel/orchestrate.rs +980 -581
  390. data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
  391. data/rust/src/kernel/pattern.rs +107 -34
  392. data/rust/src/kernel/query_comparators.rs +118 -41
  393. data/rust/src/kernel/query_ordering.rs +13 -5
  394. data/rust/src/kernel/read_model.rs +479 -181
  395. data/rust/src/kernel/reference_lookup.rs +122 -26
  396. data/rust/src/kernel/repository.rs +293 -66
  397. data/rust/src/kernel/routing.rs +58 -42
  398. data/rust/src/kernel/ruby_float.rs +139 -0
  399. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
  400. data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
  401. data/rust/src/kernel/vocab/field_hint.rs +19 -25
  402. data/rust/src/kernel/vocab/mod.rs +6 -6
  403. data/rust/src/kernel/vocab/query_comparator.rs +23 -24
  404. data/rust/src/kernel/vocab/refusal_template.rs +501 -297
  405. data/rust/src/lib.rs +50 -0
  406. data/rust/src/main.rs +4 -1
  407. data/rust/web/Cargo.toml +36 -0
  408. data/rust/web/clippy.toml +4 -0
  409. metadata +82 -15
  410. data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
  411. data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
  412. data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
  413. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
  414. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
  415. data/lib/hecks/ports/authentication.port +0 -6
  416. data/lib/hecks/ports/authentication.rb +0 -67
  417. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
  418. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
  419. data/rust/host/src/approval.rs +0 -652
@@ -21,8 +21,20 @@ pub struct StoredKeys {
21
21
 
22
22
  impl StoredKeys {
23
23
  fn from_json(value: &Value) -> Self {
24
- let text = |field: &str| value.get(field).and_then(|v| v.as_str()).unwrap_or_default().to_string();
25
- Self { secret_key: text("secret_key"), publishable_key: text("publishable_key"), webhook_secret: text("webhook_secret"), webhook_endpoint_id: text("webhook_endpoint_id"), saved_at: text("saved_at") }
24
+ let text = |field: &str| {
25
+ value
26
+ .get(field)
27
+ .and_then(|v| v.as_str())
28
+ .unwrap_or_default()
29
+ .to_string()
30
+ };
31
+ Self {
32
+ secret_key: text("secret_key"),
33
+ publishable_key: text("publishable_key"),
34
+ webhook_secret: text("webhook_secret"),
35
+ webhook_endpoint_id: text("webhook_endpoint_id"),
36
+ saved_at: text("saved_at"),
37
+ }
26
38
  }
27
39
 
28
40
  fn to_json(&self) -> Value {
@@ -45,8 +57,16 @@ pub struct StoredDocument {
45
57
 
46
58
  impl StoredDocument {
47
59
  fn from_json(value: &Value) -> Self {
48
- let entry = |mode: &str| value.get(mode).filter(|v| v.is_object()).map(StoredKeys::from_json);
49
- Self { test: entry("test"), live: entry("live") }
60
+ let entry = |mode: &str| {
61
+ value
62
+ .get(mode)
63
+ .filter(|v| v.is_object())
64
+ .map(StoredKeys::from_json)
65
+ };
66
+ Self {
67
+ test: entry("test"),
68
+ live: entry("live"),
69
+ }
50
70
  }
51
71
 
52
72
  fn to_json(&self) -> Value {
@@ -61,17 +81,29 @@ impl StoredDocument {
61
81
 
62
82
  /// The entry saved for `mode` ("test" or "live").
63
83
  pub fn mode(&self, mode: &str) -> Option<&StoredKeys> {
64
- if mode == "live" { self.live.as_ref() } else { self.test.as_ref() }
84
+ if mode == "live" {
85
+ self.live.as_ref()
86
+ } else {
87
+ self.test.as_ref()
88
+ }
65
89
  }
66
90
 
67
91
  /// Replaces (or with `None` removes) the entry for `mode`.
68
92
  pub fn set(&mut self, mode: &str, keys: Option<StoredKeys>) {
69
- if mode == "live" { self.live = keys } else { self.test = keys }
93
+ if mode == "live" {
94
+ self.live = keys;
95
+ } else {
96
+ self.test = keys;
97
+ }
70
98
  }
71
99
 
72
100
  /// Every webhook signing secret saved, for either mode.
73
101
  pub fn webhook_secrets(&self) -> impl Iterator<Item = &str> {
74
- [self.test.as_ref(), self.live.as_ref()].into_iter().flatten().map(|k| k.webhook_secret.as_str()).filter(|s| !s.is_empty())
102
+ [self.test.as_ref(), self.live.as_ref()]
103
+ .into_iter()
104
+ .flatten()
105
+ .map(|k| k.webhook_secret.as_str())
106
+ .filter(|s| !s.is_empty())
75
107
  }
76
108
  }
77
109
 
@@ -94,7 +126,10 @@ pub struct AwsRawStore {
94
126
  impl AwsRawStore {
95
127
  /// A store over the secret named `secret_id`; nothing is fetched until the first use.
96
128
  pub fn new(secret_id: String) -> Self {
97
- Self { secret_id, fetcher: tokio::sync::OnceCell::new() }
129
+ Self {
130
+ secret_id,
131
+ fetcher: tokio::sync::OnceCell::new(),
132
+ }
98
133
  }
99
134
 
100
135
  async fn fetcher(&self) -> &AwsSecretFetcher {
@@ -105,11 +140,17 @@ impl AwsRawStore {
105
140
  #[async_trait::async_trait]
106
141
  impl RawStore for AwsRawStore {
107
142
  async fn read(&self) -> anyhow::Result<Option<String>> {
108
- self.fetcher().await.fetch_secret_string_if_present(&self.secret_id).await
143
+ self.fetcher()
144
+ .await
145
+ .fetch_secret_string_if_present(&self.secret_id)
146
+ .await
109
147
  }
110
148
 
111
149
  async fn write(&self, document: &str) -> anyhow::Result<()> {
112
- self.fetcher().await.put_secret_string(&self.secret_id, document).await
150
+ self.fetcher()
151
+ .await
152
+ .put_secret_string(&self.secret_id, document)
153
+ .await
113
154
  }
114
155
  }
115
156
 
@@ -123,12 +164,21 @@ pub struct KeyStore {
123
164
  impl KeyStore {
124
165
  /// A cached view over `raw`, starting with an empty cache.
125
166
  pub fn new(raw: Arc<dyn RawStore>) -> Self {
126
- Self { raw, cache: Mutex::new(None) }
167
+ Self {
168
+ raw,
169
+ cache: Mutex::new(None),
170
+ }
127
171
  }
128
172
 
129
173
  fn cached(&self) -> Option<Arc<StoredDocument>> {
130
- let cache = self.cache.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
131
- cache.as_ref().filter(|(at, _)| at.elapsed() < CACHE_TTL).map(|(_, document)| document.clone())
174
+ let cache = self
175
+ .cache
176
+ .lock()
177
+ .unwrap_or_else(std::sync::PoisonError::into_inner);
178
+ cache
179
+ .as_ref()
180
+ .filter(|(at, _)| at.elapsed() < CACHE_TTL)
181
+ .map(|(_, document)| document.clone())
132
182
  }
133
183
 
134
184
  /// The document, from the cache when it is under a minute old.
@@ -143,11 +193,18 @@ impl KeyStore {
143
193
  pub async fn refresh(&self) -> anyhow::Result<Arc<StoredDocument>> {
144
194
  let text = self.raw.read().await?;
145
195
  let document = match text {
146
- Some(text) => StoredDocument::from_json(&serde_json::from_str::<Value>(&text).map_err(|_| anyhow::anyhow!("the stored payment keys are not valid JSON"))?),
196
+ Some(text) => StoredDocument::from_json(
197
+ &serde_json::from_str::<Value>(&text)
198
+ .map_err(|_| anyhow::anyhow!("the stored payment keys are not valid JSON"))?,
199
+ ),
147
200
  None => StoredDocument::default(),
148
201
  };
149
202
  let document = Arc::new(document);
150
- *self.cache.lock().unwrap_or_else(|poisoned| poisoned.into_inner()) = Some((Instant::now(), document.clone()));
203
+ *self
204
+ .cache
205
+ .lock()
206
+ .unwrap_or_else(std::sync::PoisonError::into_inner) =
207
+ Some((Instant::now(), document.clone()));
151
208
  Ok(document)
152
209
  }
153
210
 
@@ -165,40 +222,59 @@ impl KeyStore {
165
222
 
166
223
  /// Forgets the cached document.
167
224
  pub fn invalidate(&self) {
168
- *self.cache.lock().unwrap_or_else(|poisoned| poisoned.into_inner()) = None;
225
+ *self
226
+ .cache
227
+ .lock()
228
+ .unwrap_or_else(std::sync::PoisonError::into_inner) = None;
169
229
  }
170
230
  }
171
231
 
172
232
  /// The secret's name from a raw `PAYMENTS_ACCOUNT_SECRET_ID` value, or `None`
173
233
  /// when it is unset or blank.
174
234
  fn configured_secret_id(configured: Option<&str>) -> Option<String> {
175
- configured.map(str::trim).filter(|id| !id.is_empty()).map(String::from)
235
+ configured
236
+ .map(str::trim)
237
+ .filter(|id| !id.is_empty())
238
+ .map(String::from)
176
239
  }
177
240
 
178
241
  /// Whether this process runs as a container task or a Lambda function on AWS.
179
242
  fn on_aws() -> bool {
180
- ["ECS_CONTAINER_METADATA_URI_V4", "AWS_LAMBDA_FUNCTION_NAME"].iter().any(|name| std::env::var(name).is_ok_and(|v| !v.is_empty()))
243
+ ["ECS_CONTAINER_METADATA_URI_V4", "AWS_LAMBDA_FUNCTION_NAME"]
244
+ .iter()
245
+ .any(|name| std::env::var(name).is_ok_and(|v| !v.is_empty()))
181
246
  }
182
247
 
183
248
  /// The message that stops the boot when checkout is enabled on AWS without a
184
249
  /// secret to keep the business's keys in; `None` when the boot may go on.
185
- pub fn refusal_at_boot(checkout_enabled: bool, on_aws: bool, configured: Option<&str>) -> Option<String> {
250
+ pub fn refusal_at_boot(
251
+ checkout_enabled: bool,
252
+ on_aws: bool,
253
+ configured: Option<&str>,
254
+ ) -> Option<String> {
186
255
  let missing = checkout_enabled && on_aws && configured_secret_id(configured).is_none();
187
256
  missing.then(|| format!("{SECRET_ID_VARIABLE} is required on AWS when checkout is enabled: name the Secrets Manager secret that holds the business's payment keys"))
188
257
  }
189
258
 
190
259
  /// `refusal_at_boot` for this process's environment.
191
260
  pub fn check_boot(checkout_enabled: bool) -> Result<(), String> {
192
- match refusal_at_boot(checkout_enabled, on_aws(), std::env::var(SECRET_ID_VARIABLE).ok().as_deref()) {
193
- Some(message) => Err(message),
194
- None => Ok(()),
195
- }
261
+ refusal_at_boot(
262
+ checkout_enabled,
263
+ on_aws(),
264
+ std::env::var(SECRET_ID_VARIABLE).ok().as_deref(),
265
+ )
266
+ .map_or(Ok(()), Err)
196
267
  }
197
268
 
198
269
  /// The process-wide store, built once so its cache is shared by every request.
199
270
  pub fn default_store() -> Option<Arc<KeyStore>> {
200
271
  static STORE: OnceLock<Option<Arc<KeyStore>>> = OnceLock::new();
201
- STORE.get_or_init(|| configured_secret_id(std::env::var(SECRET_ID_VARIABLE).ok().as_deref()).map(|id| Arc::new(KeyStore::new(Arc::new(AwsRawStore::new(id)))))).clone()
272
+ STORE
273
+ .get_or_init(|| {
274
+ configured_secret_id(std::env::var(SECRET_ID_VARIABLE).ok().as_deref())
275
+ .map(|id| Arc::new(KeyStore::new(Arc::new(AwsRawStore::new(id)))))
276
+ })
277
+ .clone()
202
278
  }
203
279
 
204
280
  /// `YYYY-MM-DDTHH:MM:SSZ` for a Unix time in seconds (proleptic Gregorian,
@@ -208,18 +284,33 @@ pub fn utc_timestamp(seconds: u64) -> String {
208
284
  let z = days + 719_468;
209
285
  let era = z.div_euclid(146_097);
210
286
  let day_of_era = z.rem_euclid(146_097);
211
- let year_of_era = (day_of_era - day_of_era / 1_460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
287
+ let year_of_era =
288
+ (day_of_era - day_of_era / 1_460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
212
289
  let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
213
290
  let month_shifted = (5 * day_of_year + 2) / 153;
214
291
  let day = day_of_year - (153 * month_shifted + 2) / 5 + 1;
215
- let month = if month_shifted < 10 { month_shifted + 3 } else { month_shifted - 9 };
292
+ let month = if month_shifted < 10 {
293
+ month_shifted + 3
294
+ } else {
295
+ month_shifted - 9
296
+ };
216
297
  let year = year_of_era + era * 400 + i64::from(month <= 2);
217
- format!("{year:04}-{month:02}-{day:02}T{:02}:{:02}:{:02}Z", rest / 3_600, rest % 3_600 / 60, rest % 60)
298
+ format!(
299
+ "{year:04}-{month:02}-{day:02}T{:02}:{:02}:{:02}Z",
300
+ rest / 3_600,
301
+ rest % 3_600 / 60,
302
+ rest % 60
303
+ )
218
304
  }
219
305
 
220
306
  /// The current time as `utc_timestamp` writes it.
221
307
  pub fn now_timestamp() -> String {
222
- utc_timestamp(std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs())
308
+ utc_timestamp(
309
+ std::time::SystemTime::now()
310
+ .duration_since(std::time::UNIX_EPOCH)
311
+ .unwrap_or_default()
312
+ .as_secs(),
313
+ )
223
314
  }
224
315
 
225
316
  /// An in-memory stand-in for Secrets Manager that counts its calls, for the
@@ -239,15 +330,23 @@ pub(crate) struct MemoryStore {
239
330
  impl RawStore for MemoryStore {
240
331
  async fn read(&self) -> anyhow::Result<Option<String>> {
241
332
  self.reads.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
242
- Ok(self.document.lock().unwrap().clone())
333
+ Ok(self
334
+ .document
335
+ .lock()
336
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
337
+ .clone())
243
338
  }
244
339
 
245
340
  async fn write(&self, document: &str) -> anyhow::Result<()> {
246
- self.writes.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
341
+ self.writes
342
+ .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
247
343
  if self.fail_writes.load(std::sync::atomic::Ordering::SeqCst) {
248
344
  return Err(anyhow::anyhow!("writing the secret failed"));
249
345
  }
250
- *self.document.lock().unwrap() = Some(document.to_string());
346
+ *self
347
+ .document
348
+ .lock()
349
+ .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(document.to_string());
251
350
  Ok(())
252
351
  }
253
352
  }
@@ -258,7 +357,13 @@ mod tests {
258
357
  use std::sync::atomic::Ordering;
259
358
 
260
359
  fn keys(secret: &str) -> StoredKeys {
261
- StoredKeys { secret_key: secret.into(), publishable_key: "pk_test_x".into(), webhook_secret: "whsec_x".into(), webhook_endpoint_id: "we_1".into(), saved_at: "2026-09-26T05:00:00Z".into() }
360
+ StoredKeys {
361
+ secret_key: secret.into(),
362
+ publishable_key: "pk_test_x".into(),
363
+ webhook_secret: "whsec_x".into(),
364
+ webhook_endpoint_id: "we_1".into(),
365
+ saved_at: "2026-09-26T05:00:00Z".into(),
366
+ }
262
367
  }
263
368
 
264
369
  #[tokio::test]
@@ -267,12 +372,23 @@ mod tests {
267
372
  let store = KeyStore::new(raw.clone());
268
373
  assert!(store.document().await.unwrap().test.is_none());
269
374
  assert!(store.document().await.unwrap().test.is_none());
270
- assert_eq!(raw.reads.load(Ordering::SeqCst), 1, "the second read was served from the cache");
271
-
272
- store.update(|doc| doc.set("test", Some(keys("rk_test_one")))).await.unwrap();
375
+ assert_eq!(
376
+ raw.reads.load(Ordering::SeqCst),
377
+ 1,
378
+ "the second read was served from the cache"
379
+ );
380
+
381
+ store
382
+ .update(|doc| doc.set("test", Some(keys("rk_test_one"))))
383
+ .await
384
+ .unwrap();
273
385
  let reads_after_update = raw.reads.load(Ordering::SeqCst);
274
386
  let document = store.document().await.unwrap();
275
- assert_eq!(document.test.as_ref().unwrap().secret_key, "rk_test_one", "the write invalidated the cache");
387
+ assert_eq!(
388
+ document.test.as_ref().unwrap().secret_key,
389
+ "rk_test_one",
390
+ "the write invalidated the cache"
391
+ );
276
392
  assert_eq!(raw.reads.load(Ordering::SeqCst), reads_after_update + 1);
277
393
  assert_eq!(raw.writes.load(Ordering::SeqCst), 1);
278
394
  }
@@ -281,20 +397,38 @@ mod tests {
281
397
  async fn the_stored_json_has_exactly_the_agreed_shape() {
282
398
  let raw = Arc::new(MemoryStore::default());
283
399
  let store = KeyStore::new(raw.clone());
284
- store.update(|doc| doc.set("live", Some(keys("rk_live_one")))).await.unwrap();
285
- let stored: serde_json::Value = serde_json::from_str(raw.document.lock().unwrap().as_ref().unwrap()).unwrap();
400
+ store
401
+ .update(|doc| doc.set("live", Some(keys("rk_live_one"))))
402
+ .await
403
+ .unwrap();
404
+ let stored: serde_json::Value =
405
+ serde_json::from_str(raw.document.lock().unwrap().as_ref().unwrap()).unwrap();
286
406
  assert!(stored.get("test").is_none(), "an unsaved mode has no entry");
287
407
  let live = stored["live"].as_object().unwrap();
288
408
  let mut names: Vec<&str> = live.keys().map(String::as_str).collect();
289
409
  names.sort_unstable();
290
- assert_eq!(names, ["publishable_key", "saved_at", "secret_key", "webhook_endpoint_id", "webhook_secret"]);
410
+ assert_eq!(
411
+ names,
412
+ [
413
+ "publishable_key",
414
+ "saved_at",
415
+ "secret_key",
416
+ "webhook_endpoint_id",
417
+ "webhook_secret"
418
+ ]
419
+ );
291
420
  }
292
421
 
293
422
  #[tokio::test]
294
423
  async fn an_unreadable_document_is_an_error_that_does_not_quote_it() {
295
424
  let raw = Arc::new(MemoryStore::default());
296
425
  *raw.document.lock().unwrap() = Some("rk_live_SECRET not json".to_string());
297
- let error = KeyStore::new(raw).document().await.err().expect("an error").to_string();
426
+ let error = KeyStore::new(raw)
427
+ .document()
428
+ .await
429
+ .err()
430
+ .expect("an error")
431
+ .to_string();
298
432
  assert!(!error.contains("rk_live_SECRET"), "{error}");
299
433
  }
300
434
 
@@ -303,21 +437,39 @@ mod tests {
303
437
  assert_eq!(configured_secret_id(None), None);
304
438
  assert_eq!(configured_secret_id(Some("")), None);
305
439
  assert_eq!(configured_secret_id(Some(" ")), None);
306
- assert_eq!(configured_secret_id(Some(" payments-keys ")), Some("payments-keys".to_string()));
440
+ assert_eq!(
441
+ configured_secret_id(Some(" payments-keys ")),
442
+ Some("payments-keys".to_string())
443
+ );
307
444
  }
308
445
 
309
446
  #[test]
310
447
  fn checkout_on_aws_without_a_secret_id_refuses_the_boot_and_names_the_variable() {
311
448
  let message = refusal_at_boot(true, true, None).expect("refused");
312
449
  assert!(message.contains("PAYMENTS_ACCOUNT_SECRET_ID"), "{message}");
313
- assert!(refusal_at_boot(true, true, Some(" ")).is_some(), "a blank value is unset");
450
+ assert!(
451
+ refusal_at_boot(true, true, Some(" ")).is_some(),
452
+ "a blank value is unset"
453
+ );
314
454
  }
315
455
 
316
456
  #[test]
317
457
  fn every_other_combination_boots() {
318
- assert_eq!(refusal_at_boot(true, true, Some("payments-keys")), None, "set: unchanged");
319
- assert_eq!(refusal_at_boot(true, false, None), None, "off AWS: only environment keys");
320
- assert_eq!(refusal_at_boot(false, true, None), None, "checkout off: payments are not served");
458
+ assert_eq!(
459
+ refusal_at_boot(true, true, Some("payments-keys")),
460
+ None,
461
+ "set: unchanged"
462
+ );
463
+ assert_eq!(
464
+ refusal_at_boot(true, false, None),
465
+ None,
466
+ "off AWS: only environment keys"
467
+ );
468
+ assert_eq!(
469
+ refusal_at_boot(false, true, None),
470
+ None,
471
+ "checkout off: payments are not served"
472
+ );
321
473
  assert_eq!(refusal_at_boot(false, false, None), None);
322
474
  }
323
475
 
@@ -24,7 +24,9 @@ pub fn default_roles() -> Vec<String> {
24
24
 
25
25
  /// Parses the variable's value; `None` (unset) gives the default list.
26
26
  pub fn parse(raw: Option<&str>) -> Result<Vec<String>, String> {
27
- let Some(raw) = raw else { return Ok(default_roles()) };
27
+ let Some(raw) = raw else {
28
+ return Ok(default_roles());
29
+ };
28
30
  let refuse = |why: &str| Err(format!("{VARIABLE} is invalid: {why}"));
29
31
  let entries: Vec<&str> = raw.split(',').map(str::trim).collect();
30
32
  if entries.len() > MAX_ENTRIES {
@@ -33,13 +35,21 @@ pub fn parse(raw: Option<&str>) -> Result<Vec<String>, String> {
33
35
  for (index, entry) in entries.iter().enumerate() {
34
36
  let position = index + 1;
35
37
  if entry.is_empty() {
36
- return refuse(&format!("entry {position} is empty; name at least one role and no blank entries"));
38
+ return refuse(&format!(
39
+ "entry {position} is empty; name at least one role and no blank entries"
40
+ ));
37
41
  }
38
42
  if entry.len() > MAX_ENTRY_LENGTH {
39
- return refuse(&format!("entry {position} is longer than {MAX_ENTRY_LENGTH} characters"));
43
+ return refuse(&format!(
44
+ "entry {position} is longer than {MAX_ENTRY_LENGTH} characters"
45
+ ));
40
46
  }
41
47
  let starts_well = entry.starts_with(|c: char| c.is_ascii_alphanumeric());
42
- if !starts_well || !entry.chars().all(|c| c.is_ascii_alphanumeric() || c == ' ' || c == '-') {
48
+ if !starts_well
49
+ || !entry
50
+ .chars()
51
+ .all(|c| c.is_ascii_alphanumeric() || c == ' ' || c == '-')
52
+ {
43
53
  return refuse(&format!("entry {position} is not a role name: letters, digits, spaces and hyphens only, starting with a letter or digit"));
44
54
  }
45
55
  }
@@ -48,7 +58,10 @@ pub fn parse(raw: Option<&str>) -> Result<Vec<String>, String> {
48
58
 
49
59
  /// Reads the setting into `cell` the first time and never again, whatever the environment
50
60
  /// does afterwards. `read` stands for the environment lookup.
51
- pub fn read_once(cell: &OnceLock<Result<Vec<String>, String>>, read: impl FnOnce() -> Option<String>) -> &Result<Vec<String>, String> {
61
+ pub fn read_once(
62
+ cell: &OnceLock<Result<Vec<String>, String>>,
63
+ read: impl FnOnce() -> Option<String>,
64
+ ) -> &Result<Vec<String>, String> {
52
65
  cell.get_or_init(|| parse(read().as_deref()))
53
66
  }
54
67
 
@@ -112,14 +125,33 @@ mod tests {
112
125
  #[test]
113
126
  fn no_wildcard_and_no_hierarchy() {
114
127
  assert!(parse(Some("*")).is_err());
115
- assert!(!manages(&roles(Some("Admin")), "Owner"), "naming Admin does not admit Owner");
128
+ assert!(
129
+ !manages(&roles(Some("Admin")), "Owner"),
130
+ "naming Admin does not admit Owner"
131
+ );
116
132
  }
117
133
 
118
134
  #[test]
119
135
  fn an_invalid_setting_is_refused_naming_the_variable() {
120
136
  let too_many = ["Role"; MAX_ENTRIES + 1].join(",");
121
137
  let too_long = "A".repeat(MAX_ENTRY_LENGTH + 1);
122
- for bad in ["", " ", ",", "Owner,", ",Owner", "Owner,,Admin", "Own/er", "Owner\nAdmin", "Own\u{7}er", "Admin;Owner", "-Admin", "Ad_min", "Régisseur", too_many.as_str(), too_long.as_str()] {
138
+ for bad in [
139
+ "",
140
+ " ",
141
+ ",",
142
+ "Owner,",
143
+ ",Owner",
144
+ "Owner,,Admin",
145
+ "Own/er",
146
+ "Owner\nAdmin",
147
+ "Own\u{7}er",
148
+ "Admin;Owner",
149
+ "-Admin",
150
+ "Ad_min",
151
+ "Régisseur",
152
+ too_many.as_str(),
153
+ too_long.as_str(),
154
+ ] {
123
155
  let message = parse(Some(bad)).expect_err(bad);
124
156
  assert!(message.starts_with(VARIABLE), "{message}");
125
157
  }