hecks 3.13.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (440) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/adapters/fake_processor.rb +34 -0
  3. data/examples/pizzas/adapters/payment_signature.rb +66 -0
  4. data/examples/pizzas/adapters/payment_webhook.rb +87 -0
  5. data/examples/pizzas/bluebook/pizzas.behaviors +33 -0
  6. data/examples/pizzas/bluebook/pizzas.bluebook +4 -4
  7. data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
  8. data/examples/pizzas/config.ru +22 -0
  9. data/lib/hecks/adapters/driven/prism.rb +0 -2
  10. data/lib/hecks/adapters/driven.rb +0 -2
  11. data/lib/hecks/adapters/driving/json.rb +1 -7
  12. data/lib/hecks/bluebook/dsl/attribute_collector.rb +15 -5
  13. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
  14. data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
  15. data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
  16. data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
  17. data/lib/hecks/bluebook/hecksagon.rb +1 -1
  18. data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
  19. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
  20. data/lib/hecks/bluebook/meta_validator.rb +0 -11
  21. data/lib/hecks/bluebook/named_pattern.rb +33 -0
  22. data/lib/hecks/bluebook/translation.rb +18 -10
  23. data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
  24. data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
  25. data/lib/hecks/bluebook/translation_expression.rb +39 -0
  26. data/lib/hecks/bluebook/translation_plan.rb +70 -0
  27. data/lib/hecks/bluebook.rb +3 -0
  28. data/lib/hecks/chapters.rb +3 -3
  29. data/lib/hecks/cli/domain_stub.rb +1 -1
  30. data/lib/hecks/cli/interview_aggregate.rb +4 -4
  31. data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
  32. data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
  33. data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
  34. data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
  35. data/lib/hecks/composer/adapters/jev.adapter +3 -0
  36. data/lib/hecks/composer/adapters/jev.rb +66 -0
  37. data/lib/hecks/composer/agent.rb +113 -0
  38. data/lib/hecks/composer/assistant.rb +110 -0
  39. data/lib/hecks/composer/behavior_run.rb +53 -0
  40. data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
  41. data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
  42. data/lib/hecks/composer/board.rb +65 -0
  43. data/lib/hecks/composer/board_edit/command_text.rb +39 -0
  44. data/lib/hecks/composer/board_edit/source.rb +84 -0
  45. data/lib/hecks/composer/board_edit.rb +144 -0
  46. data/lib/hecks/composer/check.rb +89 -0
  47. data/lib/hecks/composer/domains.rb +81 -0
  48. data/lib/hecks/composer/jobs.rb +134 -0
  49. data/lib/hecks/composer/layout.rb +37 -0
  50. data/lib/hecks/composer/live.rb +150 -0
  51. data/lib/hecks/composer/outline.rb +48 -0
  52. data/lib/hecks/composer/page.html +989 -0
  53. data/lib/hecks/composer/panels.rb +142 -0
  54. data/lib/hecks/composer/parse.rb +83 -0
  55. data/lib/hecks/composer/plain.rb +28 -0
  56. data/lib/hecks/composer/ports/composer_agent.port +7 -0
  57. data/lib/hecks/composer/session.rb +141 -0
  58. data/lib/hecks/composer/toolbox.rb +166 -0
  59. data/lib/hecks/composer/web.rb +156 -0
  60. data/lib/hecks/composer/workbench.rb +51 -0
  61. data/lib/hecks/composer/workspace.rb +81 -0
  62. data/lib/hecks/composer.rb +17 -0
  63. data/lib/hecks/corpus/adapters.rb +22 -0
  64. data/lib/hecks/corpus/rust.rb +1 -1
  65. data/lib/hecks/corpus/worlds.rb +19 -0
  66. data/lib/hecks/corpus.rb +5 -0
  67. data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -34
  68. data/lib/hecks/forms/app/commands.rb +2 -3
  69. data/lib/hecks/forms/app/queries.rb +2 -3
  70. data/lib/hecks/forms/app/records.rb +17 -5
  71. data/lib/hecks/forms/command_form_renderer.rb +42 -16
  72. data/lib/hecks/forms/field_controls.rb +2 -2
  73. data/lib/hecks/forms/field_renderer.rb +1 -1
  74. data/lib/hecks/forms/field_shape.rb +36 -17
  75. data/lib/hecks/forms/index_renderer.rb +2 -1
  76. data/lib/hecks/forms/page.rb +1 -1
  77. data/lib/hecks/forms/params.rb +0 -13
  78. data/lib/hecks/forms/query_form_renderer.rb +7 -6
  79. data/lib/hecks/forms/record_renderer.rb +11 -7
  80. data/lib/hecks/forms/rule_words.rb +100 -0
  81. data/lib/hecks/forms/value_object_shape.rb +2 -1
  82. data/lib/hecks/framework/bluebook/change.bluebook +2 -2
  83. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
  84. data/lib/hecks/fuzzing/domain_generator/block_rendering.rb +1 -1
  85. data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
  86. data/lib/hecks/fuzzing/replay/filters.rb +2 -6
  87. data/lib/hecks/gate/stages.yml +25 -0
  88. data/lib/hecks/grammar/evolve/seeds.rb +0 -6
  89. data/lib/hecks/grammar/evolve.rb +0 -3
  90. data/lib/hecks/grammar/translation.bluebook +1 -1
  91. data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
  92. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
  93. data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
  94. data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
  95. data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
  96. data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
  97. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
  98. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
  99. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
  100. data/lib/hecks/hecks/codebase.behaviors +6 -0
  101. data/lib/hecks/hecks/codebase.bluebook +58 -37
  102. data/lib/hecks/hecks/composer.behaviors +68 -0
  103. data/lib/hecks/hecks/context_map.hecksagon +1 -1
  104. data/lib/hecks/hecks/custodian.behaviors +0 -6
  105. data/lib/hecks/hecks/custodian.bluebook +15 -92
  106. data/lib/hecks/hecks/hecks.bluebook +2 -2
  107. data/lib/hecks/hecks/hecks.hecksagon +2 -2
  108. data/lib/hecks/hecks/hecks.world +1 -1
  109. data/lib/hecks/hecks/rehearsal.bluebook +10 -16
  110. data/lib/hecks/hecks/tooling.bluebook +22 -22
  111. data/lib/hecks/kernel_capabilities/templates.rb +4 -4
  112. data/lib/hecks/kernel_capabilities.rb +1 -1
  113. data/lib/hecks/language/bluebook/aggregate.bluebook +1 -0
  114. data/lib/hecks/language/bluebook/command.bluebook +1 -0
  115. data/lib/hecks/language/bluebook/entity.bluebook +1 -0
  116. data/lib/hecks/language/bluebook/query.bluebook +1 -0
  117. data/lib/hecks/language/bluebook/shape.bluebook +8 -7
  118. data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
  119. data/lib/hecks/language/hecksagon/port_operation.bluebook +1 -0
  120. data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
  121. data/lib/hecks/ports/access_control.rb +2 -2
  122. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
  123. data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
  124. data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
  125. data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
  126. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
  127. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
  128. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
  129. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
  130. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
  131. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
  132. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
  133. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
  134. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
  135. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
  136. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
  137. data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
  138. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
  139. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
  140. data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
  141. data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
  142. data/lib/hecks/ports.rb +0 -1
  143. data/lib/hecks/projection_files.rb +3 -1
  144. data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
  145. data/lib/hecks/projections/glossary/entries.rb +10 -4
  146. data/lib/hecks/projections/glossary/groups.rb +42 -0
  147. data/lib/hecks/projections/glossary/html/page.rb +7 -2
  148. data/lib/hecks/projections/glossary/html/parser.rb +1 -1
  149. data/lib/hecks/projections/glossary/html/sections.rb +31 -11
  150. data/lib/hecks/projections/glossary/html.rb +1 -2
  151. data/lib/hecks/projections/glossary/markdown.rb +31 -28
  152. data/lib/hecks/projections/glossary/mermaid.rb +3 -0
  153. data/lib/hecks/projections/glossary/page.css +143 -153
  154. data/lib/hecks/projections/glossary/page.js +63 -3
  155. data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
  156. data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
  157. data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
  158. data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
  159. data/lib/hecks/projections/glossary/sentences.rb +13 -3
  160. data/lib/hecks/projections/glossary/slugs.rb +4 -1
  161. data/lib/hecks/projections/glossary.rb +2 -1
  162. data/lib/hecks/projections/plain_english.rb +97 -0
  163. data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
  164. data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
  165. data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
  166. data/lib/hecks/projections/rust_vocabulary.rb +14 -6
  167. data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
  168. data/lib/hecks/projections/statements/actions.rb +99 -0
  169. data/lib/hecks/projections/statements/relationships.rb +86 -0
  170. data/lib/hecks/projections/statements.rb +15 -24
  171. data/lib/hecks/projector/exporter/adapters.rb +112 -0
  172. data/lib/hecks/projector/exporter/translations.rb +18 -35
  173. data/lib/hecks/projector/exporter/worlds.rb +103 -0
  174. data/lib/hecks/projector/exporter.rb +5 -1
  175. data/lib/hecks/quality_control/adapters/agent.rb +6 -7
  176. data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
  177. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
  178. data/lib/hecks/quality_control/quality_control.bluebook +51 -51
  179. data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
  180. data/lib/hecks/runtime/outbox.rb +2 -9
  181. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
  182. data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
  183. data/lib/hecks/runtime/value.rb +13 -0
  184. data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
  185. data/lib/hecks/rust_build/wasm.rb +0 -12
  186. data/lib/hecks/rust_build/write_if_changed.rb +12 -12
  187. data/lib/hecks/rust_format.rb +77 -0
  188. data/lib/hecks/site/bluebook/site.bluebook +5 -5
  189. data/lib/hecks/sme/bluebook/sme.bluebook +12 -12
  190. data/lib/hecks/tickets/bluebook/tickets.bluebook +3 -3
  191. data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
  192. data/lib/hecks/tools/fuzz_sweep.rb +1 -3
  193. data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
  194. data/lib/hecks/tools/translation_audit.rb +8 -10
  195. data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
  196. data/lib/hecks/version.rb +1 -1
  197. data/lib/hecks/vocabulary.rb +2 -1
  198. data/lib/hecks.rb +3 -0
  199. data/prebuilt/{58208399bada10b0db99928c48e38789288fc58f7b71a72b21bfa386680e32ed.marshal → 02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal} +0 -0
  200. data/prebuilt/{2b59737fbdbd5f44edda0fddce2611f45d76087b3a7c782d1f1a17ea58c77948.marshal → bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal} +0 -0
  201. data/prebuilt/verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json +1 -0
  202. data/rust/Cargo.toml +40 -0
  203. data/rust/build/Cargo.toml +36 -0
  204. data/rust/build/clippy.toml +4 -0
  205. data/rust/build/src/adapters_pass.rs +196 -0
  206. data/rust/build/src/binding_pass.rs +54 -13
  207. data/rust/build/src/build_artifact.rs +23 -5
  208. data/rust/build/src/cargo_sync.rs +69 -26
  209. data/rust/build/src/lineage_pass.rs +101 -87
  210. data/rust/build/src/main.rs +11 -2
  211. data/rust/build/src/pipeline.rs +80 -19
  212. data/rust/build/src/reserved_names.rs +6 -50
  213. data/rust/build/src/resolve.rs +82 -35
  214. data/rust/build/src/rustfmt.rs +100 -0
  215. data/rust/build/src/seams_pass.rs +85 -16
  216. data/rust/build/src/subprocess.rs +30 -6
  217. data/rust/build/src/tmp.rs +5 -2
  218. data/rust/build/src/translation_pass.rs +72 -22
  219. data/rust/build/src/world_pass.rs +109 -0
  220. data/rust/clippy.toml +4 -0
  221. data/rust/codegen/Cargo.toml +36 -0
  222. data/rust/codegen/clippy.toml +4 -0
  223. data/rust/codegen/src/asks.rs +40 -14
  224. data/rust/codegen/src/attr.rs +2 -2
  225. data/rust/codegen/src/bridging.rs +676 -117
  226. data/rust/codegen/src/commands.rs +979 -373
  227. data/rust/codegen/src/constraints.rs +88 -25
  228. data/rust/codegen/src/dependency_planning.rs +128 -34
  229. data/rust/codegen/src/domain_generator.rs +300 -170
  230. data/rust/codegen/src/exemplar.rs +87 -32
  231. data/rust/codegen/src/expr_emitter.rs +54 -6
  232. data/rust/codegen/src/fielded.rs +193 -41
  233. data/rust/codegen/src/hecks_naming.rs +124 -16
  234. data/rust/codegen/src/hecks_naming_tests.rs +81 -0
  235. data/rust/codegen/src/json.rs +80 -44
  236. data/rust/codegen/src/json_codec.rs +585 -149
  237. data/rust/codegen/src/literal.rs +104 -138
  238. data/rust/codegen/src/main.rs +145 -52
  239. data/rust/codegen/src/manifest.rs +76 -14
  240. data/rust/codegen/src/mutations.rs +1212 -239
  241. data/rust/codegen/src/naming.rs +111 -60
  242. data/rust/codegen/src/optional_pass.rs +90 -28
  243. data/rust/codegen/src/ports.rs +5 -4
  244. data/rust/codegen/src/prelude.rs +139 -49
  245. data/rust/codegen/src/queries.rs +601 -226
  246. data/rust/codegen/src/reactions.rs +268 -74
  247. data/rust/codegen/src/read_models.rs +606 -205
  248. data/rust/codegen/src/reference_specs.rs +16 -3
  249. data/rust/codegen/src/registry.rs +128 -79
  250. data/rust/codegen/src/reserved_names.rs +6 -50
  251. data/rust/codegen/src/shared.rs +71 -22
  252. data/rust/codegen/src/sidecars.rs +15 -5
  253. data/rust/codegen/src/skip_reason.rs +8 -2
  254. data/rust/codegen/src/types.rs +306 -108
  255. data/rust/host/Cargo.lock +1 -0
  256. data/rust/host/Cargo.toml +40 -4
  257. data/rust/host/HECKS_RELEASE +1 -1
  258. data/rust/host/clippy.toml +4 -0
  259. data/rust/host/src/api.rs +698 -218
  260. data/rust/host/src/auth.rs +491 -214
  261. data/rust/host/src/b64.rs +91 -0
  262. data/rust/host/src/bin/expr_harness.rs +6 -2
  263. data/rust/host/src/bin/lineage_harness.rs +47 -11
  264. data/rust/host/src/bin/mint_harness.rs +70 -37
  265. data/rust/host/src/bin/validate_harness.rs +16 -8
  266. data/rust/host/src/boot.rs +144 -68
  267. data/rust/host/src/boundary_fuzz/api.rs +130 -32
  268. data/rust/host/src/boundary_fuzz/auth.rs +108 -23
  269. data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
  270. data/rust/host/src/boundary_fuzz/ir.rs +164 -12
  271. data/rust/host/src/boundary_fuzz/server.rs +283 -36
  272. data/rust/host/src/boundary_fuzz/web.rs +56 -8
  273. data/rust/host/src/checkout.rs +78 -32
  274. data/rust/host/src/clock.rs +8 -0
  275. data/rust/host/src/commerce_ir.rs +333 -95
  276. data/rust/host/src/crypto.rs +33 -0
  277. data/rust/host/src/dispatch.rs +990 -331
  278. data/rust/host/src/expr_json.rs +1021 -273
  279. data/rust/host/src/extension.rs +24 -4
  280. data/rust/host/src/field_hints.rs +11 -7
  281. data/rust/host/src/fuzz_support.rs +85 -13
  282. data/rust/host/src/host.rs +100 -0
  283. data/rust/host/src/http.rs +22 -0
  284. data/rust/host/src/ir.rs +326 -16
  285. data/rust/host/src/journal.rs +277 -176
  286. data/rust/host/src/lambda_client.rs +219 -53
  287. data/rust/host/src/lib.rs +16 -4
  288. data/rust/host/src/log.rs +62 -11
  289. data/rust/host/src/mint.rs +1153 -326
  290. data/rust/host/src/needs.rs +185 -44
  291. data/rust/host/src/payments/keystore.rs +197 -45
  292. data/rust/host/src/payments/roles.rs +191 -0
  293. data/rust/host/src/payments/tests.rs +1445 -275
  294. data/rust/host/src/payments.rs +409 -121
  295. data/rust/host/src/platform_signin/attack_tests.rs +5 -5
  296. data/rust/host/src/platform_signin/config.rs +11 -11
  297. data/rust/host/src/platform_signin/cookie.rs +2 -2
  298. data/rust/host/src/platform_signin/extension.rs +2 -1
  299. data/rust/host/src/platform_signin/handler.rs +9 -8
  300. data/rust/host/src/platform_signin/jti.rs +4 -2
  301. data/rust/host/src/platform_signin/jwks.rs +2 -1
  302. data/rust/host/src/platform_signin/jws.rs +2 -37
  303. data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
  304. data/rust/host/src/platform_signin/mode_tests.rs +5 -3
  305. data/rust/host/src/platform_signin/pages.rs +20 -20
  306. data/rust/host/src/platform_signin/session.rs +8 -1
  307. data/rust/host/src/platform_signin/settings.rs +6 -6
  308. data/rust/host/src/platform_signin/strict_json.rs +12 -9
  309. data/rust/host/src/platform_signin/verify.rs +41 -41
  310. data/rust/host/src/platform_signin/wiring_support.rs +13 -18
  311. data/rust/host/src/presentation.rs +207 -63
  312. data/rust/host/src/presentation_write.rs +502 -187
  313. data/rust/host/src/query_step.rs +69 -18
  314. data/rust/host/src/rate_limit.rs +541 -142
  315. data/rust/host/src/reference_transform.rs +77 -25
  316. data/rust/host/src/reference_validate.rs +443 -104
  317. data/rust/host/src/resend.rs +190 -55
  318. data/rust/host/src/secrets.rs +116 -21
  319. data/rust/host/src/server.rs +483 -104
  320. data/rust/host/src/storage_shape.rs +88 -21
  321. data/rust/host/src/test_pg.rs +17 -7
  322. data/rust/host/src/ui_schema.rs +495 -207
  323. data/rust/host/src/wasm_runner.rs +21 -6
  324. data/rust/host/src/web/commerce.rs +101 -23
  325. data/rust/host/src/web/newsletter/cooldown.rs +10 -3
  326. data/rust/host/src/web/newsletter/template.rs +26 -7
  327. data/rust/host/src/web/newsletter/tests.rs +112 -23
  328. data/rust/host/src/web/newsletter.rs +209 -59
  329. data/rust/host/src/web/newsletter_send.rs +251 -79
  330. data/rust/host/src/web/registration_receipt/tests.rs +16 -4
  331. data/rust/host/src/web/registration_receipt.rs +100 -24
  332. data/rust/host/src/web/registrations.rs +1384 -332
  333. data/rust/host/src/web/resend_webhook.rs +138 -80
  334. data/rust/host/src/web.rs +2416 -508
  335. data/rust/lsp/Cargo.toml +36 -0
  336. data/rust/lsp/clippy.toml +4 -0
  337. data/rust/lsp/src/json.rs +29 -22
  338. data/rust/lsp/src/main.rs +81 -31
  339. data/rust/lsp/src/outline.rs +23 -7
  340. data/rust/lsp/src/rpc.rs +17 -4
  341. data/rust/parser/Cargo.toml +36 -0
  342. data/rust/parser/clippy.toml +4 -0
  343. data/rust/parser/src/build/closed_sets.rs +6 -1
  344. data/rust/parser/src/build/identity.rs +4 -3
  345. data/rust/parser/src/build/mod.rs +1 -0
  346. data/rust/parser/src/build/named_pattern.rs +26 -0
  347. data/rust/parser/src/build/naming.rs +2 -80
  348. data/rust/parser/src/build/pattern_subset.rs +6 -8
  349. data/rust/parser/src/build/query_derive.rs +3 -1
  350. data/rust/parser/src/build/query_inference.rs +14 -5
  351. data/rust/parser/src/build/read_model.rs +4 -1
  352. data/rust/parser/src/build/references.rs +4 -3
  353. data/rust/parser/src/canonical.rs +34 -15
  354. data/rust/parser/src/emit.rs +187 -105
  355. data/rust/parser/src/expr/ast_json.rs +134 -42
  356. data/rust/parser/src/expr/evaluator.rs +27 -9
  357. data/rust/parser/src/expr/mod.rs +54 -13
  358. data/rust/parser/src/expr/resolver.rs +299 -92
  359. data/rust/parser/src/ir.rs +35 -3
  360. data/rust/parser/src/keywords.rs +7168 -510
  361. data/rust/parser/src/lex.rs +11 -25
  362. data/rust/parser/src/lib.rs +1 -0
  363. data/rust/parser/src/main.rs +65 -6
  364. data/rust/parser/src/parse/adapter.rs +157 -0
  365. data/rust/parser/src/parse/aggregate.rs +19 -10
  366. data/rust/parser/src/parse/chapter.rs +16 -17
  367. data/rust/parser/src/parse/command.rs +35 -16
  368. data/rust/parser/src/parse/domain_port.rs +4 -7
  369. data/rust/parser/src/parse/entity.rs +21 -11
  370. data/rust/parser/src/parse/hecksagon.rs +10 -4
  371. data/rust/parser/src/parse/lifecycle.rs +9 -5
  372. data/rust/parser/src/parse/mod.rs +43 -91
  373. data/rust/parser/src/parse/needs.rs +21 -12
  374. data/rust/parser/src/parse/policy.rs +2 -2
  375. data/rust/parser/src/parse/process_manager.rs +9 -11
  376. data/rust/parser/src/parse/query.rs +8 -4
  377. data/rust/parser/src/parse/read_model.rs +57 -16
  378. data/rust/parser/src/parse/value_object.rs +7 -2
  379. data/rust/parser/src/parse/world.rs +322 -0
  380. data/rust/parser/src/ruby_value.rs +7 -6
  381. data/rust/src/exemplar/json.rs +4 -4
  382. data/rust/src/exemplar/mutations.rs +18 -0
  383. data/rust/src/exemplar/read_models.rs +1 -10
  384. data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
  385. data/rust/src/kernel/attribute_shapes/list.rs +2 -2
  386. data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
  387. data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
  388. data/rust/src/kernel/cli.rs +229 -112
  389. data/rust/src/kernel/dispatch.rs +323 -115
  390. data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
  391. data/rust/src/kernel/expr.rs +203 -86
  392. data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
  393. data/rust/src/kernel/expression_operators/comparison.rs +87 -2
  394. data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
  395. data/rust/src/kernel/expression_operators/logical.rs +9 -3
  396. data/rust/src/kernel/expression_operators/membership.rs +3 -1
  397. data/rust/src/kernel/expression_operators/mod.rs +34 -21
  398. data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
  399. data/rust/src/kernel/expression_operators/positional.rs +84 -30
  400. data/rust/src/kernel/expression_operators/presence.rs +52 -13
  401. data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
  402. data/rust/src/kernel/expression_operators/sized.rs +24 -8
  403. data/rust/src/kernel/expression_operators/text.rs +120 -27
  404. data/rust/src/kernel/expression_operators/to_string.rs +10 -4
  405. data/rust/src/kernel/json.rs +230 -148
  406. data/rust/src/kernel/mod.rs +46 -33
  407. data/rust/src/kernel/named_query.rs +63 -15
  408. data/rust/src/kernel/needs.rs +112 -25
  409. data/rust/src/kernel/orchestrate.rs +980 -581
  410. data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
  411. data/rust/src/kernel/pattern.rs +107 -34
  412. data/rust/src/kernel/query_comparators.rs +118 -41
  413. data/rust/src/kernel/query_ordering.rs +13 -5
  414. data/rust/src/kernel/read_model.rs +479 -181
  415. data/rust/src/kernel/reference_lookup.rs +122 -26
  416. data/rust/src/kernel/repository.rs +293 -66
  417. data/rust/src/kernel/routing.rs +58 -42
  418. data/rust/src/kernel/ruby_float.rs +139 -0
  419. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
  420. data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
  421. data/rust/src/kernel/vocab/field_hint.rs +19 -25
  422. data/rust/src/kernel/vocab/mod.rs +6 -6
  423. data/rust/src/kernel/vocab/query_comparator.rs +23 -24
  424. data/rust/src/kernel/vocab/refusal_template.rs +501 -297
  425. data/rust/src/lib.rs +50 -0
  426. data/rust/src/main.rs +4 -1
  427. data/rust/web/Cargo.toml +36 -0
  428. data/rust/web/clippy.toml +4 -0
  429. metadata +85 -15
  430. data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
  431. data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
  432. data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
  433. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
  434. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
  435. data/lib/hecks/ports/authentication.port +0 -6
  436. data/lib/hecks/ports/authentication.rb +0 -67
  437. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
  438. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
  439. data/prebuilt/verdicts-48318ca172edd21443b1ad02801c697fff100b746e828f09db987647641a5df2.json +0 -1
  440. data/rust/host/src/approval.rs +0 -652
@@ -98,7 +98,8 @@ pub fn parse_body(
98
98
  // An inline type-position `one_of(...)` set is returned beside the value object (see
99
99
  // build/closed_sets.rs); `ValueObject` has no nested-value-objects field.
100
100
  "attribute" => {
101
- let (attribute, one_of, closed_set) = build_value_object_attribute(file, &gated.args)?;
101
+ let (attribute, one_of, closed_set) =
102
+ build_value_object_attribute(file, &gated.args)?;
102
103
  synthesized.extend(closed_set);
103
104
  if let Some(values) = one_of {
104
105
  let vo_name = vo.name.clone();
@@ -140,7 +141,11 @@ pub fn parse_body(
140
141
  fn build_value_object_attribute(
141
142
  file: &str,
142
143
  args: &super::ArgumentGateResult,
143
- ) -> ParseResult<(ir::Attribute, Option<Vec<ruby_value::Value>>, Option<ir::ValueObject>)> {
144
+ ) -> ParseResult<(
145
+ ir::Attribute,
146
+ Option<Vec<ruby_value::Value>>,
147
+ Option<ir::ValueObject>,
148
+ )> {
144
149
  let (attribute, closed_set) = super::build_attribute(file, 0, "attribute", args)?;
145
150
  let one_of = super::named_raw(args, "one_of").map(|raw| {
146
151
  let trimmed = raw.trim();
@@ -0,0 +1,322 @@
1
+ //! The `World` construct: a `.world` file's `Hecks.world "Name" do ... end` blocks, each holding
2
+ //! the scalar words `realm`, `latest`, `default_adapter`, `default_database` and any number of
3
+ //! open-vocabulary bindings (`persisted_by("Heki") do ... end`, `launcher "Launcher", k: v`).
4
+ //! Printed by `hecks-parse world`; worlds never enter `ir.json`.
5
+ //!
6
+ //! The settings bag is open, so a binding verb has no keyword row: it is shape-matched, and only
7
+ //! its verb, adapter and setting names are recorded. Setting values are never read into the IR
8
+ //! (they can be secrets) and nothing is ever evaluated.
9
+
10
+ use crate::diag::{Diagnostic, ParseResult};
11
+ use crate::ir;
12
+ use crate::keywords;
13
+ use crate::lex::{self, LineShape, Opener, SourceLine};
14
+ use crate::ruby_value::{self, Value};
15
+
16
+ /// Ruby methods that do something rather than name a binding. The settings bag is open, so these
17
+ /// are the words refused by name: a `.world` file only declares, it never runs anything.
18
+ const RUBY_KERNEL_WORDS: &[&str] = &[
19
+ "puts",
20
+ "print",
21
+ "p",
22
+ "pp",
23
+ "require",
24
+ "require_relative",
25
+ "load",
26
+ "eval",
27
+ "instance_eval",
28
+ "system",
29
+ "exec",
30
+ "spawn",
31
+ "raise",
32
+ "fail",
33
+ "abort",
34
+ "exit",
35
+ "sleep",
36
+ "send",
37
+ "public_send",
38
+ "binding",
39
+ "at_exit",
40
+ "warn",
41
+ "gets",
42
+ "open",
43
+ ];
44
+
45
+ /// Parses every top-level `world` block of one `.world` file, in file order, unmerged.
46
+ pub fn parse_file(path: &str, source: &str) -> ParseResult<Vec<ir::World>> {
47
+ let joined = lex::join_continuations(source);
48
+ let lines = lex::lines(&joined);
49
+ let mut pos = 0usize;
50
+ let mut worlds = Vec::new();
51
+
52
+ while pos < lines.len() {
53
+ let (row, call, header_line) = super::file::parse_header(path, &lines, &mut pos)?;
54
+ if row.inner != "World" {
55
+ return Err(Diagnostic::new(
56
+ path,
57
+ header_line,
58
+ format!(
59
+ "a .world file's own top-level blocks must all be 'world'; got '{}'",
60
+ call.word
61
+ ),
62
+ ));
63
+ }
64
+ let domain = super::file::header_name(&call)
65
+ .filter(|name| !name.is_empty())
66
+ .ok_or_else(|| {
67
+ Diagnostic::new(path, header_line, "'world' needs a non-empty quoted name")
68
+ })?;
69
+ worlds.push(parse_body(path, &lines, &mut pos, domain)?);
70
+ }
71
+ Ok(worlds)
72
+ }
73
+
74
+ /// Folds blocks into one world per distinct domain, in order of first appearance, as the Ruby
75
+ /// registry does: a later scalar replaces an earlier one; a later binding replaces the earlier
76
+ /// binding with the same key wholesale, in the earlier slot; new keys append.
77
+ pub fn merge(blocks: Vec<ir::World>) -> Vec<ir::World> {
78
+ let mut merged: Vec<ir::World> = Vec::new();
79
+ for block in blocks {
80
+ let Some(existing) = merged.iter_mut().find(|w| w.domain == block.domain) else {
81
+ merged.push(block);
82
+ continue;
83
+ };
84
+ if block.realm.is_some() {
85
+ existing.realm = block.realm;
86
+ }
87
+ if block.latest.is_some() {
88
+ existing.latest = block.latest;
89
+ }
90
+ if block.default_adapter.is_some() {
91
+ existing.default_adapter = block.default_adapter;
92
+ }
93
+ existing.default_database |= block.default_database;
94
+ for binding in block.bindings {
95
+ upsert(&mut existing.bindings, binding);
96
+ }
97
+ }
98
+ merged
99
+ }
100
+
101
+ /// A repeated (verb, adapter) key keeps its first position with the last contents.
102
+ fn upsert(bindings: &mut Vec<ir::Binding>, binding: ir::Binding) {
103
+ let same = |b: &ir::Binding| {
104
+ b.verb == binding.verb && b.adapter.to_lowercase() == binding.adapter.to_lowercase()
105
+ };
106
+ match bindings.iter_mut().find(|b| same(b)) {
107
+ Some(slot) => *slot = binding,
108
+ None => bindings.push(binding),
109
+ }
110
+ }
111
+
112
+ fn parse_body(
113
+ file: &str,
114
+ lines: &[SourceLine],
115
+ pos: &mut usize,
116
+ domain: String,
117
+ ) -> ParseResult<ir::World> {
118
+ let mut world = ir::World {
119
+ domain,
120
+ ..ir::World::default()
121
+ };
122
+ loop {
123
+ let line = *lines.get(*pos).ok_or_else(|| {
124
+ let last = lines.last().map_or(0, |l| l.number);
125
+ Diagnostic::new(file, last, "unexpected end of file — still inside World")
126
+ })?;
127
+ let LineShape::Call(call) = lex::classify(file, &line)? else {
128
+ *pos += 1;
129
+ return Ok(world);
130
+ };
131
+ match call.word.as_str() {
132
+ "default_database" => {
133
+ *pos += 1;
134
+ world.default_database = default_database_set(file, line.number, &call)?;
135
+ }
136
+ "realm" | "latest" | "default_adapter" => {
137
+ let gated = super::next_line(file, lines, pos, "World")?.ok_or_else(|| {
138
+ Diagnostic::new(file, line.number, "unexpected 'end' inside World")
139
+ })?;
140
+ let value = scalar_text(file, line.number, &call.word, &gated.args)?;
141
+ match call.word.as_str() {
142
+ "realm" => world.realm = Some(value),
143
+ "latest" => world.latest = Some(value),
144
+ _ => world.default_adapter = Some(value),
145
+ }
146
+ }
147
+ _ => {
148
+ *pos += 1;
149
+ let binding = parse_binding(file, lines, pos, line.number, &call)?;
150
+ upsert(&mut world.bindings, binding);
151
+ }
152
+ }
153
+ }
154
+ }
155
+
156
+ /// A non-empty string literal, as Ruby's world language requires of these words.
157
+ fn scalar_text(
158
+ file: &str,
159
+ line: usize,
160
+ word: &str,
161
+ args: &super::ArgumentGateResult,
162
+ ) -> ParseResult<String> {
163
+ let raw = args.positional.first().map_or("", |(_, v)| v.trim());
164
+ if !raw.starts_with('"') {
165
+ return Err(Diagnostic::new(
166
+ file,
167
+ line,
168
+ format!("'{word}' takes a quoted string"),
169
+ ));
170
+ }
171
+ let text = super::positional_text(file, line, word, args, 1)?;
172
+ if text.is_empty() {
173
+ return Err(Diagnostic::new(
174
+ file,
175
+ line,
176
+ format!("'{word}' must not be empty"),
177
+ ));
178
+ }
179
+ Ok(text)
180
+ }
181
+
182
+ /// `default_database` takes a string or any Ruby expression (an environment lookup). The expression
183
+ /// is neither evaluated nor kept: only that it was set. A literal empty string or `nil` refuses.
184
+ fn default_database_set(file: &str, line: usize, call: &lex::Call) -> ParseResult<bool> {
185
+ let items = ruby_value::split_items(&call.args);
186
+ let single = items.len() == 1 && call.opener == Opener::None;
187
+ if !single || super::as_named(&items[0]).is_some() {
188
+ return Err(Diagnostic::new(
189
+ file,
190
+ line,
191
+ "'default_database' takes exactly one value and no block",
192
+ ));
193
+ }
194
+ if matches!(ruby_value::read(&items[0]), Value::Str(ref s) if s.is_empty()) || items[0] == "nil"
195
+ {
196
+ return Err(Diagnostic::new(
197
+ file,
198
+ line,
199
+ "'default_database' must not be empty",
200
+ ));
201
+ }
202
+ Ok(true)
203
+ }
204
+
205
+ /// One binding call: `verb("Adapter") do ... end`, or `verb "Adapter", k: v, ...` with no block.
206
+ /// Records the verb, the first positional string, the keyword names, then the body's top-level
207
+ /// keys. A verb that is a grammar word elsewhere is refused, as Ruby's word gate refuses it.
208
+ fn parse_binding(
209
+ file: &str,
210
+ lines: &[SourceLine],
211
+ pos: &mut usize,
212
+ number: usize,
213
+ call: &lex::Call,
214
+ ) -> ParseResult<ir::Binding> {
215
+ if RUBY_KERNEL_WORDS.contains(&call.word.as_str()) {
216
+ return Err(Diagnostic::new(
217
+ file,
218
+ number,
219
+ format!(
220
+ "'{}' is a Ruby method, not a binding verb; a .world file only declares",
221
+ call.word
222
+ ),
223
+ ));
224
+ }
225
+ if keywords::KEYWORDS
226
+ .iter()
227
+ .any(|k| k.live() && k.word == call.word)
228
+ {
229
+ return Err(Diagnostic::new(
230
+ file,
231
+ number,
232
+ format!(
233
+ "'{}' is a grammar word, not a binding verb, and World does not admit it",
234
+ call.word
235
+ ),
236
+ ));
237
+ }
238
+ let mut adapter = None;
239
+ let mut settings: Vec<String> = Vec::new();
240
+ for item in ruby_value::split_items(&call.args) {
241
+ if let Some((name, _value)) = super::as_named(&item) {
242
+ push_unique(&mut settings, name.to_string());
243
+ } else if adapter.is_none() && item.starts_with('"') {
244
+ adapter = Some(match ruby_value::read(&item) {
245
+ Value::Str(s) => s,
246
+ other => ruby_value::to_s(&other),
247
+ });
248
+ } else {
249
+ return Err(Diagnostic::new(
250
+ file,
251
+ number,
252
+ format!(
253
+ "'{}' takes one quoted adapter name, then keyword arguments",
254
+ call.word
255
+ ),
256
+ ));
257
+ }
258
+ }
259
+ let adapter = adapter.ok_or_else(|| {
260
+ Diagnostic::new(
261
+ file,
262
+ number,
263
+ format!("'{}' needs a quoted adapter name", call.word),
264
+ )
265
+ })?;
266
+ match call.opener {
267
+ Opener::None => {}
268
+ Opener::DoBlock { .. } => body_keys(file, lines, pos, &mut settings)?,
269
+ Opener::BraceBlock { .. } => {
270
+ return Err(Diagnostic::new(
271
+ file,
272
+ number,
273
+ format!("'{}' takes a `do ... end` block, not braces", call.word),
274
+ ))
275
+ }
276
+ }
277
+ Ok(ir::Binding {
278
+ verb: call.word.clone(),
279
+ adapter,
280
+ settings,
281
+ })
282
+ }
283
+
284
+ /// Collects a binding block's top-level setting names, tracking nested blocks through the shape
285
+ /// gate so a malformed line inside still refuses. Values are not read.
286
+ fn body_keys(
287
+ file: &str,
288
+ lines: &[SourceLine],
289
+ pos: &mut usize,
290
+ settings: &mut Vec<String>,
291
+ ) -> ParseResult<()> {
292
+ let mut depth = 1;
293
+ while depth > 0 {
294
+ let line = *lines.get(*pos).ok_or_else(|| {
295
+ let last = lines.last().map_or(0, |l| l.number);
296
+ Diagnostic::new(
297
+ file,
298
+ last,
299
+ "unexpected end of file — still inside a binding block",
300
+ )
301
+ })?;
302
+ *pos += 1;
303
+ match lex::classify(file, &line)? {
304
+ LineShape::End => depth -= 1,
305
+ LineShape::Call(call) => {
306
+ if depth == 1 {
307
+ push_unique(settings, call.word.clone());
308
+ }
309
+ if matches!(call.opener, Opener::DoBlock { .. }) {
310
+ depth += 1;
311
+ }
312
+ }
313
+ }
314
+ }
315
+ Ok(())
316
+ }
317
+
318
+ fn push_unique(names: &mut Vec<String>, name: String) {
319
+ if !names.contains(&name) {
320
+ names.push(name);
321
+ }
322
+ }
@@ -10,8 +10,8 @@ pub enum Value {
10
10
  Float(f64),
11
11
  Str(String),
12
12
  Symbol(String),
13
- Hash(Vec<(String, Value)>),
14
- Array(Vec<Value>),
13
+ Hash(Vec<(String, Self)>),
14
+ Array(Vec<Self>),
15
15
  /// A bare word `Literal.read` tolerates; kept apart from `Str` so `render` adds no quotes.
16
16
  Bare(String),
17
17
  }
@@ -306,9 +306,7 @@ fn scan_adjacent_strings(raw: &str) -> Option<(String, usize)> {
306
306
  }
307
307
 
308
308
  // Defensive: return `None` rather than panic on an empty or odd `raw`.
309
- let Some(&quote) = chars.get(i).filter(|c| **c == '"' || **c == '\'') else {
310
- return None;
311
- };
309
+ let &quote = chars.get(i).filter(|c| **c == '"' || **c == '\'')?;
312
310
 
313
311
  let start = i;
314
312
  i += 1;
@@ -487,7 +485,10 @@ mod tests {
487
485
  #[test]
488
486
  fn double_quoted_escapes_read_as_ruby_reads_them() {
489
487
  assert_eq!(read(r#""a\tb\nc""#), Value::Str("a\tb\nc".to_string()));
490
- assert_eq!(read(r#""\e\s\a\0""#), Value::Str("\u{1b} \u{7}\0".to_string()));
488
+ assert_eq!(
489
+ read(r#""\e\s\a\0""#),
490
+ Value::Str("\u{1b} \u{7}\0".to_string())
491
+ );
491
492
  assert_eq!(
492
493
  read(r#""\x41\101é\u{1F4A5 41}""#),
493
494
  Value::Str("AA\u{e9}\u{1F4A5}A".to_string())
@@ -10,7 +10,7 @@ impl TmplKind {
10
10
  pub fn to_json(&self) -> crate::kernel::Json {
11
11
  let member = match self {
12
12
  // TMPL:closed_set_codec:TO_JSON_ARM BEGIN
13
- TmplKind::TmplMemberA => "tmpl_member_a",
13
+ Self::TmplMemberA => "tmpl_member_a",
14
14
  // TMPL:closed_set_codec:TO_JSON_ARM END
15
15
  };
16
16
  crate::kernel::Json::obj(vec![("tmpl_field_name", crate::kernel::Json::str(member))])
@@ -21,7 +21,7 @@ impl TmplKind {
21
21
  // does in `validate!`: before a missing field, and before membership.
22
22
  let unknown = v.unknown_keys(&["tmpl_field_name"]);
23
23
  if !unknown.is_empty() {
24
- let unknown: Vec<&str> = unknown.iter().map(|key| key.as_str()).collect();
24
+ let unknown: Vec<&str> = unknown.iter().map(String::as_str).collect();
25
25
  return Err(crate::kernel::Refusal::UnknownArgument(
26
26
  crate::kernel::refusal_wording::UnknownArgumentUnknownArgsArgs {
27
27
  command: "tmpl_closed_set_type",
@@ -72,7 +72,7 @@ impl TmplKind {
72
72
  }
73
73
  match candidate.ruby_to_s().as_str() {
74
74
  // TMPL:closed_set_codec:FROM_JSON_ARM BEGIN
75
- "tmpl_member_a" => Ok(TmplKind::TmplMemberA),
75
+ "tmpl_member_a" => Ok(Self::TmplMemberA),
76
76
  // TMPL:closed_set_codec:FROM_JSON_ARM END
77
77
  _ => Err(crate::kernel::Refusal::InvariantViolation(
78
78
  crate::kernel::refusal_wording::InvariantViolationClosedSetMemberArgs {
@@ -134,7 +134,7 @@ tmpl_to_json_fields_block()
134
134
  // membership of the whole row.
135
135
  let unknown = v.unknown_keys(&TMPL_DECLARED_NAMES);
136
136
  if !unknown.is_empty() {
137
- let unknown: Vec<&str> = unknown.iter().map(|key| key.as_str()).collect();
137
+ let unknown: Vec<&str> = unknown.iter().map(String::as_str).collect();
138
138
  return Err(crate::kernel::Refusal::UnknownArgument(
139
139
  crate::kernel::refusal_wording::UnknownArgumentUnknownArgsArgs {
140
140
  command: "tmpl_table_type",
@@ -78,3 +78,21 @@ fn tmpl_mutation_remove_host(record: &mut TmplRemoveHost) {
78
78
  record.tmpl_field.retain(|item| item.tmpl_id_field != tmpl_remove_match_placeholder());
79
79
  // TMPL:mutation_remove END
80
80
  }
81
+
82
+ // `remove:` on a value-object list drops every element equal by value to the argument, so
83
+ // the element type derives `PartialEq` and the retained ones are those that differ.
84
+ #[derive(PartialEq)]
85
+ struct TmplRemoveValueElement {
86
+ tmpl_value: i64,
87
+ }
88
+ struct TmplRemoveValueHost {
89
+ tmpl_field: Vec<TmplRemoveValueElement>,
90
+ }
91
+ fn tmpl_remove_value_placeholder() -> TmplRemoveValueElement {
92
+ TmplRemoveValueElement { tmpl_value: 0 }
93
+ }
94
+ fn tmpl_mutation_remove_value_host(record: &mut TmplRemoveValueHost) {
95
+ // TMPL:mutation_remove_value BEGIN
96
+ record.tmpl_field.retain(|item| *item != tmpl_remove_value_placeholder());
97
+ // TMPL:mutation_remove_value END
98
+ }
@@ -33,16 +33,7 @@ crate::kernel::read_model::ReadModelDef {
33
33
  ],
34
34
  authorization: Some(crate::kernel::named_query::TenantAuth { query_name: "tmpl_query_name", tenant_field: "tmpl_tenant_field", policy: "tmpl_policy" }),
35
35
  group_by: None,
36
- count: false,
37
- median_field: None,
38
- sum_field: None,
39
- avg_field: None,
40
- min_field: None,
41
- max_field: None,
42
- percentile_field: None,
43
- percentile_at: None,
44
- any_field: None,
45
- all_field: None,
36
+ reduction: None,
46
37
  },
47
38
  ];
48
39
  // TMPL:read_model_table END
@@ -5,9 +5,11 @@ use crate::kernel::Refusal;
5
5
 
6
6
  /// A scalar (or `Nil`) with segments left resolves to `Nil`, like Ruby's `walk_path`.
7
7
  /// This makes `flag.nil?` read as a value instead of a refusal.
8
- pub fn step<'a>(current: Field<'a>, seg: &str, head: &str) -> Result<Field<'a>, Refusal> {
8
+ pub fn step<'a>(current: &Field<'a>, seg: &str, head: &str) -> Result<Field<'a>, Refusal> {
9
9
  match current {
10
- Field::Nested(obj) => obj.field(seg).ok_or_else(|| eval_error(format!("cannot resolve {seg:?} on {head:?}"))),
10
+ Field::Nested(obj) => (*obj)
11
+ .field(seg)
12
+ .ok_or_else(|| eval_error(format!("cannot resolve {seg:?} on {head:?}"))),
11
13
  Field::Value(_) => Ok(Field::Value(Value::Nil)),
12
14
  }
13
15
  }
@@ -16,7 +18,9 @@ pub fn step<'a>(current: Field<'a>, seg: &str, head: &str) -> Result<Field<'a>,
16
18
  pub fn finish(current: Field<'_>, path: &str) -> Result<Value, Refusal> {
17
19
  match current {
18
20
  Field::Value(v) => Ok(v),
19
- Field::Nested(obj) => obj.as_scalar().ok_or_else(|| eval_error(format!("{path} resolved to an object, not a scalar"))),
21
+ Field::Nested(obj) => obj
22
+ .as_scalar()
23
+ .ok_or_else(|| eval_error(format!("{path} resolved to an object, not a scalar"))),
20
24
  }
21
25
  }
22
26
 
@@ -39,50 +43,86 @@ mod tests {
39
43
  }
40
44
 
41
45
  // `Field` derives neither `Debug` nor `PartialEq`; assertions compare the `finish`ed `Value`.
42
- fn stepped_value(current: Field<'_>, seg: &str, head: &str) -> Value {
46
+ fn stepped_value(current: &Field<'_>, seg: &str, head: &str) -> Value {
43
47
  finish(step(current, seg, head).unwrap(), "test.path").unwrap()
44
48
  }
45
49
 
46
50
  #[test]
47
51
  fn a_further_segment_on_a_bool_scalar_resolves_to_nil_not_a_refusal() {
48
52
  // `.nil?` has no dedicated node, so it arrives as a trailing segment on a resolved scalar.
49
- assert_eq!(stepped_value(Field::Value(Value::Bool(true)), "nil?", "previous_sessions"), Value::Nil);
53
+ assert_eq!(
54
+ stepped_value(
55
+ &Field::Value(Value::Bool(true)),
56
+ "nil?",
57
+ "previous_sessions"
58
+ ),
59
+ Value::Nil
60
+ );
50
61
  }
51
62
 
52
63
  #[test]
53
64
  fn this_holds_for_a_false_scalar_too() {
54
- assert_eq!(stepped_value(Field::Value(Value::Bool(false)), "nil?", "previous_sessions"), Value::Nil);
65
+ assert_eq!(
66
+ stepped_value(
67
+ &Field::Value(Value::Bool(false)),
68
+ "nil?",
69
+ "previous_sessions"
70
+ ),
71
+ Value::Nil
72
+ );
55
73
  }
56
74
 
57
75
  #[test]
58
76
  fn this_holds_for_every_scalar_shape_not_just_bool() {
59
- for value in [Value::Int(0), Value::Float(1.5), Value::Str("x".to_string()), Value::List(3)] {
60
- assert_eq!(stepped_value(Field::Value(value.clone()), "nil?", "head"), Value::Nil, "expected Nil for {value:?}");
77
+ for value in [
78
+ Value::Int(0),
79
+ Value::Float(1.5),
80
+ Value::Str("x".to_string()),
81
+ Value::List(3),
82
+ ] {
83
+ assert_eq!(
84
+ stepped_value(&Field::Value(value.clone()), "nil?", "head"),
85
+ Value::Nil,
86
+ "expected Nil for {value:?}"
87
+ );
61
88
  }
62
89
  }
63
90
 
64
91
  #[test]
65
92
  fn a_further_segment_on_an_already_nil_value_stays_nil() {
66
93
  // An unset optional field already reads as `Nil` before `step` runs.
67
- assert_eq!(stepped_value(Field::Value(Value::Nil), "nil?", "superseded_by"), Value::Nil);
94
+ assert_eq!(
95
+ stepped_value(&Field::Value(Value::Nil), "nil?", "superseded_by"),
96
+ Value::Nil
97
+ );
68
98
  }
69
99
 
70
100
  #[test]
71
101
  fn a_real_nested_field_still_walks_normally() {
72
- let customer = Customer { status: "active".to_string() };
73
- assert_eq!(stepped_value(Field::Nested(&customer), "status", "customer"), Value::Str("active".to_string()));
102
+ let customer = Customer {
103
+ status: "active".to_string(),
104
+ };
105
+ assert_eq!(
106
+ stepped_value(&Field::Nested(&customer), "status", "customer"),
107
+ Value::Str("active".to_string())
108
+ );
74
109
  }
75
110
 
76
111
  #[test]
77
112
  fn an_unknown_nested_field_still_refuses() {
78
113
  // A generated `field()` is exhaustive, so an unknown name is a codegen bug: keep refusing.
79
- let customer = Customer { status: "active".to_string() };
80
- let stepped = step(Field::Nested(&customer), "not_a_real_field", "customer");
114
+ let customer = Customer {
115
+ status: "active".to_string(),
116
+ };
117
+ let stepped = step(&Field::Nested(&customer), "not_a_real_field", "customer");
81
118
  assert!(stepped.is_err());
82
119
  }
83
120
 
84
121
  #[test]
85
122
  fn finish_unwraps_a_resolved_nil_to_a_plain_value() {
86
- assert_eq!(finish(Field::Value(Value::Nil), "previous_sessions.nil?").unwrap(), Value::Nil);
123
+ assert_eq!(
124
+ finish(Field::Value(Value::Nil), "previous_sessions.nil?").unwrap(),
125
+ Value::Nil
126
+ );
87
127
  }
88
128
  }
@@ -3,14 +3,14 @@
3
3
 
4
4
  use crate::kernel::expr::Value;
5
5
 
6
- pub fn is_empty(v: &Value) -> Option<bool> {
6
+ pub const fn is_empty(v: &Value) -> Option<bool> {
7
7
  match v {
8
8
  Value::List(n) => Some(*n == 0),
9
9
  _ => None,
10
10
  }
11
11
  }
12
12
 
13
- pub fn size(v: &Value) -> Option<i64> {
13
+ pub const fn size(v: &Value) -> Option<i64> {
14
14
  match v {
15
15
  Value::List(n) => Some(*n as i64),
16
16
  _ => None,
@@ -6,10 +6,10 @@
6
6
  // `for_attribute` itself takes, read directly rather than
7
7
  // re-derived here.
8
8
 
9
- pub mod scalar;
9
+ pub mod composite;
10
10
  pub mod list;
11
11
  pub mod optional;
12
- pub mod composite;
12
+ pub mod scalar;
13
13
 
14
14
  /// One variant per real attribute shape the Ruby
15
15
  /// grammar admits, in the order Coercion::SHAPES declares them. Every match
@@ -31,14 +31,14 @@ impl AttributeShape {
31
31
  /// variant names — `hecks measure_kernel_coverage`'s own existence
32
32
  /// check reads this, so the file-name spelling here is the ONE
33
33
  /// place that ever needs to change if a name is renamed.
34
- pub fn module_name(&self) -> &'static str {
34
+ pub const fn module_name(&self) -> &'static str {
35
35
  match self {
36
- AttributeShape::Scalar => "scalar",
37
- AttributeShape::List => "list",
38
- AttributeShape::Optional => "optional",
39
- AttributeShape::Composite => "composite",
36
+ Self::Scalar => "scalar",
37
+ Self::List => "list",
38
+ Self::Optional => "optional",
39
+ Self::Composite => "composite",
40
40
  }
41
41
  }
42
42
 
43
- pub const ALL: &'static [AttributeShape] = &[AttributeShape::Scalar, AttributeShape::List, AttributeShape::Optional, AttributeShape::Composite];
43
+ pub const ALL: &'static [Self] = &[Self::Scalar, Self::List, Self::Optional, Self::Composite];
44
44
  }
@@ -4,7 +4,7 @@
4
4
  use crate::kernel::expr::Value;
5
5
 
6
6
  // Mirrors `Resolver.numeric`: `Int` and `Float` mix freely; any other `Value` is not a number.
7
- pub fn numeric(v: &Value) -> Option<f64> {
7
+ pub const fn numeric(v: &Value) -> Option<f64> {
8
8
  match v {
9
9
  Value::Int(i) => Some(*i as f64),
10
10
  Value::Float(f) => Some(*f),
@@ -24,7 +24,7 @@ pub fn to_s(v: &Value) -> Option<Value> {
24
24
  }
25
25
 
26
26
  // Only `Str` has a length; `Int`/`Float`/`Bool` answer `None` so the caller refuses.
27
- pub fn is_empty(v: &Value) -> Option<bool> {
27
+ pub const fn is_empty(v: &Value) -> Option<bool> {
28
28
  match v {
29
29
  Value::Str(s) => Some(s.is_empty()),
30
30
  _ => None,