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
@@ -93,7 +93,6 @@ fn ends_outside_quotes(text: &str) -> bool {
93
93
  }
94
94
  if ch == '"' {
95
95
  quoting = !quoting;
96
- continue;
97
96
  }
98
97
  }
99
98
  !quoting
@@ -163,12 +162,8 @@ fn strip_comment(line: &str) -> &str {
163
162
  #[derive(Debug, Clone, PartialEq, Eq)]
164
163
  pub enum Opener {
165
164
  None,
166
- DoBlock {
167
- params: Option<String>,
168
- },
169
- BraceBlock {
170
- body: String,
171
- },
165
+ DoBlock { params: Option<String> },
166
+ BraceBlock { body: String },
172
167
  }
173
168
 
174
169
  #[derive(Debug, Clone, PartialEq, Eq)]
@@ -194,7 +189,7 @@ const FORBIDDEN_LEADING_WORDS: &[&str] = &[
194
189
  /// Only a file's top line is receiver-qualified; a nested `Hecks.x` must stay an error.
195
190
  const FILE_RECEIVER_PREFIX: &str = "Hecks.";
196
191
 
197
- pub fn classify<'a>(file: &str, line: &SourceLine<'a>) -> Result<LineShape, Diagnostic> {
192
+ pub fn classify(file: &str, line: &SourceLine<'_>) -> Result<LineShape, Diagnostic> {
198
193
  let text = line
199
194
  .text
200
195
  .strip_prefix(FILE_RECEIVER_PREFIX)
@@ -232,9 +227,8 @@ pub fn classify<'a>(file: &str, line: &SourceLine<'a>) -> Result<LineShape, Diag
232
227
  file,
233
228
  line.number,
234
229
  format!(
235
- "'{}' looks like a local variable assignment at byte {top_level_eq} — this \
236
- parser never interprets bare Ruby expressions or assignment",
237
- text
230
+ "'{text}' looks like a local variable assignment at byte {top_level_eq} — this \
231
+ parser never interprets bare Ruby expressions or assignment"
238
232
  ),
239
233
  ));
240
234
  }
@@ -256,9 +250,8 @@ fn leading_identifier_end(text: &str) -> usize {
256
250
  if ch.is_ascii_alphabetic() || ch == '_' {
257
251
  end = idx + ch.len_utf8();
258
252
  continue;
259
- } else {
260
- return 0;
261
253
  }
254
+ return 0;
262
255
  }
263
256
  if ch.is_ascii_alphanumeric() || ch == '_' {
264
257
  end = idx + ch.len_utf8();
@@ -346,8 +339,7 @@ fn trailing_do(rest: &str) -> Option<(&str, Option<String>)> {
346
339
  if let Some(before) = trimmed.strip_suffix(" do") {
347
340
  return Some((before, None));
348
341
  }
349
- if trimmed.ends_with('|') {
350
- let without_trailing_pipe = &trimmed[..trimmed.len() - 1];
342
+ if let Some(without_trailing_pipe) = trimmed.strip_suffix('|') {
351
343
  if let Some(open_pipe) = without_trailing_pipe.rfind('|') {
352
344
  let params = &without_trailing_pipe[open_pipe + 1..];
353
345
  let before_pipe = trimmed[..open_pipe].trim_end();
@@ -400,10 +392,7 @@ fn find_top_level_brace(text: &str) -> Option<usize> {
400
392
 
401
393
  /// A `{` after `:`, `,` or `>` opens a hash literal; `>` covers a parenless `:k => { ... }`.
402
394
  fn is_hash_literal_brace(before: &str) -> bool {
403
- matches!(
404
- before.trim_end().chars().last(),
405
- Some(':') | Some(',') | Some('>')
406
- )
395
+ matches!(before.trim_end().chars().last(), Some(':' | ',' | '>'))
407
396
  }
408
397
 
409
398
  fn matching_brace_body(text: &str) -> Option<(String, &str)> {
@@ -488,7 +477,7 @@ pub fn capture_do_block_body<'a>(
488
477
 
489
478
  loop {
490
479
  let line = *lines.get(*pos).ok_or_else(|| {
491
- let last = lines.last().map(|l| l.number).unwrap_or(0);
480
+ let last = lines.last().map_or(0, |l| l.number);
492
481
  Diagnostic::new(
493
482
  file,
494
483
  last,
@@ -566,10 +555,7 @@ mod tests {
566
555
 
567
556
  #[test]
568
557
  fn does_not_strip_a_bare_lowercase_call() {
569
- assert_eq!(
570
- strip_aggregate_receiver("attaches \"Governance\""),
571
- None
572
- );
558
+ assert_eq!(strip_aggregate_receiver("attaches \"Governance\""), None);
573
559
  assert_eq!(strip_aggregate_receiver("subscribe \"Deposited\""), None);
574
560
  }
575
561
 
@@ -615,7 +601,7 @@ mod tests {
615
601
  shape,
616
602
  LineShape::Call(Call {
617
603
  word: "identified_by".to_string(),
618
- args: "".to_string(),
604
+ args: String::new(),
619
605
  opener: Opener::BraceBlock {
620
606
  body: "name.value".to_string()
621
607
  }
@@ -0,0 +1 @@
1
+
@@ -6,7 +6,14 @@ mod canonical;
6
6
  mod diag;
7
7
  mod emit;
8
8
  mod expr;
9
+ // The one `Hecks::Naming` port, compiled from `hecks-codegen`'s source: neither crate depends on
10
+ // the other. This crate reads only part of it.
11
+ #[allow(dead_code)]
12
+ #[path = "../../codegen/src/hecks_naming.rs"]
13
+ mod hecks_naming;
9
14
  mod ir;
15
+ // Generated table; not every column is consumed by this binary.
16
+ #[allow(dead_code)]
10
17
  mod keywords;
11
18
  mod lex;
12
19
  mod parse;
@@ -37,7 +44,7 @@ enum RunError {
37
44
 
38
45
  impl From<diag::Diagnostic> for RunError {
39
46
  fn from(d: diag::Diagnostic) -> Self {
40
- RunError::Parse(d)
47
+ Self::Parse(d)
41
48
  }
42
49
  }
43
50
 
@@ -45,12 +52,17 @@ fn run(args: &[String]) -> Result<(), RunError> {
45
52
  match args.first().map(String::as_str) {
46
53
  Some("chapter") => run_chapter(&args[1..]),
47
54
  Some("resolve") => run_resolve(&args[1..]),
48
- Some("coverage") => run_coverage(&args[1..]),
55
+ Some("adapter") => run_adapter(&args[1..]),
56
+ Some("world") => run_world(&args[1..]),
57
+ Some("coverage") => {
58
+ run_coverage();
59
+ Ok(())
60
+ }
49
61
  Some(other) => Err(RunError::Usage(format!(
50
- "unknown subcommand '{other}' — expected 'chapter', 'resolve', or 'coverage'"
62
+ "unknown subcommand '{other}' — expected 'chapter', 'resolve', 'adapter', 'world', or 'coverage'"
51
63
  ))),
52
64
  None => Err(RunError::Usage(
53
- "hecks-parse <chapter|resolve|coverage> ...".to_string(),
65
+ "hecks-parse <chapter|resolve|adapter|world|coverage> ...".to_string(),
54
66
  )),
55
67
  }
56
68
  }
@@ -143,11 +155,59 @@ fn run_resolve(args: &[String]) -> Result<(), RunError> {
143
155
  Ok(())
144
156
  }
145
157
 
158
+ /// `hecks-parse adapter <file.adapter>...`: prints the declared adapters as a JSON array, in
159
+ /// file order then declaration order.
160
+ fn run_adapter(args: &[String]) -> Result<(), RunError> {
161
+ if args.is_empty() {
162
+ return Err(RunError::Usage(
163
+ "hecks-parse adapter <file.adapter>...".to_string(),
164
+ ));
165
+ }
166
+ let mut adapters = Vec::new();
167
+ for path in args {
168
+ let source = fs::read_to_string(path)
169
+ .map_err(|e| RunError::Usage(format!("could not read '{path}': {e}")))?;
170
+ adapters.extend(parse::adapter::parse_file(path, &source)?);
171
+ }
172
+ let value = emit::JsonValue::Array(adapters.iter().map(emit::adapter_json).collect());
173
+ println!("{}", emit::write(&value));
174
+ Ok(())
175
+ }
176
+
177
+ /// `hecks-parse world <file.world>...`: prints one world per distinct domain, in order of first
178
+ /// appearance across the files, later blocks of a domain merged into the first.
179
+ fn run_world(args: &[String]) -> Result<(), RunError> {
180
+ if args.is_empty() {
181
+ return Err(RunError::Usage(
182
+ "hecks-parse world <file.world>...".to_string(),
183
+ ));
184
+ }
185
+ let mut blocks = Vec::new();
186
+ for path in args {
187
+ let source = fs::read_to_string(path)
188
+ .map_err(|e| RunError::Usage(format!("could not read '{path}': {e}")))?;
189
+ blocks.extend(parse::world::parse_file(path, &source)?);
190
+ }
191
+ let worlds = parse::world::merge(blocks);
192
+ let value = emit::JsonValue::Array(worlds.iter().map(emit::world_json).collect());
193
+ println!("{}", emit::write(&value));
194
+ Ok(())
195
+ }
196
+
146
197
  /// `(word, context)` pairs this parser builds real IR for, printed by `hecks-parse coverage`.
147
198
  /// A word that is only gated, never built into IR, must stay off this list.
148
199
  const COVERED_PAIRS: &[(&str, &str)] = &[
149
200
  ("bluebook", "File"),
150
201
  ("hecksagon", "File"),
202
+ ("adapter", "File"),
203
+ ("port", "Adapter"),
204
+ ("field", "Adapter"),
205
+ ("secret", "Adapter"),
206
+ ("world", "File"),
207
+ ("realm", "World"),
208
+ ("latest", "World"),
209
+ ("default_database", "World"),
210
+ ("default_adapter", "World"),
151
211
  ("vision", "Bluebook"),
152
212
  ("core", "Bluebook"),
153
213
  ("supporting", "Bluebook"),
@@ -248,11 +308,10 @@ const COVERED_PAIRS: &[(&str, &str)] = &[
248
308
  ("one_of", "Type"),
249
309
  ];
250
310
 
251
- fn run_coverage(_args: &[String]) -> Result<(), RunError> {
311
+ fn run_coverage() {
252
312
  let pairs: Vec<String> = COVERED_PAIRS
253
313
  .iter()
254
314
  .map(|(word, context)| format!("[\"{word}\", \"{context}\"]"))
255
315
  .collect();
256
316
  println!("[{}]", pairs.join(", "));
257
- Ok(())
258
317
  }
@@ -0,0 +1,157 @@
1
+ //! The `Adapter` construct: a `.adapter` file's `Hecks.adapter "Name" do ... end` blocks, each
2
+ //! declaring a `port`, `field`s (optionally defaulted) and `secret`s. Printed by
3
+ //! `hecks-parse adapter`; adapters never enter `ir.json`.
4
+
5
+ use crate::diag::{Diagnostic, ParseResult};
6
+ use crate::ir;
7
+ use crate::lex::{self, SourceLine};
8
+ use crate::ruby_value::{self, Value};
9
+
10
+ /// Parses every top-level `adapter` block of one `.adapter` file, in file order.
11
+ ///
12
+ /// A top-level line that is exactly `require "path"` is plain Ruby, not grammar, and is skipped;
13
+ /// any other top-level word goes through the normal gates and refuses there.
14
+ pub fn parse_file(path: &str, source: &str) -> ParseResult<Vec<ir::Adapter>> {
15
+ let joined = lex::join_continuations(source);
16
+ let lines = lex::lines(&joined);
17
+ let mut pos = 0usize;
18
+ let mut adapters = Vec::new();
19
+
20
+ while pos < lines.len() {
21
+ if is_require_line(lines[pos].text) {
22
+ pos += 1;
23
+ continue;
24
+ }
25
+ let (row, call, header_line) = super::file::parse_header(path, &lines, &mut pos)?;
26
+ if row.inner != "Adapter" {
27
+ return Err(Diagnostic::new(
28
+ path,
29
+ header_line,
30
+ format!(
31
+ "a .adapter file's own top-level blocks must all be 'adapter'; got '{}'",
32
+ call.word
33
+ ),
34
+ ));
35
+ }
36
+ let name = super::file::header_name(&call)
37
+ .ok_or_else(|| Diagnostic::new(path, header_line, "'adapter' needs a quoted name"))?;
38
+ adapters.push(parse_body(path, &lines, &mut pos, name)?);
39
+ }
40
+ Ok(adapters)
41
+ }
42
+
43
+ /// `require` followed by exactly one double-quoted string, nothing else.
44
+ fn is_require_line(text: &str) -> bool {
45
+ let Some(rest) = text.strip_prefix("require ") else {
46
+ return false;
47
+ };
48
+ let rest = rest.trim();
49
+ rest.len() >= 2
50
+ && rest.starts_with('"')
51
+ && rest.ends_with('"')
52
+ && !rest[1..rest.len() - 1].contains(['"', '\\'])
53
+ }
54
+
55
+ fn parse_body(
56
+ file: &str,
57
+ lines: &[SourceLine],
58
+ pos: &mut usize,
59
+ name: String,
60
+ ) -> ParseResult<ir::Adapter> {
61
+ let mut adapter = ir::Adapter {
62
+ name,
63
+ ..ir::Adapter::default()
64
+ };
65
+ while let Some(gated) = super::next_line(file, lines, pos, "Adapter")? {
66
+ let number = gated.line.number;
67
+ match gated.row.word {
68
+ // A repeat replaces the earlier value, as the Ruby builder's setter does.
69
+ "port" => {
70
+ adapter.port = Some(super::positional_text(
71
+ file,
72
+ number,
73
+ "port",
74
+ &gated.args,
75
+ 1,
76
+ )?);
77
+ }
78
+ "field" => {
79
+ let field = super::positional_symbol(file, number, "field", &gated.args, 1)?;
80
+ if let Some(raw) = super::named_raw(&gated.args, "default") {
81
+ let value = default_value(file, number, raw)?;
82
+ // A repeated key keeps its first position with the last value.
83
+ match adapter.defaults.iter_mut().find(|(k, _)| *k == field) {
84
+ Some(slot) => slot.1 = value,
85
+ None => adapter.defaults.push((field.clone(), value)),
86
+ }
87
+ }
88
+ adapter.fields.push(field);
89
+ }
90
+ "secret" => {
91
+ adapter.secrets.push(super::positional_symbol(
92
+ file,
93
+ number,
94
+ "secret",
95
+ &gated.args,
96
+ 1,
97
+ )?);
98
+ }
99
+ _ => {
100
+ return Err(super::not_built_yet(
101
+ "Adapter",
102
+ gated.row,
103
+ file,
104
+ number,
105
+ &gated.call.word,
106
+ ))
107
+ }
108
+ }
109
+ }
110
+ Ok(adapter)
111
+ }
112
+
113
+ /// A JSON-native default: string, integer, boolean or nil. Anything else is refused rather than
114
+ /// guessed at, and a string may use braces only for the `{name}` and `{storage_name}`
115
+ /// placeholders (Ruby's `^(?:[^{}]|\{(?:name|storage_name)\})*$`).
116
+ fn default_value(file: &str, line: usize, raw: &str) -> ParseResult<Value> {
117
+ let value = ruby_value::read(raw);
118
+ match &value {
119
+ Value::Nil | Value::Bool(_) | Value::Int(_) => {}
120
+ Value::Str(text) if braces_are_placeholders(text) => {}
121
+ Value::Str(text) => {
122
+ return Err(Diagnostic::new(
123
+ file,
124
+ line,
125
+ format!(
126
+ "'field' default \"{text}\" uses braces; only {{name}} and {{storage_name}} are allowed"
127
+ ),
128
+ ))
129
+ }
130
+ _ => {
131
+ return Err(Diagnostic::new(
132
+ file,
133
+ line,
134
+ format!(
135
+ "'field' default ({}) must be a string, integer, true, false or nil",
136
+ raw.trim()
137
+ ),
138
+ ))
139
+ }
140
+ }
141
+ Ok(value)
142
+ }
143
+
144
+ fn braces_are_placeholders(text: &str) -> bool {
145
+ let mut rest = text;
146
+ while let Some(at) = rest.find(['{', '}']) {
147
+ let tail = &rest[at..];
148
+ let Some(after) = ["{name}", "{storage_name}"]
149
+ .iter()
150
+ .find_map(|p| tail.strip_prefix(p))
151
+ else {
152
+ return false;
153
+ };
154
+ rest = after;
155
+ }
156
+ true
157
+ }
@@ -107,6 +107,16 @@ pub struct PendingChapterGiven {
107
107
  pub file: String,
108
108
  pub line: usize,
109
109
  }
110
+ /// An aggregate with its policies and the givens left for the chapter to resolve.
111
+ pub type ParsedAggregate = (
112
+ ir::Aggregate,
113
+ Vec<ir::Policy>,
114
+ Vec<PendingChapterGiven>,
115
+ Vec<(usize, command::PendingCommandGiven)>,
116
+ Vec<entity::PendingChapterEntityGiven>,
117
+ Vec<entity::PendingEntityCommandGiven>,
118
+ );
119
+
110
120
  /// Type-form `identified_by` resolves after the loop: its value object may be declared later.
111
121
  pub fn parse_body(
112
122
  file: &str,
@@ -116,14 +126,7 @@ pub fn parse_body(
116
126
  chapter_named_givens: &mut Vec<(String, ir::Given)>,
117
127
  // Chapter-wide entity-scoped pool, passed unchanged to every top-level entity.
118
128
  chapter_entity_named_givens: &mut Vec<(String, ir::Given)>,
119
- ) -> ParseResult<(
120
- ir::Aggregate,
121
- Vec<ir::Policy>,
122
- Vec<PendingChapterGiven>,
123
- Vec<(usize, command::PendingCommandGiven)>,
124
- Vec<entity::PendingChapterEntityGiven>,
125
- Vec<entity::PendingEntityCommandGiven>,
126
- )> {
129
+ ) -> ParseResult<ParsedAggregate> {
127
130
  let mut aggregate = ir::Aggregate {
128
131
  name: name.to_string(),
129
132
  ..Default::default()
@@ -189,7 +192,7 @@ pub fn parse_body(
189
192
  "description",
190
193
  &gated.args,
191
194
  1,
192
- )?)
195
+ )?);
193
196
  }
194
197
  // Origin, not identity: captured raw, like a literal Hash default.
195
198
  "provenance" => {
@@ -600,7 +603,13 @@ pub fn parse_body(
600
603
  }
601
604
  }
602
605
 
603
- lifecycle::seal_commands(file, *pos, &aggregate.name, aggregate.lifecycle.as_ref(), &aggregate.commands)?;
606
+ lifecycle::seal_commands(
607
+ file,
608
+ *pos,
609
+ &aggregate.name,
610
+ aggregate.lifecycle.as_ref(),
611
+ &aggregate.commands,
612
+ )?;
604
613
 
605
614
  Ok((
606
615
  aggregate,
@@ -58,15 +58,11 @@ pub fn parse_chapter(chapter_name: &str, files: &[(String, String)]) -> ParseRes
58
58
  match row.inner {
59
59
  "Bluebook" => {
60
60
  let declared_version = super::file::header_version(&call);
61
- if bluebook.is_none() {
62
- let mut built = ir::Bluebook {
63
- name: chapter_name.to_string(),
64
- ..Default::default()
65
- };
66
- built.version = declared_version.clone();
67
- bluebook = Some(built);
68
- }
69
- let target = bluebook.as_mut().expect("just set above");
61
+ let target = bluebook.get_or_insert_with(|| ir::Bluebook {
62
+ name: chapter_name.to_string(),
63
+ version: declared_version.clone(),
64
+ ..Default::default()
65
+ });
70
66
  if let Some(version) = declared_version {
71
67
  match target.version.as_deref() {
72
68
  Some(existing) if existing != version => {
@@ -373,6 +369,7 @@ pub fn resolve_hecksagon_dependencies(
373
369
 
374
370
  /// Parses a `Hecks.bluebook "Name" do ... end` body into an existing accumulator.
375
371
  /// Policies bubble as in `BluebookBuilder#build`: aggregate policies first, then chapter-level.
372
+ #[allow(clippy::too_many_arguments)] // established signature
376
373
  fn parse_body_into(
377
374
  file: &str,
378
375
  lines: &[SourceLine],
@@ -389,10 +386,7 @@ fn parse_body_into(
389
386
  ) -> ParseResult<()> {
390
387
  // The given pool spans the whole chapter, not one file: a later concept file may reference
391
388
  // a named given an earlier one declared. Entries are (owner aggregate name, `Given`).
392
- loop {
393
- let Some(gated) = super::next_line(file, lines, pos, "Bluebook")? else {
394
- break;
395
- };
389
+ while let Some(gated) = super::next_line(file, lines, pos, "Bluebook")? {
396
390
  let line = gated.line.number;
397
391
 
398
392
  match gated.row.word {
@@ -403,11 +397,16 @@ fn parse_body_into(
403
397
  "vision",
404
398
  &gated.args,
405
399
  1,
406
- )?)
400
+ )?);
407
401
  }
408
402
  "namespace" => {
409
- bluebook.namespace =
410
- Some(super::positional_text(file, line, "namespace", &gated.args, 1)?)
403
+ bluebook.namespace = Some(super::positional_text(
404
+ file,
405
+ line,
406
+ "namespace",
407
+ &gated.args,
408
+ 1,
409
+ )?);
411
410
  }
412
411
  "formerly_known_as" => {
413
412
  bluebook.formerly_known_as = Some(super::positional_text(
@@ -416,7 +415,7 @@ fn parse_body_into(
416
415
  "formerly_known_as",
417
416
  &gated.args,
418
417
  1,
419
- )?)
418
+ )?);
420
419
  }
421
420
  // One `Provision` row per named argument, in source order; required keys are
422
421
  // checked by Ruby's `Validation#validate_provisions!`.
@@ -113,6 +113,7 @@ pub struct PendingCommandGiven {
113
113
  /// Parses a `command "Name" do ... end` body.
114
114
  /// `owner` tells a self `reference_to` from a cross-reference. `preconditions` and the `owner_*`
115
115
  /// slices hold what the owner declared so far, so declaration order matters.
116
+ #[allow(clippy::too_many_arguments)] // established signature
116
117
  pub fn parse_body(
117
118
  file: &str,
118
119
  lines: &[SourceLine],
@@ -164,7 +165,13 @@ pub fn parse_body(
164
165
  owner_entities,
165
166
  );
166
167
  refuse_duplicate_targets(file, *pos, &command)?;
167
- super::needs::refuse_undeclared(file, *pos, &command.name, &command.needs, &command.attributes)?;
168
+ super::needs::refuse_undeclared(
169
+ file,
170
+ *pos,
171
+ &command.name,
172
+ &command.needs,
173
+ &command.attributes,
174
+ )?;
168
175
  return Ok((command, pending));
169
176
  };
170
177
  let line = gated.line.number;
@@ -177,7 +184,7 @@ pub fn parse_body(
177
184
  "role",
178
185
  &gated.args,
179
186
  1,
180
- )?)
187
+ )?);
181
188
  }
182
189
  "goal" => {
183
190
  command.goal = Some(super::positional_literal_text(
@@ -186,7 +193,7 @@ pub fn parse_body(
186
193
  "goal",
187
194
  &gated.args,
188
195
  1,
189
- )?)
196
+ )?);
190
197
  }
191
198
  // A synthesized inline `one_of(...)` closed set is dropped; `CommandBuilder#build`
192
199
  // ignores it.
@@ -218,7 +225,9 @@ pub fn parse_body(
218
225
  canonical: canonical::apply(&raw),
219
226
  });
220
227
  }
221
- "needs" => super::needs::declare(file, line, &command.name, &gated.args, &mut command.needs)?,
228
+ "needs" => {
229
+ super::needs::declare(file, line, &command.name, &gated.args, &mut command.needs)?;
230
+ }
222
231
  "provenance" => {
223
232
  let raw = super::named_raw(&gated.args, "from")
224
233
  .ok_or_else(|| Diagnostic::new(file, line, "'provenance' requires a from:"))?;
@@ -283,7 +292,7 @@ fn resolve_implicit_attributes(
283
292
  for job in jobs {
284
293
  match job {
285
294
  Job::BareSet(target) => {
286
- resolve_bare_set(&mut command.attributes, &target, owner_attributes)
295
+ resolve_bare_set(&mut command.attributes, &target, owner_attributes);
287
296
  }
288
297
  Job::Append { target, fields } => resolve_append_fields(
289
298
  &mut command.attributes,
@@ -544,7 +553,11 @@ fn build_mutation(
544
553
  }
545
554
  // `state(:field)` is the record's own field, neither argument nor literal; classified
546
555
  // before the Symbol/literal split.
547
- let computed = if op == "set" { crate::expr::resolver::arithmetic_text(raw.trim()) } else { None };
556
+ let computed = if op == "set" {
557
+ crate::expr::resolver::arithmetic_text(raw.trim())
558
+ } else {
559
+ None
560
+ };
548
561
  let source = match state_ref(raw.trim()) {
549
562
  Some(name) => ir::MutationSource::State(name),
550
563
  None if computed.is_some() => ir::MutationSource::Expression(computed.unwrap_or_default()),
@@ -581,13 +594,12 @@ fn build_delegation(
581
594
  ));
582
595
  }
583
596
 
584
- let fields = match super::named_raw(args, "with") {
585
- Some(raw) => parse_hash_literal_pairs(raw)
597
+ let fields = super::named_raw(args, "with").map_or_else(Vec::new, |raw| {
598
+ parse_hash_literal_pairs(raw)
586
599
  .into_iter()
587
600
  .map(|(k, v)| (k, ruby_value::render(&ruby_value::read(&v))))
588
- .collect(),
589
- None => Vec::new(),
590
- };
601
+ .collect()
602
+ });
591
603
  Ok(ir::Mutation::Delegate { target, fields })
592
604
  }
593
605
 
@@ -613,15 +625,22 @@ fn build_correction(
613
625
  ));
614
626
  }
615
627
 
616
- let as_field = match super::named_symbol(args, "as") {
617
- Some(name) => ruby_value::render(&ruby_value::Value::Str(name)),
618
- None => "nil".to_string(),
628
+ let as_field = super::named_symbol(args, "as").map_or_else(
629
+ || "nil".to_string(),
630
+ |name| ruby_value::render(&ruby_value::Value::Str(name)),
631
+ );
632
+ let reverses = if super::named_flag(args, "reverses") {
633
+ "true"
634
+ } else {
635
+ "false"
619
636
  };
620
- let reverses = if super::named_flag(args, "reverses") { "true" } else { "false" };
621
637
 
622
638
  let fields = vec![
623
639
  ("as".to_string(), as_field),
624
- ("reason".to_string(), ruby_value::render(&ruby_value::Value::Str(reason))),
640
+ (
641
+ "reason".to_string(),
642
+ ruby_value::render(&ruby_value::Value::Str(reason)),
643
+ ),
625
644
  ("reverses".to_string(), reverses.to_string()),
626
645
  ];
627
646
  Ok(ir::Mutation::Correction { target, fields })
@@ -68,7 +68,7 @@ fn parse_operation_body(
68
68
 
69
69
  loop {
70
70
  let Some(gated) = super::next_line(file, lines, pos, "PortOperation")? else {
71
- validate_operation(file, pos_line(lines, pos), &operation)?;
71
+ validate_operation(file, pos_line(lines, *pos), &operation)?;
72
72
  return Ok(operation);
73
73
  };
74
74
  let line = gated.line.number;
@@ -82,7 +82,7 @@ fn parse_operation_body(
82
82
  "emits" => {
83
83
  operation
84
84
  .emits
85
- .push(super::positional_text(file, line, "emits", &gated.args, 1)?)
85
+ .push(super::positional_text(file, line, "emits", &gated.args, 1)?);
86
86
  }
87
87
  "reference_to" => {
88
88
  let target_raw =
@@ -109,11 +109,8 @@ fn parse_operation_body(
109
109
  }
110
110
 
111
111
  /// The last consumed line, for diagnostics; 0 when none.
112
- fn pos_line(lines: &[SourceLine], pos: &usize) -> usize {
113
- lines
114
- .get(pos.saturating_sub(1))
115
- .map(|l| l.number)
116
- .unwrap_or(0)
112
+ fn pos_line(lines: &[SourceLine], pos: usize) -> usize {
113
+ lines.get(pos.saturating_sub(1)).map_or(0, |l| l.number)
117
114
  }
118
115
 
119
116
  /// An inbound operation must emit something. The receiver comes from the routing envelope,