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
@@ -1,7 +1,11 @@
1
1
  //! Lambda custom-runtime entry point (or a long-lived server under
2
2
  //! HECKS_SERVE_MODE=1): boots Postgres/wasm state, then dispatches events.
3
3
 
4
- use crate::{approval, auth, extension, ir, journal, lambda_client, log, mint, rate_limit, secrets, server, storage_shape};
4
+ use crate::host::Host;
5
+ use crate::{
6
+ auth, extension, ir, journal, lambda_client, log, mint, rate_limit, secrets, server,
7
+ storage_shape,
8
+ };
5
9
 
6
10
  use lambda_runtime::{service_fn, Error, LambdaEvent};
7
11
  use std::path::PathBuf;
@@ -37,8 +41,10 @@ pub async fn run() -> Result<(), Error> {
37
41
  let secret_fetcher = secrets::AwsSecretFetcher::from_env().await;
38
42
  let database_url = match std::env::var("DB_SECRET_ARN") {
39
43
  Ok(secret_arn) => {
40
- let db_host = std::env::var("DB_HOST").map_err(|_| "DB_HOST is required when DB_SECRET_ARN is set")?;
41
- let db_name = std::env::var("DB_NAME").map_err(|_| "DB_NAME is required when DB_SECRET_ARN is set")?;
44
+ let db_host = std::env::var("DB_HOST")
45
+ .map_err(|_| "DB_HOST is required when DB_SECRET_ARN is set")?;
46
+ let db_name = std::env::var("DB_NAME")
47
+ .map_err(|_| "DB_NAME is required when DB_SECRET_ARN is set")?;
42
48
  let secret_json = secret_fetcher
43
49
  .fetch_secret_string(&secret_arn)
44
50
  .await
@@ -56,10 +62,7 @@ pub async fn run() -> Result<(), Error> {
56
62
  // Secrets-Manager-at-cold-start approach as DB_SECRET_ARN above,
57
63
  // applied to auth.rs's GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET and
58
64
  // web.rs's SESSION_SECRET.
59
- //
60
- // Safety: this runs before any request is dispatched, so nothing
61
- // else reads or writes the environment concurrently with these
62
- // `set_var` calls.
65
+ // They go into `secrets::provide`, not the process environment.
63
66
  if let Ok(oauth_secret_id) = std::env::var("GOOGLE_OAUTH_SECRET_ID") {
64
67
  let secret_json = secret_fetcher
65
68
  .fetch_secret_string(&oauth_secret_id)
@@ -67,10 +70,8 @@ pub async fn run() -> Result<(), Error> {
67
70
  .map_err(|e| format!("fetching GOOGLE_OAUTH_SECRET_ID from Secrets Manager: {e:#}"))?;
68
71
  let client_id = secrets::extract_field(&secret_json, "client_id")?;
69
72
  let client_secret = secrets::extract_field(&secret_json, "client_secret")?;
70
- unsafe {
71
- std::env::set_var("GOOGLE_CLIENT_ID", client_id);
72
- std::env::set_var("GOOGLE_CLIENT_SECRET", client_secret);
73
- }
73
+ secrets::provide("GOOGLE_CLIENT_ID", client_id);
74
+ secrets::provide("GOOGLE_CLIENT_SECRET", client_secret);
74
75
  }
75
76
  if let Ok(session_secret_arn) = std::env::var("SESSION_SECRET_ARN") {
76
77
  let secret_json = secret_fetcher
@@ -78,9 +79,7 @@ pub async fn run() -> Result<(), Error> {
78
79
  .await
79
80
  .map_err(|e| format!("fetching SESSION_SECRET_ARN from Secrets Manager: {e:#}"))?;
80
81
  let session_secret = secrets::extract_field(&secret_json, "session_secret")?;
81
- unsafe {
82
- std::env::set_var("SESSION_SECRET", session_secret);
83
- }
82
+ secrets::provide("SESSION_SECRET", session_secret);
84
83
  }
85
84
  for extension in extension::installed() {
86
85
  extension.boot_secrets(&secret_fetcher).await;
@@ -125,7 +124,8 @@ pub async fn run() -> Result<(), Error> {
125
124
  }
126
125
  // NoTlsStream and RustlsStream can't unify in one branch, so the
127
126
  // two paths are spawned separately.
128
- let connect_phase = log::phase_with("db_connect", serde_json::json!({ "tls": !local_postgres }));
127
+ let connect_phase =
128
+ log::phase_with("db_connect", serde_json::json!({ "tls": !local_postgres }));
129
129
  let client = if local_postgres {
130
130
  let (client, connection) = config
131
131
  .connect(tokio_postgres::NoTls)
@@ -164,11 +164,17 @@ pub async fn run() -> Result<(), Error> {
164
164
  // existing schema is the ordinary case on every boot after the first.
165
165
  if let Some(schema) = &schema {
166
166
  client
167
- .batch_execute(&format!("CREATE SCHEMA IF NOT EXISTS {}", journal::quote_ident(schema)))
167
+ .batch_execute(&format!(
168
+ "CREATE SCHEMA IF NOT EXISTS {}",
169
+ journal::quote_ident(schema)
170
+ ))
168
171
  .await
169
172
  .map_err(|e| format!("creating schema {schema:?}: {e:#}"))?;
170
173
  client
171
- .batch_execute(&format!("SET search_path TO {}", journal::quote_ident(schema)))
174
+ .batch_execute(&format!(
175
+ "SET search_path TO {}",
176
+ journal::quote_ident(schema)
177
+ ))
172
178
  .await
173
179
  .map_err(|e| format!("setting search_path to {schema:?}: {e:#}"))?;
174
180
  }
@@ -187,7 +193,11 @@ pub async fn run() -> Result<(), Error> {
187
193
  let mut aggregates: Vec<mint::Aggregate> = ir::lineage_capable_aggregates(ir)
188
194
  .into_iter()
189
195
  .map(|(qualified, storage_name)| {
190
- let name = qualified.rsplit("::").next().unwrap_or(&qualified).to_string();
196
+ let name = qualified
197
+ .rsplit("::")
198
+ .next()
199
+ .unwrap_or(&qualified)
200
+ .to_string();
191
201
  mint::Aggregate { name, storage_name }
192
202
  })
193
203
  .collect();
@@ -195,7 +205,12 @@ pub async fn run() -> Result<(), Error> {
195
205
  // aggregate can be vendored, so it may be missing from this domain's
196
206
  // lineage-capable list. Mint its head anyway for Google-auth to read.
197
207
  if let Some(membership) = ir::membership_provider(ir) {
198
- let name = membership.aggregate.rsplit("::").next().unwrap_or(&membership.aggregate).to_string();
208
+ let name = membership
209
+ .aggregate
210
+ .rsplit("::")
211
+ .next()
212
+ .unwrap_or(&membership.aggregate)
213
+ .to_string();
199
214
  let storage_name = journal::snake(&name);
200
215
  if !aggregates.iter().any(|a| a.storage_name == storage_name) {
201
216
  aggregates.push(mint::Aggregate { name, storage_name });
@@ -205,14 +220,16 @@ pub async fn run() -> Result<(), Error> {
205
220
  // The era-partitioned lineage subsystem is provisioned only when
206
221
  // something needs it: a lineage-capable aggregate, or Google auth
207
222
  // configured (ADR 0034 requires it for Member sessions either way).
208
- let needs_lineage = !aggregates.is_empty() || std::env::var("GOOGLE_CLIENT_ID").is_ok();
223
+ let needs_lineage = !aggregates.is_empty() || secrets::var("GOOGLE_CLIENT_ID").is_ok();
209
224
 
210
225
  let era: Option<i32> = if needs_lineage {
211
226
  // Idempotent (gated by `provisioner?`); must run before the
212
227
  // first `journal::held_eras` call, since `hecks_eras` doesn't
213
228
  // exist yet on a truly fresh Postgres instance.
214
229
  let phase = log::phase("ensure_base");
215
- mint::ensure_base(&client, &domain).await.map_err(|e| format!("provisioning hecks_eras for {domain}: {e:#}"))?;
230
+ mint::ensure_base(&client, &domain)
231
+ .await
232
+ .map_err(|e| format!("provisioning hecks_eras for {domain}: {e:#}"))?;
216
233
  phase.end();
217
234
 
218
235
  // Compares this binary's own shape hash (`storage_shape`, over
@@ -222,20 +239,26 @@ pub async fn run() -> Result<(), Error> {
222
239
  // `hecks scaffold_translation` (no edge covers it) or
223
240
  // `hecks audit_translation --approve` (needs review) otherwise.
224
241
  let era_phase = log::phase("era_resolve");
225
- let held = journal::held_eras(&client, &domain).await.map_err(|e| format!("checking hecks_eras for {domain}: {e:#}"))?;
242
+ let held = journal::held_eras(&client, &domain)
243
+ .await
244
+ .map_err(|e| format!("checking hecks_eras for {domain}: {e:#}"))?;
226
245
 
227
246
  // The decision itself is a pure, unit-tested function
228
247
  // (`mint::decide_boot_action`); everything below just carries
229
248
  // out whichever of its four outcomes came back.
230
249
  let decision = mint::decide_boot_action(&held, &my_label);
231
- era_phase.end_with(serde_json::json!({ "held_eras": held.len(), "decision": decision.name() }));
250
+ era_phase
251
+ .end_with(serde_json::json!({ "held_eras": held.len(), "decision": decision.name() }));
232
252
  let ordinal: i32 = match decision {
233
253
  // Adopting an existing era still provisions what this binary
234
254
  // is about to write into, the same as minting does --
235
255
  // skipping it would let a matched-era host write into
236
256
  // head-snapshot tables that were never created.
237
257
  mint::BootDecision::UseExisting { ordinal } => {
238
- let phase = log::phase_with("adopt_head_snapshots", serde_json::json!({ "era": ordinal }));
258
+ let phase = log::phase_with(
259
+ "adopt_head_snapshots",
260
+ serde_json::json!({ "era": ordinal }),
261
+ );
239
262
  mint::adopt_head_snapshots(&client, &domain, &aggregates, ordinal)
240
263
  .await
241
264
  .map_err(|e| format!("adopting era {ordinal} of {domain}: {e:#}"))?;
@@ -243,10 +266,14 @@ pub async fn run() -> Result<(), Error> {
243
266
  ordinal
244
267
  }
245
268
  mint::BootDecision::HoldFirst => {
246
- let source_text =
247
- ir.get("source_text").and_then(serde_json::Value::as_str).ok_or("ir.json is missing source_text — regenerate with hecks project_rust")?;
269
+ let source_text = ir
270
+ .get("source_text")
271
+ .and_then(serde_json::Value::as_str)
272
+ .ok_or("ir.json is missing source_text — regenerate with hecks project_rust")?;
248
273
  let phase = log::phase_with("hold_first", serde_json::json!({ "era": 1 }));
249
- mint::hold_first(&client, &domain, source_text, ir, &aggregates, None).await.map_err(|e| format!("minting era 1 of {domain}: {e:#}"))?;
274
+ mint::hold_first(&client, &domain, source_text, ir, &aggregates, None)
275
+ .await
276
+ .map_err(|e| format!("minting era 1 of {domain}: {e:#}"))?;
250
277
  phase.end();
251
278
  1
252
279
  }
@@ -258,8 +285,15 @@ pub async fn run() -> Result<(), Error> {
258
285
  )
259
286
  .into());
260
287
  }
261
- mint::BootDecision::Mint { ordinal, from_ordinal, from_label } => {
262
- let mut labels: Vec<String> = held.iter().map(|held_era| held_era.label.clone().unwrap_or_default()).collect();
288
+ mint::BootDecision::Mint {
289
+ ordinal,
290
+ from_ordinal,
291
+ from_label,
292
+ } => {
293
+ let mut labels: Vec<String> = held
294
+ .iter()
295
+ .map(|held_era| held_era.label.clone().unwrap_or_default())
296
+ .collect();
263
297
  labels.push(my_label.clone());
264
298
  let edges = mint::parse_edges(ir, &domain);
265
299
  let chain = mint::edge_chain(&edges, &labels).map_err(|e| {
@@ -270,40 +304,60 @@ pub async fn run() -> Result<(), Error> {
270
304
  )
271
305
  })?;
272
306
 
273
- // Checks every edge in the chain with a compute/rekey
274
- // rule, not just the newest -- a chain can carry more
275
- // than one, matched against the raw ir.json edge
276
- // (digest-relevant shape) by the same from/to pair.
277
- let raw_edges = ir.get("translations").and_then(serde_json::Value::as_array).cloned().unwrap_or_default();
278
- let committed_approvals = approval::committed(ir);
279
- let approval_phase = log::phase_with("approval_check", serde_json::json!({ "edges": chain.len(), "committed": committed_approvals.len() }));
280
- for edge in &chain {
281
- let Some(raw_edge) = raw_edges.iter().find(|candidate| {
282
- candidate.get("domain").and_then(serde_json::Value::as_str) == Some(domain.as_str())
283
- && candidate.get("from").and_then(serde_json::Value::as_str) == Some(edge.from.as_str())
284
- && candidate.get("to").and_then(serde_json::Value::as_str) == Some(edge.to.as_str())
285
- }) else {
286
- return Err(format!("cannot boot: {domain}'s edge {} -> {} vanished between parsing and approval-checking it", edge.from, edge.to).into());
287
- };
288
- approval::check(&client, &domain, raw_edge, ordinal, &committed_approvals).await.map_err(|e| format!("{e:#}"))?;
289
- }
290
- approval_phase.end();
307
+ // The raw ir.json edges, matched to the chain by from/to pair for the audit.
308
+ let raw_edges = ir
309
+ .get("translations")
310
+ .and_then(serde_json::Value::as_array)
311
+ .cloned()
312
+ .unwrap_or_default();
291
313
 
292
314
  // Runs before anything is minted, over the live compiled
293
315
  // chain (plain SELECTs, never a persisted matview), so a
294
316
  // refusal leaves no half-born era.
295
- let watermarks: std::collections::HashMap<i32, Option<i64>> = held.iter().map(|held_era| (held_era.ordinal, held_era.watermark)).collect();
296
- let audit_phase = log::phase_with("audit_before_mint", serde_json::json!({ "era": ordinal, "edges": chain.len() }));
297
- mint::audit_before_mint(&client, &domain, ir, &aggregates, ordinal, &chain, &raw_edges, &watermarks)
298
- .await
299
- .map_err(|e| format!("{e:#}"))?;
317
+ let watermarks: std::collections::HashMap<i32, Option<i64>> = held
318
+ .iter()
319
+ .map(|held_era| (held_era.ordinal, held_era.watermark))
320
+ .collect();
321
+ let audit_phase = log::phase_with(
322
+ "audit_before_mint",
323
+ serde_json::json!({ "era": ordinal, "edges": chain.len() }),
324
+ );
325
+ mint::audit_before_mint(
326
+ &client,
327
+ &domain,
328
+ ir,
329
+ &aggregates,
330
+ ordinal,
331
+ &chain,
332
+ &raw_edges,
333
+ &watermarks,
334
+ )
335
+ .await
336
+ .map_err(|e| format!("{e:#}"))?;
300
337
  audit_phase.end();
301
338
 
302
- let held_text = ir.get("source_text").and_then(serde_json::Value::as_str).ok_or("ir.json is missing source_text — regenerate with hecks project_rust")?;
303
- let mint_phase = log::phase_with("mint_era", serde_json::json!({ "era": ordinal, "edges": chain.len() }));
304
- mint::mint_era(&client, &domain, ordinal, &my_hash, &my_label, held_text, &aggregates, &chain, None, &mint::lifecycle_defaults(ir))
305
- .await
306
- .map_err(|e| format!("minting era {ordinal} of {domain}: {e:#}"))?;
339
+ let held_text = ir
340
+ .get("source_text")
341
+ .and_then(serde_json::Value::as_str)
342
+ .ok_or("ir.json is missing source_text — regenerate with hecks project_rust")?;
343
+ let mint_phase = log::phase_with(
344
+ "mint_era",
345
+ serde_json::json!({ "era": ordinal, "edges": chain.len() }),
346
+ );
347
+ mint::mint_era(
348
+ &client,
349
+ &domain,
350
+ ordinal,
351
+ &my_hash,
352
+ &my_label,
353
+ held_text,
354
+ &aggregates,
355
+ &chain,
356
+ None,
357
+ &mint::lifecycle_defaults(ir),
358
+ )
359
+ .await
360
+ .map_err(|e| format!("minting era {ordinal} of {domain}: {e:#}"))?;
307
361
  mint_phase.end();
308
362
  ordinal
309
363
  }
@@ -317,7 +371,11 @@ pub async fn run() -> Result<(), Error> {
317
371
  // -- never "everything this kernel can mutate". A domain that binds
318
372
  // everything to plain `Postgres` mirrors nothing.
319
373
  let mirrored = ir::mirrored_aggregates(ir);
320
- let lineage_config = Arc::new(journal::LineageConfig { domain, era, mirrored: Some(mirrored) });
374
+ let lineage_config = Arc::new(journal::LineageConfig {
375
+ domain,
376
+ era,
377
+ mirrored: Some(mirrored),
378
+ });
321
379
 
322
380
  // Mutex, not a bare Arc<Client> -- dispatch::handle needs
323
381
  // Client::transaction (&mut Client) to hold the advisory lock
@@ -338,9 +396,19 @@ pub async fn run() -> Result<(), Error> {
338
396
  "boot",
339
397
  serde_json::json!({ "mode": "serve", "domain": &lineage_config.domain, "era": &my_label, "boot_ms": boot_started.elapsed().as_millis() as u64 }),
340
398
  );
341
- let version = server::version_body(&my_label, &my_hash, std::env::var("HECKS_BUILD").ok().as_deref());
399
+ let version = server::version_body(
400
+ &my_label,
401
+ &my_hash,
402
+ std::env::var("HECKS_BUILD").ok().as_deref(),
403
+ );
342
404
  let limits = Arc::new(rate_limit::RateLimits::from_env());
343
- let state = server::ServerState { client, wasm_path, lineage_config, invoker, limits };
405
+ let state = server::ServerState {
406
+ client,
407
+ wasm_path,
408
+ lineage_config,
409
+ invoker,
410
+ limits,
411
+ };
344
412
  return server::serve(state, version).await;
345
413
  }
346
414
 
@@ -351,7 +419,11 @@ pub async fn run() -> Result<(), Error> {
351
419
  let invoker = Arc::clone(&invoker);
352
420
  async move {
353
421
  let (body, _context) = event.into_parts();
354
- server::dispatch_body(body, &client, &wasm_path, &lineage_config, invoker.as_ref()).await
422
+ server::dispatch_body(
423
+ body,
424
+ Host::new(&client, &wasm_path, &lineage_config, invoker.as_ref()),
425
+ )
426
+ .await
355
427
  }
356
428
  }))
357
429
  .await
@@ -364,12 +436,17 @@ fn parse_database_url(url: &str) -> Result<tokio_postgres::Config, String> {
364
436
  let rest = url
365
437
  .strip_prefix("postgres://")
366
438
  .or_else(|| url.strip_prefix("postgresql://"))
367
- .ok_or_else(|| format!("DATABASE_URL doesn't start with postgres:// or postgresql://"))?;
439
+ .ok_or_else(|| {
440
+ "DATABASE_URL doesn't start with postgres:// or postgresql://".to_string()
441
+ })?;
368
442
 
369
443
  let (authority, db_and_query) = rest
370
444
  .split_once('/')
371
445
  .ok_or_else(|| "DATABASE_URL has no '/' separating host from database name".to_string())?;
372
- let dbname = db_and_query.split(['?', '#']).next().unwrap_or(db_and_query);
446
+ let dbname = db_and_query
447
+ .split(['?', '#'])
448
+ .next()
449
+ .unwrap_or(db_and_query);
373
450
 
374
451
  let (credentials, host_and_port) = match authority.rsplit_once('@') {
375
452
  Some((credentials, host_and_port)) => (Some(credentials), host_and_port),
@@ -404,7 +481,7 @@ fn database_url_is_local(config: &tokio_postgres::Config) -> bool {
404
481
  use tokio_postgres::config::Host;
405
482
  config.get_hosts().iter().any(|host| match host {
406
483
  Host::Tcp(name) => name == "localhost" || name == "127.0.0.1" || name == "::1",
407
- _ => false,
484
+ Host::Unix(_) => false,
408
485
  })
409
486
  }
410
487
 
@@ -414,10 +491,9 @@ mod tests {
414
491
 
415
492
  #[test]
416
493
  fn parses_a_password_with_uri_reserved_characters() {
417
- let config = parse_database_url(
418
- "postgres://postgres:T3st$Pa?#ss*>Word@db.example.com:5432/pizzas",
419
- )
420
- .expect("should parse despite ?/# in the password");
494
+ let config =
495
+ parse_database_url("postgres://postgres:T3st$Pa?#ss*>Word@db.example.com:5432/pizzas")
496
+ .expect("should parse despite ?/# in the password");
421
497
  assert_eq!(config.get_hosts().len(), 1);
422
498
  assert_eq!(config.get_ports(), &[5432]);
423
499
  assert_eq!(config.get_user(), Some("postgres"));
@@ -7,11 +7,20 @@ use super::*;
7
7
  use crate::fuzz_support::*;
8
8
 
9
9
  fn envelope_is_structured(response: &Value) {
10
- let status = response["statusCode"].as_u64().expect("a refusal carries its status");
11
- assert!((400..600).contains(&status), "a refusal is a 4xx/5xx, got {status}");
10
+ let status = response["statusCode"]
11
+ .as_u64()
12
+ .expect("a refusal carries its status");
13
+ assert!(
14
+ (400..600).contains(&status),
15
+ "a refusal is a 4xx/5xx, got {status}"
16
+ );
12
17
  assert_eq!(response["headers"]["content-type"], "application/json");
13
- let body: Value = serde_json::from_str(response["body"].as_str().expect("a JSON body")).expect("the body is JSON");
14
- assert!(body["error"].is_string() && body["message"].is_string(), "the refusal names itself: {body}");
18
+ let body: Value = serde_json::from_str(response["body"].as_str().expect("a JSON body"))
19
+ .expect("the body is JSON");
20
+ assert!(
21
+ body["error"].is_string() && body["message"].is_string(),
22
+ "the refusal names itself: {body}"
23
+ );
15
24
  }
16
25
 
17
26
  fn client_aggregate() -> Value {
@@ -45,12 +54,22 @@ fn parsed_body_refuses_everything_that_is_not_an_object_with_a_structured_400()
45
54
  mutate_text(&mut rng, &valid)
46
55
  }
47
56
  2 => random_value(&mut rng, 0).to_string(),
48
- _ => format!("{}{}", " \t\n".repeat(rng.below(3)), random_value(&mut rng, 1)),
57
+ _ => format!(
58
+ "{}{}",
59
+ " \t\n".repeat(rng.below(3)),
60
+ random_value(&mut rng, 1)
61
+ ),
49
62
  };
50
63
  match parsed_body(&text) {
51
64
  Ok(body) => {
52
- assert!(body.is_object(), "seed {seed}: {text:?} parsed to a non-object");
53
- assert!(text.trim().is_empty() || serde_json::from_str::<Value>(&text).is_ok(), "seed {seed}: {text:?} is not JSON yet was accepted");
65
+ assert!(
66
+ body.is_object(),
67
+ "seed {seed}: {text:?} parsed to a non-object"
68
+ );
69
+ assert!(
70
+ text.trim().is_empty() || serde_json::from_str::<Value>(&text).is_ok(),
71
+ "seed {seed}: {text:?} is not JSON yet was accepted"
72
+ );
54
73
  }
55
74
  Err(refusal) => {
56
75
  assert_eq!(refusal["statusCode"], 400, "seed {seed}: {text:?}");
@@ -74,7 +93,19 @@ fn parsed_body_survives_depth_width_and_size() {
74
93
  ] {
75
94
  let _ = parsed_body(&text);
76
95
  }
77
- for text in ["[]", "1", "\"s\"", "null", "true", "{", "{\"a\":1}{}", "{} {}", "\u{feff}{}", "{\"a\":NaN}", "{\"a\":1e999}"] {
96
+ for text in [
97
+ "[]",
98
+ "1",
99
+ "\"s\"",
100
+ "null",
101
+ "true",
102
+ "{",
103
+ "{\"a\":1}{}",
104
+ "{} {}",
105
+ "\u{feff}{}",
106
+ "{\"a\":NaN}",
107
+ "{\"a\":1e999}",
108
+ ] {
78
109
  if let Err(refusal) = parsed_body(text) {
79
110
  assert_eq!(refusal["statusCode"], 400, "{text:?}");
80
111
  }
@@ -93,8 +124,15 @@ fn slugify_always_answers_a_clean_non_empty_slug() {
93
124
  let text = random_text(&mut rng);
94
125
  let slug = slugify(&text);
95
126
  assert!(!slug.is_empty(), "seed {seed}: {text:?} slugged to nothing");
96
- assert!(slug.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-'), "seed {seed}: {text:?} -> {slug:?}");
97
- assert!(!slug.starts_with('-') && !slug.ends_with('-') && !slug.contains("--"), "seed {seed}: {text:?} -> {slug:?}");
127
+ assert!(
128
+ slug.chars()
129
+ .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-'),
130
+ "seed {seed}: {text:?} -> {slug:?}"
131
+ );
132
+ assert!(
133
+ !slug.starts_with('-') && !slug.ends_with('-') && !slug.contains("--"),
134
+ "seed {seed}: {text:?} -> {slug:?}"
135
+ );
98
136
  }
99
137
  });
100
138
  }
@@ -113,26 +151,55 @@ fn identity_minting_survives_any_rule_and_leaves_args_alone_when_it_refuses() {
113
151
  continue;
114
152
  }
115
153
  let value = match key {
116
- "field" if rng.chance(2) => json!(*rng.pick(&["reference", "name", "owner", "status", "missing", ""])),
117
- "strategy" if rng.chance(2) => json!(*rng.pick(&["slug", "sequence", "port", "nope", ""])),
118
- "source" if rng.chance(2) => json!(*rng.pick(&["name", "reference", "owner", "missing", ""])),
119
- "pad" if rng.chance(2) => json!(*rng.pick(&[0u64, 1, 3, 64, 65, 1_000, 1_000_000, 1u64 << 40, 1u64 << 62, u64::MAX])),
154
+ "field" if rng.chance(2) => json!(*rng.pick(&[
155
+ "reference",
156
+ "name",
157
+ "owner",
158
+ "status",
159
+ "missing",
160
+ ""
161
+ ])),
162
+ "strategy" if rng.chance(2) => {
163
+ json!(*rng.pick(&["slug", "sequence", "port", "nope", ""]))
164
+ }
165
+ "source" if rng.chance(2) => {
166
+ json!(*rng.pick(&["name", "reference", "owner", "missing", ""]))
167
+ }
168
+ "pad" if rng.chance(2) => json!(*rng.pick(&[
169
+ 0u64,
170
+ 1,
171
+ 3,
172
+ 64,
173
+ 65,
174
+ 1_000,
175
+ 1_000_000,
176
+ 1u64 << 40,
177
+ 1u64 << 62,
178
+ u64::MAX
179
+ ])),
120
180
  _ => random_value(&mut rng, 2),
121
181
  };
122
182
  rule.insert(key.to_string(), value);
123
183
  }
124
- let presentation = json!({"collections": {"Client": {"identity": Value::Object(rule)}}});
125
- let mut args = random_value(&mut rng, 0);
126
- if rng.chance(2) {
127
- args = json!({"name": random_value(&mut rng, 2)});
128
- }
184
+ let presentation =
185
+ json!({"collections": {"Client": {"identity": Value::Object(rule)}}});
186
+ // Drawn before the coin flip, so a seed replays the same stream.
187
+ let drawn = random_value(&mut rng, 0);
188
+ let mut args = if rng.chance(2) {
189
+ json!({"name": random_value(&mut rng, 2)})
190
+ } else {
191
+ drawn
192
+ };
129
193
  let before = args.clone();
130
194
  let existing = *rng.pick(&[0usize, 1, 41, 999_999, usize::MAX - 1]);
131
195
  match apply_identity(&presentation, &aggregate, &mut args, existing) {
132
196
  Ok(()) => {}
133
197
  Err(refusal) => {
134
198
  envelope_is_structured(&refusal);
135
- assert_eq!(args, before, "seed {seed}: a refused identity must not have touched the args");
199
+ assert_eq!(
200
+ args, before,
201
+ "seed {seed}: a refused identity must not have touched the args"
202
+ );
136
203
  }
137
204
  }
138
205
  }
@@ -146,13 +213,19 @@ fn an_absurd_sequence_pad_is_refused_rather_than_allocating_it() {
146
213
  for pad in [65u64, 1_000_000, 1 << 40, u64::MAX] {
147
214
  let presentation = json!({"collections": {"Client": {"identity": {"field": "reference", "strategy": "sequence", "prefix": "C-", "pad": pad}}}});
148
215
  let mut args = json!({"name": {"value": "Acme"}});
149
- let refusal = apply_identity(&presentation, &aggregate, &mut args, 1).expect_err("a pad this wide is refused");
216
+ let refusal = apply_identity(&presentation, &aggregate, &mut args, 1)
217
+ .expect_err("a pad this wide is refused");
150
218
  envelope_is_structured(&refusal);
151
- assert_eq!(args, json!({"name": {"value": "Acme"}}), "pad {pad}: nothing was minted");
219
+ assert_eq!(
220
+ args,
221
+ json!({"name": {"value": "Acme"}}),
222
+ "pad {pad}: nothing was minted"
223
+ );
152
224
  }
153
225
  let presentation = json!({"collections": {"Client": {"identity": {"field": "reference", "strategy": "sequence", "prefix": "C-", "pad": 64}}}});
154
226
  let mut args = json!({});
155
- apply_identity(&presentation, &aggregate, &mut args, 0).expect("the widest allowed pad still mints");
227
+ apply_identity(&presentation, &aggregate, &mut args, 0)
228
+ .expect("the widest allowed pad still mints");
156
229
  assert_eq!(args["reference"]["value"].as_str().unwrap().len(), 2 + 64);
157
230
  }
158
231
 
@@ -169,9 +242,19 @@ fn preconditions_survive_any_config_and_instances_shape() {
169
242
  } else {
170
243
  json!({"field": *rng.pick(&["owner", "name", "status", "missing"]), "state": random_value(&mut rng, 2), "message": random_value(&mut rng, 2)})
171
244
  };
172
- let args = if rng.chance(3) { random_value(&mut rng, 0) } else { json!({"owner": random_value(&mut rng, 2)}) };
173
- let instances = if rng.chance(3) { random_value(&mut rng, 0) } else { json!({format!("{}::Member#m1", domain_name(&domain)): random_value(&mut rng, 2)}) };
174
- if let Err(refusal) = check_precondition(&domain, &aggregate, &rule, &args, &instances) {
245
+ let args = if rng.chance(3) {
246
+ random_value(&mut rng, 0)
247
+ } else {
248
+ json!({"owner": random_value(&mut rng, 2)})
249
+ };
250
+ let instances = if rng.chance(3) {
251
+ random_value(&mut rng, 0)
252
+ } else {
253
+ json!({format!("{}::Member#m1", domain_name(&domain)): random_value(&mut rng, 2)})
254
+ };
255
+ if let Err(refusal) =
256
+ check_precondition(&domain, &aggregate, &rule, &args, &instances)
257
+ {
175
258
  envelope_is_structured(&refusal);
176
259
  }
177
260
  }
@@ -203,9 +286,12 @@ fn sorting_records_of_mixed_types_never_panics_on_an_inconsistent_order() {
203
286
  })
204
287
  .collect();
205
288
  for descending in [false, true] {
206
- let spec = Some(SortSpec { field: "col".to_string(), descending });
207
- sort_records(&mut records, &spec);
208
- sort_records(&mut records, &None);
289
+ let spec = Some(SortSpec {
290
+ field: "col".to_string(),
291
+ descending,
292
+ });
293
+ sort_records(&mut records, spec.as_ref());
294
+ sort_records(&mut records, None);
209
295
  }
210
296
  }
211
297
  });
@@ -218,7 +304,7 @@ fn query_arguments_and_sort_params_survive_hostile_input() {
218
304
  guarded(&format!("seed {seed}"), move || {
219
305
  let mut rng = Rng::new(seed ^ 0x9A);
220
306
  let aggregate = client_aggregate();
221
- let domain = json!({"name": "Crm", "aggregates": [aggregate.clone()]});
307
+ let domain = json!({"name": "Crm", "aggregates": [aggregate]});
222
308
  for _ in 0..30 {
223
309
  let named = if rng.chance(3) {
224
310
  random_value(&mut rng, 0)
@@ -235,10 +321,22 @@ fn query_arguments_and_sort_params_survive_hostile_input() {
235
321
  params.insert("name".to_string(), random_text(&mut rng));
236
322
  let _ = query_args_from_params(&aggregate, &named, &params);
237
323
 
238
- let sort: HashMap<String, String> = [("sort", random_text(&mut rng)), ("direction", random_text(&mut rng))].into_iter().map(|(k, v)| (k.to_string(), v)).collect();
324
+ let sort: HashMap<String, String> = [
325
+ ("sort", random_text(&mut rng)),
326
+ ("direction", random_text(&mut rng)),
327
+ ]
328
+ .into_iter()
329
+ .map(|(k, v)| (k.to_string(), v))
330
+ .collect();
239
331
  let config = random_value(&mut rng, 0);
240
332
  let _ = resolve_sort(&domain, &config, "Client", &aggregate, &sort);
241
- let _ = resolve_sort(&domain, &json!({}), "Client", &aggregate, &HashMap::from([("sort".to_string(), "__state__".to_string())]));
333
+ let _ = resolve_sort(
334
+ &domain,
335
+ &json!({}),
336
+ "Client",
337
+ &aggregate,
338
+ &HashMap::from([("sort".to_string(), "__state__".to_string())]),
339
+ );
242
340
  }
243
341
  });
244
342
  }