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
@@ -2,7 +2,7 @@
2
2
  //! edge made of renames and moves needs: the declared rules plus the SQL expression
3
3
  //! `Translation::RuleCompiler.compile_rules` builds for them.
4
4
  //!
5
- //! A rule kind this pass does not compile (convert, drop, retype, compute, rekey, backfill) is
5
+ //! A rule kind this pass does not compile (convert, drop, retype, backfill) is
6
6
  //! refused by name, so a domain never ships an edge whose SQL was silently left out.
7
7
 
8
8
  use std::path::Path;
@@ -20,7 +20,9 @@ struct Aggregate {
20
20
  /// The `translations` IR list for the `.bluebook` files under `bluebook_dir/translations`, ordered
21
21
  /// by file name (an era-step id), as the registry loads them.
22
22
  pub fn edges(bluebook_dir: &Path) -> Result<Vec<Json>, String> {
23
- let Ok(entries) = std::fs::read_dir(bluebook_dir.join("translations")) else { return Ok(Vec::new()) };
23
+ let Ok(entries) = std::fs::read_dir(bluebook_dir.join("translations")) else {
24
+ return Ok(Vec::new());
25
+ };
24
26
  let mut paths: Vec<_> = entries
25
27
  .filter_map(Result::ok)
26
28
  .map(|entry| entry.path())
@@ -29,7 +31,8 @@ pub fn edges(bluebook_dir: &Path) -> Result<Vec<Json>, String> {
29
31
  paths.sort();
30
32
  let mut edges = Vec::new();
31
33
  for path in paths {
32
- let text = std::fs::read_to_string(&path).map_err(|e| format!("reading {}: {e}", path.display()))?;
34
+ let text = std::fs::read_to_string(&path)
35
+ .map_err(|e| format!("reading {}: {e}", path.display()))?;
33
36
  edges.push(edge(&text).map_err(|why| format!("{}: {why}", path.display()))?);
34
37
  }
35
38
  Ok(edges)
@@ -44,16 +47,31 @@ fn edge(text: &str) -> Result<Json, String> {
44
47
  continue;
45
48
  }
46
49
  if let Some(rest) = line.strip_prefix("Hecks.data_translation") {
47
- header = Some((first_string(rest)?, keyword(rest, "from")?, keyword(rest, "to")?));
50
+ header = Some((
51
+ first_string(rest)?,
52
+ keyword(rest, "from")?,
53
+ keyword(rest, "to")?,
54
+ ));
48
55
  } else if let Some(rest) = line.strip_prefix("aggregate ") {
49
56
  let was = keyword(rest, "was").ok();
50
- aggregates.push(Aggregate { name: first_string(rest)?, was, renames: Vec::new(), moves: Vec::new() });
57
+ aggregates.push(Aggregate {
58
+ name: first_string(rest)?,
59
+ was,
60
+ renames: Vec::new(),
61
+ moves: Vec::new(),
62
+ });
51
63
  } else if let Some(rest) = line.strip_prefix("move ") {
52
64
  let aggregate = aggregates.last_mut().ok_or("a move outside an aggregate")?;
53
- aggregate.moves.push((first_word(rest)?, keyword(rest, "to")?));
65
+ aggregate
66
+ .moves
67
+ .push((first_word(rest)?, keyword(rest, "to")?));
54
68
  } else if let Some(rest) = line.strip_prefix("rename ") {
55
- let aggregate = aggregates.last_mut().ok_or("a rename outside an aggregate")?;
56
- aggregate.renames.push((first_word(rest)?, keyword(rest, "to")?));
69
+ let aggregate = aggregates
70
+ .last_mut()
71
+ .ok_or("a rename outside an aggregate")?;
72
+ aggregate
73
+ .renames
74
+ .push((first_word(rest)?, keyword(rest, "to")?));
57
75
  } else {
58
76
  return Err(format!(
59
77
  "hecks-build cannot compile the translation rule `{line}` yet (only rename and move are ported), \
@@ -67,30 +85,46 @@ fn edge(text: &str) -> Result<Json, String> {
67
85
  ("from".into(), Json::String(from)),
68
86
  ("to".into(), Json::String(to)),
69
87
  ("retired".into(), Json::Array(Vec::new())),
70
- ("aggregates".into(), Json::Array(aggregates.iter().map(aggregate_json).collect())),
88
+ (
89
+ "aggregates".into(),
90
+ Json::Array(aggregates.iter().map(aggregate_json).collect()),
91
+ ),
71
92
  ]))
72
93
  }
73
94
 
74
95
  fn aggregate_json(aggregate: &Aggregate) -> Json {
75
- let renames = aggregate.renames.iter().map(|(old, new)| (old.clone(), Json::String(new.clone()))).collect();
96
+ let renames = aggregate
97
+ .renames
98
+ .iter()
99
+ .map(|(old, new)| (old.clone(), Json::String(new.clone())))
100
+ .collect();
76
101
  let moves = aggregate
77
102
  .moves
78
103
  .iter()
79
- .map(|(from, to)| Json::Object(vec![("from".into(), Json::String(from.clone())), ("to".into(), Json::String(to.clone()))]))
104
+ .map(|(from, to)| {
105
+ Json::Object(vec![
106
+ ("from".into(), Json::String(from.clone())),
107
+ ("to".into(), Json::String(to.clone())),
108
+ ])
109
+ })
80
110
  .collect();
81
111
  let empty = || Json::Array(Vec::new());
82
112
  Json::Object(vec![
83
113
  ("name".into(), Json::String(aggregate.name.clone())),
84
- ("was".into(), aggregate.was.clone().map_or(Json::Null, Json::String)),
114
+ (
115
+ "was".into(),
116
+ aggregate.was.clone().map_or(Json::Null, Json::String),
117
+ ),
85
118
  ("renames".into(), Json::Object(renames)),
86
119
  ("moves".into(), Json::Array(moves)),
87
120
  ("converts".into(), empty()),
88
121
  ("drops".into(), empty()),
89
122
  ("retypes".into(), empty()),
90
- ("computes".into(), empty()),
91
- ("rekeys".into(), empty()),
92
123
  ("backfills".into(), empty()),
93
- ("compiled_state_expression".into(), Json::String(compile(aggregate))),
124
+ (
125
+ "compiled_state_expression".into(),
126
+ Json::String(compile(aggregate)),
127
+ ),
94
128
  ("compiled_id_expression".into(), Json::Null),
95
129
  ])
96
130
  }
@@ -99,7 +133,11 @@ fn aggregate_json(aggregate: &Aggregate) -> Json {
99
133
  fn compile(aggregate: &Aggregate) -> String {
100
134
  let mut sql = "state".to_string();
101
135
  for (old, new) in &aggregate.renames {
102
- sql = format!("hecks_tr_rename({sql}, {}, {})", text_literal(old), text_literal(new));
136
+ sql = format!(
137
+ "hecks_tr_rename({sql}, {}, {})",
138
+ text_literal(old),
139
+ text_literal(new)
140
+ );
103
141
  }
104
142
  for (from, to) in &aggregate.moves {
105
143
  sql = format!(
@@ -123,9 +161,13 @@ fn path_literal(path: &str) -> String {
123
161
 
124
162
  /// The first `"quoted"` string of `text`.
125
163
  fn first_string(text: &str) -> Result<String, String> {
126
- let open = text.find('"').ok_or_else(|| format!("no quoted name in `{text}`"))?;
164
+ let open = text
165
+ .find('"')
166
+ .ok_or_else(|| format!("no quoted name in `{text}`"))?;
127
167
  let rest = &text[open + 1..];
128
- let close = rest.find('"').ok_or_else(|| format!("unterminated string in `{text}`"))?;
168
+ let close = rest
169
+ .find('"')
170
+ .ok_or_else(|| format!("unterminated string in `{text}`"))?;
129
171
  Ok(rest[..close].to_string())
130
172
  }
131
173
 
@@ -133,16 +175,24 @@ fn first_string(text: &str) -> Result<String, String> {
133
175
  fn first_word(text: &str) -> Result<String, String> {
134
176
  let word = text.split(',').next().unwrap_or_default().trim();
135
177
  let word = word.strip_prefix(':').unwrap_or(word).trim_matches('"');
136
- if word.is_empty() { Err(format!("no source in `{text}`")) } else { Ok(word.to_string()) }
178
+ if word.is_empty() {
179
+ Err(format!("no source in `{text}`"))
180
+ } else {
181
+ Ok(word.to_string())
182
+ }
137
183
  }
138
184
 
139
185
  /// The value of `key: value` in `text`, a string or a symbol.
140
186
  fn keyword(text: &str, key: &str) -> Result<String, String> {
141
187
  let marker = format!("{key}:");
142
- let at = text.find(&marker).ok_or_else(|| format!("no `{key}:` in `{text}`"))?;
188
+ let at = text
189
+ .find(&marker)
190
+ .ok_or_else(|| format!("no `{key}:` in `{text}`"))?;
143
191
  let value = text[at + marker.len()..].trim_start();
144
192
  if let Some(symbol) = value.strip_prefix(':') {
145
- let end = symbol.find(|c: char| !(c.is_alphanumeric() || c == '_')).unwrap_or(symbol.len());
193
+ let end = symbol
194
+ .find(|c: char| !(c.is_alphanumeric() || c == '_'))
195
+ .unwrap_or(symbol.len());
146
196
  return Ok(symbol[..end].to_string());
147
197
  }
148
198
  first_string(value)
@@ -161,7 +211,7 @@ mod tests {
161
211
 
162
212
  #[test]
163
213
  fn refuses_a_rule_it_cannot_compile() {
164
- let text = "Hecks.data_translation \"A\", from: \"1\", to: \"2\" do\n aggregate \"B\" do\n compute :a, to: \"b\", sql: \"1\"\n end\nend\n";
214
+ let text = "Hecks.data_translation \"A\", from: \"1\", to: \"2\" do\n aggregate \"B\" do\n backfill :a, default: 1\n end\nend\n";
165
215
  assert!(edge(text).unwrap_err().contains("cannot compile"));
166
216
  }
167
217
  }
@@ -0,0 +1,109 @@
1
+ //! The `world` fact: what the domain's `.world` file says about persistence, read through
2
+ //! `hecks-parse world`, so `rust/host` can refuse a world it has no backend for. Only the
3
+ //! setting names travel, never their values.
4
+
5
+ use std::path::Path;
6
+
7
+ use crate::json::Json;
8
+ use crate::subprocess;
9
+
10
+ /// Sets `world` right after `adapters`. With no `.world` file beside the domain it is the empty fact.
11
+ ///
12
+ /// # Errors
13
+ ///
14
+ /// Returns the parser's failure, or a message naming output that is not the JSON array
15
+ /// `hecks-parse world` prints.
16
+ pub fn run(ir: &mut Json, parser_bin: &Path, world: Option<&Path>) -> Result<(), String> {
17
+ let Some(world) = world else {
18
+ ir.set("world", fact(None));
19
+ return Ok(());
20
+ };
21
+ let domain = ir
22
+ .get("name")
23
+ .and_then(Json::as_str)
24
+ .unwrap_or_default()
25
+ .to_string();
26
+ let world_arg = world.to_string_lossy();
27
+ let printed = subprocess::run_capture(&parser_bin.to_string_lossy(), &["world", &world_arg])?;
28
+ let parsed = Json::parse(&printed).map_err(|e| format!("parsing hecks-parse world: {e}"))?;
29
+ let entry = parsed
30
+ .each()
31
+ .iter()
32
+ .find(|w| w.get("domain").and_then(Json::as_str) == Some(domain.as_str()));
33
+ ir.set("world", fact(entry));
34
+ Ok(())
35
+ }
36
+
37
+ fn fact(entry: Option<&Json>) -> Json {
38
+ let default_adapter = entry
39
+ .and_then(|w| w.get("default_adapter"))
40
+ .and_then(Json::as_str)
41
+ .map_or(Json::Null, |a| Json::String(a.to_string()));
42
+ let default_database = entry
43
+ .and_then(|w| w.get("default_database"))
44
+ .is_some_and(|v| matches!(v, Json::Bool(true)));
45
+ Json::Object(vec![
46
+ ("default_adapter".to_string(), default_adapter),
47
+ ("default_database".to_string(), Json::Bool(default_database)),
48
+ ("persistence".to_string(), Json::Array(persistence(entry))),
49
+ ])
50
+ }
51
+
52
+ fn persistence(entry: Option<&Json>) -> Vec<Json> {
53
+ let bindings = entry
54
+ .and_then(|w| w.get("bindings"))
55
+ .map_or(&[][..], Json::each);
56
+ bindings
57
+ .iter()
58
+ .filter(|b| b.get("verb").and_then(Json::as_str) == Some("persisted_by"))
59
+ .map(|b| {
60
+ Json::Object(vec![
61
+ (
62
+ "adapter".to_string(),
63
+ b.get("adapter").cloned().unwrap_or(Json::Null),
64
+ ),
65
+ (
66
+ "settings".to_string(),
67
+ b.get("settings")
68
+ .cloned()
69
+ .unwrap_or(Json::Array(Vec::new())),
70
+ ),
71
+ ])
72
+ })
73
+ .collect()
74
+ }
75
+
76
+ #[cfg(test)]
77
+ mod tests {
78
+ use super::*;
79
+
80
+ fn parsed(text: &str) -> Json {
81
+ Json::parse(text).expect("fixture json")
82
+ }
83
+
84
+ #[test]
85
+ fn fact_keeps_only_persisted_by_bindings_and_the_defaults() {
86
+ let entry = parsed(
87
+ r#"{"domain":"C","default_adapter":"PostgresEra","default_database":true,"bindings":[
88
+ {"verb":"persisted_by","adapter":"PostgresEra","settings":["allow_superuser"]},
89
+ {"verb":"deployed_to","adapter":"AwsLambda","settings":["region"]}]}"#,
90
+ );
91
+ let got = fact(Some(&entry));
92
+ assert_eq!(
93
+ got.get("default_adapter").and_then(Json::as_str),
94
+ Some("PostgresEra")
95
+ );
96
+ assert_eq!(got.get("persistence").map(|p| p.each().len()), Some(1));
97
+ assert!(matches!(
98
+ got.get("default_database"),
99
+ Some(Json::Bool(true))
100
+ ));
101
+ }
102
+
103
+ #[test]
104
+ fn fact_for_a_missing_domain_is_empty_not_absent() {
105
+ let got = fact(None);
106
+ assert!(matches!(got.get_raw("default_adapter"), Some(Json::Null)));
107
+ assert_eq!(got.get("persistence").map(|p| p.each().len()), Some(0));
108
+ }
109
+ }
data/rust/clippy.toml ADDED
@@ -0,0 +1,4 @@
1
+ # The restriction lints (unwrap, expect, panic) guard library code; a test panics to fail.
2
+ allow-unwrap-in-tests = true
3
+ allow-expect-in-tests = true
4
+ allow-panic-in-tests = true
@@ -18,3 +18,39 @@ path = "src/main.rs"
18
18
  # Debug info is off: CI binaries are run once and never attached to a debugger.
19
19
  [profile.dev]
20
20
  debug = 0
21
+
22
+ # Clippy beyond its defaults: pedantic and nursery, plus the restriction lints that keep a panic
23
+ # out of library code. `hecks gate_run.gate stage=ci only=rust_clippy` makes each of these an error.
24
+ # The same table is in every crate's Cargo.toml; change them together.
25
+ [lints.clippy]
26
+ pedantic = { level = "warn", priority = -1 }
27
+ nursery = { level = "warn", priority = -1 }
28
+ unwrap_used = "warn"
29
+ expect_used = "warn"
30
+ panic = "warn"
31
+ dbg_macro = "warn"
32
+ todo = "warn"
33
+ unimplemented = "warn"
34
+ # Lints that ask for prose or an attribute on every item rather than for better code.
35
+ missing_errors_doc = "allow"
36
+ missing_panics_doc = "allow"
37
+ must_use_candidate = "allow"
38
+ return_self_not_must_use = "allow"
39
+ module_name_repetitions = "allow"
40
+ too_many_lines = "allow"
41
+ too_long_first_doc_paragraph = "allow"
42
+ doc_markdown = "allow"
43
+ # Numeric conversions here are deliberate: JSON numbers are f64 and ids are small.
44
+ cast_possible_truncation = "allow"
45
+ cast_possible_wrap = "allow"
46
+ cast_precision_loss = "allow"
47
+ cast_sign_loss = "allow"
48
+ # Naming heuristics that fire on ordinary short names.
49
+ similar_names = "allow"
50
+ many_single_char_names = "allow"
51
+ struct_field_names = "allow"
52
+ # Heuristics that cost more clarity than they buy here.
53
+ redundant_pub_crate = "allow"
54
+ future_not_send = "allow"
55
+ significant_drop_tightening = "allow"
56
+ match_same_arms = "allow"
@@ -0,0 +1,4 @@
1
+ # The restriction lints (unwrap, expect, panic) guard library code; a test panics to fail.
2
+ allow-unwrap-in-tests = true
3
+ allow-expect-in-tests = true
4
+ allow-panic-in-tests = true
@@ -2,8 +2,8 @@
2
2
  //! mirroring `Hecks::Bluebook::AskResolution`: by the event's aggregate and the ask's name, with
3
3
  //! `chosen` (`ask_via`) breaking a tie between ports.
4
4
 
5
+ use crate::hecks_naming;
5
6
  use crate::json::Json;
6
- use crate::naming;
7
7
 
8
8
  /// Policies whose `ask` is replaced by the `Aggregate::Port.Operation` trigger it resolves to,
9
9
  /// so every table below reads the verb as it reads a port-naming `trigger`.
@@ -26,14 +26,23 @@ fn with_trigger(policy: &Json, verb: String) -> Json {
26
26
  resolved
27
27
  }
28
28
 
29
+ #[allow(
30
+ clippy::panic,
31
+ reason = "a drifted exemplar or invalid IR aborts generation on purpose"
32
+ )]
29
33
  fn resolve(policy: &Json, ask: &str, aggregates: &[Json]) -> String {
30
34
  let name = policy.get("name").map(Json::to_s).unwrap_or_default();
31
35
  let on_event = policy.get("on_event").map(Json::to_s).unwrap_or_default();
32
- let aggregate = event_aggregate(&on_event, aggregates)
33
- .unwrap_or_else(|| panic!("policy {name}: ask :{ask} reacts to {on_event:?}, whose aggregate is not declared"));
36
+ let aggregate = event_aggregate(&on_event, aggregates).unwrap_or_else(|| {
37
+ panic!("policy {name}: ask :{ask} reacts to {on_event:?}, whose aggregate is not declared")
38
+ });
34
39
  let agg_name = aggregate.get("name").map(Json::to_s).unwrap_or_default();
35
40
  let mut found = candidates(aggregate, ask);
36
- if found.len() > 1 && found.iter().any(|(_, op)| op.get("chosen").is_some_and(Json::as_bool)) {
41
+ if found.len() > 1
42
+ && found
43
+ .iter()
44
+ .any(|(_, op)| op.get("chosen").is_some_and(Json::as_bool))
45
+ {
37
46
  found.retain(|(_, op)| op.get("chosen").is_some_and(Json::as_bool));
38
47
  }
39
48
  match found.as_slice() {
@@ -45,13 +54,16 @@ fn resolve(policy: &Json, ask: &str, aggregates: &[Json]) -> String {
45
54
 
46
55
  // The aggregate the event is qualified with, or else the one whose commands emit the bare event.
47
56
  fn event_aggregate<'a>(on_event: &str, aggregates: &'a [Json]) -> Option<&'a Json> {
48
- let named = |wanted: &str| aggregates.iter().find(|a| a.get("name").map(Json::to_s).unwrap_or_default() == wanted);
57
+ let named = |wanted: &str| {
58
+ aggregates
59
+ .iter()
60
+ .find(|a| a.get("name").map(Json::to_s).unwrap_or_default() == wanted)
61
+ };
49
62
  match on_event.split_once('.') {
50
63
  Some((qualifier, _)) => named(qualifier.rsplit("::").next().unwrap_or(qualifier)),
51
64
  None => aggregates.iter().find(|a| {
52
65
  a.get("commands")
53
- .map(Json::each)
54
- .unwrap_or(&[])
66
+ .map_or_default(Json::each)
55
67
  .iter()
56
68
  .any(|c| c.get("emits").map(Json::to_s).unwrap_or_default() == on_event)
57
69
  }),
@@ -61,11 +73,11 @@ fn event_aggregate<'a>(on_event: &str, aggregates: &'a [Json]) -> Option<&'a Jso
61
73
  // Every outbound operation of the aggregate's ports whose snake-cased name is the ask.
62
74
  fn candidates<'a>(aggregate: &'a Json, ask: &str) -> Vec<(String, &'a Json)> {
63
75
  let mut found = Vec::new();
64
- for port in aggregate.get("ports").map(Json::each).unwrap_or(&[]) {
76
+ for port in aggregate.get("ports").map_or_default(Json::each) {
65
77
  let port_name = port.get("name").map(Json::to_s).unwrap_or_default();
66
- for op in port.get("operations").map(Json::each).unwrap_or(&[]) {
78
+ for op in port.get("operations").map_or_default(Json::each) {
67
79
  let outbound = op.get("direction").map(Json::to_s).as_deref() == Some("outbound");
68
- let spelled = naming::snake(&op.get("name").map(Json::to_s).unwrap_or_default());
80
+ let spelled = hecks_naming::snake(&op.get("name").map(Json::to_s).unwrap_or_default());
69
81
  if outbound && spelled == ask {
70
82
  found.push((port_name.clone(), op));
71
83
  }
@@ -82,7 +94,9 @@ mod tests {
82
94
  // The neutral corpus fixture `spec/corpus/asks/errand.json`: the Ruby spec holds the same
83
95
  // `targets` against the Ruby runtime's reaction log, so the two cannot drift.
84
96
  fn corpus(name: &str) -> Json {
85
- let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../spec/corpus/asks").join(name);
97
+ let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
98
+ .join("../../spec/corpus/asks")
99
+ .join(name);
86
100
  Json::parse(&std::fs::read_to_string(path).expect("corpus file")).expect("corpus json")
87
101
  }
88
102
 
@@ -91,12 +105,24 @@ mod tests {
91
105
  let ir = corpus("errand.ir.json");
92
106
  let expect = corpus("errand.json");
93
107
  let domain = ir.get("name").map(Json::to_s).unwrap_or_default();
94
- let resolved = resolve_asks(ir.get("policies").unwrap().each(), ir.get("aggregates").unwrap().each());
108
+ let resolved = resolve_asks(
109
+ ir.get("policies").unwrap().each(),
110
+ ir.get("aggregates").unwrap().each(),
111
+ );
95
112
 
96
113
  for policy in &resolved {
97
114
  let name = policy.get("name").map(Json::to_s).unwrap_or_default();
98
- let target = format!("{domain}::{}", policy.get("trigger_command").map(Json::to_s).unwrap_or_default());
99
- let frozen = expect.get("targets").and_then(|t| t.get(&name)).map(Json::to_s);
115
+ let target = format!(
116
+ "{domain}::{}",
117
+ policy
118
+ .get("trigger_command")
119
+ .map(Json::to_s)
120
+ .unwrap_or_default()
121
+ );
122
+ let frozen = expect
123
+ .get("targets")
124
+ .and_then(|t| t.get(&name))
125
+ .map(Json::to_s);
100
126
  assert_eq!(frozen, Some(target), "{name}");
101
127
  }
102
128
  }
@@ -12,11 +12,11 @@ pub fn type_name(attr: &Json) -> &str {
12
12
  }
13
13
 
14
14
  pub fn list(attr: &Json) -> bool {
15
- attr.get("list").map(Json::as_bool).unwrap_or(false)
15
+ attr.get("list").is_some_and(Json::as_bool)
16
16
  }
17
17
 
18
18
  pub fn optional(attr: &Json) -> bool {
19
- attr.get("optional").map(Json::as_bool).unwrap_or(false)
19
+ attr.get("optional").is_some_and(Json::as_bool)
20
20
  }
21
21
 
22
22
  pub fn default(attr: &Json) -> Option<&Json> {