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
data/rust/Cargo.toml CHANGED
@@ -6,6 +6,10 @@
6
6
  # `--no-default-features --features <name>`.
7
7
  [features]
8
8
  default = ["reserved_names_fixture"]
9
+ tally = []
10
+ relay = []
11
+ meter = []
12
+ courier = []
9
13
  attribute_constraints_fixture = []
10
14
  optional_nested_value_object_fixture = []
11
15
  computed_sets_fixture = []
@@ -67,3 +71,39 @@ path = "src/main.rs"
67
71
  # Only this dev profile is affected; release builds are unchanged.
68
72
  [profile.dev]
69
73
  debug = 0
74
+
75
+ # Clippy beyond its defaults: pedantic and nursery, plus the restriction lints that keep a panic
76
+ # out of library code. `hecks gate_run.gate stage=ci only=rust_clippy` makes each of these an error.
77
+ # The same table is in every crate's Cargo.toml; change them together.
78
+ [lints.clippy]
79
+ pedantic = { level = "warn", priority = -1 }
80
+ nursery = { level = "warn", priority = -1 }
81
+ unwrap_used = "warn"
82
+ expect_used = "warn"
83
+ panic = "warn"
84
+ dbg_macro = "warn"
85
+ todo = "warn"
86
+ unimplemented = "warn"
87
+ # Lints that ask for prose or an attribute on every item rather than for better code.
88
+ missing_errors_doc = "allow"
89
+ missing_panics_doc = "allow"
90
+ must_use_candidate = "allow"
91
+ return_self_not_must_use = "allow"
92
+ module_name_repetitions = "allow"
93
+ too_many_lines = "allow"
94
+ too_long_first_doc_paragraph = "allow"
95
+ doc_markdown = "allow"
96
+ # Numeric conversions here are deliberate: JSON numbers are f64 and ids are small.
97
+ cast_possible_truncation = "allow"
98
+ cast_possible_wrap = "allow"
99
+ cast_precision_loss = "allow"
100
+ cast_sign_loss = "allow"
101
+ # Naming heuristics that fire on ordinary short names.
102
+ similar_names = "allow"
103
+ many_single_char_names = "allow"
104
+ struct_field_names = "allow"
105
+ # Heuristics that cost more clarity than they buy here.
106
+ redundant_pub_crate = "allow"
107
+ future_not_send = "allow"
108
+ significant_drop_tightening = "allow"
109
+ match_same_arms = "allow"
@@ -17,3 +17,39 @@ path = "src/main.rs"
17
17
  # Debug info is off: CI binaries are run once and never attached to a debugger.
18
18
  [profile.dev]
19
19
  debug = 0
20
+
21
+ # Clippy beyond its defaults: pedantic and nursery, plus the restriction lints that keep a panic
22
+ # out of library code. `hecks gate_run.gate stage=ci only=rust_clippy` makes each of these an error.
23
+ # The same table is in every crate's Cargo.toml; change them together.
24
+ [lints.clippy]
25
+ pedantic = { level = "warn", priority = -1 }
26
+ nursery = { level = "warn", priority = -1 }
27
+ unwrap_used = "warn"
28
+ expect_used = "warn"
29
+ panic = "warn"
30
+ dbg_macro = "warn"
31
+ todo = "warn"
32
+ unimplemented = "warn"
33
+ # Lints that ask for prose or an attribute on every item rather than for better code.
34
+ missing_errors_doc = "allow"
35
+ missing_panics_doc = "allow"
36
+ must_use_candidate = "allow"
37
+ return_self_not_must_use = "allow"
38
+ module_name_repetitions = "allow"
39
+ too_many_lines = "allow"
40
+ too_long_first_doc_paragraph = "allow"
41
+ doc_markdown = "allow"
42
+ # Numeric conversions here are deliberate: JSON numbers are f64 and ids are small.
43
+ cast_possible_truncation = "allow"
44
+ cast_possible_wrap = "allow"
45
+ cast_precision_loss = "allow"
46
+ cast_sign_loss = "allow"
47
+ # Naming heuristics that fire on ordinary short names.
48
+ similar_names = "allow"
49
+ many_single_char_names = "allow"
50
+ struct_field_names = "allow"
51
+ # Heuristics that cost more clarity than they buy here.
52
+ redundant_pub_crate = "allow"
53
+ future_not_send = "allow"
54
+ significant_drop_tightening = "allow"
55
+ 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
@@ -0,0 +1,196 @@
1
+ //! Rust port of `Projector::Exporter#persistence_adapters`: the `adapters` key, the declarations
2
+ //! of the persistence adapters a domain binds, so `rust/host` looks adapters up in the IR instead
3
+ //! of a hard-coded list. Only declarations: a world's setting values never enter it.
4
+
5
+ use std::path::{Path, PathBuf};
6
+
7
+ use crate::json::Json;
8
+ use crate::subprocess;
9
+
10
+ // Never a source `.adapter`: build output and copies. Hidden directories are skipped too, as
11
+ // Ruby's `Dir.glob` skips them.
12
+ const SKIPPED_DIRS: [&str; 5] = ["target", "vendor", "node_modules", "tmp", ".git"];
13
+
14
+ /// The `adapters` array for `ir`: one entry per distinct adapter name in
15
+ /// `ir["persistence"]["aggregates"][].adapter` that an `.adapter` file under `directories`
16
+ /// declares, in name byte order. `hecks-parse adapter` reads all the files in one run.
17
+ ///
18
+ /// # Errors
19
+ ///
20
+ /// Returns the parser's failure, or a message naming an unreadable directory or output that
21
+ /// is not the JSON array `hecks-parse adapter` prints.
22
+ pub fn declarations(parser_bin: &Path, directories: &[PathBuf], ir: &Json) -> Result<Json, String> {
23
+ let names = bound_names(ir);
24
+ if names.is_empty() {
25
+ return Ok(Json::Array(Vec::new()));
26
+ }
27
+ let files = files_under(directories)?;
28
+ if files.is_empty() {
29
+ return Ok(Json::Array(Vec::new()));
30
+ }
31
+ let mut args = vec!["adapter".to_string()];
32
+ args.extend(files.iter().map(|p| p.to_string_lossy().to_string()));
33
+ let arg_refs: Vec<&str> = args.iter().map(String::as_str).collect();
34
+ let printed = subprocess::run_capture(&parser_bin.to_string_lossy(), &arg_refs)?;
35
+ let parsed = Json::parse(&printed).map_err(|e| format!("parsing hecks-parse adapter: {e}"))?;
36
+ Ok(select(&parsed, &names))
37
+ }
38
+
39
+ /// The distinct adapter names `ir`'s persistence block binds, in byte order.
40
+ pub fn bound_names(ir: &Json) -> Vec<String> {
41
+ let aggregates = ir
42
+ .get("persistence")
43
+ .and_then(|persistence| persistence.get("aggregates"))
44
+ .map_or(&[][..], Json::each);
45
+ let mut names: Vec<String> = aggregates
46
+ .iter()
47
+ .filter_map(|aggregate| aggregate.get("adapter").and_then(Json::as_str))
48
+ .map(str::to_string)
49
+ .collect();
50
+ names.sort();
51
+ names.dedup();
52
+ names
53
+ }
54
+
55
+ /// The entries of `declared` whose name is in `names`, the first of any repeated name
56
+ /// winning, in name byte order.
57
+ pub fn select(declared: &Json, names: &[String]) -> Json {
58
+ let mut chosen: Vec<(String, Json)> = Vec::new();
59
+ for adapter in declared.each() {
60
+ let Some(name) = adapter.get("name").and_then(Json::as_str) else {
61
+ continue;
62
+ };
63
+ if names.iter().any(|wanted| wanted == name) && !chosen.iter().any(|(n, _)| n == name) {
64
+ chosen.push((name.to_string(), adapter.clone()));
65
+ }
66
+ }
67
+ chosen.sort_by(|a, b| a.0.as_bytes().cmp(b.0.as_bytes()));
68
+ Json::Array(chosen.into_iter().map(|(_, adapter)| adapter).collect())
69
+ }
70
+
71
+ /// Every `.adapter` file beneath `directories`, as absolute paths, unique and in byte order of
72
+ /// the path text. A directory that does not exist contributes none.
73
+ ///
74
+ /// # Errors
75
+ ///
76
+ /// Returns a message when a directory that exists cannot be read.
77
+ pub fn files_under(directories: &[PathBuf]) -> Result<Vec<PathBuf>, String> {
78
+ let mut found = Vec::new();
79
+ for directory in directories {
80
+ let absolute = std::path::absolute(directory)
81
+ .map_err(|e| format!("resolving {}: {e}", directory.display()))?;
82
+ if absolute.is_dir() {
83
+ walk(&absolute, &mut found)?;
84
+ }
85
+ }
86
+ found.sort_by(|a, b| {
87
+ a.to_string_lossy()
88
+ .as_bytes()
89
+ .cmp(b.to_string_lossy().as_bytes())
90
+ });
91
+ found.dedup();
92
+ Ok(found)
93
+ }
94
+
95
+ fn walk(directory: &Path, found: &mut Vec<PathBuf>) -> Result<(), String> {
96
+ let entries = std::fs::read_dir(directory)
97
+ .map_err(|e| format!("reading {}: {e}", directory.display()))?;
98
+ for entry in entries {
99
+ let entry = entry.map_err(|e| format!("reading {}: {e}", directory.display()))?;
100
+ let path = entry.path();
101
+ let file_name = entry.file_name().to_string_lossy().to_string();
102
+ let is_dir = entry.file_type().is_ok_and(|kind| kind.is_dir());
103
+ if is_dir {
104
+ if !file_name.starts_with('.') && !SKIPPED_DIRS.contains(&file_name.as_str()) {
105
+ walk(&path, found)?;
106
+ }
107
+ } else if !file_name.starts_with('.') && file_name.ends_with(".adapter") && path.is_file() {
108
+ found.push(path);
109
+ }
110
+ }
111
+ Ok(())
112
+ }
113
+
114
+ #[cfg(test)]
115
+ mod tests {
116
+ use super::*;
117
+ use crate::tmp::TempDir;
118
+
119
+ fn parsed(text: &str) -> Json {
120
+ Json::parse(text).expect("fixture json")
121
+ }
122
+
123
+ fn ir(adapters: &[&str]) -> Json {
124
+ let rows: Vec<String> = adapters
125
+ .iter()
126
+ .map(|a| format!("{{\"name\":\"A\",\"storage_name\":\"a\",\"adapter\":\"{a}\"}}"))
127
+ .collect();
128
+ parsed(&format!(
129
+ "{{\"persistence\":{{\"aggregates\":[{}]}}}}",
130
+ rows.join(",")
131
+ ))
132
+ }
133
+
134
+ #[test]
135
+ fn bound_names_are_distinct_and_sorted() {
136
+ let names = bound_names(&ir(&["Postgres", "Heki", "Postgres"]));
137
+ assert_eq!(names, vec!["Heki".to_string(), "Postgres".to_string()]);
138
+ assert!(bound_names(&parsed("{}")).is_empty());
139
+ }
140
+
141
+ #[test]
142
+ fn select_keeps_bound_declarations_sorted_and_first_wins() {
143
+ let declared = parsed(
144
+ "[{\"name\":\"Postgres\",\"port\":\"persistence\",\"fields\":[],\"secrets\":[],\"defaults\":{}},\
145
+ {\"name\":\"Unbound\",\"port\":null,\"fields\":[],\"secrets\":[],\"defaults\":{}},\
146
+ {\"name\":\"Heki\",\"port\":\"persistence\",\"fields\":[\"dir\"],\"secrets\":[],\"defaults\":{\"dir\":\"data\"}},\
147
+ {\"name\":\"Heki\",\"port\":null,\"fields\":[],\"secrets\":[],\"defaults\":{}}]",
148
+ );
149
+ let wanted = ["Heki", "Missing", "Postgres"].map(str::to_string);
150
+ let chosen = select(&declared, &wanted);
151
+ let names: Vec<&str> = chosen
152
+ .each()
153
+ .iter()
154
+ .filter_map(|a| a.get("name").and_then(Json::as_str))
155
+ .collect();
156
+ assert_eq!(names, vec!["Heki", "Postgres"]);
157
+ let heki_fields = chosen.each()[0].get("fields").map(|f| f.each().len());
158
+ assert_eq!(heki_fields, Some(1));
159
+ }
160
+
161
+ #[test]
162
+ fn files_under_skips_build_and_hidden_directories() {
163
+ let dir = TempDir::new("adapters-pass").expect("temp dir");
164
+ let root = dir.path();
165
+ for relative in [
166
+ "lib/a/heki.adapter",
167
+ "lib/b/memory.adapter",
168
+ "lib/target/skipped.adapter",
169
+ "lib/vendor/skipped.adapter",
170
+ "lib/node_modules/skipped.adapter",
171
+ "lib/tmp/skipped.adapter",
172
+ "lib/.hidden/skipped.adapter",
173
+ "lib/a/notes.txt",
174
+ ] {
175
+ let path = root.join(relative);
176
+ std::fs::create_dir_all(path.parent().expect("parent")).expect("mkdir");
177
+ std::fs::write(&path, "").expect("write");
178
+ }
179
+ let scanned = [root.join("lib"), root.join("lib"), root.join("absent")];
180
+ let found = files_under(&scanned).expect("scan");
181
+ let relative: Vec<String> = found
182
+ .iter()
183
+ .map(|p| {
184
+ let under = p.strip_prefix(root).expect("under root");
185
+ under.to_string_lossy().to_string()
186
+ })
187
+ .collect();
188
+ assert_eq!(relative, vec!["lib/a/heki.adapter", "lib/b/memory.adapter"]);
189
+ }
190
+
191
+ #[test]
192
+ fn no_bound_adapter_means_an_empty_array_without_running_the_parser() {
193
+ let none = declarations(Path::new("/no/such/parser"), &[], &ir(&[])).expect("empty");
194
+ assert_eq!(none, Json::Array(Vec::new()));
195
+ }
196
+ }
@@ -1,5 +1,6 @@
1
1
  //! Rust port of the binding facts `lib/hecks/rust_build/project_rust/target_ir.rb` adds beside the
2
- //! declared IR: `persistence`, `translations` and `source_text`. The capability seams
2
+ //! declared IR: `persistence`, `adapters` (the bound adapters' declarations, from the closure
3
+ //! the caller passes), `translations` and `source_text`. The capability seams
3
4
  //! (authorization, identity, payments, ...) are `seams_pass`'s.
4
5
 
5
6
  use std::collections::HashMap;
@@ -10,10 +11,21 @@ use crate::lineage_pass;
10
11
  use crate::translation_pass;
11
12
 
12
13
  /// Sets `persistence`, the first of the binding facts, in `TargetIr#call`'s order.
13
- pub fn run(ir: &mut Json, hecksagon_path: Option<&Path>, world_path: Option<&Path>) -> Result<(), String> {
14
- let chapter_name = ir.get("name").and_then(Json::as_str).unwrap_or_default().to_string();
14
+ pub fn run(
15
+ ir: &mut Json,
16
+ hecksagon_path: Option<&Path>,
17
+ world_path: Option<&Path>,
18
+ adapter_declarations: impl FnOnce(&Json) -> Result<Json, String>,
19
+ ) -> Result<(), String> {
20
+ let chapter_name = ir
21
+ .get("name")
22
+ .and_then(Json::as_str)
23
+ .unwrap_or_default()
24
+ .to_string();
15
25
  let persistence = persistence(ir, &chapter_name, hecksagon_path, world_path)?;
16
26
  ir.set("persistence", persistence);
27
+ let adapters = adapter_declarations(ir)?;
28
+ ir.set("adapters", adapters);
17
29
  Ok(())
18
30
  }
19
31
 
@@ -23,7 +35,11 @@ pub fn run(ir: &mut Json, hecksagon_path: Option<&Path>, world_path: Option<&Pat
23
35
  /// `translations` carries each `translations/*.bluebook` edge with its precompiled SQL, from
24
36
  /// `translation_pass`; an edge using a rule kind that pass does not compile is refused, never
25
37
  /// shipped without its SQL. A `translates` declaration in a bluebook is refused outright.
26
- pub fn finish(ir: &mut Json, bluebook_files: &[PathBuf], bluebook_dir: &Path) -> Result<(), String> {
38
+ pub fn finish(
39
+ ir: &mut Json,
40
+ bluebook_files: &[PathBuf],
41
+ bluebook_dir: &Path,
42
+ ) -> Result<(), String> {
27
43
  let mut texts = Vec::new();
28
44
  for path in bluebook_files {
29
45
  let text = read(path)?;
@@ -35,7 +51,10 @@ pub fn finish(ir: &mut Json, bluebook_files: &[PathBuf], bluebook_dir: &Path) ->
35
51
  }
36
52
  texts.push(text);
37
53
  }
38
- ir.set("translations", Json::Array(translation_pass::edges(bluebook_dir)?));
54
+ ir.set(
55
+ "translations",
56
+ Json::Array(translation_pass::edges(bluebook_dir)?),
57
+ );
39
58
  if !texts.is_empty() {
40
59
  ir.set("source_text", Json::String(texts.join("\n")));
41
60
  }
@@ -47,11 +66,19 @@ fn declares_translation(line: &str) -> bool {
47
66
  line.starts_with("translates") || line.starts_with("translation ")
48
67
  }
49
68
 
50
- fn persistence(ir: &Json, chapter: &str, hecksagon: Option<&Path>, world: Option<&Path>) -> Result<Json, String> {
69
+ fn persistence(
70
+ ir: &Json,
71
+ chapter: &str,
72
+ hecksagon: Option<&Path>,
73
+ world: Option<&Path>,
74
+ ) -> Result<Json, String> {
51
75
  let (binds, hecksagon_default) = match hecksagon {
52
76
  Some(path) => {
53
77
  let text = read(path)?;
54
- (lineage_pass::persistence_binds(&text), domain_default(&text, chapter))
78
+ (
79
+ lineage_pass::persistence_binds(&text),
80
+ domain_default(&text, chapter),
81
+ )
55
82
  }
56
83
  None => (HashMap::new(), None),
57
84
  };
@@ -61,8 +88,12 @@ fn persistence(ir: &Json, chapter: &str, hecksagon: Option<&Path>, world: Option
61
88
  };
62
89
  let declared = hecksagon_default.or(world_default);
63
90
  let mut aggregates = Vec::new();
64
- for aggregate in ir.get("aggregates").map(Json::each).unwrap_or(&[]) {
65
- let name = aggregate.get("name").and_then(Json::as_str).unwrap_or_default().to_string();
91
+ for aggregate in ir.get("aggregates").map_or(&[][..], Json::each) {
92
+ let name = aggregate
93
+ .get("name")
94
+ .and_then(Json::as_str)
95
+ .unwrap_or_default()
96
+ .to_string();
66
97
  let adapter = match (binds.get(&name), &declared, hecksagon.is_some()) {
67
98
  (Some(adapter), _, _) => adapter.clone(),
68
99
  (None, Some(adapter), _) => adapter.clone(),
@@ -73,11 +104,17 @@ fn persistence(ir: &Json, chapter: &str, hecksagon: Option<&Path>, world: Option
73
104
  };
74
105
  aggregates.push(Json::Object(vec![
75
106
  ("name".to_string(), Json::String(name.clone())),
76
- ("storage_name".to_string(), Json::String(lineage_pass::snake_case(&name))),
107
+ (
108
+ "storage_name".to_string(),
109
+ Json::String(crate::hecks_naming::snake(&name)),
110
+ ),
77
111
  ("adapter".to_string(), Json::String(adapter)),
78
112
  ]));
79
113
  }
80
- Ok(Json::Object(vec![("aggregates".to_string(), Json::Array(aggregates))]))
114
+ Ok(Json::Object(vec![(
115
+ "aggregates".to_string(),
116
+ Json::Array(aggregates),
117
+ )]))
81
118
  }
82
119
 
83
120
  // The bare `persisted_by "X"` line of the chapter's own `Hecks.hecksagon` block: every aggregate
@@ -90,8 +127,12 @@ fn domain_default(text: &str, chapter: &str) -> Option<String> {
90
127
  in_chapter = rest.contains(&format!("\"{chapter}\""));
91
128
  continue;
92
129
  }
93
- let Some(rest) = line.trim_start().strip_prefix("persisted_by ") else { continue };
94
- let Some(open) = rest.find('"').filter(|_| in_chapter) else { continue };
130
+ let Some(rest) = line.trim_start().strip_prefix("persisted_by ") else {
131
+ continue;
132
+ };
133
+ let Some(open) = rest.find('"').filter(|_| in_chapter) else {
134
+ continue;
135
+ };
95
136
  let quoted = &rest[open + 1..];
96
137
  found = quoted.find('"').map(|close| quoted[..close].to_string());
97
138
  }
@@ -26,20 +26,38 @@ pub fn build_wasm(root: &Path, rust_dir: &Path, target_mod_name: &str) -> Result
26
26
  }
27
27
 
28
28
  println!("== cargo build --release --target wasm32-wasip1 (via rustup run stable) ==");
29
- subprocess::run_in(rust_dir, "rustup", &["run", "stable", "cargo", "build", "--release", "--target", "wasm32-wasip1"])?;
29
+ subprocess::run_in(
30
+ rust_dir,
31
+ "rustup",
32
+ &[
33
+ "run",
34
+ "stable",
35
+ "cargo",
36
+ "build",
37
+ "--release",
38
+ "--target",
39
+ "wasm32-wasip1",
40
+ ],
41
+ )?;
30
42
 
31
43
  let dist_dir = root.join("rust/dist");
32
- std::fs::create_dir_all(&dist_dir).map_err(|e| format!("creating {}: {e}", dist_dir.display()))?;
44
+ std::fs::create_dir_all(&dist_dir)
45
+ .map_err(|e| format!("creating {}: {e}", dist_dir.display()))?;
33
46
 
34
47
  let built = rust_dir.join("target/wasm32-wasip1/release/rust.wasm");
35
48
  let out = dist_dir.join(format!("{target_mod_name}.wasm"));
36
- std::fs::copy(&built, &out).map_err(|e| format!("copying {} to {}: {e}", built.display(), out.display()))?;
49
+ std::fs::copy(&built, &out)
50
+ .map_err(|e| format!("copying {} to {}: {e}", built.display(), out.display()))?;
37
51
  println!("wrote {}", out.display());
38
52
 
39
- let ir_json = root.join("rust/src/generated").join(target_mod_name).join("ir.json");
53
+ let ir_json = root
54
+ .join("rust/src/generated")
55
+ .join(target_mod_name)
56
+ .join("ir.json");
40
57
  if ir_json.is_file() {
41
58
  let ir_out = dist_dir.join(format!("{target_mod_name}.ir.json"));
42
- std::fs::copy(&ir_json, &ir_out).map_err(|e| format!("copying {} to {}: {e}", ir_json.display(), ir_out.display()))?;
59
+ std::fs::copy(&ir_json, &ir_out)
60
+ .map_err(|e| format!("copying {} to {}: {e}", ir_json.display(), ir_out.display()))?;
43
61
  println!("wrote {}", ir_out.display());
44
62
  }
45
63
 
@@ -1,6 +1,7 @@
1
1
  //! Rewrites the root `generated/mod.rs` and syncs `rust/Cargo.toml` `[features]`.
2
2
  //! Ports `sync_mod_and_cargo!`, keeping its output byte-identical.
3
3
 
4
+ use std::fmt::Write as _;
4
5
  use std::path::Path;
5
6
 
6
7
  // Both tables come from the reserved-word vocabularies via `hecks project_reserved_names`.
@@ -11,21 +12,30 @@ pub fn valid_domain_mod_name(name: &str) -> bool {
11
12
  let mut chars = name.chars();
12
13
  let starts_ok = matches!(chars.next(), Some(c) if c == '_' || c.is_ascii_lowercase());
13
14
  let rest_ok = chars.all(|c| c == '_' || c.is_ascii_lowercase() || c.is_ascii_digit());
14
- starts_ok && rest_ok && !RUST_KEYWORDS.contains(&name) && !CARGO_RESERVED_DOMAIN_NAMES.contains(&name)
15
+ starts_ok
16
+ && rest_ok
17
+ && !RUST_KEYWORDS.contains(&name)
18
+ && !CARGO_RESERVED_DOMAIN_NAMES.contains(&name)
15
19
  }
16
20
 
17
21
  /// Lists the directories under `out_root`, and those with a `merged.rs` (activatable domains).
18
22
  fn scan_generated_dirs(out_root: &Path) -> Result<(Vec<String>, Vec<String>), String> {
19
23
  let mut all_dirs = Vec::new();
20
- for entry in std::fs::read_dir(out_root).map_err(|e| format!("reading {}: {e}", out_root.display()))? {
24
+ for entry in
25
+ std::fs::read_dir(out_root).map_err(|e| format!("reading {}: {e}", out_root.display()))?
26
+ {
21
27
  let entry = entry.map_err(|e| format!("reading {}: {e}", out_root.display()))?;
22
- if entry.file_type().map(|t| t.is_dir()).unwrap_or(false) {
28
+ if entry.file_type().is_ok_and(|t| t.is_dir()) {
23
29
  all_dirs.push(entry.file_name().to_string_lossy().to_string());
24
30
  }
25
31
  }
26
32
  all_dirs.sort();
27
33
 
28
- let domains = all_dirs.iter().filter(|name| out_root.join(name).join("merged.rs").is_file()).cloned().collect();
34
+ let domains = all_dirs
35
+ .iter()
36
+ .filter(|name| out_root.join(name).join("merged.rs").is_file())
37
+ .cloned()
38
+ .collect();
29
39
 
30
40
  Ok((all_dirs, domains))
31
41
  }
@@ -35,7 +45,12 @@ fn scan_generated_dirs(out_root: &Path) -> Result<(Vec<String>, Vec<String>), St
35
45
  /// Cargo has no mutually-exclusive features, so the default domain's re-export backs off when
36
46
  /// another domain feature is on, and two non-default features hit an explicit `compile_error!`
37
47
  /// instead of a duplicate-import E0252.
38
- fn write_root_mod(out_root: &Path, all_dirs: &[String], domains: &[String], target_mod_name: &str) -> Result<(), String> {
48
+ fn write_root_mod(
49
+ out_root: &Path,
50
+ all_dirs: &[String],
51
+ domains: &[String],
52
+ target_mod_name: &str,
53
+ ) -> Result<(), String> {
39
54
  let mut out = String::new();
40
55
  out.push_str("// GENERATED by hecks project_rust — re-run it to refresh this list.\n");
41
56
  out.push_str("//\n");
@@ -53,9 +68,9 @@ fn write_root_mod(out_root: &Path, all_dirs: &[String], domains: &[String], targ
53
68
  out.push_str("// it via `attaches`.\n");
54
69
  for name in all_dirs {
55
70
  if domains.iter().any(|d| d == name) {
56
- out.push_str(&format!("#[cfg(feature = {name:?})]\n"));
71
+ let _ = writeln!(out, "#[cfg(feature = {name:?})]");
57
72
  }
58
- out.push_str(&format!("pub mod {name};\n"));
73
+ let _ = writeln!(out, "pub mod {name};");
59
74
  }
60
75
  out.push('\n');
61
76
  out.push_str("// `active` is whichever ONE domain's own merged Store/dispatch table\n");
@@ -65,7 +80,10 @@ fn write_root_mod(out_root: &Path, all_dirs: &[String], domains: &[String], targ
65
80
  out.push_str("// no matter which domain that resolves to.\n");
66
81
  out.push_str("//\n");
67
82
  out.push_str("// DOMAIN FEATURES ARE MUTUALLY EXCLUSIVE (R5) — Cargo has no native\n");
68
- out.push_str(&format!("// concept of that, and `default = [{target_mod_name:?}]` in\n"));
83
+ let _ = writeln!(
84
+ out,
85
+ "// concept of that, and `default = [{target_mod_name:?}]` in"
86
+ );
69
87
  out.push_str("// Cargo.toml stays enabled unless a build passes\n");
70
88
  out.push_str("// `--no-default-features`, so a plain `cargo build --features banking`\n");
71
89
  out.push_str("// used to enable BOTH the default domain's feature and banking's,\n");
@@ -84,17 +102,27 @@ fn write_root_mod(out_root: &Path, all_dirs: &[String], domains: &[String], targ
84
102
  out.push_str("// `compile_error!` naming both features instead of a cryptic\n");
85
103
  out.push_str("// \"defined multiple times\".\n");
86
104
  for name in domains {
87
- let other_domains: Vec<&String> = domains.iter().filter(|n| n.as_str() != name.as_str()).collect();
105
+ let other_domains: Vec<&String> = domains
106
+ .iter()
107
+ .filter(|n| n.as_str() != name.as_str())
108
+ .collect();
88
109
  if name.as_str() == target_mod_name && !other_domains.is_empty() {
89
- let guard = other_domains.iter().map(|o| format!("feature = {o:?}")).collect::<Vec<_>>().join(", ");
90
- out.push_str(&format!("#[cfg(all(feature = {name:?}, not(any({guard}))))]\n"));
110
+ let guard = other_domains
111
+ .iter()
112
+ .map(|o| format!("feature = {o:?}"))
113
+ .collect::<Vec<_>>()
114
+ .join(", ");
115
+ let _ = writeln!(out, "#[cfg(all(feature = {name:?}, not(any({guard}))))]");
91
116
  } else {
92
- out.push_str(&format!("#[cfg(feature = {name:?})]\n"));
117
+ let _ = writeln!(out, "#[cfg(feature = {name:?})]");
93
118
  }
94
- out.push_str(&format!("pub use {name}::merged as active;\n"));
119
+ let _ = writeln!(out, "pub use {name}::merged as active;");
95
120
  }
96
121
 
97
- let non_default_domains: Vec<&String> = domains.iter().filter(|n| n.as_str() != target_mod_name).collect();
122
+ let non_default_domains: Vec<&String> = domains
123
+ .iter()
124
+ .filter(|n| n.as_str() != target_mod_name)
125
+ .collect();
98
126
  if non_default_domains.len() > 1 {
99
127
  out.push('\n');
100
128
  out.push_str("// Explicit conflict guard — see point 2 above. Only fires when TWO\n");
@@ -106,10 +134,10 @@ fn write_root_mod(out_root: &Path, all_dirs: &[String], domains: &[String], targ
106
134
  for j in (i + 1)..non_default_domains.len() {
107
135
  let a = non_default_domains[i];
108
136
  let b = non_default_domains[j];
109
- out.push_str(&format!("#[cfg(all(feature = {a:?}, feature = {b:?}))]\n"));
110
- out.push_str(&format!(
111
- "compile_error!(\"domain features are mutually exclusive — enable only one of: {domains_joined} (both {a} and {b} are enabled)\");\n"
112
- ));
137
+ let _ = writeln!(out, "#[cfg(all(feature = {a:?}, feature = {b:?}))]");
138
+ let _ = writeln!(out,
139
+ "compile_error!(\"domain features are mutually exclusive — enable only one of: {domains_joined} (both {a} and {b} are enabled)\");"
140
+ );
113
141
  }
114
142
  }
115
143
  }
@@ -123,7 +151,9 @@ fn write_root_mod(out_root: &Path, all_dirs: &[String], domains: &[String], targ
123
151
  /// The `[features]` table text, from its header to the next `[section]` header or EOF.
124
152
  /// Empty if there is no `[features]` header.
125
153
  fn features_table_slice(text: &str) -> &str {
126
- let Some(start) = text.find("[features]") else { return "" };
154
+ let Some(start) = text.find("[features]") else {
155
+ return "";
156
+ };
127
157
  let rest = &text[start..];
128
158
  let mut pos = 0;
129
159
  for (i, line) in rest.split_inclusive('\n').enumerate() {
@@ -135,8 +165,13 @@ fn features_table_slice(text: &str) -> &str {
135
165
  rest
136
166
  }
137
167
 
138
- fn sync_cargo_toml_features(cargo_toml_path: &Path, domains: &[String], target_mod_name: &str) -> Result<(), String> {
139
- let mut cargo_toml = std::fs::read_to_string(cargo_toml_path).map_err(|e| format!("reading {}: {e}", cargo_toml_path.display()))?;
168
+ fn sync_cargo_toml_features(
169
+ cargo_toml_path: &Path,
170
+ domains: &[String],
171
+ target_mod_name: &str,
172
+ ) -> Result<(), String> {
173
+ let mut cargo_toml = std::fs::read_to_string(cargo_toml_path)
174
+ .map_err(|e| format!("reading {}: {e}", cargo_toml_path.display()))?;
140
175
 
141
176
  if !cargo_toml.contains("[features]") {
142
177
  let preamble = "# ONE FEATURE PER GENERATED DOMAIN — selects which domain's own\n\
@@ -157,7 +192,9 @@ fn sync_cargo_toml_features(cargo_toml_path: &Path, domains: &[String], target_m
157
192
  // Scoped to `[features]`: matching `name = ` file-wide hits `[package]` keys like `version`.
158
193
  for name in domains {
159
194
  let already_declared = features_table_slice(&cargo_toml).lines().any(|line| {
160
- line.trim_start().strip_prefix(name.as_str()).map(|rest| rest.trim_start().starts_with('=')).unwrap_or(false)
195
+ line.trim_start()
196
+ .strip_prefix(name.as_str())
197
+ .is_some_and(|rest| rest.trim_start().starts_with('='))
161
198
  });
162
199
  if already_declared {
163
200
  continue;
@@ -165,10 +202,14 @@ fn sync_cargo_toml_features(cargo_toml_path: &Path, domains: &[String], target_m
165
202
  cargo_toml = insert_after_default(&cargo_toml, &format!("{name} = []"));
166
203
  }
167
204
 
168
- cargo_toml = replace_default_line(&cargo_toml, &format!("default = [{:?}]", target_mod_name));
205
+ cargo_toml = replace_default_line(&cargo_toml, &format!("default = [{target_mod_name:?}]"));
169
206
 
170
- std::fs::write(cargo_toml_path, &cargo_toml).map_err(|e| format!("writing {}: {e}", cargo_toml_path.display()))?;
171
- println!("wrote {} (default feature: {target_mod_name})", cargo_toml_path.display());
207
+ std::fs::write(cargo_toml_path, &cargo_toml)
208
+ .map_err(|e| format!("writing {}: {e}", cargo_toml_path.display()))?;
209
+ println!(
210
+ "wrote {} (default feature: {target_mod_name})",
211
+ cargo_toml_path.display()
212
+ );
172
213
  Ok(())
173
214
  }
174
215
 
@@ -209,7 +250,9 @@ fn replace_default_line(text: &str, new_line: &str) -> String {
209
250
 
210
251
  fn is_default_line(line_content: &str) -> bool {
211
252
  let trimmed = line_content.trim_start();
212
- trimmed.strip_prefix("default").map(|rest| rest.trim_start().starts_with('=')).unwrap_or(false)
253
+ trimmed
254
+ .strip_prefix("default")
255
+ .is_some_and(|rest| rest.trim_start().starts_with('='))
213
256
  }
214
257
 
215
258
  /// Rewrites only the root `mod.rs`, for a generation into a directory other than `rust/src/generated`.