hecks 3.14.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (427) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/adapters/fake_processor.rb +34 -0
  3. data/examples/pizzas/adapters/payment_signature.rb +66 -0
  4. data/examples/pizzas/adapters/payment_webhook.rb +87 -0
  5. data/examples/pizzas/bluebook/pizzas.behaviors +33 -0
  6. data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
  7. data/examples/pizzas/config.ru +22 -0
  8. data/exe/hecks +1 -1
  9. data/lib/hecks/adapters/driven/prism.rb +0 -2
  10. data/lib/hecks/adapters/driven.rb +0 -2
  11. data/lib/hecks/adapters/driving/json.rb +9 -16
  12. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
  13. data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
  14. data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
  15. data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
  16. data/lib/hecks/bluebook/hecksagon.rb +1 -1
  17. data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
  18. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
  19. data/lib/hecks/bluebook/meta_validator.rb +0 -11
  20. data/lib/hecks/bluebook/translation.rb +18 -10
  21. data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
  22. data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
  23. data/lib/hecks/bluebook/translation_expression.rb +39 -0
  24. data/lib/hecks/bluebook/translation_plan.rb +70 -0
  25. data/lib/hecks/bluebook.rb +2 -0
  26. data/lib/hecks/chapters.rb +3 -3
  27. data/lib/hecks/cli/project_cli/launcher_source.rb +1 -1
  28. data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
  29. data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
  30. data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
  31. data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
  32. data/lib/hecks/composer/adapters/jev.adapter +3 -0
  33. data/lib/hecks/composer/adapters/jev.rb +66 -0
  34. data/lib/hecks/composer/agent.rb +113 -0
  35. data/lib/hecks/composer/assistant.rb +110 -0
  36. data/lib/hecks/composer/behavior_run.rb +53 -0
  37. data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
  38. data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
  39. data/lib/hecks/composer/board.rb +65 -0
  40. data/lib/hecks/composer/board_edit/command_text.rb +39 -0
  41. data/lib/hecks/composer/board_edit/source.rb +84 -0
  42. data/lib/hecks/composer/board_edit.rb +144 -0
  43. data/lib/hecks/composer/check.rb +89 -0
  44. data/lib/hecks/composer/domains.rb +81 -0
  45. data/lib/hecks/composer/jobs.rb +134 -0
  46. data/lib/hecks/composer/layout.rb +37 -0
  47. data/lib/hecks/composer/live.rb +150 -0
  48. data/lib/hecks/composer/outline.rb +48 -0
  49. data/lib/hecks/composer/page.html +989 -0
  50. data/lib/hecks/composer/panels.rb +142 -0
  51. data/lib/hecks/composer/parse.rb +83 -0
  52. data/lib/hecks/composer/plain.rb +28 -0
  53. data/lib/hecks/composer/ports/composer_agent.port +7 -0
  54. data/lib/hecks/composer/session.rb +141 -0
  55. data/lib/hecks/composer/toolbox.rb +166 -0
  56. data/lib/hecks/composer/web.rb +156 -0
  57. data/lib/hecks/composer/workbench.rb +51 -0
  58. data/lib/hecks/composer/workspace.rb +81 -0
  59. data/lib/hecks/composer.rb +17 -0
  60. data/lib/hecks/corpus/adapters.rb +22 -0
  61. data/lib/hecks/corpus/rust.rb +1 -1
  62. data/lib/hecks/corpus/worlds.rb +19 -0
  63. data/lib/hecks/corpus.rb +5 -0
  64. data/lib/hecks/forms/app/commands.rb +2 -3
  65. data/lib/hecks/forms/app/queries.rb +2 -3
  66. data/lib/hecks/forms/app/records.rb +17 -5
  67. data/lib/hecks/forms/app/responses.rb +3 -1
  68. data/lib/hecks/forms/command_form_renderer.rb +42 -16
  69. data/lib/hecks/forms/field_controls.rb +2 -2
  70. data/lib/hecks/forms/field_renderer.rb +1 -1
  71. data/lib/hecks/forms/field_shape.rb +36 -17
  72. data/lib/hecks/forms/index_renderer.rb +2 -1
  73. data/lib/hecks/forms/page.rb +1 -1
  74. data/lib/hecks/forms/params.rb +0 -13
  75. data/lib/hecks/forms/query_form_renderer.rb +7 -6
  76. data/lib/hecks/forms/record_renderer.rb +11 -7
  77. data/lib/hecks/forms/rule_words.rb +100 -0
  78. data/lib/hecks/forms/value_object_shape.rb +2 -1
  79. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
  80. data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
  81. data/lib/hecks/fuzzing/replay/filters.rb +2 -6
  82. data/lib/hecks/gate/stages.yml +25 -0
  83. data/lib/hecks/grammar/evolve/seeds.rb +0 -6
  84. data/lib/hecks/grammar/evolve.rb +0 -3
  85. data/lib/hecks/grammar/translation.bluebook +1 -1
  86. data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
  87. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
  88. data/lib/hecks/hecks/adapters/first_admin.rb +3 -3
  89. data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
  90. data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
  91. data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
  92. data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
  93. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
  94. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
  95. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
  96. data/lib/hecks/hecks/codebase.behaviors +6 -0
  97. data/lib/hecks/hecks/codebase.bluebook +21 -0
  98. data/lib/hecks/hecks/composer.behaviors +68 -0
  99. data/lib/hecks/hecks/context_map.hecksagon +1 -1
  100. data/lib/hecks/hecks/custodian.behaviors +0 -6
  101. data/lib/hecks/hecks/custodian.bluebook +3 -80
  102. data/lib/hecks/hecks/hecks.hecksagon +2 -2
  103. data/lib/hecks/hecks/hecks.world +1 -1
  104. data/lib/hecks/hecks/rehearsal.bluebook +7 -13
  105. data/lib/hecks/kernel_capabilities/templates.rb +4 -4
  106. data/lib/hecks/kernel_capabilities.rb +1 -1
  107. data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
  108. data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
  109. data/lib/hecks/ports/access_control.rb +2 -2
  110. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
  111. data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
  112. data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
  113. data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
  114. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
  115. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
  116. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
  117. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
  118. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
  119. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
  120. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
  121. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
  122. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
  123. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
  124. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
  125. data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
  126. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
  127. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
  128. data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
  129. data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
  130. data/lib/hecks/ports.rb +0 -1
  131. data/lib/hecks/projection_files.rb +3 -1
  132. data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
  133. data/lib/hecks/projections/glossary/entries.rb +10 -4
  134. data/lib/hecks/projections/glossary/groups.rb +42 -0
  135. data/lib/hecks/projections/glossary/html/page.rb +7 -2
  136. data/lib/hecks/projections/glossary/html/parser.rb +1 -1
  137. data/lib/hecks/projections/glossary/html/sections.rb +31 -11
  138. data/lib/hecks/projections/glossary/html.rb +1 -2
  139. data/lib/hecks/projections/glossary/markdown.rb +31 -28
  140. data/lib/hecks/projections/glossary/mermaid.rb +3 -0
  141. data/lib/hecks/projections/glossary/page.css +143 -153
  142. data/lib/hecks/projections/glossary/page.js +63 -3
  143. data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
  144. data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
  145. data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
  146. data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
  147. data/lib/hecks/projections/glossary/sentences.rb +13 -3
  148. data/lib/hecks/projections/glossary/slugs.rb +4 -1
  149. data/lib/hecks/projections/glossary.rb +2 -1
  150. data/lib/hecks/projections/plain_english.rb +97 -0
  151. data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
  152. data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
  153. data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
  154. data/lib/hecks/projections/rust_vocabulary.rb +14 -6
  155. data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
  156. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +7 -2
  157. data/lib/hecks/projections/statements/actions.rb +99 -0
  158. data/lib/hecks/projections/statements/relationships.rb +86 -0
  159. data/lib/hecks/projections/statements.rb +15 -24
  160. data/lib/hecks/projector/exporter/adapters.rb +112 -0
  161. data/lib/hecks/projector/exporter/translations.rb +18 -35
  162. data/lib/hecks/projector/exporter/worlds.rb +103 -0
  163. data/lib/hecks/projector/exporter.rb +5 -1
  164. data/lib/hecks/quality_control/adapters/agent.rb +6 -7
  165. data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
  166. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
  167. data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
  168. data/lib/hecks/runtime/outbox.rb +2 -9
  169. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
  170. data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
  171. data/lib/hecks/runtime/value/unwrapping.rb +60 -0
  172. data/lib/hecks/runtime/value.rb +10 -21
  173. data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
  174. data/lib/hecks/rust_build/wasm.rb +0 -12
  175. data/lib/hecks/rust_build/write_if_changed.rb +12 -12
  176. data/lib/hecks/rust_format.rb +77 -0
  177. data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
  178. data/lib/hecks/tools/fuzz_sweep.rb +1 -3
  179. data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
  180. data/lib/hecks/tools/translation_audit.rb +8 -10
  181. data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
  182. data/lib/hecks/version.rb +1 -1
  183. data/lib/hecks/vocabulary.rb +2 -1
  184. data/lib/hecks.rb +3 -0
  185. data/prebuilt/{1a6f596be124228be93eba1d86dbc9d2933190fa389b284331241cb9f27115a0.marshal → ad68f9e06c8068c2a4176b57abf3083d591d5d8980c2b3e897b97331f965b643.marshal} +0 -0
  186. data/prebuilt/{a883a7e568667aa5ac904c82daff356bdf9e996252a36af6def4a4a43efb967e.marshal → ee22ec8642c4006717ba2b80f46e5efb81de459eef9dfec043e838c0ad4fd720.marshal} +0 -0
  187. data/prebuilt/{verdicts-872993b1aa26d73fe48f28251374b8585fed145d02aab42be0f00de386b6ff80.json → verdicts-b69f5dcbba79c20daa0493476fa7c317004a30aa2bc3d3dc6a722ea70ae2b43b.json} +1 -1
  188. data/rust/Cargo.toml +40 -0
  189. data/rust/build/Cargo.toml +36 -0
  190. data/rust/build/clippy.toml +4 -0
  191. data/rust/build/src/adapters_pass.rs +196 -0
  192. data/rust/build/src/binding_pass.rs +54 -13
  193. data/rust/build/src/build_artifact.rs +23 -5
  194. data/rust/build/src/cargo_sync.rs +69 -26
  195. data/rust/build/src/lineage_pass.rs +101 -87
  196. data/rust/build/src/main.rs +11 -2
  197. data/rust/build/src/pipeline.rs +80 -19
  198. data/rust/build/src/reserved_names.rs +6 -50
  199. data/rust/build/src/resolve.rs +82 -35
  200. data/rust/build/src/rustfmt.rs +100 -0
  201. data/rust/build/src/seams_pass.rs +85 -16
  202. data/rust/build/src/subprocess.rs +30 -6
  203. data/rust/build/src/tmp.rs +5 -2
  204. data/rust/build/src/translation_pass.rs +72 -22
  205. data/rust/build/src/world_pass.rs +109 -0
  206. data/rust/clippy.toml +4 -0
  207. data/rust/codegen/Cargo.toml +36 -0
  208. data/rust/codegen/clippy.toml +4 -0
  209. data/rust/codegen/src/asks.rs +40 -14
  210. data/rust/codegen/src/attr.rs +2 -2
  211. data/rust/codegen/src/bridging.rs +676 -117
  212. data/rust/codegen/src/commands.rs +979 -373
  213. data/rust/codegen/src/constraints.rs +88 -25
  214. data/rust/codegen/src/dependency_planning.rs +128 -34
  215. data/rust/codegen/src/domain_generator.rs +306 -170
  216. data/rust/codegen/src/exemplar.rs +87 -32
  217. data/rust/codegen/src/expr_emitter.rs +54 -6
  218. data/rust/codegen/src/fielded.rs +193 -41
  219. data/rust/codegen/src/hecks_naming.rs +124 -16
  220. data/rust/codegen/src/hecks_naming_tests.rs +81 -0
  221. data/rust/codegen/src/json.rs +80 -44
  222. data/rust/codegen/src/json_codec.rs +585 -149
  223. data/rust/codegen/src/literal.rs +104 -138
  224. data/rust/codegen/src/main.rs +151 -52
  225. data/rust/codegen/src/manifest.rs +76 -14
  226. data/rust/codegen/src/mutations.rs +1212 -239
  227. data/rust/codegen/src/naming.rs +111 -60
  228. data/rust/codegen/src/optional_pass.rs +90 -28
  229. data/rust/codegen/src/ports.rs +5 -4
  230. data/rust/codegen/src/prelude.rs +139 -49
  231. data/rust/codegen/src/presenters.rs +335 -0
  232. data/rust/codegen/src/queries.rs +601 -226
  233. data/rust/codegen/src/reactions.rs +268 -74
  234. data/rust/codegen/src/read_models.rs +574 -229
  235. data/rust/codegen/src/reference_specs.rs +16 -3
  236. data/rust/codegen/src/registry.rs +128 -79
  237. data/rust/codegen/src/reserved_names.rs +6 -50
  238. data/rust/codegen/src/shared.rs +71 -22
  239. data/rust/codegen/src/sidecars.rs +15 -5
  240. data/rust/codegen/src/skip_reason.rs +8 -2
  241. data/rust/codegen/src/types.rs +306 -108
  242. data/rust/host/Cargo.lock +1 -0
  243. data/rust/host/Cargo.toml +40 -4
  244. data/rust/host/HECKS_RELEASE +1 -1
  245. data/rust/host/clippy.toml +4 -0
  246. data/rust/host/src/api.rs +698 -218
  247. data/rust/host/src/auth.rs +491 -214
  248. data/rust/host/src/b64.rs +91 -0
  249. data/rust/host/src/bin/expr_harness.rs +6 -2
  250. data/rust/host/src/bin/lineage_harness.rs +47 -11
  251. data/rust/host/src/bin/mint_harness.rs +70 -37
  252. data/rust/host/src/bin/validate_harness.rs +16 -8
  253. data/rust/host/src/boot.rs +144 -68
  254. data/rust/host/src/boundary_fuzz/api.rs +130 -32
  255. data/rust/host/src/boundary_fuzz/auth.rs +108 -23
  256. data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
  257. data/rust/host/src/boundary_fuzz/ir.rs +164 -12
  258. data/rust/host/src/boundary_fuzz/server.rs +283 -36
  259. data/rust/host/src/boundary_fuzz/web.rs +56 -8
  260. data/rust/host/src/checkout.rs +78 -32
  261. data/rust/host/src/clock.rs +8 -0
  262. data/rust/host/src/commerce_ir.rs +333 -95
  263. data/rust/host/src/crypto.rs +33 -0
  264. data/rust/host/src/dispatch.rs +1037 -331
  265. data/rust/host/src/expr_json.rs +1021 -273
  266. data/rust/host/src/extension.rs +24 -4
  267. data/rust/host/src/field_hints.rs +11 -7
  268. data/rust/host/src/fuzz_support.rs +85 -13
  269. data/rust/host/src/host.rs +100 -0
  270. data/rust/host/src/http.rs +22 -0
  271. data/rust/host/src/ir.rs +326 -16
  272. data/rust/host/src/journal.rs +277 -176
  273. data/rust/host/src/lambda_client.rs +219 -53
  274. data/rust/host/src/lib.rs +16 -4
  275. data/rust/host/src/log.rs +62 -11
  276. data/rust/host/src/mint.rs +1153 -326
  277. data/rust/host/src/needs.rs +185 -44
  278. data/rust/host/src/payments/keystore.rs +197 -45
  279. data/rust/host/src/payments/roles.rs +39 -7
  280. data/rust/host/src/payments/tests.rs +1427 -282
  281. data/rust/host/src/payments.rs +391 -112
  282. data/rust/host/src/platform_signin/attack_tests.rs +5 -5
  283. data/rust/host/src/platform_signin/config.rs +11 -11
  284. data/rust/host/src/platform_signin/cookie.rs +2 -2
  285. data/rust/host/src/platform_signin/extension.rs +2 -1
  286. data/rust/host/src/platform_signin/handler.rs +9 -8
  287. data/rust/host/src/platform_signin/jti.rs +4 -2
  288. data/rust/host/src/platform_signin/jwks.rs +2 -1
  289. data/rust/host/src/platform_signin/jws.rs +2 -37
  290. data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
  291. data/rust/host/src/platform_signin/mode_tests.rs +5 -3
  292. data/rust/host/src/platform_signin/pages.rs +20 -20
  293. data/rust/host/src/platform_signin/session.rs +8 -1
  294. data/rust/host/src/platform_signin/settings.rs +6 -6
  295. data/rust/host/src/platform_signin/strict_json.rs +12 -9
  296. data/rust/host/src/platform_signin/verify.rs +41 -41
  297. data/rust/host/src/platform_signin/wiring_support.rs +13 -18
  298. data/rust/host/src/presentation.rs +207 -63
  299. data/rust/host/src/presentation_write.rs +502 -187
  300. data/rust/host/src/query_step.rs +69 -18
  301. data/rust/host/src/rate_limit.rs +541 -142
  302. data/rust/host/src/reference_transform.rs +77 -25
  303. data/rust/host/src/reference_validate.rs +443 -104
  304. data/rust/host/src/resend.rs +190 -55
  305. data/rust/host/src/secrets.rs +116 -21
  306. data/rust/host/src/server.rs +555 -104
  307. data/rust/host/src/storage_shape.rs +88 -21
  308. data/rust/host/src/test_pg.rs +17 -7
  309. data/rust/host/src/ui_schema.rs +495 -207
  310. data/rust/host/src/wasm_runner.rs +21 -6
  311. data/rust/host/src/web/commerce.rs +101 -23
  312. data/rust/host/src/web/newsletter/cooldown.rs +10 -3
  313. data/rust/host/src/web/newsletter/template.rs +26 -7
  314. data/rust/host/src/web/newsletter/tests.rs +112 -23
  315. data/rust/host/src/web/newsletter.rs +209 -59
  316. data/rust/host/src/web/newsletter_send.rs +251 -79
  317. data/rust/host/src/web/registration_receipt/tests.rs +16 -4
  318. data/rust/host/src/web/registration_receipt.rs +100 -24
  319. data/rust/host/src/web/registrations.rs +1384 -332
  320. data/rust/host/src/web/resend_webhook.rs +138 -80
  321. data/rust/host/src/web.rs +2416 -508
  322. data/rust/lsp/Cargo.toml +36 -0
  323. data/rust/lsp/clippy.toml +4 -0
  324. data/rust/lsp/src/json.rs +29 -22
  325. data/rust/lsp/src/main.rs +81 -31
  326. data/rust/lsp/src/outline.rs +23 -7
  327. data/rust/lsp/src/rpc.rs +17 -4
  328. data/rust/parser/Cargo.toml +36 -0
  329. data/rust/parser/clippy.toml +4 -0
  330. data/rust/parser/src/build/closed_sets.rs +6 -1
  331. data/rust/parser/src/build/identity.rs +4 -3
  332. data/rust/parser/src/build/mod.rs +1 -1
  333. data/rust/parser/src/build/named_pattern.rs +4 -1
  334. data/rust/parser/src/build/naming.rs +2 -80
  335. data/rust/parser/src/build/pattern_subset.rs +6 -8
  336. data/rust/parser/src/build/query_derive.rs +3 -1
  337. data/rust/parser/src/build/query_inference.rs +14 -5
  338. data/rust/parser/src/build/read_model.rs +4 -1
  339. data/rust/parser/src/build/references.rs +4 -3
  340. data/rust/parser/src/canonical.rs +34 -15
  341. data/rust/parser/src/emit.rs +187 -105
  342. data/rust/parser/src/expr/ast_json.rs +134 -42
  343. data/rust/parser/src/expr/evaluator.rs +27 -9
  344. data/rust/parser/src/expr/mod.rs +54 -13
  345. data/rust/parser/src/expr/resolver.rs +299 -92
  346. data/rust/parser/src/ir.rs +35 -3
  347. data/rust/parser/src/keywords.rs +7168 -516
  348. data/rust/parser/src/lex.rs +11 -25
  349. data/rust/parser/src/lib.rs +1 -0
  350. data/rust/parser/src/main.rs +65 -6
  351. data/rust/parser/src/parse/adapter.rs +157 -0
  352. data/rust/parser/src/parse/aggregate.rs +19 -10
  353. data/rust/parser/src/parse/chapter.rs +16 -17
  354. data/rust/parser/src/parse/command.rs +35 -16
  355. data/rust/parser/src/parse/domain_port.rs +4 -7
  356. data/rust/parser/src/parse/entity.rs +21 -11
  357. data/rust/parser/src/parse/hecksagon.rs +10 -4
  358. data/rust/parser/src/parse/lifecycle.rs +9 -5
  359. data/rust/parser/src/parse/mod.rs +22 -91
  360. data/rust/parser/src/parse/needs.rs +21 -12
  361. data/rust/parser/src/parse/policy.rs +2 -2
  362. data/rust/parser/src/parse/process_manager.rs +9 -11
  363. data/rust/parser/src/parse/query.rs +8 -4
  364. data/rust/parser/src/parse/read_model.rs +57 -16
  365. data/rust/parser/src/parse/value_object.rs +7 -2
  366. data/rust/parser/src/parse/world.rs +322 -0
  367. data/rust/parser/src/ruby_value.rs +7 -6
  368. data/rust/src/exemplar/json.rs +4 -4
  369. data/rust/src/exemplar/mutations.rs +18 -0
  370. data/rust/src/exemplar/read_models.rs +1 -10
  371. data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
  372. data/rust/src/kernel/attribute_shapes/list.rs +2 -2
  373. data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
  374. data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
  375. data/rust/src/kernel/cli.rs +244 -114
  376. data/rust/src/kernel/dispatch.rs +323 -115
  377. data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
  378. data/rust/src/kernel/expr.rs +203 -86
  379. data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
  380. data/rust/src/kernel/expression_operators/comparison.rs +87 -2
  381. data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
  382. data/rust/src/kernel/expression_operators/logical.rs +9 -3
  383. data/rust/src/kernel/expression_operators/membership.rs +3 -1
  384. data/rust/src/kernel/expression_operators/mod.rs +34 -21
  385. data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
  386. data/rust/src/kernel/expression_operators/positional.rs +84 -30
  387. data/rust/src/kernel/expression_operators/presence.rs +52 -13
  388. data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
  389. data/rust/src/kernel/expression_operators/sized.rs +24 -8
  390. data/rust/src/kernel/expression_operators/text.rs +120 -27
  391. data/rust/src/kernel/expression_operators/to_string.rs +10 -4
  392. data/rust/src/kernel/json.rs +244 -148
  393. data/rust/src/kernel/mod.rs +47 -33
  394. data/rust/src/kernel/named_query.rs +63 -15
  395. data/rust/src/kernel/needs.rs +112 -25
  396. data/rust/src/kernel/orchestrate.rs +980 -581
  397. data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
  398. data/rust/src/kernel/pattern.rs +107 -34
  399. data/rust/src/kernel/present.rs +261 -0
  400. data/rust/src/kernel/query_comparators.rs +118 -41
  401. data/rust/src/kernel/query_ordering.rs +13 -5
  402. data/rust/src/kernel/read_model.rs +479 -181
  403. data/rust/src/kernel/reference_lookup.rs +122 -26
  404. data/rust/src/kernel/repository.rs +293 -66
  405. data/rust/src/kernel/routing.rs +58 -42
  406. data/rust/src/kernel/ruby_float.rs +139 -0
  407. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
  408. data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
  409. data/rust/src/kernel/vocab/field_hint.rs +19 -25
  410. data/rust/src/kernel/vocab/mod.rs +6 -6
  411. data/rust/src/kernel/vocab/query_comparator.rs +23 -24
  412. data/rust/src/kernel/vocab/refusal_template.rs +501 -297
  413. data/rust/src/lib.rs +50 -0
  414. data/rust/src/main.rs +4 -1
  415. data/rust/web/Cargo.toml +36 -0
  416. data/rust/web/clippy.toml +4 -0
  417. metadata +85 -15
  418. data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
  419. data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
  420. data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
  421. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
  422. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
  423. data/lib/hecks/ports/authentication.port +0 -6
  424. data/lib/hecks/ports/authentication.rb +0 -67
  425. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
  426. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
  427. data/rust/host/src/approval.rs +0 -652
@@ -8,8 +8,12 @@ use crate::subprocess;
8
8
 
9
9
  pub fn bluebook_files(directory: &Path) -> Result<Vec<PathBuf>, String> {
10
10
  let mut files = Vec::new();
11
- for entry in std::fs::read_dir(directory).map_err(|e| format!("reading {}: {e}", directory.display()))? {
12
- let path = entry.map_err(|e| format!("reading {}: {e}", directory.display()))?.path();
11
+ for entry in
12
+ std::fs::read_dir(directory).map_err(|e| format!("reading {}: {e}", directory.display()))?
13
+ {
14
+ let path = entry
15
+ .map_err(|e| format!("reading {}: {e}", directory.display()))?
16
+ .path();
13
17
  if path.extension().and_then(|extension| extension.to_str()) == Some("bluebook") {
14
18
  files.push(path);
15
19
  }
@@ -20,7 +24,8 @@ pub fn bluebook_files(directory: &Path) -> Result<Vec<PathBuf>, String> {
20
24
 
21
25
  // Plain text scan of the `Hecks.bluebook "Name"` line; the file is never executed.
22
26
  pub fn header_chapter_name(path: &Path) -> Result<String, String> {
23
- let text = std::fs::read_to_string(path).map_err(|e| format!("reading {}: {e}", path.display()))?;
27
+ let text =
28
+ std::fs::read_to_string(path).map_err(|e| format!("reading {}: {e}", path.display()))?;
24
29
  for line in text.lines() {
25
30
  let trimmed = line.trim_start();
26
31
  if let Some(rest) = trimmed.strip_prefix("Hecks.bluebook") {
@@ -29,7 +34,10 @@ pub fn header_chapter_name(path: &Path) -> Result<String, String> {
29
34
  }
30
35
  }
31
36
  }
32
- Err(format!("{} has no 'Hecks.bluebook \"Name\"' header", path.display()))
37
+ Err(format!(
38
+ "{} has no 'Hecks.bluebook \"Name\"' header",
39
+ path.display()
40
+ ))
33
41
  }
34
42
 
35
43
  fn extract_quoted(rest: &str) -> Option<String> {
@@ -52,25 +60,17 @@ pub fn framework_members(root: &Path) -> Result<Vec<(String, PathBuf)>, String>
52
60
  if path.extension().and_then(|e| e.to_str()) != Some("bluebook") {
53
61
  continue;
54
62
  }
55
- let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("").to_string();
56
- members.push((pascal(&stem), path));
63
+ let stem = path
64
+ .file_stem()
65
+ .and_then(|s| s.to_str())
66
+ .unwrap_or("")
67
+ .to_string();
68
+ members.push((crate::hecks_naming::pascal(&stem), path));
57
69
  }
58
70
  members.sort();
59
71
  Ok(members)
60
72
  }
61
73
 
62
- pub fn pascal(text: &str) -> String {
63
- text.split('_')
64
- .map(|part| {
65
- let mut chars = part.chars();
66
- match chars.next() {
67
- Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
68
- None => String::new(),
69
- }
70
- })
71
- .collect()
72
- }
73
-
74
74
  // Discovery, not a mirrored filename list, so a new concept cannot drift from this pipeline.
75
75
  pub fn grammar_files(root: &Path) -> Result<Vec<PathBuf>, String> {
76
76
  let dir = root.join("lib/hecks/language/bluebook");
@@ -78,27 +78,50 @@ pub fn grammar_files(root: &Path) -> Result<Vec<PathBuf>, String> {
78
78
  }
79
79
 
80
80
  // The names `attaches "Name"` gave, for a chapter the gem carries.
81
- pub fn resolve_gem_chapters(parser_bin: &Path, chapter_name: &str, hecksagon_path: &Path) -> Result<Vec<String>, String> {
81
+ pub fn resolve_gem_chapters(
82
+ parser_bin: &Path,
83
+ chapter_name: &str,
84
+ hecksagon_path: &Path,
85
+ ) -> Result<Vec<String>, String> {
82
86
  resolve_json(parser_bin, chapter_name, hecksagon_path, "gem_chapters")
83
87
  }
84
88
 
85
89
  // The names `attaches "name", from: :vendor` gave, for a vendored package.
86
- pub fn resolve_vendored_packages(parser_bin: &Path, chapter_name: &str, hecksagon_path: &Path) -> Result<Vec<String>, String> {
87
- resolve_json(parser_bin, chapter_name, hecksagon_path, "vendored_packages")
90
+ pub fn resolve_vendored_packages(
91
+ parser_bin: &Path,
92
+ chapter_name: &str,
93
+ hecksagon_path: &Path,
94
+ ) -> Result<Vec<String>, String> {
95
+ resolve_json(
96
+ parser_bin,
97
+ chapter_name,
98
+ hecksagon_path,
99
+ "vendored_packages",
100
+ )
88
101
  }
89
102
 
90
103
  // Where the gem keeps the chapters other than framework members, relative to `lib/hecks`; the
91
104
  // same places `Hecks::Chapters::GLOBS` lists.
92
- const OTHER_GEM_CHAPTER_DIRS: &[&str] = &["language", "grammar", "tenancy/bluebook", "deploy/bluebook", "quality_control"];
105
+ const OTHER_GEM_CHAPTER_DIRS: &[&str] = &[
106
+ "language",
107
+ "grammar",
108
+ "tenancy/bluebook",
109
+ "deploy/bluebook",
110
+ "quality_control",
111
+ ];
93
112
 
94
113
  // The names of the chapters the gem carries beside the framework members, found by reading each
95
114
  // file's header. Only the Ruby runtime loads them, so the build uses this list to tell a real
96
115
  // gem chapter from a misspelt name.
97
116
  pub fn other_gem_chapter_names(root: &Path) -> Result<Vec<String>, String> {
98
117
  fn walk(directory: &Path, names: &mut Vec<String>) -> Result<(), String> {
99
- let Ok(entries) = std::fs::read_dir(directory) else { return Ok(()) };
118
+ let Ok(entries) = std::fs::read_dir(directory) else {
119
+ return Ok(());
120
+ };
100
121
  for entry in entries {
101
- let path = entry.map_err(|e| format!("reading {}: {e}", directory.display()))?.path();
122
+ let path = entry
123
+ .map_err(|e| format!("reading {}: {e}", directory.display()))?
124
+ .path();
102
125
  if path.is_dir() {
103
126
  walk(&path, names)?;
104
127
  } else if path.extension().and_then(|e| e.to_str()) == Some("bluebook") {
@@ -118,13 +141,26 @@ pub fn other_gem_chapter_names(root: &Path) -> Result<Vec<String>, String> {
118
141
  Ok(names)
119
142
  }
120
143
 
121
- fn resolve_json(parser_bin: &Path, chapter_name: &str, hecksagon_path: &Path, key: &str) -> Result<Vec<String>, String> {
144
+ fn resolve_json(
145
+ parser_bin: &Path,
146
+ chapter_name: &str,
147
+ hecksagon_path: &Path,
148
+ key: &str,
149
+ ) -> Result<Vec<String>, String> {
122
150
  let parser_bin_str = parser_bin.to_string_lossy().to_string();
123
151
  let hecksagon_str = hecksagon_path.to_string_lossy().to_string();
124
- let out = subprocess::run_capture(&parser_bin_str, &["resolve", "--chapter", chapter_name, &hecksagon_str])?;
125
- let json = Json::parse(&out).map_err(|e| format!("parsing 'hecks-parse resolve' output: {e}\n{out}"))?;
126
- let names = json.get(key).map(Json::each).unwrap_or(&[]);
127
- Ok(names.iter().filter_map(Json::as_str).map(str::to_string).collect())
152
+ let out = subprocess::run_capture(
153
+ &parser_bin_str,
154
+ &["resolve", "--chapter", chapter_name, &hecksagon_str],
155
+ )?;
156
+ let json = Json::parse(&out)
157
+ .map_err(|e| format!("parsing 'hecks-parse resolve' output: {e}\n{out}"))?;
158
+ let names = json.get(key).map_or(&[][..], Json::each);
159
+ Ok(names
160
+ .iter()
161
+ .filter_map(Json::as_str)
162
+ .map(str::to_string)
163
+ .collect())
128
164
  }
129
165
 
130
166
  /// `/\A[a-z][a-z0-9_]*\z/` — the shape `hecks vendor` accepts for a package directory name.
@@ -132,7 +168,8 @@ fn resolve_json(parser_bin: &Path, chapter_name: &str, hecksagon_path: &Path, ke
132
168
  /// so `../x` or an absolute path must never reach either.
133
169
  pub fn valid_package_name(name: &str) -> bool {
134
170
  let mut chars = name.chars();
135
- matches!(chars.next(), Some(c) if c.is_ascii_lowercase()) && chars.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
171
+ matches!(chars.next(), Some(c) if c.is_ascii_lowercase())
172
+ && chars.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
136
173
  }
137
174
 
138
175
  pub fn vendored_bluebook_files(domain: &Path, pkg_name: &str) -> Result<Vec<PathBuf>, String> {
@@ -141,7 +178,11 @@ pub fn vendored_bluebook_files(domain: &Path, pkg_name: &str) -> Result<Vec<Path
141
178
  "attaches {pkg_name:?}, from: :vendor is not a package name — it must match [a-z][a-z0-9_]* (the name `hecks vendor` accepts)"
142
179
  ));
143
180
  }
144
- let dir = domain.join("vendor").join("embryonaut_bluebooks").join(pkg_name).join("bluebook");
181
+ let dir = domain
182
+ .join("vendor")
183
+ .join("embryonaut_bluebooks")
184
+ .join(pkg_name)
185
+ .join("bluebook");
145
186
  if !dir.is_dir() {
146
187
  return Err(format!(
147
188
  "attaches {pkg_name:?}, from: :vendor names no vendored bluebook at {} — run hecks vendor {pkg_name}",
@@ -169,14 +210,20 @@ mod tests {
169
210
 
170
211
  #[test]
171
212
  fn pascal_cases_snake_case_stems() {
172
- assert_eq!(pascal("governance"), "Governance");
173
- assert_eq!(pascal("console_settings"), "ConsoleSettings");
174
- assert_eq!(pascal("identity"), "Identity");
213
+ assert_eq!(crate::hecks_naming::pascal("governance"), "Governance");
214
+ assert_eq!(
215
+ crate::hecks_naming::pascal("console_settings"),
216
+ "ConsoleSettings"
217
+ );
218
+ assert_eq!(crate::hecks_naming::pascal("identity"), "Identity");
175
219
  }
176
220
 
177
221
  #[test]
178
222
  fn extracts_the_quoted_chapter_name() {
179
223
  assert_eq!(extract_quoted(r#" "Pizzas""#), Some("Pizzas".to_string()));
180
- assert_eq!(extract_quoted(r#" "Console Settings" do"#), Some("Console Settings".to_string()));
224
+ assert_eq!(
225
+ extract_quoted(r#" "Console Settings" do"#),
226
+ Some("Console Settings".to_string())
227
+ );
181
228
  }
182
229
  }
@@ -0,0 +1,100 @@
1
+ //! Lays generated Rust out with rustfmt, as `Hecks::RustFormat` does for `hecks project_rust`, so
2
+ //! the tree `hecks-build` writes matches the one `hecks project_rust` writes, byte for byte.
3
+
4
+ use std::io::Write as _;
5
+ use std::path::{Path, PathBuf};
6
+ use std::process::{Command, Stdio};
7
+
8
+ const EDITION: &str = "2021";
9
+
10
+ // The most passes made; a text still changing after them keeps the last result, as in Ruby.
11
+ const PASSES: usize = 3;
12
+
13
+ /// Formats every `.rs` file of this run's own output: the `mod.rs` at `out_root` and each of
14
+ /// `dirs` beneath it, skipping a directory that is not there. A file is rewritten only when its
15
+ /// layout changes, so mtimes stay put and Cargo does not rebuild what did not change.
16
+ pub fn format_generated(out_root: &Path, dirs: &[String]) -> Result<(), String> {
17
+ let mut files = vec![out_root.join("mod.rs")];
18
+ for dir in dirs {
19
+ collect_rust_files(&out_root.join(dir), &mut files)?;
20
+ }
21
+ for file in files.iter().filter(|f| f.is_file()) {
22
+ format_file(file)?;
23
+ }
24
+ Ok(())
25
+ }
26
+
27
+ fn collect_rust_files(dir: &Path, into: &mut Vec<PathBuf>) -> Result<(), String> {
28
+ let Ok(entries) = std::fs::read_dir(dir) else {
29
+ return Ok(());
30
+ };
31
+ let mut paths: Vec<PathBuf> = entries.filter_map(|e| e.ok().map(|e| e.path())).collect();
32
+ paths.sort();
33
+ for path in paths {
34
+ if path.is_dir() {
35
+ collect_rust_files(&path, into)?;
36
+ } else if path.extension().is_some_and(|ext| ext == "rs") {
37
+ into.push(path);
38
+ }
39
+ }
40
+ Ok(())
41
+ }
42
+
43
+ fn format_file(path: &Path) -> Result<(), String> {
44
+ let text =
45
+ std::fs::read_to_string(path).map_err(|e| format!("reading {}: {e}", path.display()))?;
46
+ let formatted = format_source(&text)?;
47
+ if formatted != text {
48
+ std::fs::write(path, formatted).map_err(|e| format!("writing {}: {e}", path.display()))?;
49
+ }
50
+ Ok(())
51
+ }
52
+
53
+ fn format_source(text: &str) -> Result<String, String> {
54
+ let mut current = text.to_string();
55
+ for _ in 0..PASSES {
56
+ let formatted = pass(&current)?;
57
+ if formatted == current {
58
+ break;
59
+ }
60
+ current = formatted;
61
+ }
62
+ Ok(current)
63
+ }
64
+
65
+ // One rustfmt run over stdin. Text rustfmt cannot parse is returned as it came, with a warning:
66
+ // the compiler will name the real fault. Any other failure of rustfmt is an error.
67
+ fn pass(text: &str) -> Result<String, String> {
68
+ let mut child = Command::new("rustfmt")
69
+ .args(["--edition", EDITION, "--emit", "stdout"])
70
+ .stdin(Stdio::piped())
71
+ .stdout(Stdio::piped())
72
+ .stderr(Stdio::piped())
73
+ .spawn()
74
+ .map_err(|e| {
75
+ format!("running rustfmt: {e} (generated Rust is formatted with it; run `rustup component add rustfmt`)")
76
+ })?;
77
+ let mut stdin = child.stdin.take().ok_or("rustfmt gave no stdin")?;
78
+ let input = text.to_string();
79
+ // Written from a thread: a large file fills the pipe before rustfmt starts reading its output.
80
+ let writer = std::thread::spawn(move || stdin.write_all(input.as_bytes()));
81
+ let output = child
82
+ .wait_with_output()
83
+ .map_err(|e| format!("waiting for rustfmt: {e}"))?;
84
+ let _ = writer.join();
85
+ if output.status.success() {
86
+ return String::from_utf8(output.stdout)
87
+ .map_err(|e| format!("rustfmt output was not UTF-8: {e}"));
88
+ }
89
+ let stderr = String::from_utf8_lossy(&output.stderr);
90
+ let first = stderr.lines().next().unwrap_or_default().to_string();
91
+ // A parse error names `<stdin>`; anything else is rustfmt itself failing, and falling back to
92
+ // the unformatted text would write the wrong bytes without a word.
93
+ if !stderr.contains("<stdin>") {
94
+ return Err(format!("rustfmt failed: {first}"));
95
+ }
96
+ eprintln!(
97
+ "hecks-build: rustfmt could not format generated Rust, leaving it as generated: {first}"
98
+ );
99
+ Ok(text.to_string())
100
+ }
@@ -16,9 +16,27 @@ struct Seam {
16
16
 
17
17
  // In `TargetIr::SEAMS` order, which is the order the keys land in `ir.json`.
18
18
  const SEAMS: &[Seam] = &[
19
- Seam { capability: "authorization", export_as: "authorization", verbs: &["grant", "assignments"], aggregates: &[("assignment_aggregate", "assignments")], marks: &[] },
20
- Seam { capability: "membership", export_as: "membership", verbs: &["admit", "grant", "people"], aggregates: &[("aggregate", "admit")], marks: &[] },
21
- Seam { capability: "identity", export_as: "identity", verbs: &["register", "link", "resolve"], aggregates: &[], marks: &[] },
19
+ Seam {
20
+ capability: "authorization",
21
+ export_as: "authorization",
22
+ verbs: &["grant", "assignments"],
23
+ aggregates: &[("assignment_aggregate", "assignments")],
24
+ marks: &[],
25
+ },
26
+ Seam {
27
+ capability: "membership",
28
+ export_as: "membership",
29
+ verbs: &["admit", "grant", "people"],
30
+ aggregates: &[("aggregate", "admit")],
31
+ marks: &[],
32
+ },
33
+ Seam {
34
+ capability: "identity",
35
+ export_as: "identity",
36
+ verbs: &["register", "link", "resolve"],
37
+ aggregates: &[],
38
+ marks: &[],
39
+ },
22
40
  Seam {
23
41
  capability: "newsletter",
24
42
  export_as: "newsletter",
@@ -30,21 +48,41 @@ const SEAMS: &[Seam] = &[
30
48
  capability: "newsletter_issues",
31
49
  export_as: "newsletter_issues",
32
50
  verbs: &["send_issue", "record_delivery"],
33
- aggregates: &[("issue_aggregate", "send_issue"), ("delivery_aggregate", "record_delivery")],
51
+ aggregates: &[
52
+ ("issue_aggregate", "send_issue"),
53
+ ("delivery_aggregate", "record_delivery"),
54
+ ],
34
55
  marks: &[],
35
56
  },
36
- Seam { capability: "payments", export_as: "payments", verbs: &["initiate", "succeeded", "failed"], aggregates: &[("aggregate", "initiate")], marks: &["holds_seat"] },
57
+ Seam {
58
+ capability: "payments",
59
+ export_as: "payments",
60
+ verbs: &["initiate", "succeeded", "failed"],
61
+ aggregates: &[("aggregate", "initiate")],
62
+ marks: &["holds_seat"],
63
+ },
37
64
  Seam {
38
65
  capability: "registrations",
39
66
  export_as: "registrations",
40
67
  verbs: &["schedule", "request"],
41
- aggregates: &[("event_aggregate", "schedule"), ("registration_aggregate", "request")],
68
+ aggregates: &[
69
+ ("event_aggregate", "schedule"),
70
+ ("registration_aggregate", "request"),
71
+ ],
42
72
  marks: &["frees_seat"],
43
73
  },
44
74
  Seam {
45
75
  capability: "payment_connection",
46
76
  export_as: "payment_connection",
47
- verbs: &["connect", "reconnect", "disconnect", "suspend", "resume", "enable", "disable"],
77
+ verbs: &[
78
+ "connect",
79
+ "reconnect",
80
+ "disconnect",
81
+ "suspend",
82
+ "resume",
83
+ "enable",
84
+ "disable",
85
+ ],
48
86
  aggregates: &[("aggregate", "connect")],
49
87
  marks: &[],
50
88
  },
@@ -60,7 +98,12 @@ pub fn run(target: &mut Json, members: &[Json]) -> Result<(), String> {
60
98
  let mut membership = false;
61
99
  let mut identity = false;
62
100
  for seam in SEAMS {
63
- let Some(provider) = chapters.iter().find(|chapter| provision(chapter, seam.capability).is_some()) else { continue };
101
+ let Some(provider) = chapters
102
+ .iter()
103
+ .find(|chapter| provision(chapter, seam.capability).is_some())
104
+ else {
105
+ continue;
106
+ };
64
107
  let exported = export(seam, provider);
65
108
  membership |= seam.capability == "membership";
66
109
  identity |= seam.capability == "identity";
@@ -79,7 +122,12 @@ pub fn run(target: &mut Json, members: &[Json]) -> Result<(), String> {
79
122
  fn export(seam: &Seam, provider: &Json) -> Json {
80
123
  let name = name_of(provider);
81
124
  let entries = provision(provider, seam.capability).unwrap_or_default();
82
- let verb = |key: &str| entries.iter().find(|(k, _)| k == key).map(|(_, v)| format!("{name}::{v}"));
125
+ let verb = |key: &str| {
126
+ entries
127
+ .iter()
128
+ .find(|(k, _)| k == key)
129
+ .map(|(_, v)| format!("{name}::{v}"))
130
+ };
83
131
 
84
132
  let mut out = vec![("provider".to_string(), Json::String(name.clone()))];
85
133
  for key in seam.verbs {
@@ -87,10 +135,19 @@ fn export(seam: &Seam, provider: &Json) -> Json {
87
135
  }
88
136
  for (exported, key) in seam.aggregates {
89
137
  let aggregate = verb(key).map(|v| v.split('.').next().unwrap_or_default().to_string());
90
- out.push((exported.to_string(), aggregate.map_or(Json::Null, Json::String)));
138
+ out.push((
139
+ exported.to_string(),
140
+ aggregate.map_or(Json::Null, Json::String),
141
+ ));
91
142
  }
92
143
  for key in seam.marks {
93
- if let Some(states) = marked_states(provider, entries.iter().find(|(k, _)| k == key).map(|(_, v)| v.as_str())) {
144
+ if let Some(states) = marked_states(
145
+ provider,
146
+ entries
147
+ .iter()
148
+ .find(|(k, _)| k == key)
149
+ .map(|(_, v)| v.as_str()),
150
+ ) {
94
151
  out.push((key.to_string(), states));
95
152
  }
96
153
  }
@@ -101,11 +158,15 @@ fn export(seam: &Seam, provider: &Json) -> Json {
101
158
  fn provision(chapter: &Json, capability: &str) -> Option<Vec<(String, String)>> {
102
159
  let entries: Vec<(String, String)> = chapter
103
160
  .get("provides")
104
- .map(Json::each)
105
- .unwrap_or(&[])
161
+ .map_or(&[][..], Json::each)
106
162
  .iter()
107
163
  .filter(|entry| entry.get("capability").and_then(Json::as_str) == Some(capability))
108
- .filter_map(|entry| Some((entry.get("key")?.as_str()?.to_string(), entry.get("verb")?.as_str()?.to_string())))
164
+ .filter_map(|entry| {
165
+ Some((
166
+ entry.get("key")?.as_str()?.to_string(),
167
+ entry.get("verb")?.as_str()?.to_string(),
168
+ ))
169
+ })
109
170
  .collect();
110
171
  (!entries.is_empty()).then_some(entries)
111
172
  }
@@ -114,10 +175,18 @@ fn provision(chapter: &Json, capability: &str) -> Option<Vec<(String, String)>>
114
175
  // entry is not declared or the mark is.
115
176
  fn marked_states(provider: &Json, entry: Option<&str>) -> Option<Json> {
116
177
  let (aggregate_name, mark) = entry?.split_once('.')?;
117
- let aggregate = provider.get("aggregates").map(Json::each)?.iter().find(|a| a.get("name").and_then(Json::as_str) == Some(aggregate_name))?;
178
+ let aggregate = provider
179
+ .get("aggregates")
180
+ .map(Json::each)?
181
+ .iter()
182
+ .find(|a| a.get("name").and_then(Json::as_str) == Some(aggregate_name))?;
118
183
  aggregate.get("lifecycle")?.get("marks")?.get(mark).cloned()
119
184
  }
120
185
 
121
186
  fn name_of(chapter: &Json) -> String {
122
- chapter.get("name").and_then(Json::as_str).unwrap_or_default().to_string()
187
+ chapter
188
+ .get("name")
189
+ .and_then(Json::as_str)
190
+ .unwrap_or_default()
191
+ .to_string()
123
192
  }
@@ -5,7 +5,10 @@ use std::path::Path;
5
5
  use std::process::{Command, Stdio};
6
6
 
7
7
  pub fn run_capture(program: &str, args: &[&str]) -> Result<String, String> {
8
- let output = Command::new(program).args(args).output().map_err(|e| format!("running {program} {}: {e}", args.join(" ")))?;
8
+ let output = Command::new(program)
9
+ .args(args)
10
+ .output()
11
+ .map_err(|e| format!("running {program} {}: {e}", args.join(" ")))?;
9
12
  if !output.status.success() {
10
13
  return Err(format!(
11
14
  "{program} {} failed:\n{}",
@@ -13,11 +16,19 @@ pub fn run_capture(program: &str, args: &[&str]) -> Result<String, String> {
13
16
  String::from_utf8_lossy(&output.stderr)
14
17
  ));
15
18
  }
16
- String::from_utf8(output.stdout).map_err(|e| format!("{program} {}: stdout was not valid UTF-8: {e}", args.join(" ")))
19
+ String::from_utf8(output.stdout).map_err(|e| {
20
+ format!(
21
+ "{program} {}: stdout was not valid UTF-8: {e}",
22
+ args.join(" ")
23
+ )
24
+ })
17
25
  }
18
26
 
19
27
  pub fn run(program: &str, args: &[&str]) -> Result<(), String> {
20
- let status = Command::new(program).args(args).status().map_err(|e| format!("running {program} {}: {e}", args.join(" ")))?;
28
+ let status = Command::new(program)
29
+ .args(args)
30
+ .status()
31
+ .map_err(|e| format!("running {program} {}: {e}", args.join(" ")))?;
21
32
  if !status.success() {
22
33
  return Err(format!("{program} {} failed", args.join(" ")));
23
34
  }
@@ -29,9 +40,19 @@ pub fn run_in(dir: &Path, program: &str, args: &[&str]) -> Result<(), String> {
29
40
  .args(args)
30
41
  .current_dir(dir)
31
42
  .status()
32
- .map_err(|e| format!("running {program} {} in {}: {e}", args.join(" "), dir.display()))?;
43
+ .map_err(|e| {
44
+ format!(
45
+ "running {program} {} in {}: {e}",
46
+ args.join(" "),
47
+ dir.display()
48
+ )
49
+ })?;
33
50
  if !status.success() {
34
- return Err(format!("{program} {} failed in {}", args.join(" "), dir.display()));
51
+ return Err(format!(
52
+ "{program} {} failed in {}",
53
+ args.join(" "),
54
+ dir.display()
55
+ ));
35
56
  }
36
57
  Ok(())
37
58
  }
@@ -45,7 +66,10 @@ pub fn cargo_build_quiet(dir: &Path) -> Result<(), String> {
45
66
  .status()
46
67
  .map_err(|e| format!("running cargo build in {}: {e}", dir.display()))?;
47
68
  if !status.success() {
48
- return Err(format!("cargo build failed in {} — run it there directly to see why", dir.display()));
69
+ return Err(format!(
70
+ "cargo build failed in {} — run it there directly to see why",
71
+ dir.display()
72
+ ));
49
73
  }
50
74
  Ok(())
51
75
  }
@@ -13,9 +13,12 @@ impl TempDir {
13
13
  pub fn new(prefix: &str) -> Result<Self, String> {
14
14
  let base = std::env::temp_dir();
15
15
  let pid = std::process::id();
16
- let nanos = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_nanos()).unwrap_or(0);
16
+ let nanos = std::time::SystemTime::now()
17
+ .duration_since(std::time::UNIX_EPOCH)
18
+ .map_or(0, |d| d.as_nanos());
17
19
  let path = base.join(format!("{prefix}-{pid}-{nanos}"));
18
- std::fs::create_dir_all(&path).map_err(|e| format!("creating temp dir {}: {e}", path.display()))?;
20
+ std::fs::create_dir_all(&path)
21
+ .map_err(|e| format!("creating temp dir {}: {e}", path.display()))?;
19
22
  Ok(Self { path })
20
23
  }
21
24