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
@@ -14,26 +14,39 @@ pub mod query_ordering;
14
14
  pub mod vocab;
15
15
  // Kept at its old path so existing `refusal_wording::RefusalSite` call sites still resolve.
16
16
  pub use vocab::refusal_template as refusal_wording;
17
- pub mod read_model;
18
17
  pub mod query_comparators;
18
+ pub mod read_model;
19
19
  pub mod reference_lookup;
20
20
  pub mod repository;
21
21
  pub mod routing;
22
+ pub mod ruby_float;
22
23
 
23
- pub use dispatch::{aggregate_step_site, apply_entity_command, decode_aggregate_arguments, decode_entity_arguments, dispatch, dispatch_entity, enforce_invariants, entity_step_site, ArgumentGates, EnsuresSpec, EntityInvariants, GivenSpec, Hydrate, InvariantSet, InvariantSpec, StepSite, TransitionCheck};
24
- pub use expr::{interpret, BlockMode, Bound, Comparison, EvalContext, Expr, Field, Fielded, NoFields, StripSide, Value, WithParent};
24
+ pub use dispatch::{
25
+ aggregate_step_site, apply_entity_command, decode_aggregate_arguments, decode_entity_arguments,
26
+ dispatch, dispatch_entity, enforce_invariants, entity_step_site, ArgumentGates, EnsuresSpec,
27
+ EntityInvariants, GivenSpec, Hydrate, InvariantSet, InvariantSpec, StepSite, TransitionCheck,
28
+ };
29
+ pub use expr::{
30
+ interpret, BlockMode, Bound, Comparison, EvalContext, Expr, Field, Fielded, NoFields,
31
+ StripSide, Value, WithParent,
32
+ };
25
33
  pub use json::Json;
26
34
  pub use named_query::{QueryCondition, QueryConditionValue, QueryDef};
27
35
  pub use orchestrate::{
28
- orchestrate, CompletedCompensation, CrossDomainPolicyRule, DispatchSpec, Handler, PendingCrossDomainReaction, PolicyRule, ProcessManagerDef,
29
- SagaInstance, Tables, WithValue, MAX_REACTION_DEPTH, REFUSED,
36
+ orchestrate, CompletedCompensation, CrossDomainPolicyRule, DispatchSpec, Handler,
37
+ PendingCrossDomainReaction, PolicyRule, ProcessManagerDef, SagaInstance, Tables, WithValue,
38
+ MAX_REACTION_DEPTH, REFUSED,
30
39
  };
31
40
  pub use reference_lookup::{
32
- command_deref, owner_deref, parent_deref, seeded_projections, DerefNode, ProjectedFieldSpec, ReferenceLookup, ReferenceSpec, ReferenceTable,
33
- SetProjectedField, WithReferences, DEREFERENCE_DEPTH,
41
+ command_deref, owner_deref, parent_deref, seeded_projections, DerefNode, ProjectedFieldSpec,
42
+ ReferenceLookup, ReferenceSpec, ReferenceTable, SetProjectedField, WithReferences,
43
+ DEREFERENCE_DEPTH,
34
44
  };
35
45
  pub use refusal_wording::RefusalSite;
36
- pub use repository::{check_reference, check_role_via, filter_entries, holds_role_via, row_json, AggregateScan, InMemoryRepository, Repository};
46
+ pub use repository::{
47
+ check_reference, check_role_via, filter_entries, holds_role_via, row_json, AggregateScan,
48
+ InMemoryRepository, Repository,
49
+ };
37
50
  pub use routing::{CommandInvocation, RoutingEnvelope};
38
51
 
39
52
  /// A domain event raised by a successful command dispatch.
@@ -77,20 +90,20 @@ pub enum Refusal {
77
90
 
78
91
  impl Refusal {
79
92
  /// The refusal class name, spelled as Ruby raises it; the semantics corpus compares it.
80
- pub fn kind(&self) -> &'static str {
93
+ pub const fn kind(&self) -> &'static str {
81
94
  match self {
82
- Refusal::GivenNotMet(_) => "GivenNotMet",
83
- Refusal::EnsuresNotMet(_) => "EnsuresNotMet",
84
- Refusal::InvariantViolation(_) => "InvariantViolation",
85
- Refusal::LifecycleRefused(_) => "LifecycleRefused",
86
- Refusal::AlreadyExists(_) => "AlreadyExists",
87
- Refusal::NotFound(_) => "NotFound",
88
- Refusal::TypeMismatch(_) => "TypeMismatch",
89
- Refusal::AbsentArgument(_) => "AbsentArgument",
90
- Refusal::UnknownArgument(_) => "UnknownArgument",
91
- Refusal::Unauthorized(_) => "Unauthorized",
92
- Refusal::Fault(_) => "Fault",
93
- Refusal::NothingToCorrect(_) => "NothingToCorrect",
95
+ Self::GivenNotMet(_) => "GivenNotMet",
96
+ Self::EnsuresNotMet(_) => "EnsuresNotMet",
97
+ Self::InvariantViolation(_) => "InvariantViolation",
98
+ Self::LifecycleRefused(_) => "LifecycleRefused",
99
+ Self::AlreadyExists(_) => "AlreadyExists",
100
+ Self::NotFound(_) => "NotFound",
101
+ Self::TypeMismatch(_) => "TypeMismatch",
102
+ Self::AbsentArgument(_) => "AbsentArgument",
103
+ Self::UnknownArgument(_) => "UnknownArgument",
104
+ Self::Unauthorized(_) => "Unauthorized",
105
+ Self::Fault(_) => "Fault",
106
+ Self::NothingToCorrect(_) => "NothingToCorrect",
94
107
  }
95
108
  }
96
109
  }
@@ -98,18 +111,18 @@ impl Refusal {
98
111
  impl std::fmt::Display for Refusal {
99
112
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100
113
  match self {
101
- Refusal::GivenNotMet(msg)
102
- | Refusal::EnsuresNotMet(msg)
103
- | Refusal::InvariantViolation(msg)
104
- | Refusal::LifecycleRefused(msg)
105
- | Refusal::AlreadyExists(msg)
106
- | Refusal::NotFound(msg)
107
- | Refusal::TypeMismatch(msg)
108
- | Refusal::AbsentArgument(msg)
109
- | Refusal::UnknownArgument(msg)
110
- | Refusal::Unauthorized(msg)
111
- | Refusal::Fault(msg)
112
- | Refusal::NothingToCorrect(msg) => write!(f, "{msg}"),
114
+ Self::GivenNotMet(msg)
115
+ | Self::EnsuresNotMet(msg)
116
+ | Self::InvariantViolation(msg)
117
+ | Self::LifecycleRefused(msg)
118
+ | Self::AlreadyExists(msg)
119
+ | Self::NotFound(msg)
120
+ | Self::TypeMismatch(msg)
121
+ | Self::AbsentArgument(msg)
122
+ | Self::UnknownArgument(msg)
123
+ | Self::Unauthorized(msg)
124
+ | Self::Fault(msg)
125
+ | Self::NothingToCorrect(msg) => write!(f, "{msg}"),
113
126
  }
114
127
  }
115
128
  }
@@ -2,7 +2,9 @@
2
2
  //! A query outside the compiled subset has no row and is refused by the string-form step.
3
3
 
4
4
  use super::refusal_wording::UnauthorizedTenantRequiredArgs;
5
- use super::{query_comparators::QueryComparator, query_ordering, repository, AggregateScan, Json, Refusal};
5
+ use super::{
6
+ query_comparators::QueryComparator, query_ordering, repository, AggregateScan, Json, Refusal,
7
+ };
6
8
 
7
9
  /// One declared query, compiled.
8
10
  ///
@@ -60,7 +62,12 @@ pub enum QueryConditionValue {
60
62
 
61
63
  /// Runs a declared query: chained `filter_entries` per condition (keeping id order), then
62
64
  /// `query_ordering::apply` for `order_by`/`offset`/`limit`, as Ruby filters then orders.
63
- pub fn run(store: &impl AggregateScan, def: &QueryDef, args: &Json, caller_role: Option<&str>) -> Result<Vec<(String, Json)>, Refusal> {
65
+ pub fn run(
66
+ store: &impl AggregateScan,
67
+ def: &QueryDef,
68
+ args: &Json,
69
+ caller_role: Option<&str>,
70
+ ) -> Result<Vec<(String, Json)>, Refusal> {
64
71
  run_cross_domain(store, def, args, &[], caller_role)
65
72
  }
66
73
 
@@ -77,7 +84,11 @@ pub fn run_cross_domain(
77
84
  if let Some(auth) = &def.authorization {
78
85
  if args.get(auth.tenant_field).is_none() {
79
86
  return Err(Refusal::Unauthorized(
80
- UnauthorizedTenantRequiredArgs { query: auth.query_name, field: auth.tenant_field }.render_args(),
87
+ UnauthorizedTenantRequiredArgs {
88
+ query: auth.query_name,
89
+ field: auth.tenant_field,
90
+ }
91
+ .render_args(),
81
92
  ));
82
93
  }
83
94
  }
@@ -100,21 +111,35 @@ pub fn run_cross_domain(
100
111
  // cloned. ANDing per row keeps what chaining one filter per condition kept.
101
112
  let mut entries: Vec<(String, Json)> = Vec::new();
102
113
  let known = store.scan_each(def.aggregate, &mut |id, record| {
103
- let kept = wants
104
- .iter()
105
- .all(|(c, want)| repository::entry_matches(record, c.field, c.comparator, want, cross_domain));
114
+ let kept = wants.iter().all(|(c, want)| {
115
+ repository::entry_matches(record, c.field, c.comparator, want, cross_domain)
116
+ });
106
117
  if kept {
107
118
  entries.push((id.to_string(), record.clone()));
108
119
  }
109
120
  });
110
121
  if !known {
111
- return Err(Refusal::TypeMismatch(format!("unknown aggregate {:?}", def.aggregate)));
122
+ return Err(Refusal::TypeMismatch(format!(
123
+ "unknown aggregate {:?}",
124
+ def.aggregate
125
+ )));
112
126
  }
113
127
  entries.sort_by(|a, b| a.0.cmp(&b.0));
114
128
 
115
- entries = super::read_model::apply_reference_hops(entries, def.reference_hop_conditions, args, store)?;
129
+ entries = super::read_model::apply_reference_hops(
130
+ entries,
131
+ def.reference_hop_conditions,
132
+ args,
133
+ store,
134
+ )?;
116
135
 
117
- Ok(query_ordering::apply(entries, def.order_by.as_ref(), def.offset.as_ref(), def.limit.as_ref(), args))
136
+ Ok(query_ordering::apply(
137
+ entries,
138
+ def.order_by.as_ref(),
139
+ def.offset.as_ref(),
140
+ def.limit.as_ref(),
141
+ args,
142
+ ))
118
143
  }
119
144
 
120
145
  /// Finds a query by verb; a linear scan is enough for the small generated `QUERIES` table.
@@ -146,7 +171,11 @@ pub fn find_entity<'a>(table: &'a [EntityQueryDef], verb: &str) -> Option<&'a En
146
171
  table.iter().find(|def| def.verb == verb)
147
172
  }
148
173
 
149
- pub fn run_entity(store: &impl AggregateScan, def: &EntityQueryDef, args: &Json) -> Result<Vec<Json>, Refusal> {
174
+ pub fn run_entity(
175
+ store: &impl AggregateScan,
176
+ def: &EntityQueryDef,
177
+ args: &Json,
178
+ ) -> Result<Vec<Json>, Refusal> {
150
179
  let records = store
151
180
  .scan(def.aggregate)
152
181
  .ok_or_else(|| Refusal::TypeMismatch(format!("unknown aggregate {:?}", def.aggregate)))?;
@@ -155,7 +184,11 @@ pub fn run_entity(store: &impl AggregateScan, def: &EntityQueryDef, args: &Json)
155
184
  let mut entries: Vec<(String, Json)> = Vec::new();
156
185
  for (parent_id, record) in records {
157
186
  if let Some(elements) = record.get(def.list_field).and_then(Json::as_array) {
158
- entries.extend(elements.iter().map(|element| (parent_id.clone(), element.clone())));
187
+ entries.extend(
188
+ elements
189
+ .iter()
190
+ .map(|element| (parent_id.clone(), element.clone())),
191
+ );
159
192
  }
160
193
  }
161
194
 
@@ -169,10 +202,23 @@ pub fn run_entity(store: &impl AggregateScan, def: &EntityQueryDef, args: &Json)
169
202
  }
170
203
 
171
204
  // `query_ordering::apply` re-sorts stably by parent id, so identity order survives per parent.
172
- entries.sort_by(|(parent_a, a), (parent_b, b)| parent_a.cmp(parent_b).then_with(|| identity_order(a, b, def.identity_keys)));
173
- let ordered = query_ordering::apply(entries, def.order_by.as_ref(), def.offset.as_ref(), def.limit.as_ref(), args);
205
+ entries.sort_by(|(parent_a, a), (parent_b, b)| {
206
+ parent_a
207
+ .cmp(parent_b)
208
+ .then_with(|| identity_order(a, b, def.identity_keys))
209
+ });
210
+ let ordered = query_ordering::apply(
211
+ entries,
212
+ def.order_by.as_ref(),
213
+ def.offset.as_ref(),
214
+ def.limit.as_ref(),
215
+ args,
216
+ );
174
217
 
175
- Ok(ordered.into_iter().map(|(parent_id, element)| row_under_parent(def.parent_key, parent_id, element)).collect())
218
+ Ok(ordered
219
+ .into_iter()
220
+ .map(|(parent_id, element)| row_under_parent(def.parent_key, parent_id, element))
221
+ .collect())
176
222
  }
177
223
 
178
224
  fn identity_order(a: &Json, b: &Json, keys: &[&str]) -> std::cmp::Ordering {
@@ -193,7 +239,9 @@ fn comparable(value: Option<&Json>) -> Option<&Json> {
193
239
  fn compare_comparable(a: Option<&Json>, b: Option<&Json>) -> std::cmp::Ordering {
194
240
  use std::cmp::Ordering;
195
241
  match (a, b) {
196
- (Some(Json::Num(x, _)), Some(Json::Num(y, _))) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
242
+ (Some(Json::Num(x, _)), Some(Json::Num(y, _))) => {
243
+ x.partial_cmp(y).unwrap_or(Ordering::Equal)
244
+ }
197
245
  (Some(Json::Str(x)), Some(Json::Str(y))) => x.cmp(y),
198
246
  (None | Some(Json::Null), None | Some(Json::Null)) => Ordering::Equal,
199
247
  (None | Some(Json::Null), _) => Ordering::Less,
@@ -17,7 +17,7 @@ use std::cell::{Cell, RefCell};
17
17
  use std::collections::HashMap;
18
18
 
19
19
  /// One needed fact of a command: its name and the type of the command's attribute of that name.
20
- #[derive(Debug, Clone, PartialEq)]
20
+ #[derive(Debug, Clone, PartialEq, Eq)]
21
21
  pub struct Need {
22
22
  pub fact: String,
23
23
  pub attribute_type: String,
@@ -42,8 +42,15 @@ fn parse_table(table: Option<&Json>) -> HashMap<String, Vec<Need>> {
42
42
  .iter()
43
43
  .filter_map(|n| {
44
44
  let fact = n.get("fact")?.as_str()?.to_string();
45
- let attribute_type = n.get("type").and_then(Json::as_str).unwrap_or("").to_string();
46
- Some(Need { fact, attribute_type })
45
+ let attribute_type = n
46
+ .get("type")
47
+ .and_then(Json::as_str)
48
+ .unwrap_or("")
49
+ .to_string();
50
+ Some(Need {
51
+ fact,
52
+ attribute_type,
53
+ })
47
54
  })
48
55
  .collect();
49
56
  (name.clone(), needs)
@@ -94,7 +101,10 @@ pub fn now_secs(occurred_at: Option<&str>) -> i64 {
94
101
  return fixed;
95
102
  }
96
103
  occurred_at.and_then(parse_iso8601_utc).unwrap_or_else(|| {
97
- std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs() as i64).unwrap_or_default()
104
+ std::time::SystemTime::now()
105
+ .duration_since(std::time::UNIX_EPOCH)
106
+ .map(|d| d.as_secs() as i64)
107
+ .unwrap_or_default()
98
108
  })
99
109
  }
100
110
 
@@ -103,9 +113,17 @@ fn parse_iso8601_utc(text: &str) -> Option<i64> {
103
113
  let text = text.strip_suffix('Z')?;
104
114
  let (date, time) = text.split_once('T')?;
105
115
  let mut d = date.split('-');
106
- let (y, m, day): (i64, i64, i64) = (d.next()?.parse().ok()?, d.next()?.parse().ok()?, d.next()?.parse().ok()?);
116
+ let (y, m, day): (i64, i64, i64) = (
117
+ d.next()?.parse().ok()?,
118
+ d.next()?.parse().ok()?,
119
+ d.next()?.parse().ok()?,
120
+ );
107
121
  let mut t = time.split(':');
108
- let (h, mi, s): (i64, i64, i64) = (t.next()?.parse().ok()?, t.next()?.parse().ok()?, t.next()?.split('.').next()?.parse().ok()?);
122
+ let (h, mi, s): (i64, i64, i64) = (
123
+ t.next()?.parse().ok()?,
124
+ t.next()?.parse().ok()?,
125
+ t.next()?.split('.').next()?.parse().ok()?,
126
+ );
109
127
  // Howard Hinnant's days-from-civil.
110
128
  let y = if m <= 2 { y - 1 } else { y };
111
129
  let era = if y >= 0 { y } else { y - 399 } / 400;
@@ -121,8 +139,16 @@ fn answer(need: &Need, occurred_at: Option<&str>) -> Option<Json> {
121
139
  // `now` is epoch seconds; `today` the day those fall in, whole days since the epoch (UTC).
122
140
  "now" | "today" => {
123
141
  let secs = now_secs(occurred_at);
124
- let answer = if need.fact == "today" { secs.div_euclid(86_400) } else { secs };
125
- Some(if need.attribute_type == "Integer" { Json::int(answer) } else { Json::obj(vec![("value", Json::int(answer))]) })
142
+ let answer = if need.fact == "today" {
143
+ secs.div_euclid(86_400)
144
+ } else {
145
+ secs
146
+ };
147
+ Some(if need.attribute_type == "Integer" {
148
+ Json::int(answer)
149
+ } else {
150
+ Json::obj(vec![("value", Json::int(answer))])
151
+ })
126
152
  }
127
153
  _ => None,
128
154
  }
@@ -132,14 +158,20 @@ fn answer(need: &Need, occurred_at: Option<&str>) -> Option<Json> {
132
158
  /// needed filling (no needs, or every fact supplied). Reads `with` when the call carries one,
133
159
  /// else the flat object, as `CommandInvocation` does.
134
160
  pub fn enrich(verb: &str, args: &Json, occurred_at: Option<&str>) -> Option<Json> {
135
- let needs = TABLE.with(|t| t.borrow().get(verb).cloned()).unwrap_or_default();
136
- let defaults = DEFAULTS.with(|t| t.borrow().get(verb).cloned()).unwrap_or_default();
161
+ let needs = TABLE
162
+ .with(|t| t.borrow().get(verb).cloned())
163
+ .unwrap_or_default();
164
+ let defaults = DEFAULTS
165
+ .with(|t| t.borrow().get(verb).cloned())
166
+ .unwrap_or_default();
137
167
  if needs.is_empty() && defaults.is_empty() {
138
168
  return None;
139
169
  }
140
170
  let explicit = matches!(args.get("with"), Some(Json::Object(_)));
141
171
  let facts = if explicit { args.get("with")? } else { args };
142
- let Json::Object(held) = facts else { return None };
172
+ let Json::Object(held) = facts else {
173
+ return None;
174
+ };
143
175
  let mut filled = held.clone();
144
176
  let before = filled.len();
145
177
  for need in &needs {
@@ -161,8 +193,21 @@ pub fn enrich(verb: &str, args: &Json, occurred_at: Option<&str>) -> Option<Json
161
193
  if !explicit {
162
194
  return Some(Json::Object(filled));
163
195
  }
164
- let Json::Object(envelope) = args else { return None };
165
- Some(Json::Object(envelope.iter().map(|(k, v)| if k == "with" { (k.clone(), Json::Object(filled.clone())) } else { (k.clone(), v.clone()) }).collect()))
196
+ let Json::Object(envelope) = args else {
197
+ return None;
198
+ };
199
+ Some(Json::Object(
200
+ envelope
201
+ .iter()
202
+ .map(|(k, v)| {
203
+ if k == "with" {
204
+ (k.clone(), Json::Object(filled.clone()))
205
+ } else {
206
+ (k.clone(), v.clone())
207
+ }
208
+ })
209
+ .collect(),
210
+ ))
166
211
  }
167
212
 
168
213
  /// `args` with each fact the query `question` needs and the caller left out answered, or `None`
@@ -170,7 +215,9 @@ pub fn enrich(verb: &str, args: &Json, occurred_at: Option<&str>) -> Option<Json
170
215
  /// arguments are the flat object of its step.
171
216
  pub fn enrich_query(question: &str, args: &Json, occurred_at: Option<&str>) -> Option<Json> {
172
217
  let needs = QUERY_TABLE.with(|t| t.borrow().get(question).cloned())?;
173
- let Json::Object(held) = args else { return None };
218
+ let Json::Object(held) = args else {
219
+ return None;
220
+ };
174
221
  let mut filled = held.clone();
175
222
  for need in &needs {
176
223
  if held.iter().any(|(k, _)| k == &need.fact) {
@@ -207,7 +254,10 @@ mod tests {
207
254
  install(Some(&table()));
208
255
  fix_clock(Some(1_700_000_000));
209
256
  let filled = enrich("D::A.Instant", &with(vec![("x", Json::int(1))]), None).unwrap();
210
- assert_eq!(filled.get("with").unwrap().get("now"), Some(&Json::obj(vec![("value", Json::int(1_700_000_000))])));
257
+ assert_eq!(
258
+ filled.get("with").unwrap().get("now"),
259
+ Some(&Json::obj(vec![("value", Json::int(1_700_000_000))]))
260
+ );
211
261
  assert_eq!(filled.get("with").unwrap().get("x"), Some(&Json::int(1)));
212
262
  }
213
263
 
@@ -231,9 +281,20 @@ mod tests {
231
281
  fn an_integer_attribute_gets_a_bare_integer_and_an_instant_a_value_object() {
232
282
  install(Some(&table()));
233
283
  fix_clock(Some(9));
234
- assert_eq!(enrich("D::A.Seconds", &with(vec![]), None).unwrap().get("with").unwrap().get("now"), Some(&Json::int(9)));
235
284
  assert_eq!(
236
- enrich("D::A.Instant", &with(vec![]), None).unwrap().get("with").unwrap().get("now"),
285
+ enrich("D::A.Seconds", &with(vec![]), None)
286
+ .unwrap()
287
+ .get("with")
288
+ .unwrap()
289
+ .get("now"),
290
+ Some(&Json::int(9))
291
+ );
292
+ assert_eq!(
293
+ enrich("D::A.Instant", &with(vec![]), None)
294
+ .unwrap()
295
+ .get("with")
296
+ .unwrap()
297
+ .get("now"),
237
298
  Some(&Json::obj(vec![("value", Json::int(9))]))
238
299
  );
239
300
  }
@@ -255,7 +316,8 @@ mod tests {
255
316
  }
256
317
 
257
318
  fn defaults() -> Json {
258
- Json::parse(r#"{"D::A.Start": {"runs": 30}, "D::A.Instant": {"holder": "nobody"}}"#).unwrap()
319
+ Json::parse(r#"{"D::A.Start": {"runs": 30}, "D::A.Instant": {"holder": "nobody"}}"#)
320
+ .unwrap()
259
321
  }
260
322
 
261
323
  #[test]
@@ -263,7 +325,10 @@ mod tests {
263
325
  install(None);
264
326
  install_defaults(Some(&defaults()));
265
327
  let filled = enrich("D::A.Start", &with(vec![("ref", Json::int(1))]), None).unwrap();
266
- assert_eq!(filled.get("with").unwrap().get("runs"), Some(&Json::int(30)));
328
+ assert_eq!(
329
+ filled.get("with").unwrap().get("runs"),
330
+ Some(&Json::int(30))
331
+ );
267
332
  assert_eq!(filled.get("with").unwrap().get("ref"), Some(&Json::int(1)));
268
333
  }
269
334
 
@@ -271,7 +336,10 @@ mod tests {
271
336
  fn a_supplied_argument_keeps_its_value_over_the_default() {
272
337
  install(None);
273
338
  install_defaults(Some(&defaults()));
274
- assert_eq!(enrich("D::A.Start", &with(vec![("runs", Json::int(7))]), None), None);
339
+ assert_eq!(
340
+ enrich("D::A.Start", &with(vec![("runs", Json::int(7))]), None),
341
+ None
342
+ );
275
343
  }
276
344
 
277
345
  #[test]
@@ -288,8 +356,14 @@ mod tests {
288
356
  install_defaults(Some(&defaults()));
289
357
  fix_clock(Some(3));
290
358
  let filled = enrich("D::A.Instant", &with(vec![]), None).unwrap();
291
- assert_eq!(filled.get("with").unwrap().get("now"), Some(&Json::obj(vec![("value", Json::int(3))])));
292
- assert_eq!(filled.get("with").unwrap().get("holder"), Some(&Json::str("nobody")));
359
+ assert_eq!(
360
+ filled.get("with").unwrap().get("now"),
361
+ Some(&Json::obj(vec![("value", Json::int(3))]))
362
+ );
363
+ assert_eq!(
364
+ filled.get("with").unwrap().get("holder"),
365
+ Some(&Json::str("nobody"))
366
+ );
293
367
  fix_clock(None);
294
368
  }
295
369
 
@@ -322,7 +396,14 @@ mod tests {
322
396
  fn a_query_value_the_caller_supplied_is_kept() {
323
397
  install_queries(Some(&query_table()));
324
398
  fix_clock(Some(1));
325
- assert_eq!(enrich_query("D::A.Expired", &Json::obj(vec![("now", Json::int(7))]), None), None);
399
+ assert_eq!(
400
+ enrich_query(
401
+ "D::A.Expired",
402
+ &Json::obj(vec![("now", Json::int(7))]),
403
+ None
404
+ ),
405
+ None
406
+ );
326
407
  fix_clock(None);
327
408
  install_queries(None);
328
409
  }
@@ -352,10 +433,16 @@ mod tests {
352
433
  install_queries(Some(&query_table()));
353
434
  fix_clock(Some((19_000 * 86_400) + 86_399));
354
435
  let filled = enrich_query("D::A.Today", &Json::obj(vec![]), None).unwrap();
355
- assert_eq!(filled.get("today"), Some(&Json::obj(vec![("value", Json::int(19_000))])));
436
+ assert_eq!(
437
+ filled.get("today"),
438
+ Some(&Json::obj(vec![("value", Json::int(19_000))]))
439
+ );
356
440
  fix_clock(Some(19_001 * 86_400));
357
441
  let next = enrich_query("D::A.Today", &Json::obj(vec![]), None).unwrap();
358
- assert_eq!(next.get("today"), Some(&Json::obj(vec![("value", Json::int(19_001))])));
442
+ assert_eq!(
443
+ next.get("today"),
444
+ Some(&Json::obj(vec![("value", Json::int(19_001))]))
445
+ );
359
446
  fix_clock(None);
360
447
  install_queries(None);
361
448
  }