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
@@ -19,20 +19,20 @@ pub enum Value {
19
19
  /// above. Produced by an array literal or `.split`; never used for
20
20
  /// `.include?`'s haystack (`expr_emitter.rb`'s `emit_include` rewrites
21
21
  /// that shape at codegen time instead).
22
- Array(Vec<Value>),
22
+ Array(Vec<Self>),
23
23
  }
24
24
 
25
25
  impl Value {
26
26
  /// Ruby's `Evaluator.truthy?`: false only for `Nil`/`Bool(false)`.
27
- pub fn truthy(&self) -> bool {
28
- !matches!(self, Value::Nil | Value::Bool(false))
27
+ pub const fn truthy(&self) -> bool {
28
+ !matches!(self, Self::Nil | Self::Bool(false))
29
29
  }
30
30
 
31
31
  /// The text of a `Str`, else `None`; a generated `SetProjectedField` arm narrows a
32
32
  /// projected scalar to its field's type with one of the `into_*` readers.
33
33
  pub fn into_string(self) -> Option<String> {
34
34
  match self {
35
- Value::Str(s) => Some(s),
35
+ Self::Str(s) => Some(s),
36
36
  _ => None,
37
37
  }
38
38
  }
@@ -40,7 +40,7 @@ impl Value {
40
40
  /// The integer of an `Int`, else `None`.
41
41
  pub fn into_i64(self) -> Option<i64> {
42
42
  match self {
43
- Value::Int(n) => Some(n),
43
+ Self::Int(n) => Some(n),
44
44
  _ => None,
45
45
  }
46
46
  }
@@ -48,7 +48,7 @@ impl Value {
48
48
  /// The float of a `Float`, else `None`.
49
49
  pub fn into_f64(self) -> Option<f64> {
50
50
  match self {
51
- Value::Float(f) => Some(f),
51
+ Self::Float(f) => Some(f),
52
52
  _ => None,
53
53
  }
54
54
  }
@@ -56,7 +56,7 @@ impl Value {
56
56
  /// The boolean of a `Bool`, else `None`.
57
57
  pub fn into_bool(self) -> Option<bool> {
58
58
  match self {
59
- Value::Bool(b) => Some(b),
59
+ Self::Bool(b) => Some(b),
60
60
  _ => None,
61
61
  }
62
62
  }
@@ -94,7 +94,7 @@ pub struct Bound<'a> {
94
94
  pub rest: &'a dyn Fielded,
95
95
  }
96
96
 
97
- impl<'a> Fielded for Bound<'a> {
97
+ impl Fielded for Bound<'_> {
98
98
  fn field(&self, name: &str) -> Option<Field<'_>> {
99
99
  if name == self.name {
100
100
  return Some(match &self.value {
@@ -130,7 +130,7 @@ pub struct WithOld<'a> {
130
130
  pub old: &'a dyn Fielded,
131
131
  }
132
132
 
133
- impl<'a> Fielded for WithOld<'a> {
133
+ impl Fielded for WithOld<'_> {
134
134
  fn field(&self, name: &str) -> Option<Field<'_>> {
135
135
  if name == "old" {
136
136
  return Some(Field::Nested(self.old));
@@ -154,7 +154,7 @@ pub struct StateFirst<'a> {
154
154
  pub settled: &'a dyn Fielded,
155
155
  }
156
156
 
157
- impl<'a> Fielded for StateFirst<'a> {
157
+ impl Fielded for StateFirst<'_> {
158
158
  fn field(&self, name: &str) -> Option<Field<'_>> {
159
159
  if self.settled.field(name).is_some() {
160
160
  return None;
@@ -178,7 +178,7 @@ pub struct WithParent<'a> {
178
178
  pub parent: &'a dyn Fielded,
179
179
  }
180
180
 
181
- impl<'a> Fielded for WithParent<'a> {
181
+ impl Fielded for WithParent<'_> {
182
182
  fn field(&self, name: &str) -> Option<Field<'_>> {
183
183
  if name == "parent" {
184
184
  return Some(Field::Nested(self.parent));
@@ -210,11 +210,11 @@ pub enum BlockMode {
210
210
  impl BlockMode {
211
211
  /// The operator's own spelling, for the "expects a list" wording
212
212
  /// Ruby renders from `"#{node.mode}?"`.
213
- pub fn ruby_name(&self) -> &'static str {
213
+ pub const fn ruby_name(&self) -> &'static str {
214
214
  match self {
215
- BlockMode::All => "all?",
216
- BlockMode::Any => "any?",
217
- BlockMode::None => "none?",
215
+ Self::All => "all?",
216
+ Self::Any => "any?",
217
+ Self::None => "none?",
218
218
  }
219
219
  }
220
220
  }
@@ -225,61 +225,106 @@ impl BlockMode {
225
225
  #[derive(Debug, Clone)]
226
226
  #[allow(dead_code)]
227
227
  pub enum Expr {
228
- Or(Box<Expr>, Box<Expr>),
229
- And(Box<Expr>, Box<Expr>),
230
- Not(Box<Expr>),
231
- Compare { op: Comparison, left: Box<Expr>, right: Box<Expr> },
232
- Include { haystack: Box<Expr>, needle: Box<Expr> },
228
+ Or(Box<Self>, Box<Self>),
229
+ And(Box<Self>, Box<Self>),
230
+ Not(Box<Self>),
231
+ Compare {
232
+ op: Comparison,
233
+ left: Box<Self>,
234
+ right: Box<Self>,
235
+ },
236
+ Include {
237
+ haystack: Box<Self>,
238
+ needle: Box<Self>,
239
+ },
233
240
  Int(i64),
234
241
  Float(f64),
235
242
  Str(String),
236
243
  Bool(bool),
237
244
  Nil,
238
- Add(Box<Expr>, Box<Expr>),
239
- Sub(Box<Expr>, Box<Expr>),
240
- Mul(Box<Expr>, Box<Expr>),
245
+ Add(Box<Self>, Box<Self>),
246
+ Sub(Box<Self>, Box<Self>),
247
+ Mul(Box<Self>, Box<Self>),
241
248
  /// Floor division on integers; a zero divisor is an evaluation fault.
242
- Div(Box<Expr>, Box<Expr>),
243
- SignTest { op: Comparison, receiver: Box<Expr> },
244
- Empty(Box<Expr>),
245
- ToS(Box<Expr>),
246
- Modulo { receiver: Box<Expr>, divisor: Box<Expr> },
247
- Size(Box<Expr>),
249
+ Div(Box<Self>, Box<Self>),
250
+ SignTest {
251
+ op: Comparison,
252
+ receiver: Box<Self>,
253
+ },
254
+ Empty(Box<Self>),
255
+ ToS(Box<Self>),
256
+ Modulo {
257
+ receiver: Box<Self>,
258
+ divisor: Box<Self>,
259
+ },
260
+ Size(Box<Self>),
248
261
  Lookup(&'static str),
249
262
  /// `receiver.any?`/`.all?`/`.none? { |param| predicate }` —
250
263
  /// evaluated once per element with `param` bound (see `Bound`).
251
- BlockPredicate { mode: BlockMode, receiver: Box<Expr>, param: &'static str, predicate: Box<Expr> },
264
+ BlockPredicate {
265
+ mode: BlockMode,
266
+ receiver: Box<Self>,
267
+ param: &'static str,
268
+ predicate: Box<Self>,
269
+ },
252
270
  /// `receiver.find { |param| predicate }.a.b` — first accepted
253
271
  /// element, projected through `path`, or nil when nothing matches.
254
- Find { receiver: Box<Expr>, param: &'static str, predicate: Box<Expr>, path: &'static [&'static str] },
272
+ Find {
273
+ receiver: Box<Self>,
274
+ param: &'static str,
275
+ predicate: Box<Self>,
276
+ path: &'static [&'static str],
277
+ },
255
278
  /// `[a, b, c]` — a leaf like `Int`/`Str`/`Nil` above, not an
256
279
  /// operator, so `interpret` handles it directly, not via
257
280
  /// `dispatch_operator`.
258
- Array(Vec<Expr>),
281
+ Array(Vec<Self>),
259
282
  /// `receiver.match?(/pattern/flags)` — `pattern` is a real
260
283
  /// Ruby-Regexp source, taken as-is between the slashes.
261
- MatchesRegex { receiver: Box<Expr>, pattern: String, flags: String },
284
+ MatchesRegex {
285
+ receiver: Box<Self>,
286
+ pattern: String,
287
+ flags: String,
288
+ },
262
289
  /// `receiver.present?` / `.blank?` — one node for the pair,
263
290
  /// `negated` distinguishing them (`.blank?` is `.present?` negated).
264
- Presence { receiver: Box<Expr>, negated: bool },
291
+ Presence {
292
+ receiver: Box<Self>,
293
+ negated: bool,
294
+ },
265
295
  /// `receiver.set?` / `.unset?` — narrower than `Presence`: only
266
296
  /// `!receiver.is_nil()`. An assigned-but-empty value is `.set?`,
267
297
  /// unlike `Presence`'s Rails-standard emptiness reading.
268
- Assignment { receiver: Box<Expr>, negated: bool },
298
+ Assignment {
299
+ receiver: Box<Self>,
300
+ negated: bool,
301
+ },
269
302
  /// `receiver.split("SEP")` — `Resolver::Split`. Produces a real
270
303
  /// `Value::Array`, not `Value::List` — see that variant's own header.
271
- Split { receiver: Box<Expr>, separator: String },
304
+ Split {
305
+ receiver: Box<Self>,
306
+ separator: String,
307
+ },
272
308
  /// `receiver.strip` / `.lstrip` / `.rstrip` — `Resolver::Strip`. Trims Ruby's own whitespace
273
309
  /// set (see `StripSide::trim`), not Unicode spaces.
274
- Strip { receiver: Box<Expr>, side: StripSide },
310
+ Strip {
311
+ receiver: Box<Self>,
312
+ side: StripSide,
313
+ },
275
314
  /// `receiver.start_with?("prefix")` — `Resolver::StartsWith`.
276
- StartsWith { receiver: Box<Expr>, substring: String },
315
+ StartsWith {
316
+ receiver: Box<Self>,
317
+ substring: String,
318
+ },
277
319
  /// `receiver.end_with?("suffix")` — `Resolver::EndsWith`.
278
- EndsWith { receiver: Box<Expr>, substring: String },
320
+ EndsWith {
321
+ receiver: Box<Self>,
322
+ substring: String,
323
+ },
279
324
  /// `receiver.first` — `Resolver::First`.
280
- First(Box<Expr>),
325
+ First(Box<Self>),
281
326
  /// `receiver.last` — `Resolver::Last`.
282
- Last(Box<Expr>),
327
+ Last(Box<Self>),
283
328
  }
284
329
 
285
330
  /// Which ends of a string `Expr::Strip` trims: `.strip` both, `.lstrip` the start, `.rstrip` the end.
@@ -295,11 +340,12 @@ impl StripSide {
295
340
  /// return and space, never a Unicode space. Rust's own `trim` would also drop U+00A0 and the
296
341
  /// rest, so the set is spelled out.
297
342
  pub fn trim(self, text: &str) -> String {
298
- let is_stripped = |c: char| matches!(c, '\0' | '\t' | '\n' | '\u{b}' | '\u{c}' | '\r' | ' ');
343
+ let is_stripped =
344
+ |c: char| matches!(c, '\0' | '\t' | '\n' | '\u{b}' | '\u{c}' | '\r' | ' ');
299
345
  match self {
300
- StripSide::Both => text.trim_matches(is_stripped),
301
- StripSide::Left => text.trim_start_matches(is_stripped),
302
- StripSide::Right => text.trim_end_matches(is_stripped),
346
+ Self::Both => text.trim_matches(is_stripped),
347
+ Self::Left => text.trim_start_matches(is_stripped),
348
+ Self::Right => text.trim_end_matches(is_stripped),
303
349
  }
304
350
  .to_string()
305
351
  }
@@ -315,7 +361,7 @@ pub struct EvalContext<'a> {
315
361
  /// Leaves (literals, `Lookup`) have no `OperatorCategory`, so they're
316
362
  /// handled directly; everything else routes through `dispatch_operator`.
317
363
  pub fn interpret(expr: &Expr, ctx: &EvalContext) -> Result<Value, Refusal> {
318
- use Expr::*;
364
+ use Expr::{Array, Bool, Float, Int, Lookup, Nil, Str};
319
365
  match expr {
320
366
  Int(v) => Ok(Value::Int(*v)),
321
367
  Float(v) => Ok(Value::Float(*v)),
@@ -323,7 +369,12 @@ pub fn interpret(expr: &Expr, ctx: &EvalContext) -> Result<Value, Refusal> {
323
369
  Bool(v) => Ok(Value::Bool(*v)),
324
370
  Nil => Ok(Value::Nil),
325
371
  Lookup(path) => lookup(path, ctx),
326
- Array(elements) => Ok(Value::Array(elements.iter().map(|e| interpret(e, ctx)).collect::<Result<Vec<_>, _>>()?)),
372
+ Array(elements) => Ok(Value::Array(
373
+ elements
374
+ .iter()
375
+ .map(|e| interpret(e, ctx))
376
+ .collect::<Result<Vec<_>, _>>()?,
377
+ )),
327
378
  _ => dispatch_operator(category_of(expr), expr, ctx),
328
379
  }
329
380
  }
@@ -332,7 +383,11 @@ pub fn interpret(expr: &Expr, ctx: &EvalContext) -> Result<Value, Refusal> {
332
383
  /// hand-written association `interpret` never calls for a leaf, hence
333
384
  /// the `unreachable!` there rather than a wrong category.
334
385
  fn category_of(expr: &Expr) -> OperatorCategory {
335
- use Expr::*;
386
+ use Expr::{
387
+ Add, And, Array, Assignment, BlockPredicate, Bool, Compare, Div, Empty, EndsWith, Find,
388
+ First, Float, Include, Int, Last, Lookup, MatchesRegex, Modulo, Mul, Nil, Not, Or,
389
+ Presence, SignTest, Size, Split, StartsWith, Str, Strip, Sub, ToS,
390
+ };
336
391
  match expr {
337
392
  Or(..) | And(..) | Not(..) => OperatorCategory::Logical,
338
393
  Include { .. } => OperatorCategory::Membership,
@@ -356,22 +411,35 @@ fn category_of(expr: &Expr) -> OperatorCategory {
356
411
  /// removing a variant (hecks project_kernel_capabilities) stops this
357
412
  /// compiling until a matching arm exists. Never add one back — it would
358
413
  /// silently route a new category to the wrong operator file.
359
- fn dispatch_operator(category: OperatorCategory, expr: &Expr, ctx: &EvalContext) -> Result<Value, Refusal> {
360
- use expression_operators::*;
414
+ fn dispatch_operator(
415
+ category: OperatorCategory,
416
+ expr: &Expr,
417
+ ctx: &EvalContext,
418
+ ) -> Result<Value, Refusal> {
419
+ use expression_operators::{
420
+ arithmetic, comparison, enumeration, logical, membership, pattern_match, positional,
421
+ presence, sign_test, sized, text, to_string, OperatorCategory,
422
+ };
361
423
  match category {
362
424
  OperatorCategory::Logical => logical::interpret(expr, ctx),
363
425
  OperatorCategory::Membership => membership::interpret(expr, ctx),
364
426
  OperatorCategory::Comparison => comparison::interpret(expr, ctx),
365
427
  OperatorCategory::Arithmetic => match expr {
366
- Expr::Add(..) | Expr::Sub(..) | Expr::Mul(..) | Expr::Div(..) => arithmetic::binary(expr, ctx),
428
+ Expr::Add(..) | Expr::Sub(..) | Expr::Mul(..) | Expr::Div(..) => {
429
+ arithmetic::binary(expr, ctx)
430
+ }
367
431
  Expr::Modulo { .. } => arithmetic::modulo(expr, ctx),
368
- _ => Err(Refusal::TypeMismatch(format!("dispatch_operator(Arithmetic, ..) called with {expr:?} — a router bug"))),
432
+ _ => Err(Refusal::TypeMismatch(format!(
433
+ "dispatch_operator(Arithmetic, ..) called with {expr:?} — a router bug"
434
+ ))),
369
435
  },
370
436
  OperatorCategory::SignTest => sign_test::interpret(expr, ctx),
371
437
  OperatorCategory::Sized => match expr {
372
438
  Expr::Empty(..) => sized::empty(expr, ctx),
373
439
  Expr::Size(..) => sized::size(expr, ctx),
374
- _ => Err(Refusal::TypeMismatch(format!("dispatch_operator(Sized, ..) called with {expr:?} — a router bug"))),
440
+ _ => Err(Refusal::TypeMismatch(format!(
441
+ "dispatch_operator(Sized, ..) called with {expr:?} — a router bug"
442
+ ))),
375
443
  },
376
444
  OperatorCategory::ToString => to_string::interpret(expr, ctx),
377
445
  OperatorCategory::Enumeration => enumeration::interpret(expr, ctx),
@@ -379,7 +447,9 @@ fn dispatch_operator(category: OperatorCategory, expr: &Expr, ctx: &EvalContext)
379
447
  OperatorCategory::Presence => match expr {
380
448
  Expr::Presence { .. } => presence::interpret(expr, ctx),
381
449
  Expr::Assignment { .. } => presence::interpret_assignment(expr, ctx),
382
- _ => Err(Refusal::TypeMismatch(format!("dispatch_operator(Presence, ..) called with {expr:?} — a router bug"))),
450
+ _ => Err(Refusal::TypeMismatch(format!(
451
+ "dispatch_operator(Presence, ..) called with {expr:?} — a router bug"
452
+ ))),
383
453
  },
384
454
  OperatorCategory::Text => text::interpret(expr, ctx),
385
455
  OperatorCategory::Positional => positional::interpret(expr, ctx),
@@ -398,15 +468,19 @@ fn lookup(path: &str, ctx: &EvalContext) -> Result<Value, Refusal> {
398
468
  /// nested object, which `.set?` reads as present without needing it to be one value.
399
469
  pub(crate) fn lookup_field<'a>(path: &str, ctx: &EvalContext<'a>) -> Result<Field<'a>, Refusal> {
400
470
  let mut segments = path.split('.');
401
- let head = segments.next().unwrap();
471
+ let head = segments.next().unwrap_or_default();
402
472
  let mut current = ctx
403
473
  .args
404
474
  .field(head)
405
475
  .or_else(|| ctx.instance.field(head))
406
- .ok_or_else(|| eval_error(format!("cannot resolve {head:?} — no such attribute or argument")))?;
476
+ .ok_or_else(|| {
477
+ eval_error(format!(
478
+ "cannot resolve {head:?} — no such attribute or argument"
479
+ ))
480
+ })?;
407
481
 
408
482
  for seg in segments {
409
- current = composite::step(current, seg, head)?;
483
+ current = composite::step(&current, seg, head)?;
410
484
  }
411
485
 
412
486
  Ok(current)
@@ -415,34 +489,54 @@ pub(crate) fn lookup_field<'a>(path: &str, ctx: &EvalContext<'a>) -> Result<Fiel
415
489
  /// The list-elements reading of a `Lookup` path — same head resolution
416
490
  /// as `lookup`, then `Fielded::items` on the last segment (a list is
417
491
  /// always the end of a path). `op` names the caller for error wording.
418
- pub(crate) fn lookup_items<'a>(path: &str, ctx: &EvalContext<'a>, op: &str) -> Result<Vec<Field<'a>>, Refusal> {
419
- let segments: Vec<&str> = path.split('.').collect();
420
- let (head, rest) = segments.split_first().unwrap();
492
+ pub(crate) fn lookup_items<'a>(
493
+ path: &str,
494
+ ctx: &EvalContext<'a>,
495
+ op: &str,
496
+ ) -> Result<Vec<Field<'a>>, Refusal> {
497
+ let mut segments = path.split('.');
498
+ let head = segments.next().unwrap_or_default();
499
+ let rest: Vec<&str> = segments.collect();
421
500
 
422
- if rest.is_empty() {
501
+ let Some((last, middle)) = rest.split_last() else {
423
502
  // args first when the key is present there, state second — a
424
503
  // non-list argument refuses rather than falling through to a
425
504
  // same-named instance list.
426
- let side: &'a dyn Fielded = if ctx.args.field(head).is_some() { ctx.args } else { ctx.instance };
427
- return side
428
- .items(head)
429
- .ok_or_else(|| eval_error(format!("{op} expects a list, got {}", describe_field(side.field(head)))));
430
- }
505
+ let side: &'a dyn Fielded = if ctx.args.field(head).is_some() {
506
+ ctx.args
507
+ } else {
508
+ ctx.instance
509
+ };
510
+ return side.items(head).ok_or_else(|| {
511
+ eval_error(format!(
512
+ "{op} expects a list, got {}",
513
+ describe_field(side.field(head))
514
+ ))
515
+ });
516
+ };
431
517
 
432
- let (last, middle) = rest.split_last().unwrap();
433
518
  let mut current = ctx
434
519
  .args
435
520
  .field(head)
436
521
  .or_else(|| ctx.instance.field(head))
437
- .ok_or_else(|| eval_error(format!("cannot resolve {head:?} — no such attribute or argument")))?;
522
+ .ok_or_else(|| {
523
+ eval_error(format!(
524
+ "cannot resolve {head:?} — no such attribute or argument"
525
+ ))
526
+ })?;
438
527
  for seg in middle {
439
- current = composite::step(current, seg, head)?;
528
+ current = composite::step(&current, seg, head)?;
440
529
  }
441
530
  match current {
442
- Field::Nested(obj) => obj
443
- .items(last)
444
- .ok_or_else(|| eval_error(format!("{op} expects a list, got {}", describe_field(obj.field(last))))),
445
- Field::Value(v) => Err(eval_error(format!("{path} — cannot look up {last:?} on scalar {v:?}"))),
531
+ Field::Nested(obj) => obj.items(last).ok_or_else(|| {
532
+ eval_error(format!(
533
+ "{op} expects a list, got {}",
534
+ describe_field(obj.field(last))
535
+ ))
536
+ }),
537
+ Field::Value(v) => Err(eval_error(format!(
538
+ "{path} — cannot look up {last:?} on scalar {v:?}"
539
+ ))),
446
540
  }
447
541
  }
448
542
 
@@ -454,7 +548,7 @@ fn describe_field(field: Option<Field<'_>>) -> String {
454
548
  }
455
549
  }
456
550
 
457
- pub(crate) fn eval_error(message: String) -> Refusal {
551
+ pub(crate) const fn eval_error(message: String) -> Refusal {
458
552
  Refusal::Fault(message)
459
553
  }
460
554
 
@@ -473,10 +567,9 @@ mod lookup_tests {
473
567
  fn field(&self, name: &str) -> Option<Field<'_>> {
474
568
  match name {
475
569
  "previous_sessions" => Some(Field::Value(Value::Bool(self.previous_sessions))),
476
- "first_time" => Some(match self.first_time {
477
- Some(b) => Field::Value(Value::Bool(b)),
478
- None => Field::Value(Value::Nil),
479
- }),
570
+ "first_time" => Some(Field::Value(
571
+ self.first_time.map_or(Value::Nil, Value::Bool),
572
+ )),
480
573
  _ => None,
481
574
  }
482
575
  }
@@ -484,7 +577,10 @@ mod lookup_tests {
484
577
 
485
578
  fn not_nil(field: &'static str, instance: &dyn Fielded) -> Value {
486
579
  let expr = Expr::Not(Box::new(Expr::Lookup(field)));
487
- let ctx = EvalContext { args: &NoFields, instance };
580
+ let ctx = EvalContext {
581
+ args: &NoFields,
582
+ instance,
583
+ };
488
584
  interpret(&expr, &ctx).expect("evaluates without refusing")
489
585
  }
490
586
 
@@ -493,8 +589,14 @@ mod lookup_tests {
493
589
  // `!previous_sessions.nil?` — the live crash, `previous_sessions:
494
590
  // true`: `.nil?` is not a dedicated node in this grammar, so this
495
591
  // is really `Lookup("previous_sessions.nil?")` negated.
496
- let attendee = Attendee { previous_sessions: true, first_time: None };
497
- assert_eq!(not_nil("previous_sessions.nil?", &attendee), Value::Bool(true));
592
+ let attendee = Attendee {
593
+ previous_sessions: true,
594
+ first_time: None,
595
+ };
596
+ assert_eq!(
597
+ not_nil("previous_sessions.nil?", &attendee),
598
+ Value::Bool(true)
599
+ );
498
600
  }
499
601
 
500
602
  #[test]
@@ -502,8 +604,14 @@ mod lookup_tests {
502
604
  // The other half of the live crash: `false` is exactly as
503
605
  // present as `true` — `walk_path` never inspects the value,
504
606
  // only whether the head attribute resolved at all.
505
- let attendee = Attendee { previous_sessions: false, first_time: None };
506
- assert_eq!(not_nil("previous_sessions.nil?", &attendee), Value::Bool(true));
607
+ let attendee = Attendee {
608
+ previous_sessions: false,
609
+ first_time: None,
610
+ };
611
+ assert_eq!(
612
+ not_nil("previous_sessions.nil?", &attendee),
613
+ Value::Bool(true)
614
+ );
507
615
  }
508
616
 
509
617
  #[test]
@@ -511,7 +619,10 @@ mod lookup_tests {
511
619
  // Matches Ruby parity: `walk_path` breaks to `nil` off any
512
620
  // scalar before inspecting `.nil?`'s segment name, so `!x.nil?`
513
621
  // reads `true` for any head attribute that resolved, set or not.
514
- let attendee = Attendee { previous_sessions: true, first_time: None };
622
+ let attendee = Attendee {
623
+ previous_sessions: true,
624
+ first_time: None,
625
+ };
515
626
  assert_eq!(not_nil("first_time.nil?", &attendee), Value::Bool(true));
516
627
  }
517
628
 
@@ -519,9 +630,15 @@ mod lookup_tests {
519
630
  fn a_genuinely_missing_head_attribute_still_refuses() {
520
631
  // An undeclared head attribute is still a codegen bug, not a
521
632
  // legitimate absent value — see `lookup`'s own doc comment.
522
- let attendee = Attendee { previous_sessions: true, first_time: None };
633
+ let attendee = Attendee {
634
+ previous_sessions: true,
635
+ first_time: None,
636
+ };
523
637
  let expr = Expr::Not(Box::new(Expr::Lookup("not_a_real_attribute.nil?")));
524
- let ctx = EvalContext { args: &NoFields, instance: &attendee };
638
+ let ctx = EvalContext {
639
+ args: &NoFields,
640
+ instance: &attendee,
641
+ };
525
642
  assert!(interpret(&expr, &ctx).is_err());
526
643
  }
527
644
  }