hecks 3.13.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 (440) 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.bluebook +4 -4
  7. data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
  8. data/examples/pizzas/config.ru +22 -0
  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 +1 -7
  12. data/lib/hecks/bluebook/dsl/attribute_collector.rb +15 -5
  13. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
  14. data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
  15. data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
  16. data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
  17. data/lib/hecks/bluebook/hecksagon.rb +1 -1
  18. data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
  19. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
  20. data/lib/hecks/bluebook/meta_validator.rb +0 -11
  21. data/lib/hecks/bluebook/named_pattern.rb +33 -0
  22. data/lib/hecks/bluebook/translation.rb +18 -10
  23. data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
  24. data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
  25. data/lib/hecks/bluebook/translation_expression.rb +39 -0
  26. data/lib/hecks/bluebook/translation_plan.rb +70 -0
  27. data/lib/hecks/bluebook.rb +3 -0
  28. data/lib/hecks/chapters.rb +3 -3
  29. data/lib/hecks/cli/domain_stub.rb +1 -1
  30. data/lib/hecks/cli/interview_aggregate.rb +4 -4
  31. data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
  32. data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
  33. data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
  34. data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
  35. data/lib/hecks/composer/adapters/jev.adapter +3 -0
  36. data/lib/hecks/composer/adapters/jev.rb +66 -0
  37. data/lib/hecks/composer/agent.rb +113 -0
  38. data/lib/hecks/composer/assistant.rb +110 -0
  39. data/lib/hecks/composer/behavior_run.rb +53 -0
  40. data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
  41. data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
  42. data/lib/hecks/composer/board.rb +65 -0
  43. data/lib/hecks/composer/board_edit/command_text.rb +39 -0
  44. data/lib/hecks/composer/board_edit/source.rb +84 -0
  45. data/lib/hecks/composer/board_edit.rb +144 -0
  46. data/lib/hecks/composer/check.rb +89 -0
  47. data/lib/hecks/composer/domains.rb +81 -0
  48. data/lib/hecks/composer/jobs.rb +134 -0
  49. data/lib/hecks/composer/layout.rb +37 -0
  50. data/lib/hecks/composer/live.rb +150 -0
  51. data/lib/hecks/composer/outline.rb +48 -0
  52. data/lib/hecks/composer/page.html +989 -0
  53. data/lib/hecks/composer/panels.rb +142 -0
  54. data/lib/hecks/composer/parse.rb +83 -0
  55. data/lib/hecks/composer/plain.rb +28 -0
  56. data/lib/hecks/composer/ports/composer_agent.port +7 -0
  57. data/lib/hecks/composer/session.rb +141 -0
  58. data/lib/hecks/composer/toolbox.rb +166 -0
  59. data/lib/hecks/composer/web.rb +156 -0
  60. data/lib/hecks/composer/workbench.rb +51 -0
  61. data/lib/hecks/composer/workspace.rb +81 -0
  62. data/lib/hecks/composer.rb +17 -0
  63. data/lib/hecks/corpus/adapters.rb +22 -0
  64. data/lib/hecks/corpus/rust.rb +1 -1
  65. data/lib/hecks/corpus/worlds.rb +19 -0
  66. data/lib/hecks/corpus.rb +5 -0
  67. data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -34
  68. data/lib/hecks/forms/app/commands.rb +2 -3
  69. data/lib/hecks/forms/app/queries.rb +2 -3
  70. data/lib/hecks/forms/app/records.rb +17 -5
  71. data/lib/hecks/forms/command_form_renderer.rb +42 -16
  72. data/lib/hecks/forms/field_controls.rb +2 -2
  73. data/lib/hecks/forms/field_renderer.rb +1 -1
  74. data/lib/hecks/forms/field_shape.rb +36 -17
  75. data/lib/hecks/forms/index_renderer.rb +2 -1
  76. data/lib/hecks/forms/page.rb +1 -1
  77. data/lib/hecks/forms/params.rb +0 -13
  78. data/lib/hecks/forms/query_form_renderer.rb +7 -6
  79. data/lib/hecks/forms/record_renderer.rb +11 -7
  80. data/lib/hecks/forms/rule_words.rb +100 -0
  81. data/lib/hecks/forms/value_object_shape.rb +2 -1
  82. data/lib/hecks/framework/bluebook/change.bluebook +2 -2
  83. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
  84. data/lib/hecks/fuzzing/domain_generator/block_rendering.rb +1 -1
  85. data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
  86. data/lib/hecks/fuzzing/replay/filters.rb +2 -6
  87. data/lib/hecks/gate/stages.yml +25 -0
  88. data/lib/hecks/grammar/evolve/seeds.rb +0 -6
  89. data/lib/hecks/grammar/evolve.rb +0 -3
  90. data/lib/hecks/grammar/translation.bluebook +1 -1
  91. data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
  92. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
  93. data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
  94. data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
  95. data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
  96. data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
  97. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
  98. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
  99. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
  100. data/lib/hecks/hecks/codebase.behaviors +6 -0
  101. data/lib/hecks/hecks/codebase.bluebook +58 -37
  102. data/lib/hecks/hecks/composer.behaviors +68 -0
  103. data/lib/hecks/hecks/context_map.hecksagon +1 -1
  104. data/lib/hecks/hecks/custodian.behaviors +0 -6
  105. data/lib/hecks/hecks/custodian.bluebook +15 -92
  106. data/lib/hecks/hecks/hecks.bluebook +2 -2
  107. data/lib/hecks/hecks/hecks.hecksagon +2 -2
  108. data/lib/hecks/hecks/hecks.world +1 -1
  109. data/lib/hecks/hecks/rehearsal.bluebook +10 -16
  110. data/lib/hecks/hecks/tooling.bluebook +22 -22
  111. data/lib/hecks/kernel_capabilities/templates.rb +4 -4
  112. data/lib/hecks/kernel_capabilities.rb +1 -1
  113. data/lib/hecks/language/bluebook/aggregate.bluebook +1 -0
  114. data/lib/hecks/language/bluebook/command.bluebook +1 -0
  115. data/lib/hecks/language/bluebook/entity.bluebook +1 -0
  116. data/lib/hecks/language/bluebook/query.bluebook +1 -0
  117. data/lib/hecks/language/bluebook/shape.bluebook +8 -7
  118. data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
  119. data/lib/hecks/language/hecksagon/port_operation.bluebook +1 -0
  120. data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
  121. data/lib/hecks/ports/access_control.rb +2 -2
  122. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
  123. data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
  124. data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
  125. data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
  126. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
  127. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
  128. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
  129. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
  130. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
  131. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
  132. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
  133. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
  134. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
  135. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
  136. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
  137. data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
  138. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
  139. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
  140. data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
  141. data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
  142. data/lib/hecks/ports.rb +0 -1
  143. data/lib/hecks/projection_files.rb +3 -1
  144. data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
  145. data/lib/hecks/projections/glossary/entries.rb +10 -4
  146. data/lib/hecks/projections/glossary/groups.rb +42 -0
  147. data/lib/hecks/projections/glossary/html/page.rb +7 -2
  148. data/lib/hecks/projections/glossary/html/parser.rb +1 -1
  149. data/lib/hecks/projections/glossary/html/sections.rb +31 -11
  150. data/lib/hecks/projections/glossary/html.rb +1 -2
  151. data/lib/hecks/projections/glossary/markdown.rb +31 -28
  152. data/lib/hecks/projections/glossary/mermaid.rb +3 -0
  153. data/lib/hecks/projections/glossary/page.css +143 -153
  154. data/lib/hecks/projections/glossary/page.js +63 -3
  155. data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
  156. data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
  157. data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
  158. data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
  159. data/lib/hecks/projections/glossary/sentences.rb +13 -3
  160. data/lib/hecks/projections/glossary/slugs.rb +4 -1
  161. data/lib/hecks/projections/glossary.rb +2 -1
  162. data/lib/hecks/projections/plain_english.rb +97 -0
  163. data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
  164. data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
  165. data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
  166. data/lib/hecks/projections/rust_vocabulary.rb +14 -6
  167. data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
  168. data/lib/hecks/projections/statements/actions.rb +99 -0
  169. data/lib/hecks/projections/statements/relationships.rb +86 -0
  170. data/lib/hecks/projections/statements.rb +15 -24
  171. data/lib/hecks/projector/exporter/adapters.rb +112 -0
  172. data/lib/hecks/projector/exporter/translations.rb +18 -35
  173. data/lib/hecks/projector/exporter/worlds.rb +103 -0
  174. data/lib/hecks/projector/exporter.rb +5 -1
  175. data/lib/hecks/quality_control/adapters/agent.rb +6 -7
  176. data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
  177. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
  178. data/lib/hecks/quality_control/quality_control.bluebook +51 -51
  179. data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
  180. data/lib/hecks/runtime/outbox.rb +2 -9
  181. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
  182. data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
  183. data/lib/hecks/runtime/value.rb +13 -0
  184. data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
  185. data/lib/hecks/rust_build/wasm.rb +0 -12
  186. data/lib/hecks/rust_build/write_if_changed.rb +12 -12
  187. data/lib/hecks/rust_format.rb +77 -0
  188. data/lib/hecks/site/bluebook/site.bluebook +5 -5
  189. data/lib/hecks/sme/bluebook/sme.bluebook +12 -12
  190. data/lib/hecks/tickets/bluebook/tickets.bluebook +3 -3
  191. data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
  192. data/lib/hecks/tools/fuzz_sweep.rb +1 -3
  193. data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
  194. data/lib/hecks/tools/translation_audit.rb +8 -10
  195. data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
  196. data/lib/hecks/version.rb +1 -1
  197. data/lib/hecks/vocabulary.rb +2 -1
  198. data/lib/hecks.rb +3 -0
  199. data/prebuilt/{58208399bada10b0db99928c48e38789288fc58f7b71a72b21bfa386680e32ed.marshal → 02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal} +0 -0
  200. data/prebuilt/{2b59737fbdbd5f44edda0fddce2611f45d76087b3a7c782d1f1a17ea58c77948.marshal → bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal} +0 -0
  201. data/prebuilt/verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json +1 -0
  202. data/rust/Cargo.toml +40 -0
  203. data/rust/build/Cargo.toml +36 -0
  204. data/rust/build/clippy.toml +4 -0
  205. data/rust/build/src/adapters_pass.rs +196 -0
  206. data/rust/build/src/binding_pass.rs +54 -13
  207. data/rust/build/src/build_artifact.rs +23 -5
  208. data/rust/build/src/cargo_sync.rs +69 -26
  209. data/rust/build/src/lineage_pass.rs +101 -87
  210. data/rust/build/src/main.rs +11 -2
  211. data/rust/build/src/pipeline.rs +80 -19
  212. data/rust/build/src/reserved_names.rs +6 -50
  213. data/rust/build/src/resolve.rs +82 -35
  214. data/rust/build/src/rustfmt.rs +100 -0
  215. data/rust/build/src/seams_pass.rs +85 -16
  216. data/rust/build/src/subprocess.rs +30 -6
  217. data/rust/build/src/tmp.rs +5 -2
  218. data/rust/build/src/translation_pass.rs +72 -22
  219. data/rust/build/src/world_pass.rs +109 -0
  220. data/rust/clippy.toml +4 -0
  221. data/rust/codegen/Cargo.toml +36 -0
  222. data/rust/codegen/clippy.toml +4 -0
  223. data/rust/codegen/src/asks.rs +40 -14
  224. data/rust/codegen/src/attr.rs +2 -2
  225. data/rust/codegen/src/bridging.rs +676 -117
  226. data/rust/codegen/src/commands.rs +979 -373
  227. data/rust/codegen/src/constraints.rs +88 -25
  228. data/rust/codegen/src/dependency_planning.rs +128 -34
  229. data/rust/codegen/src/domain_generator.rs +300 -170
  230. data/rust/codegen/src/exemplar.rs +87 -32
  231. data/rust/codegen/src/expr_emitter.rs +54 -6
  232. data/rust/codegen/src/fielded.rs +193 -41
  233. data/rust/codegen/src/hecks_naming.rs +124 -16
  234. data/rust/codegen/src/hecks_naming_tests.rs +81 -0
  235. data/rust/codegen/src/json.rs +80 -44
  236. data/rust/codegen/src/json_codec.rs +585 -149
  237. data/rust/codegen/src/literal.rs +104 -138
  238. data/rust/codegen/src/main.rs +145 -52
  239. data/rust/codegen/src/manifest.rs +76 -14
  240. data/rust/codegen/src/mutations.rs +1212 -239
  241. data/rust/codegen/src/naming.rs +111 -60
  242. data/rust/codegen/src/optional_pass.rs +90 -28
  243. data/rust/codegen/src/ports.rs +5 -4
  244. data/rust/codegen/src/prelude.rs +139 -49
  245. data/rust/codegen/src/queries.rs +601 -226
  246. data/rust/codegen/src/reactions.rs +268 -74
  247. data/rust/codegen/src/read_models.rs +606 -205
  248. data/rust/codegen/src/reference_specs.rs +16 -3
  249. data/rust/codegen/src/registry.rs +128 -79
  250. data/rust/codegen/src/reserved_names.rs +6 -50
  251. data/rust/codegen/src/shared.rs +71 -22
  252. data/rust/codegen/src/sidecars.rs +15 -5
  253. data/rust/codegen/src/skip_reason.rs +8 -2
  254. data/rust/codegen/src/types.rs +306 -108
  255. data/rust/host/Cargo.lock +1 -0
  256. data/rust/host/Cargo.toml +40 -4
  257. data/rust/host/HECKS_RELEASE +1 -1
  258. data/rust/host/clippy.toml +4 -0
  259. data/rust/host/src/api.rs +698 -218
  260. data/rust/host/src/auth.rs +491 -214
  261. data/rust/host/src/b64.rs +91 -0
  262. data/rust/host/src/bin/expr_harness.rs +6 -2
  263. data/rust/host/src/bin/lineage_harness.rs +47 -11
  264. data/rust/host/src/bin/mint_harness.rs +70 -37
  265. data/rust/host/src/bin/validate_harness.rs +16 -8
  266. data/rust/host/src/boot.rs +144 -68
  267. data/rust/host/src/boundary_fuzz/api.rs +130 -32
  268. data/rust/host/src/boundary_fuzz/auth.rs +108 -23
  269. data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
  270. data/rust/host/src/boundary_fuzz/ir.rs +164 -12
  271. data/rust/host/src/boundary_fuzz/server.rs +283 -36
  272. data/rust/host/src/boundary_fuzz/web.rs +56 -8
  273. data/rust/host/src/checkout.rs +78 -32
  274. data/rust/host/src/clock.rs +8 -0
  275. data/rust/host/src/commerce_ir.rs +333 -95
  276. data/rust/host/src/crypto.rs +33 -0
  277. data/rust/host/src/dispatch.rs +990 -331
  278. data/rust/host/src/expr_json.rs +1021 -273
  279. data/rust/host/src/extension.rs +24 -4
  280. data/rust/host/src/field_hints.rs +11 -7
  281. data/rust/host/src/fuzz_support.rs +85 -13
  282. data/rust/host/src/host.rs +100 -0
  283. data/rust/host/src/http.rs +22 -0
  284. data/rust/host/src/ir.rs +326 -16
  285. data/rust/host/src/journal.rs +277 -176
  286. data/rust/host/src/lambda_client.rs +219 -53
  287. data/rust/host/src/lib.rs +16 -4
  288. data/rust/host/src/log.rs +62 -11
  289. data/rust/host/src/mint.rs +1153 -326
  290. data/rust/host/src/needs.rs +185 -44
  291. data/rust/host/src/payments/keystore.rs +197 -45
  292. data/rust/host/src/payments/roles.rs +191 -0
  293. data/rust/host/src/payments/tests.rs +1445 -275
  294. data/rust/host/src/payments.rs +409 -121
  295. data/rust/host/src/platform_signin/attack_tests.rs +5 -5
  296. data/rust/host/src/platform_signin/config.rs +11 -11
  297. data/rust/host/src/platform_signin/cookie.rs +2 -2
  298. data/rust/host/src/platform_signin/extension.rs +2 -1
  299. data/rust/host/src/platform_signin/handler.rs +9 -8
  300. data/rust/host/src/platform_signin/jti.rs +4 -2
  301. data/rust/host/src/platform_signin/jwks.rs +2 -1
  302. data/rust/host/src/platform_signin/jws.rs +2 -37
  303. data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
  304. data/rust/host/src/platform_signin/mode_tests.rs +5 -3
  305. data/rust/host/src/platform_signin/pages.rs +20 -20
  306. data/rust/host/src/platform_signin/session.rs +8 -1
  307. data/rust/host/src/platform_signin/settings.rs +6 -6
  308. data/rust/host/src/platform_signin/strict_json.rs +12 -9
  309. data/rust/host/src/platform_signin/verify.rs +41 -41
  310. data/rust/host/src/platform_signin/wiring_support.rs +13 -18
  311. data/rust/host/src/presentation.rs +207 -63
  312. data/rust/host/src/presentation_write.rs +502 -187
  313. data/rust/host/src/query_step.rs +69 -18
  314. data/rust/host/src/rate_limit.rs +541 -142
  315. data/rust/host/src/reference_transform.rs +77 -25
  316. data/rust/host/src/reference_validate.rs +443 -104
  317. data/rust/host/src/resend.rs +190 -55
  318. data/rust/host/src/secrets.rs +116 -21
  319. data/rust/host/src/server.rs +483 -104
  320. data/rust/host/src/storage_shape.rs +88 -21
  321. data/rust/host/src/test_pg.rs +17 -7
  322. data/rust/host/src/ui_schema.rs +495 -207
  323. data/rust/host/src/wasm_runner.rs +21 -6
  324. data/rust/host/src/web/commerce.rs +101 -23
  325. data/rust/host/src/web/newsletter/cooldown.rs +10 -3
  326. data/rust/host/src/web/newsletter/template.rs +26 -7
  327. data/rust/host/src/web/newsletter/tests.rs +112 -23
  328. data/rust/host/src/web/newsletter.rs +209 -59
  329. data/rust/host/src/web/newsletter_send.rs +251 -79
  330. data/rust/host/src/web/registration_receipt/tests.rs +16 -4
  331. data/rust/host/src/web/registration_receipt.rs +100 -24
  332. data/rust/host/src/web/registrations.rs +1384 -332
  333. data/rust/host/src/web/resend_webhook.rs +138 -80
  334. data/rust/host/src/web.rs +2416 -508
  335. data/rust/lsp/Cargo.toml +36 -0
  336. data/rust/lsp/clippy.toml +4 -0
  337. data/rust/lsp/src/json.rs +29 -22
  338. data/rust/lsp/src/main.rs +81 -31
  339. data/rust/lsp/src/outline.rs +23 -7
  340. data/rust/lsp/src/rpc.rs +17 -4
  341. data/rust/parser/Cargo.toml +36 -0
  342. data/rust/parser/clippy.toml +4 -0
  343. data/rust/parser/src/build/closed_sets.rs +6 -1
  344. data/rust/parser/src/build/identity.rs +4 -3
  345. data/rust/parser/src/build/mod.rs +1 -0
  346. data/rust/parser/src/build/named_pattern.rs +26 -0
  347. data/rust/parser/src/build/naming.rs +2 -80
  348. data/rust/parser/src/build/pattern_subset.rs +6 -8
  349. data/rust/parser/src/build/query_derive.rs +3 -1
  350. data/rust/parser/src/build/query_inference.rs +14 -5
  351. data/rust/parser/src/build/read_model.rs +4 -1
  352. data/rust/parser/src/build/references.rs +4 -3
  353. data/rust/parser/src/canonical.rs +34 -15
  354. data/rust/parser/src/emit.rs +187 -105
  355. data/rust/parser/src/expr/ast_json.rs +134 -42
  356. data/rust/parser/src/expr/evaluator.rs +27 -9
  357. data/rust/parser/src/expr/mod.rs +54 -13
  358. data/rust/parser/src/expr/resolver.rs +299 -92
  359. data/rust/parser/src/ir.rs +35 -3
  360. data/rust/parser/src/keywords.rs +7168 -510
  361. data/rust/parser/src/lex.rs +11 -25
  362. data/rust/parser/src/lib.rs +1 -0
  363. data/rust/parser/src/main.rs +65 -6
  364. data/rust/parser/src/parse/adapter.rs +157 -0
  365. data/rust/parser/src/parse/aggregate.rs +19 -10
  366. data/rust/parser/src/parse/chapter.rs +16 -17
  367. data/rust/parser/src/parse/command.rs +35 -16
  368. data/rust/parser/src/parse/domain_port.rs +4 -7
  369. data/rust/parser/src/parse/entity.rs +21 -11
  370. data/rust/parser/src/parse/hecksagon.rs +10 -4
  371. data/rust/parser/src/parse/lifecycle.rs +9 -5
  372. data/rust/parser/src/parse/mod.rs +43 -91
  373. data/rust/parser/src/parse/needs.rs +21 -12
  374. data/rust/parser/src/parse/policy.rs +2 -2
  375. data/rust/parser/src/parse/process_manager.rs +9 -11
  376. data/rust/parser/src/parse/query.rs +8 -4
  377. data/rust/parser/src/parse/read_model.rs +57 -16
  378. data/rust/parser/src/parse/value_object.rs +7 -2
  379. data/rust/parser/src/parse/world.rs +322 -0
  380. data/rust/parser/src/ruby_value.rs +7 -6
  381. data/rust/src/exemplar/json.rs +4 -4
  382. data/rust/src/exemplar/mutations.rs +18 -0
  383. data/rust/src/exemplar/read_models.rs +1 -10
  384. data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
  385. data/rust/src/kernel/attribute_shapes/list.rs +2 -2
  386. data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
  387. data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
  388. data/rust/src/kernel/cli.rs +229 -112
  389. data/rust/src/kernel/dispatch.rs +323 -115
  390. data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
  391. data/rust/src/kernel/expr.rs +203 -86
  392. data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
  393. data/rust/src/kernel/expression_operators/comparison.rs +87 -2
  394. data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
  395. data/rust/src/kernel/expression_operators/logical.rs +9 -3
  396. data/rust/src/kernel/expression_operators/membership.rs +3 -1
  397. data/rust/src/kernel/expression_operators/mod.rs +34 -21
  398. data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
  399. data/rust/src/kernel/expression_operators/positional.rs +84 -30
  400. data/rust/src/kernel/expression_operators/presence.rs +52 -13
  401. data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
  402. data/rust/src/kernel/expression_operators/sized.rs +24 -8
  403. data/rust/src/kernel/expression_operators/text.rs +120 -27
  404. data/rust/src/kernel/expression_operators/to_string.rs +10 -4
  405. data/rust/src/kernel/json.rs +230 -148
  406. data/rust/src/kernel/mod.rs +46 -33
  407. data/rust/src/kernel/named_query.rs +63 -15
  408. data/rust/src/kernel/needs.rs +112 -25
  409. data/rust/src/kernel/orchestrate.rs +980 -581
  410. data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
  411. data/rust/src/kernel/pattern.rs +107 -34
  412. data/rust/src/kernel/query_comparators.rs +118 -41
  413. data/rust/src/kernel/query_ordering.rs +13 -5
  414. data/rust/src/kernel/read_model.rs +479 -181
  415. data/rust/src/kernel/reference_lookup.rs +122 -26
  416. data/rust/src/kernel/repository.rs +293 -66
  417. data/rust/src/kernel/routing.rs +58 -42
  418. data/rust/src/kernel/ruby_float.rs +139 -0
  419. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
  420. data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
  421. data/rust/src/kernel/vocab/field_hint.rs +19 -25
  422. data/rust/src/kernel/vocab/mod.rs +6 -6
  423. data/rust/src/kernel/vocab/query_comparator.rs +23 -24
  424. data/rust/src/kernel/vocab/refusal_template.rs +501 -297
  425. data/rust/src/lib.rs +50 -0
  426. data/rust/src/main.rs +4 -1
  427. data/rust/web/Cargo.toml +36 -0
  428. data/rust/web/clippy.toml +4 -0
  429. metadata +85 -15
  430. data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
  431. data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
  432. data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
  433. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
  434. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
  435. data/lib/hecks/ports/authentication.port +0 -6
  436. data/lib/hecks/ports/authentication.rb +0 -67
  437. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
  438. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
  439. data/prebuilt/verdicts-48318ca172edd21443b1ad02801c697fff100b746e828f09db987647641a5df2.json +0 -1
  440. data/rust/host/src/approval.rs +0 -652
@@ -1,8 +1,16 @@
1
1
  //! The stdin/stdout JSON CLI shared by the native binary and the wasm32-wasip1 module.
2
2
  //! `run` reads `{"steps": [...]}` and hands each step to `kernel::orchestrate` (ADR 0012).
3
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};
4
+ use super::{
5
+ named_query, orchestrate, query_comparators, read_model, repository, AggregateScan,
6
+ CompletedCompensation, Event, Json, MutationRecord, PendingCrossDomainReaction, Refusal,
7
+ SagaInstance, Tables,
8
+ };
9
+ use crate::generated::active::{
10
+ command_attributes_for_verb, command_creates, dispatch_by_name, entity_identity_head_for_path,
11
+ identity_head_for_aggregate, reference_key_for_aggregate, Store, CROSS_DOMAIN_POLICIES,
12
+ POLICIES, PROCESS_MANAGERS, QUERIES, READ_MODELS,
13
+ };
6
14
  use std::collections::HashMap;
7
15
 
8
16
  // Builds and invariant-checks a named query's value-object arguments (ADR 0037).
@@ -23,9 +31,8 @@ pub fn run(input: &str) -> String {
23
31
  // Optional `"defaults"`: each command's declared argument defaults, filled the same way.
24
32
  super::needs::install_defaults(parsed.get("defaults"));
25
33
 
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"),
34
+ let Some(steps) = parsed.get("steps").and_then(Json::as_array) else {
35
+ return error_output("expected a top-level {\"steps\": [...]} object");
29
36
  };
30
37
 
31
38
  // Optional `"seed"`: prior state as `"instances"` emits it, so a host need not replay history.
@@ -40,34 +47,46 @@ pub fn run(input: &str) -> String {
40
47
  let mut refusals: Vec<(String, Refusal)> = Vec::new();
41
48
  // Process-manager instances are domain-level, so not a `Store` field.
42
49
  // 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
- };
50
+ let mut sagas: HashMap<(String, String), SagaInstance> = parsed
51
+ .get("sagas")
52
+ .and_then(Json::as_array)
53
+ .map_or_else(HashMap::new, |entries| {
54
+ entries
55
+ .iter()
56
+ .filter_map(|entry| {
57
+ let process_manager = entry.get("process_manager")?.as_str()?.to_string();
58
+ let correlation = entry.get("correlation")?.as_str()?.to_string();
59
+ let state = entry.get("state")?.as_str()?.to_string();
60
+ let memory = entry
61
+ .get("memory")
62
+ .cloned()
63
+ .unwrap_or_else(|| Json::Object(vec![]));
64
+ // Optional: an instance persisted without a ledger rehydrates with an empty one.
65
+ let completed_compensations = entry
66
+ .get("completed_compensations")
67
+ .and_then(Json::as_array)
68
+ .map(|entries| {
69
+ entries
70
+ .iter()
71
+ .filter_map(|e| {
72
+ let command_name = e.get("command_name")?.as_str()?.to_string();
73
+ let args = e.get("args").cloned().unwrap_or(Json::Null);
74
+ Some(CompletedCompensation { command_name, args })
75
+ })
76
+ .collect()
77
+ })
78
+ .unwrap_or_default();
79
+ Some((
80
+ (process_manager, correlation),
81
+ SagaInstance {
82
+ state,
83
+ memory,
84
+ completed_compensations,
85
+ },
86
+ ))
87
+ })
88
+ .collect()
89
+ });
71
90
  // One entry per step, parallel to `steps`, so a host can read `.last()` for the newest step.
72
91
  let mut mutations_per_step: Vec<Vec<MutationRecord>> = Vec::new();
73
92
  // Parallel to `mutations_per_step`: policies that named a domain not compiled into this
@@ -92,7 +111,11 @@ pub fn run(input: &str) -> String {
92
111
  // for the run alone (ADR 0081), so the log holds no value that depends on the clock.
93
112
  let offered = args;
94
113
  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)),
114
+ Some(Json::Str(question)) => super::needs::enrich_query(
115
+ question,
116
+ args,
117
+ step.get("occurred_at").and_then(Json::as_str),
118
+ ),
96
119
  _ => None,
97
120
  };
98
121
  let args = filled.as_ref().unwrap_or(args);
@@ -110,27 +133,15 @@ pub fn run(input: &str) -> String {
110
133
  // reference twin is the same engine.
111
134
  // `reference_interpret` delegates to the same `entity_rows`).
112
135
  Json::Str(question) if question.contains("::") && named_query::find_entity(ENTITY_QUERIES, question).is_some() => {
136
+ // reason: the match guard above only admits a `question` that resolves.
137
+ #[allow(clippy::expect_used)]
113
138
  let def = named_query::find_entity(ENTITY_QUERIES, question).expect("matched by the guard above");
114
139
  match named_query::run_entity(&store, def, args) {
115
140
  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
- ]));
141
+ query_results.push(answered_entry(question, offered, Json::Array(rows), true));
123
142
  }
124
143
  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
- ]));
144
+ query_results.push(refused_entry(question, offered, &refusal, true));
134
145
  refusals.push((question.clone(), refusal));
135
146
  }
136
147
  }
@@ -139,26 +150,13 @@ pub fn run(input: &str) -> String {
139
150
  Some(def) => match check_query_args(question, args).and_then(|()| named_query::run(&store, def, args, caller_role)) {
140
151
  Ok(entries) => {
141
152
  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
- ]));
153
+ // The reference twin is `rows`: one compiled path (named_query.rs).
154
+ query_results.push(answered_entry(question, offered, rows, true));
149
155
  }
150
156
  // A refused query still gets an entry with `rows: null`, as in Ruby; one
151
157
  // compiled path means `error` and `reference_error` are the same string.
152
158
  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
- ]));
159
+ query_results.push(refused_entry(question, offered, &refusal, true));
162
160
  refusals.push((question.clone(), refusal));
163
161
  }
164
162
  },
@@ -179,24 +177,11 @@ pub fn run(input: &str) -> String {
179
177
  },
180
178
  Json::Str(question) => match read_model::find(READ_MODELS, question) {
181
179
  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.
180
+ // No reference keys: Ruby sets them only for questions with a reference twin,
181
+ // and a read model has none.
182
+ Ok(row) => query_results.push(answered_entry(question, offered, Json::Array(vec![row]), false)),
192
183
  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
- ]));
184
+ query_results.push(refused_entry(question, offered, &refusal, false));
200
185
  refusals.push((question.clone(), refusal));
201
186
  }
202
187
  },
@@ -235,16 +220,21 @@ pub fn run(input: &str) -> String {
235
220
  // See `actor_id` on the command step below.
236
221
  let caller_actor_id = step.get("actor_id").and_then(Json::as_str);
237
222
  let command_input = dry_run_command_input(args);
238
- dry_runs.push(dry_run(&store, verb, &command_input, caller_role, caller_actor_id));
223
+ dry_runs.push(dry_run(
224
+ &store,
225
+ verb,
226
+ &command_input,
227
+ caller_role,
228
+ caller_actor_id,
229
+ ));
239
230
  mutations_per_step.push(Vec::new());
240
231
  cross_domain_per_step.push(Vec::new());
241
232
  reactions_per_step.push(Json::Array(Vec::new()));
242
233
  continue;
243
234
  }
244
235
 
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\""),
236
+ let Some(verb) = step.get("verb").and_then(Json::as_str) else {
237
+ return error_output("step missing \"verb\", \"dry_run\", or \"query\"");
248
238
  };
249
239
 
250
240
  // Per-step `role:` stands in for Ruby's ambient `Hecks.as_caller`; passed to the top-level
@@ -306,19 +296,34 @@ pub fn run(input: &str) -> String {
306
296
  let refusals_json = Json::Array(
307
297
  refusals
308
298
  .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()))]))
299
+ .map(|(verb, r)| {
300
+ Json::obj(vec![
301
+ ("verb", Json::str(verb.clone())),
302
+ ("error", Json::str(r.to_string())),
303
+ ("kind", Json::str(r.kind().to_string())),
304
+ ])
305
+ })
310
306
  .collect(),
311
307
  );
312
308
  let mutations_json = Json::Array(
313
309
  mutations_per_step
314
310
  .iter()
315
- .map(|step_mutations| Json::Array(step_mutations.iter().map(mutation_to_json).collect()))
311
+ .map(|step_mutations| {
312
+ Json::Array(step_mutations.iter().map(mutation_to_json).collect())
313
+ })
316
314
  .collect(),
317
315
  );
318
316
  let cross_domain_json = Json::Array(
319
317
  cross_domain_per_step
320
318
  .iter()
321
- .map(|step_reactions| Json::Array(step_reactions.iter().map(cross_domain_reaction_to_json).collect()))
319
+ .map(|step_reactions| {
320
+ Json::Array(
321
+ step_reactions
322
+ .iter()
323
+ .map(cross_domain_reaction_to_json)
324
+ .collect(),
325
+ )
326
+ })
322
327
  .collect(),
323
328
  );
324
329
 
@@ -332,7 +337,10 @@ pub fn run(input: &str) -> String {
332
337
  ("cross_domain_reactions".to_string(), cross_domain_json),
333
338
  // Whole-run logs, not per-step.
334
339
  ("reactions".to_string(), Json::Array(reaction_log)),
335
- ("reactions_per_step".to_string(), Json::Array(reactions_per_step)),
340
+ (
341
+ "reactions_per_step".to_string(),
342
+ Json::Array(reactions_per_step),
343
+ ),
336
344
  ("sagas".to_string(), Json::Array(saga_log)),
337
345
  // State at the end of the run, fed back as `"sagas"` next time; the `"sagas"` key above
338
346
  // is the transition log, including refused transitions.
@@ -343,11 +351,31 @@ pub fn run(input: &str) -> String {
343
351
 
344
352
  /// Evaluates a command against a throwaway copy of the store; `"error"` carries the refusal
345
353
  /// 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 {
354
+ fn dry_run(
355
+ store: &Store,
356
+ verb: &str,
357
+ command_input: &Json,
358
+ caller_role: Option<&str>,
359
+ caller_actor_id: Option<&str>,
360
+ ) -> Json {
347
361
  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()))]),
362
+ match dispatch_by_name(
363
+ &mut scratch,
364
+ verb,
365
+ command_input,
366
+ caller_role,
367
+ caller_actor_id,
368
+ &mut Vec::new(),
369
+ ) {
370
+ Ok(_) => Json::obj(vec![
371
+ ("verb", Json::str(verb.to_string())),
372
+ ("ok", Json::Bool(true)),
373
+ ]),
374
+ Err(refusal) => Json::obj(vec![
375
+ ("verb", Json::str(verb.to_string())),
376
+ ("ok", Json::Bool(false)),
377
+ ("error", Json::str(refusal.to_string())),
378
+ ]),
351
379
  }
352
380
  }
353
381
 
@@ -365,14 +393,17 @@ fn tables() -> Tables<'static> {
365
393
  }
366
394
  }
367
395
 
396
+ /// Saga instances by (saga, correlation id), as `serve` keeps them across steps.
397
+ type SagaMap = HashMap<(String, String), SagaInstance>;
398
+
368
399
  /// `--serve` mode: one JSON step per stdin line, one answer per stdout line, store kept across
369
400
  /// lines. Beyond `verb` it takes `dry_run`, `snapshot`, `restore` and `instances` steps.
370
401
  pub fn serve(input: impl std::io::BufRead, mut output: impl std::io::Write) {
371
402
  let mut store = Store::new();
372
- let mut sagas: HashMap<(String, String), SagaInstance> = HashMap::new();
403
+ let mut sagas = SagaMap::new();
373
404
  let mut reaction_log: Vec<Json> = Vec::new();
374
405
  let mut saga_log: Vec<Json> = Vec::new();
375
- let mut snapshot: Option<(Store, HashMap<(String, String), SagaInstance>)> = None;
406
+ let mut snapshot: Option<(Store, SagaMap)> = None;
376
407
  let ok = || Json::obj(vec![("ok", Json::Bool(true))]);
377
408
 
378
409
  for line in input.lines() {
@@ -381,7 +412,10 @@ pub fn serve(input: impl std::io::BufRead, mut output: impl std::io::Write) {
381
412
  continue;
382
413
  }
383
414
  let answer = match Json::parse(&line) {
384
- Err(e) => Json::obj(vec![("ok", Json::Bool(false)), ("error", Json::str(format!("invalid JSON: {e}")))]),
415
+ Err(e) => Json::obj(vec![
416
+ ("ok", Json::Bool(false)),
417
+ ("error", Json::str(format!("invalid JSON: {e}"))),
418
+ ]),
385
419
  Ok(step) => {
386
420
  let empty_args = Json::Object(vec![]);
387
421
  let args = step.get("args").unwrap_or(&empty_args);
@@ -395,15 +429,27 @@ pub fn serve(input: impl std::io::BufRead, mut output: impl std::io::Write) {
395
429
  match &snapshot {
396
430
  Some((s, g)) => {
397
431
  store = s.clone();
398
- sagas = g.clone();
432
+ sagas.clone_from(g);
399
433
  ok()
400
434
  }
401
- None => Json::obj(vec![("ok", Json::Bool(false)), ("error", Json::str("no snapshot to restore"))]),
435
+ None => Json::obj(vec![
436
+ ("ok", Json::Bool(false)),
437
+ ("error", Json::str("no snapshot to restore")),
438
+ ]),
402
439
  }
403
440
  } else if step.get("instances").is_some() {
404
- Json::obj(vec![("ok", Json::Bool(true)), ("instances", Json::Object(store.instances()))])
441
+ Json::obj(vec![
442
+ ("ok", Json::Bool(true)),
443
+ ("instances", Json::Object(store.instances())),
444
+ ])
405
445
  } 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)
446
+ dry_run(
447
+ &store,
448
+ verb,
449
+ &dry_run_command_input(args),
450
+ caller_role,
451
+ caller_actor_id,
452
+ )
407
453
  } else if let Some(verb) = step.get("verb").and_then(Json::as_str) {
408
454
  let mut events: Vec<Event> = Vec::new();
409
455
  let outcome = orchestrate(
@@ -425,11 +471,26 @@ pub fn serve(input: impl std::io::BufRead, mut output: impl std::io::Write) {
425
471
  &mut saga_log,
426
472
  );
427
473
  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()))]),
474
+ Ok(()) => Json::obj(vec![
475
+ ("ok", Json::Bool(true)),
476
+ (
477
+ "events",
478
+ Json::Array(events.iter().map(event_to_json).collect()),
479
+ ),
480
+ ]),
481
+ Err(refusal) => Json::obj(vec![
482
+ ("ok", Json::Bool(false)),
483
+ ("error", Json::str(refusal.to_string())),
484
+ ]),
430
485
  }
431
486
  } else {
432
- Json::obj(vec![("ok", Json::Bool(false)), ("error", Json::str("step needs verb, dry_run, snapshot, restore, or instances"))])
487
+ Json::obj(vec![
488
+ ("ok", Json::Bool(false)),
489
+ (
490
+ "error",
491
+ Json::str("step needs verb, dry_run, snapshot, restore, or instances"),
492
+ ),
493
+ ])
433
494
  }
434
495
  }
435
496
  };
@@ -452,7 +513,10 @@ fn run_filter(store: &Store, filter: &Json) -> Result<Vec<Json>, Refusal> {
452
513
  .ok_or_else(|| Refusal::Fault(format!("unknown aggregate {aggregate:?}")))?;
453
514
 
454
515
  let matched = repository::filter_entries(entries, field, comparator, value);
455
- Ok(matched.into_iter().map(|(id, record)| repository::row_json(id, record)).collect())
516
+ Ok(matched
517
+ .into_iter()
518
+ .map(|(id, record)| repository::row_json(id, record))
519
+ .collect())
456
520
  }
457
521
 
458
522
  fn required_str<'a>(filter: &'a Json, key: &str) -> Result<&'a str, Refusal> {
@@ -464,9 +528,45 @@ fn required_str<'a>(filter: &'a Json, key: &str) -> Result<&'a str, Refusal> {
464
528
 
465
529
  /// The `refusals` verb column for a refused ad hoc filter, which has no verb; tolerates
466
530
  /// missing or mistyped fields.
531
+ /// The log entry for an answered `query` step. A question with a reference twin also carries
532
+ /// `reference_rows`, the same rows because both come from one compiled path.
533
+ fn answered_entry(question: &str, offered: &Json, rows: Json, with_reference: bool) -> Json {
534
+ let mut fields = vec![
535
+ ("query", Json::Str(question.to_string())),
536
+ ("args", offered.clone()),
537
+ ("rows", rows.clone()),
538
+ ];
539
+ if with_reference {
540
+ fields.push(("reference_rows", rows));
541
+ }
542
+ Json::obj(fields)
543
+ }
544
+
545
+ /// The log entry for a refused `query` step: `rows: null` and the refusal's text, with the
546
+ /// reference twin's null rows and identical error when the question has one.
547
+ fn refused_entry(question: &str, offered: &Json, refusal: &Refusal, with_reference: bool) -> Json {
548
+ let message = refusal.to_string();
549
+ let mut fields = vec![
550
+ ("query", Json::Str(question.to_string())),
551
+ ("args", offered.clone()),
552
+ ("rows", Json::Null),
553
+ ("error", Json::Str(message.clone())),
554
+ ];
555
+ if with_reference {
556
+ fields.push(("reference_rows", Json::Null));
557
+ fields.push(("reference_error", Json::Str(message)));
558
+ }
559
+ Json::obj(fields)
560
+ }
561
+
467
562
  fn filter_label(filter: &Json) -> String {
468
563
  let field = |key: &str| filter.get(key).and_then(Json::as_str).unwrap_or("");
469
- format!("filter {}.{} {}", field("aggregate"), field("field"), field("op"))
564
+ format!(
565
+ "filter {}.{} {}",
566
+ field("aggregate"),
567
+ field("field"),
568
+ field("op")
569
+ )
470
570
  }
471
571
 
472
572
  fn command_input<'a>(step: &'a Json, legacy_args: &'a Json) -> &'a Json {
@@ -544,7 +644,10 @@ fn event_to_json(event: &Event) -> Json {
544
644
  ("id", Json::str(event.id.clone())),
545
645
  ("payload", event.payload.clone()),
546
646
  // 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)),
647
+ (
648
+ "occurred_at",
649
+ event.occurred_at.clone().map_or(Json::Null, Json::Str),
650
+ ),
548
651
  ])
549
652
  }
550
653
 
@@ -573,8 +676,14 @@ mod routing_tests {
573
676
  let selected = command_input(&step, &legacy_args);
574
677
  let invocation = crate::kernel::CommandInvocation::from_json(selected).unwrap();
575
678
  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));
679
+ assert_eq!(
680
+ invocation.facts().get("branch_code").and_then(Json::as_str),
681
+ Some("DOWNTOWN")
682
+ );
683
+ assert_eq!(
684
+ invocation.facts().get("box_number").and_then(Json::as_i64),
685
+ Some(12)
686
+ );
578
687
  }
579
688
 
580
689
  // A domain fact named `to` must dry-run as a plain fact, never a routing attempt
@@ -591,11 +700,19 @@ mod routing_tests {
591
700
 
592
701
  assert_eq!(invocation.route(), None);
593
702
  assert_eq!(
594
- invocation.facts().get("to").and_then(|to| to.get("value")).and_then(Json::as_i64),
703
+ invocation
704
+ .facts()
705
+ .get("to")
706
+ .and_then(|to| to.get("value"))
707
+ .and_then(Json::as_i64),
595
708
  Some(570)
596
709
  );
597
710
  assert_eq!(
598
- invocation.facts().get("name").and_then(|name| name.get("value")).and_then(Json::as_str),
711
+ invocation
712
+ .facts()
713
+ .get("name")
714
+ .and_then(|name| name.get("value"))
715
+ .and_then(Json::as_str),
599
716
  Some("crew-1")
600
717
  );
601
718
  }