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,8 +1,17 @@
1
1
  //! The stdin/stdout JSON CLI shared by the native binary and the wasm32-wasip1 module.
2
- //! `run` reads `{"steps": [...]}` and hands each step to `kernel::orchestrate` (ADR 0012).
3
-
4
- use super::{named_query, orchestrate, query_comparators, read_model, repository, AggregateScan, CompletedCompensation, Event, Json, MutationRecord, PendingCrossDomainReaction, Refusal, SagaInstance, Tables};
5
- use crate::generated::active::{command_attributes_for_verb, command_creates, dispatch_by_name, entity_identity_head_for_path, identity_head_for_aggregate, reference_key_for_aggregate, Store, CROSS_DOMAIN_POLICIES, POLICIES, PROCESS_MANAGERS, QUERIES, READ_MODELS};
2
+ //! `run` reads `{"steps": [...]}` and hands each step to `kernel::orchestrate` (ADR 0012), or
3
+ //! `{"present": answer}` and answers that answer bare (`kernel::present`).
4
+
5
+ use super::{
6
+ named_query, orchestrate, query_comparators, read_model, repository, AggregateScan,
7
+ CompletedCompensation, Event, Json, MutationRecord, PendingCrossDomainReaction, Refusal,
8
+ SagaInstance, Tables,
9
+ };
10
+ use crate::generated::active::{
11
+ command_attributes_for_verb, command_creates, dispatch_by_name, entity_identity_head_for_path,
12
+ identity_head_for_aggregate, reference_key_for_aggregate, Store, CROSS_DOMAIN_POLICIES,
13
+ POLICIES, PROCESS_MANAGERS, QUERIES, READ_MODELS,
14
+ };
6
15
  use std::collections::HashMap;
7
16
 
8
17
  // Builds and invariant-checks a named query's value-object arguments (ADR 0037).
@@ -16,6 +25,18 @@ pub fn run(input: &str) -> String {
16
25
  Err(e) => return error_output(&format!("invalid JSON on stdin: {e}")),
17
26
  };
18
27
 
28
+ // `{"present": answer}`: a run's answer in the bare shape a caller reads (single-field value
29
+ // objects as their sole field); no step runs, and stored state stays wrapped.
30
+ // Parsed again keeping number text, so a Float the answer carries is written back unchanged.
31
+ if parsed.get("present").is_some() {
32
+ let answer = Json::parse_keeping_number_text(input)
33
+ .ok()
34
+ .and_then(|whole| whole.get("present").cloned())
35
+ .unwrap_or(Json::Null);
36
+ return super::present::present_answer(answer, crate::generated::active::present)
37
+ .to_json_string();
38
+ }
39
+
19
40
  // Optional `"needs"`: which commands need which outside facts (ADR 0081), passed by the host.
20
41
  super::needs::install(parsed.get("needs"));
21
42
  // Optional `"query_needs"`: which queries need which outside facts, filled the same way.
@@ -23,9 +44,8 @@ pub fn run(input: &str) -> String {
23
44
  // Optional `"defaults"`: each command's declared argument defaults, filled the same way.
24
45
  super::needs::install_defaults(parsed.get("defaults"));
25
46
 
26
- let steps = match parsed.get("steps").and_then(Json::as_array) {
27
- Some(s) => s,
28
- None => return error_output("expected a top-level {\"steps\": [...]} object"),
47
+ let Some(steps) = parsed.get("steps").and_then(Json::as_array) else {
48
+ return error_output("expected a top-level {\"steps\": [...]} object");
29
49
  };
30
50
 
31
51
  // Optional `"seed"`: prior state as `"instances"` emits it, so a host need not replay history.
@@ -40,34 +60,46 @@ pub fn run(input: &str) -> String {
40
60
  let mut refusals: Vec<(String, Refusal)> = Vec::new();
41
61
  // Process-manager instances are domain-level, so not a `Store` field.
42
62
  // Optional `"sagas"` seeds in-flight saga state the way `"seed"` seeds aggregates.
43
- let mut sagas: HashMap<(String, String), SagaInstance> = match parsed.get("sagas").and_then(Json::as_array) {
44
- Some(entries) => entries
45
- .iter()
46
- .filter_map(|entry| {
47
- let process_manager = entry.get("process_manager")?.as_str()?.to_string();
48
- let correlation = entry.get("correlation")?.as_str()?.to_string();
49
- let state = entry.get("state")?.as_str()?.to_string();
50
- let memory = entry.get("memory").cloned().unwrap_or_else(|| Json::Object(vec![]));
51
- // Optional: an instance persisted without a ledger rehydrates with an empty one.
52
- let completed_compensations = entry
53
- .get("completed_compensations")
54
- .and_then(Json::as_array)
55
- .map(|entries| {
56
- entries
57
- .iter()
58
- .filter_map(|e| {
59
- let command_name = e.get("command_name")?.as_str()?.to_string();
60
- let args = e.get("args").cloned().unwrap_or(Json::Null);
61
- Some(CompletedCompensation { command_name, args })
62
- })
63
- .collect()
64
- })
65
- .unwrap_or_default();
66
- Some(((process_manager, correlation), SagaInstance { state, memory, completed_compensations }))
67
- })
68
- .collect(),
69
- None => HashMap::new(),
70
- };
63
+ let mut sagas: HashMap<(String, String), SagaInstance> = parsed
64
+ .get("sagas")
65
+ .and_then(Json::as_array)
66
+ .map_or_else(HashMap::new, |entries| {
67
+ entries
68
+ .iter()
69
+ .filter_map(|entry| {
70
+ let process_manager = entry.get("process_manager")?.as_str()?.to_string();
71
+ let correlation = entry.get("correlation")?.as_str()?.to_string();
72
+ let state = entry.get("state")?.as_str()?.to_string();
73
+ let memory = entry
74
+ .get("memory")
75
+ .cloned()
76
+ .unwrap_or_else(|| Json::Object(vec![]));
77
+ // Optional: an instance persisted without a ledger rehydrates with an empty one.
78
+ let completed_compensations = entry
79
+ .get("completed_compensations")
80
+ .and_then(Json::as_array)
81
+ .map(|entries| {
82
+ entries
83
+ .iter()
84
+ .filter_map(|e| {
85
+ let command_name = e.get("command_name")?.as_str()?.to_string();
86
+ let args = e.get("args").cloned().unwrap_or(Json::Null);
87
+ Some(CompletedCompensation { command_name, args })
88
+ })
89
+ .collect()
90
+ })
91
+ .unwrap_or_default();
92
+ Some((
93
+ (process_manager, correlation),
94
+ SagaInstance {
95
+ state,
96
+ memory,
97
+ completed_compensations,
98
+ },
99
+ ))
100
+ })
101
+ .collect()
102
+ });
71
103
  // One entry per step, parallel to `steps`, so a host can read `.last()` for the newest step.
72
104
  let mut mutations_per_step: Vec<Vec<MutationRecord>> = Vec::new();
73
105
  // Parallel to `mutations_per_step`: policies that named a domain not compiled into this
@@ -92,7 +124,11 @@ pub fn run(input: &str) -> String {
92
124
  // for the run alone (ADR 0081), so the log holds no value that depends on the clock.
93
125
  let offered = args;
94
126
  let filled = match step.get("query") {
95
- Some(Json::Str(question)) => super::needs::enrich_query(question, args, step.get("occurred_at").and_then(Json::as_str)),
127
+ Some(Json::Str(question)) => super::needs::enrich_query(
128
+ question,
129
+ args,
130
+ step.get("occurred_at").and_then(Json::as_str),
131
+ ),
96
132
  _ => None,
97
133
  };
98
134
  let args = filled.as_ref().unwrap_or(args);
@@ -110,27 +146,15 @@ pub fn run(input: &str) -> String {
110
146
  // reference twin is the same engine.
111
147
  // `reference_interpret` delegates to the same `entity_rows`).
112
148
  Json::Str(question) if question.contains("::") && named_query::find_entity(ENTITY_QUERIES, question).is_some() => {
149
+ // reason: the match guard above only admits a `question` that resolves.
150
+ #[allow(clippy::expect_used)]
113
151
  let def = named_query::find_entity(ENTITY_QUERIES, question).expect("matched by the guard above");
114
152
  match named_query::run_entity(&store, def, args) {
115
153
  Ok(rows) => {
116
- let rows = Json::Array(rows);
117
- query_results.push(Json::obj(vec![
118
- ("query", Json::Str(question.clone())),
119
- ("args", offered.clone()),
120
- ("rows", rows.clone()),
121
- ("reference_rows", rows),
122
- ]));
154
+ query_results.push(answered_entry(question, offered, Json::Array(rows), true));
123
155
  }
124
156
  Err(refusal) => {
125
- let message = refusal.to_string();
126
- query_results.push(Json::obj(vec![
127
- ("query", Json::Str(question.clone())),
128
- ("args", offered.clone()),
129
- ("rows", Json::Null),
130
- ("error", Json::Str(message.clone())),
131
- ("reference_rows", Json::Null),
132
- ("reference_error", Json::Str(message)),
133
- ]));
157
+ query_results.push(refused_entry(question, offered, &refusal, true));
134
158
  refusals.push((question.clone(), refusal));
135
159
  }
136
160
  }
@@ -139,26 +163,13 @@ pub fn run(input: &str) -> String {
139
163
  Some(def) => match check_query_args(question, args).and_then(|()| named_query::run(&store, def, args, caller_role)) {
140
164
  Ok(entries) => {
141
165
  let rows = Json::Array(entries.into_iter().map(|(id, record)| repository::row_json(id, record)).collect());
142
- query_results.push(Json::obj(vec![
143
- ("query", Json::Str(question.clone())),
144
- ("args", offered.clone()),
145
- ("rows", rows.clone()),
146
- // Same as `rows`: one compiled path (named_query.rs).
147
- ("reference_rows", rows),
148
- ]));
166
+ // The reference twin is `rows`: one compiled path (named_query.rs).
167
+ query_results.push(answered_entry(question, offered, rows, true));
149
168
  }
150
169
  // A refused query still gets an entry with `rows: null`, as in Ruby; one
151
170
  // compiled path means `error` and `reference_error` are the same string.
152
171
  Err(refusal) => {
153
- let message = refusal.to_string();
154
- query_results.push(Json::obj(vec![
155
- ("query", Json::Str(question.clone())),
156
- ("args", offered.clone()),
157
- ("rows", Json::Null),
158
- ("error", Json::Str(message.clone())),
159
- ("reference_rows", Json::Null),
160
- ("reference_error", Json::Str(message)),
161
- ]));
172
+ query_results.push(refused_entry(question, offered, &refusal, true));
162
173
  refusals.push((question.clone(), refusal));
163
174
  }
164
175
  },
@@ -179,24 +190,11 @@ pub fn run(input: &str) -> String {
179
190
  },
180
191
  Json::Str(question) => match read_model::find(READ_MODELS, question) {
181
192
  Some(def) => match read_model::run(&store, def, args) {
182
- Ok(row) => {
183
- query_results.push(Json::obj(vec![
184
- ("query", Json::Str(question.clone())),
185
- ("args", offered.clone()),
186
- ("rows", Json::Array(vec![row])),
187
- // No `reference_rows` key: Ruby sets it only for questions with a
188
- // reference twin, and a read model has none.
189
- ]));
190
- }
191
- // As above, minus the reference keys.
193
+ // No reference keys: Ruby sets them only for questions with a reference twin,
194
+ // and a read model has none.
195
+ Ok(row) => query_results.push(answered_entry(question, offered, Json::Array(vec![row]), false)),
192
196
  Err(refusal) => {
193
- let message = refusal.to_string();
194
- query_results.push(Json::obj(vec![
195
- ("query", Json::Str(question.clone())),
196
- ("args", offered.clone()),
197
- ("rows", Json::Null),
198
- ("error", Json::Str(message)),
199
- ]));
197
+ query_results.push(refused_entry(question, offered, &refusal, false));
200
198
  refusals.push((question.clone(), refusal));
201
199
  }
202
200
  },
@@ -235,16 +233,21 @@ pub fn run(input: &str) -> String {
235
233
  // See `actor_id` on the command step below.
236
234
  let caller_actor_id = step.get("actor_id").and_then(Json::as_str);
237
235
  let command_input = dry_run_command_input(args);
238
- dry_runs.push(dry_run(&store, verb, &command_input, caller_role, caller_actor_id));
236
+ dry_runs.push(dry_run(
237
+ &store,
238
+ verb,
239
+ &command_input,
240
+ caller_role,
241
+ caller_actor_id,
242
+ ));
239
243
  mutations_per_step.push(Vec::new());
240
244
  cross_domain_per_step.push(Vec::new());
241
245
  reactions_per_step.push(Json::Array(Vec::new()));
242
246
  continue;
243
247
  }
244
248
 
245
- let verb = match step.get("verb").and_then(Json::as_str) {
246
- Some(v) => v,
247
- None => return error_output("step missing \"verb\", \"dry_run\", or \"query\""),
249
+ let Some(verb) = step.get("verb").and_then(Json::as_str) else {
250
+ return error_output("step missing \"verb\", \"dry_run\", or \"query\"");
248
251
  };
249
252
 
250
253
  // Per-step `role:` stands in for Ruby's ambient `Hecks.as_caller`; passed to the top-level
@@ -306,19 +309,34 @@ pub fn run(input: &str) -> String {
306
309
  let refusals_json = Json::Array(
307
310
  refusals
308
311
  .iter()
309
- .map(|(verb, r)| Json::obj(vec![("verb", Json::str(verb.clone())), ("error", Json::str(r.to_string())), ("kind", Json::str(r.kind().to_string()))]))
312
+ .map(|(verb, r)| {
313
+ Json::obj(vec![
314
+ ("verb", Json::str(verb.clone())),
315
+ ("error", Json::str(r.to_string())),
316
+ ("kind", Json::str(r.kind().to_string())),
317
+ ])
318
+ })
310
319
  .collect(),
311
320
  );
312
321
  let mutations_json = Json::Array(
313
322
  mutations_per_step
314
323
  .iter()
315
- .map(|step_mutations| Json::Array(step_mutations.iter().map(mutation_to_json).collect()))
324
+ .map(|step_mutations| {
325
+ Json::Array(step_mutations.iter().map(mutation_to_json).collect())
326
+ })
316
327
  .collect(),
317
328
  );
318
329
  let cross_domain_json = Json::Array(
319
330
  cross_domain_per_step
320
331
  .iter()
321
- .map(|step_reactions| Json::Array(step_reactions.iter().map(cross_domain_reaction_to_json).collect()))
332
+ .map(|step_reactions| {
333
+ Json::Array(
334
+ step_reactions
335
+ .iter()
336
+ .map(cross_domain_reaction_to_json)
337
+ .collect(),
338
+ )
339
+ })
322
340
  .collect(),
323
341
  );
324
342
 
@@ -332,7 +350,10 @@ pub fn run(input: &str) -> String {
332
350
  ("cross_domain_reactions".to_string(), cross_domain_json),
333
351
  // Whole-run logs, not per-step.
334
352
  ("reactions".to_string(), Json::Array(reaction_log)),
335
- ("reactions_per_step".to_string(), Json::Array(reactions_per_step)),
353
+ (
354
+ "reactions_per_step".to_string(),
355
+ Json::Array(reactions_per_step),
356
+ ),
336
357
  ("sagas".to_string(), Json::Array(saga_log)),
337
358
  // State at the end of the run, fed back as `"sagas"` next time; the `"sagas"` key above
338
359
  // is the transition log, including refused transitions.
@@ -343,11 +364,31 @@ pub fn run(input: &str) -> String {
343
364
 
344
365
  /// Evaluates a command against a throwaway copy of the store; `"error"` carries the refusal
345
366
  /// a real dispatch would have raised.
346
- fn dry_run(store: &Store, verb: &str, command_input: &Json, caller_role: Option<&str>, caller_actor_id: Option<&str>) -> Json {
367
+ fn dry_run(
368
+ store: &Store,
369
+ verb: &str,
370
+ command_input: &Json,
371
+ caller_role: Option<&str>,
372
+ caller_actor_id: Option<&str>,
373
+ ) -> Json {
347
374
  let mut scratch = store.clone();
348
- match dispatch_by_name(&mut scratch, verb, command_input, caller_role, caller_actor_id, &mut Vec::new()) {
349
- Ok(_) => Json::obj(vec![("verb", Json::str(verb.to_string())), ("ok", Json::Bool(true))]),
350
- Err(refusal) => Json::obj(vec![("verb", Json::str(verb.to_string())), ("ok", Json::Bool(false)), ("error", Json::str(refusal.to_string()))]),
375
+ match dispatch_by_name(
376
+ &mut scratch,
377
+ verb,
378
+ command_input,
379
+ caller_role,
380
+ caller_actor_id,
381
+ &mut Vec::new(),
382
+ ) {
383
+ Ok(_) => Json::obj(vec![
384
+ ("verb", Json::str(verb.to_string())),
385
+ ("ok", Json::Bool(true)),
386
+ ]),
387
+ Err(refusal) => Json::obj(vec![
388
+ ("verb", Json::str(verb.to_string())),
389
+ ("ok", Json::Bool(false)),
390
+ ("error", Json::str(refusal.to_string())),
391
+ ]),
351
392
  }
352
393
  }
353
394
 
@@ -365,14 +406,17 @@ fn tables() -> Tables<'static> {
365
406
  }
366
407
  }
367
408
 
409
+ /// Saga instances by (saga, correlation id), as `serve` keeps them across steps.
410
+ type SagaMap = HashMap<(String, String), SagaInstance>;
411
+
368
412
  /// `--serve` mode: one JSON step per stdin line, one answer per stdout line, store kept across
369
413
  /// lines. Beyond `verb` it takes `dry_run`, `snapshot`, `restore` and `instances` steps.
370
414
  pub fn serve(input: impl std::io::BufRead, mut output: impl std::io::Write) {
371
415
  let mut store = Store::new();
372
- let mut sagas: HashMap<(String, String), SagaInstance> = HashMap::new();
416
+ let mut sagas = SagaMap::new();
373
417
  let mut reaction_log: Vec<Json> = Vec::new();
374
418
  let mut saga_log: Vec<Json> = Vec::new();
375
- let mut snapshot: Option<(Store, HashMap<(String, String), SagaInstance>)> = None;
419
+ let mut snapshot: Option<(Store, SagaMap)> = None;
376
420
  let ok = || Json::obj(vec![("ok", Json::Bool(true))]);
377
421
 
378
422
  for line in input.lines() {
@@ -381,7 +425,10 @@ pub fn serve(input: impl std::io::BufRead, mut output: impl std::io::Write) {
381
425
  continue;
382
426
  }
383
427
  let answer = match Json::parse(&line) {
384
- Err(e) => Json::obj(vec![("ok", Json::Bool(false)), ("error", Json::str(format!("invalid JSON: {e}")))]),
428
+ Err(e) => Json::obj(vec![
429
+ ("ok", Json::Bool(false)),
430
+ ("error", Json::str(format!("invalid JSON: {e}"))),
431
+ ]),
385
432
  Ok(step) => {
386
433
  let empty_args = Json::Object(vec![]);
387
434
  let args = step.get("args").unwrap_or(&empty_args);
@@ -395,15 +442,27 @@ pub fn serve(input: impl std::io::BufRead, mut output: impl std::io::Write) {
395
442
  match &snapshot {
396
443
  Some((s, g)) => {
397
444
  store = s.clone();
398
- sagas = g.clone();
445
+ sagas.clone_from(g);
399
446
  ok()
400
447
  }
401
- None => Json::obj(vec![("ok", Json::Bool(false)), ("error", Json::str("no snapshot to restore"))]),
448
+ None => Json::obj(vec![
449
+ ("ok", Json::Bool(false)),
450
+ ("error", Json::str("no snapshot to restore")),
451
+ ]),
402
452
  }
403
453
  } else if step.get("instances").is_some() {
404
- Json::obj(vec![("ok", Json::Bool(true)), ("instances", Json::Object(store.instances()))])
454
+ Json::obj(vec![
455
+ ("ok", Json::Bool(true)),
456
+ ("instances", Json::Object(store.instances())),
457
+ ])
405
458
  } else if let Some(verb) = step.get("dry_run").and_then(Json::as_str) {
406
- dry_run(&store, verb, &dry_run_command_input(args), caller_role, caller_actor_id)
459
+ dry_run(
460
+ &store,
461
+ verb,
462
+ &dry_run_command_input(args),
463
+ caller_role,
464
+ caller_actor_id,
465
+ )
407
466
  } else if let Some(verb) = step.get("verb").and_then(Json::as_str) {
408
467
  let mut events: Vec<Event> = Vec::new();
409
468
  let outcome = orchestrate(
@@ -425,11 +484,26 @@ pub fn serve(input: impl std::io::BufRead, mut output: impl std::io::Write) {
425
484
  &mut saga_log,
426
485
  );
427
486
  match outcome {
428
- Ok(()) => Json::obj(vec![("ok", Json::Bool(true)), ("events", Json::Array(events.iter().map(event_to_json).collect()))]),
429
- Err(refusal) => Json::obj(vec![("ok", Json::Bool(false)), ("error", Json::str(refusal.to_string()))]),
487
+ Ok(()) => Json::obj(vec![
488
+ ("ok", Json::Bool(true)),
489
+ (
490
+ "events",
491
+ Json::Array(events.iter().map(event_to_json).collect()),
492
+ ),
493
+ ]),
494
+ Err(refusal) => Json::obj(vec![
495
+ ("ok", Json::Bool(false)),
496
+ ("error", Json::str(refusal.to_string())),
497
+ ]),
430
498
  }
431
499
  } else {
432
- Json::obj(vec![("ok", Json::Bool(false)), ("error", Json::str("step needs verb, dry_run, snapshot, restore, or instances"))])
500
+ Json::obj(vec![
501
+ ("ok", Json::Bool(false)),
502
+ (
503
+ "error",
504
+ Json::str("step needs verb, dry_run, snapshot, restore, or instances"),
505
+ ),
506
+ ])
433
507
  }
434
508
  }
435
509
  };
@@ -452,7 +526,10 @@ fn run_filter(store: &Store, filter: &Json) -> Result<Vec<Json>, Refusal> {
452
526
  .ok_or_else(|| Refusal::Fault(format!("unknown aggregate {aggregate:?}")))?;
453
527
 
454
528
  let matched = repository::filter_entries(entries, field, comparator, value);
455
- Ok(matched.into_iter().map(|(id, record)| repository::row_json(id, record)).collect())
529
+ Ok(matched
530
+ .into_iter()
531
+ .map(|(id, record)| repository::row_json(id, record))
532
+ .collect())
456
533
  }
457
534
 
458
535
  fn required_str<'a>(filter: &'a Json, key: &str) -> Result<&'a str, Refusal> {
@@ -464,9 +541,45 @@ fn required_str<'a>(filter: &'a Json, key: &str) -> Result<&'a str, Refusal> {
464
541
 
465
542
  /// The `refusals` verb column for a refused ad hoc filter, which has no verb; tolerates
466
543
  /// missing or mistyped fields.
544
+ /// The log entry for an answered `query` step. A question with a reference twin also carries
545
+ /// `reference_rows`, the same rows because both come from one compiled path.
546
+ fn answered_entry(question: &str, offered: &Json, rows: Json, with_reference: bool) -> Json {
547
+ let mut fields = vec![
548
+ ("query", Json::Str(question.to_string())),
549
+ ("args", offered.clone()),
550
+ ("rows", rows.clone()),
551
+ ];
552
+ if with_reference {
553
+ fields.push(("reference_rows", rows));
554
+ }
555
+ Json::obj(fields)
556
+ }
557
+
558
+ /// The log entry for a refused `query` step: `rows: null` and the refusal's text, with the
559
+ /// reference twin's null rows and identical error when the question has one.
560
+ fn refused_entry(question: &str, offered: &Json, refusal: &Refusal, with_reference: bool) -> Json {
561
+ let message = refusal.to_string();
562
+ let mut fields = vec![
563
+ ("query", Json::Str(question.to_string())),
564
+ ("args", offered.clone()),
565
+ ("rows", Json::Null),
566
+ ("error", Json::Str(message.clone())),
567
+ ];
568
+ if with_reference {
569
+ fields.push(("reference_rows", Json::Null));
570
+ fields.push(("reference_error", Json::Str(message)));
571
+ }
572
+ Json::obj(fields)
573
+ }
574
+
467
575
  fn filter_label(filter: &Json) -> String {
468
576
  let field = |key: &str| filter.get(key).and_then(Json::as_str).unwrap_or("");
469
- format!("filter {}.{} {}", field("aggregate"), field("field"), field("op"))
577
+ format!(
578
+ "filter {}.{} {}",
579
+ field("aggregate"),
580
+ field("field"),
581
+ field("op")
582
+ )
470
583
  }
471
584
 
472
585
  fn command_input<'a>(step: &'a Json, legacy_args: &'a Json) -> &'a Json {
@@ -544,7 +657,10 @@ fn event_to_json(event: &Event) -> Json {
544
657
  ("id", Json::str(event.id.clone())),
545
658
  ("payload", event.payload.clone()),
546
659
  // Key order matches Ruby's `Event#to_h`; `None` serialises as null.
547
- ("occurred_at", event.occurred_at.clone().map(Json::Str).unwrap_or(Json::Null)),
660
+ (
661
+ "occurred_at",
662
+ event.occurred_at.clone().map_or(Json::Null, Json::Str),
663
+ ),
548
664
  ])
549
665
  }
550
666
 
@@ -573,8 +689,14 @@ mod routing_tests {
573
689
  let selected = command_input(&step, &legacy_args);
574
690
  let invocation = crate::kernel::CommandInvocation::from_json(selected).unwrap();
575
691
  assert_eq!(invocation.route(), None);
576
- assert_eq!(invocation.facts().get("branch_code").and_then(Json::as_str), Some("DOWNTOWN"));
577
- assert_eq!(invocation.facts().get("box_number").and_then(Json::as_i64), Some(12));
692
+ assert_eq!(
693
+ invocation.facts().get("branch_code").and_then(Json::as_str),
694
+ Some("DOWNTOWN")
695
+ );
696
+ assert_eq!(
697
+ invocation.facts().get("box_number").and_then(Json::as_i64),
698
+ Some(12)
699
+ );
578
700
  }
579
701
 
580
702
  // A domain fact named `to` must dry-run as a plain fact, never a routing attempt
@@ -591,11 +713,19 @@ mod routing_tests {
591
713
 
592
714
  assert_eq!(invocation.route(), None);
593
715
  assert_eq!(
594
- invocation.facts().get("to").and_then(|to| to.get("value")).and_then(Json::as_i64),
716
+ invocation
717
+ .facts()
718
+ .get("to")
719
+ .and_then(|to| to.get("value"))
720
+ .and_then(Json::as_i64),
595
721
  Some(570)
596
722
  );
597
723
  assert_eq!(
598
- invocation.facts().get("name").and_then(|name| name.get("value")).and_then(Json::as_str),
724
+ invocation
725
+ .facts()
726
+ .get("name")
727
+ .and_then(|name| name.get("value"))
728
+ .and_then(Json::as_str),
599
729
  Some("crew-1")
600
730
  );
601
731
  }