hecks 2.9.0 → 3.0.1

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 (600) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +51 -6
  3. data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
  4. data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
  5. data/lib/hecks/adapters/driven/lambda.rb +1 -1
  6. data/lib/hecks/adapters/driven/memory.rb +16 -3
  7. data/lib/hecks/adapters/driven/postgres/reconnect.rb +6 -2
  8. data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
  9. data/lib/hecks/adapters/driven/postgres.rb +13 -4
  10. data/lib/hecks/adapters/driven/prism.rb +41 -8
  11. data/lib/hecks/adapters/driven/tenant_provisioner.rb +73 -16
  12. data/lib/hecks/behaviors/expectations.rb +1 -1
  13. data/lib/hecks/behaviors.rb +1 -1
  14. data/lib/hecks/bench/cli.rb +62 -0
  15. data/lib/hecks/bench/environment.rb +71 -0
  16. data/lib/hecks/bench/postgres_probe.rb +30 -0
  17. data/lib/hecks/bench/report.rb +76 -0
  18. data/lib/hecks/bench/ruby_runner.rb +93 -0
  19. data/lib/hecks/bench/run.rb +55 -0
  20. data/lib/hecks/bench/rust_runner.rb +101 -0
  21. data/lib/hecks/bench/stats.rb +46 -0
  22. data/lib/hecks/bench/suite.rb +98 -0
  23. data/lib/hecks/bench/workload.rb +89 -0
  24. data/lib/hecks/bench.rb +17 -0
  25. data/lib/hecks/bluebook/assembly/contracts.rb +2 -0
  26. data/lib/hecks/bluebook/attribute.rb +1 -1
  27. data/lib/hecks/bluebook/behaviour/aggregate.rb +30 -1
  28. data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -0
  29. data/lib/hecks/bluebook/chapter.rb +6 -2
  30. data/lib/hecks/bluebook/domain_port.rb +36 -5
  31. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +10 -0
  32. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +8 -0
  33. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -1
  34. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +31 -3
  35. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +122 -3
  36. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +1 -1
  37. data/lib/hecks/bluebook/dsl/query_builder.rb +27 -1
  38. data/lib/hecks/bluebook/dsl/rule_reference.rb +1 -1
  39. data/lib/hecks/bluebook/dsl/world_builder.rb +29 -2
  40. data/lib/hecks/bluebook/expression/canonical_form.rb +1 -1
  41. data/lib/hecks/bluebook/expression/resolver.rb +3 -2
  42. data/lib/hecks/bluebook/hexagon.rb +13 -5
  43. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -0
  44. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +4 -1
  45. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
  46. data/lib/hecks/bluebook/meta_validator.rb +9 -4
  47. data/lib/hecks/bluebook/model_check.rb +23 -0
  48. data/lib/hecks/bluebook/pattern_subset.rb +38 -0
  49. data/lib/hecks/bluebook/query.rb +20 -5
  50. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  51. data/lib/hecks/canonical_json.rb +30 -0
  52. data/lib/hecks/chapters.rb +102 -0
  53. data/lib/hecks/cli/behaviors.rb +61 -0
  54. data/lib/hecks/cli/canonicalise.rb +25 -0
  55. data/lib/hecks/cli/check_engine_agreement.rb +32 -0
  56. data/lib/hecks/cli/check_era.rb +88 -0
  57. data/lib/hecks/cli/console.rb +66 -0
  58. data/lib/hecks/cli/deploy_template_diff.rb +40 -0
  59. data/lib/hecks/cli/doc_coverage.rb +32 -0
  60. data/lib/hecks/cli/document.rb +2 -2
  61. data/lib/hecks/cli/expression_projection.rb +41 -0
  62. data/lib/hecks/cli/follow.rb +88 -0
  63. data/lib/hecks/cli/history.rb +44 -0
  64. data/lib/hecks/cli/ir.rb +1 -1
  65. data/lib/hecks/cli/mcp.rb +3 -3
  66. data/lib/hecks/cli/model_check.rb +15 -8
  67. data/lib/hecks/cli/pattern_cases.rb +47 -0
  68. data/lib/hecks/cli/present.rb +39 -0
  69. data/lib/hecks/cli/project_cli.rb +224 -32
  70. data/lib/hecks/cli/project_diagrams.rb +4 -3
  71. data/lib/hecks/cli/project_glossary.rb +44 -0
  72. data/lib/hecks/cli/reference.rb +46 -0
  73. data/lib/hecks/cli/refresh_projections.rb +44 -0
  74. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
  75. data/lib/hecks/cli/release.rb +70 -0
  76. data/lib/hecks/cli/release_gem.rb +59 -0
  77. data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
  78. data/lib/hecks/cli/rspec_shard_files.rb +66 -0
  79. data/lib/hecks/cli/run.rb +3 -3
  80. data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
  81. data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
  82. data/lib/hecks/cli/shape.rb +70 -0
  83. data/lib/hecks/cli/smoke_http.rb +230 -0
  84. data/lib/hecks/cli/smoke_test.rb +5 -3
  85. data/lib/hecks/cli/spec_example.rb +27 -0
  86. data/lib/hecks/cli/statements.rb +32 -0
  87. data/lib/hecks/cli/stores.rb +4 -5
  88. data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
  89. data/lib/hecks/cli.rb +201 -52
  90. data/lib/hecks/codemod.rb +324 -0
  91. data/lib/hecks/corpus.rb +423 -0
  92. data/lib/hecks/deploy/bluebook/deploy.bluebook +591 -23
  93. data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -4
  94. data/lib/hecks/deploy/oidc.json +82 -2
  95. data/lib/hecks/doc/reference.rb +351 -0
  96. data/lib/hecks/doors/cli_door.rb +196 -0
  97. data/lib/hecks/doors/cli_runner.rb +297 -0
  98. data/lib/hecks/{facade → doors}/command_request.rb +1 -1
  99. data/lib/hecks/{facade → doors}/handle.rb +2 -2
  100. data/lib/hecks/{facade → doors}/json_door.rb +4 -4
  101. data/lib/hecks/doors/launcher_options.rb +129 -0
  102. data/lib/hecks/{cli → doors}/mcp_door.rb +7 -7
  103. data/lib/hecks/doors/mcp_door_scope.rb +97 -0
  104. data/lib/hecks/{facade/surface → doors/ruby_door}/aggregate_door.rb +8 -3
  105. data/lib/hecks/{facade/surface → doors/ruby_door}/chapter.rb +3 -3
  106. data/lib/hecks/{facade/surface.rb → doors/ruby_door.rb} +36 -5
  107. data/lib/hecks/doors.rb +61 -0
  108. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +1 -1
  109. data/lib/hecks/embryonaut_bluebook.rb +1 -1
  110. data/lib/hecks/engine_agreement.rb +156 -0
  111. data/lib/hecks/facade.rb +24 -37
  112. data/lib/hecks/forms/app.rb +6 -6
  113. data/lib/hecks/forms/banking_presentation.rb +50 -0
  114. data/lib/hecks/forms/field_shape.rb +2 -1
  115. data/lib/hecks/forms/port_argument.rb +1 -1
  116. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
  117. data/lib/hecks/fuzzing/combination_miner.rb +86 -0
  118. data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
  119. data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
  120. data/lib/hecks/fuzzing/differential.rb +156 -0
  121. data/lib/hecks/fuzzing/domain_generator.rb +615 -0
  122. data/lib/hecks/fuzzing/era_boundary.rb +71 -0
  123. data/lib/hecks/fuzzing/form_census.rb +136 -0
  124. data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
  125. data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
  126. data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
  127. data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
  128. data/lib/hecks/fuzzing/outside_world.rb +45 -0
  129. data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
  130. data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
  131. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
  132. data/lib/hecks/fuzzing/properties/guards.rb +190 -0
  133. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
  134. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
  135. data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
  136. data/lib/hecks/fuzzing/properties/querying.rb +175 -0
  137. data/lib/hecks/fuzzing/properties.rb +162 -0
  138. data/lib/hecks/fuzzing/qa_settings.rb +123 -0
  139. data/lib/hecks/fuzzing/replay.rb +505 -0
  140. data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
  141. data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
  142. data/lib/hecks/fuzzing/self_consistency.rb +339 -0
  143. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
  144. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
  145. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
  146. data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
  147. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
  148. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
  149. data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
  150. data/lib/hecks/fuzzing/shrinker.rb +166 -0
  151. data/lib/hecks/fuzzing/structural_skips.rb +33 -0
  152. data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
  153. data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
  154. data/lib/hecks/fuzzing/value_generator.rb +216 -0
  155. data/lib/hecks/fuzzing.rb +23 -0
  156. data/lib/hecks/grammar/evolve.rb +336 -0
  157. data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
  158. data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
  159. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
  160. data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
  161. data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
  162. data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
  163. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
  164. data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
  165. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
  166. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
  167. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
  168. data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
  169. data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
  170. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
  171. data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
  172. data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
  173. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
  174. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
  175. data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
  176. data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
  177. data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
  178. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
  179. data/lib/hecks/hecks/adapters/git.adapter +7 -0
  180. data/lib/hecks/hecks/adapters/git.rb +102 -0
  181. data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
  182. data/lib/hecks/hecks/adapters/host_http.rb +65 -0
  183. data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
  184. data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
  185. data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
  186. data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
  187. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
  188. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
  189. data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
  190. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
  191. data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
  192. data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
  193. data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
  194. data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
  195. data/lib/hecks/hecks/adapters/local_files.rb +46 -0
  196. data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
  197. data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
  198. data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
  199. data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
  200. data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
  201. data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
  202. data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
  203. data/lib/hecks/hecks/adapters/shell.adapter +7 -0
  204. data/lib/hecks/hecks/adapters/shell.rb +49 -0
  205. data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
  206. data/lib/hecks/hecks/adapters/terminal.rb +74 -0
  207. data/lib/hecks/hecks/codebase.behaviors +172 -0
  208. data/lib/hecks/hecks/codebase.bluebook +2453 -0
  209. data/lib/hecks/hecks/context_map.hecksagon +13 -0
  210. data/lib/hecks/hecks/custodian.behaviors +179 -0
  211. data/lib/hecks/hecks/custodian.bluebook +1847 -0
  212. data/lib/hecks/hecks/deploy.behaviors +74 -0
  213. data/lib/hecks/hecks/deploy.world +12 -0
  214. data/lib/hecks/hecks/environments/memory.world +10 -0
  215. data/lib/hecks/hecks/governance.world +7 -0
  216. data/lib/hecks/hecks/hecks.behaviors +62 -0
  217. data/lib/hecks/hecks/hecks.bluebook +124 -0
  218. data/lib/hecks/hecks/hecks.hecksagon +390 -0
  219. data/lib/hecks/hecks/hecks.world +42 -0
  220. data/lib/hecks/kernel_capabilities.rb +194 -0
  221. data/lib/hecks/language/bluebook/bluebook.bluebook +11 -0
  222. data/lib/hecks/language/bluebook/query.bluebook +7 -0
  223. data/lib/hecks/language/bluebook/vocabulary.bluebook +4 -4
  224. data/lib/hecks/language/hecksagon/domain_port.bluebook +10 -0
  225. data/lib/hecks/language/hecksagon/hecksagon.bluebook +20 -0
  226. data/lib/hecks/mcp_stdio_guard.rb +2 -2
  227. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
  228. data/lib/hecks/persistence_legacy_fixture.rb +113 -0
  229. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +8 -5
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +4 -1
  231. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +15 -2
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +1 -1
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +1 -1
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +32 -15
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +15 -5
  237. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
  238. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +1 -1
  239. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +43 -24
  240. data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
  241. data/lib/hecks/ports/persistence/state_codec.rb +24 -5
  242. data/lib/hecks/projection_files.rb +285 -0
  243. data/lib/hecks/projections/bootstrap_table.rb +1 -1
  244. data/lib/hecks/projections/deploy/fargate.rb +5 -5
  245. data/lib/hecks/projections/deploy/lambda.rb +43 -32
  246. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  247. data/lib/hecks/projections/deploy/preview/template.rb +1 -1
  248. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
  249. data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
  250. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
  251. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
  252. data/lib/hecks/projections/deploy/shared.rb +1 -1
  253. data/lib/hecks/projections/diagrams.rb +3 -3
  254. data/lib/hecks/projections/model/deviations.rb +1 -1
  255. data/lib/hecks/projections/parser_table.rb +4 -4
  256. data/lib/hecks/projections/reference.rb +2 -3
  257. data/lib/hecks/projections/rust_vocabulary.rb +1 -1
  258. data/lib/hecks/projector/cli_projector.rb +106 -37
  259. data/lib/hecks/projector.rb +1 -1
  260. data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
  261. data/lib/hecks/quality_control/adapters/agent.rb +58 -0
  262. data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
  263. data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
  264. data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
  265. data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
  266. data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
  267. data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
  268. data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
  269. data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
  270. data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
  271. data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
  272. data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
  273. data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
  274. data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
  275. data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
  276. data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
  277. data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
  278. data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
  279. data/lib/hecks/quality_control/cli/child.rb +45 -0
  280. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
  281. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
  282. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
  283. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
  284. data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
  285. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
  286. data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
  287. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
  288. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
  289. data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
  290. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
  291. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
  292. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
  293. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
  294. data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
  295. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
  296. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
  297. data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
  298. data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
  299. data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
  300. data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
  301. data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
  302. data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
  303. data/lib/hecks/query_ir.rb +346 -0
  304. data/lib/hecks/query_ir_mcp.rb +198 -0
  305. data/lib/hecks/release/runner/ci_publisher.rb +2 -2
  306. data/lib/hecks/release/runner/clean_tree.rb +42 -0
  307. data/lib/hecks/release/runner/console.rb +6 -0
  308. data/lib/hecks/release/runner/gem_publisher.rb +14 -7
  309. data/lib/hecks/release/runner/npm_publisher.rb +6 -36
  310. data/lib/hecks/release/runner/preflight.rb +26 -2
  311. data/lib/hecks/release/runner/published.rb +8 -26
  312. data/lib/hecks/release/runner/tagger.rb +30 -15
  313. data/lib/hecks/release/runner.rb +57 -6
  314. data/lib/hecks/router/namespace_installer.rb +3 -3
  315. data/lib/hecks/runtime/adapter_lookup.rb +124 -0
  316. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +4 -0
  317. data/lib/hecks/runtime/dependency_planning.rb +20 -2
  318. data/lib/hecks/runtime/dispatcher.rb +53 -3
  319. data/lib/hecks/runtime/interpreting.rb +3 -0
  320. data/lib/hecks/runtime/loader.rb +65 -12
  321. data/lib/hecks/runtime/outbox.rb +1 -1
  322. data/lib/hecks/runtime/policy_interpreter.rb +5 -2
  323. data/lib/hecks/runtime/port_operation_interpreter.rb +2 -10
  324. data/lib/hecks/runtime/query_interpreter.rb +77 -0
  325. data/lib/hecks/runtime/reaction_outcome.rb +89 -0
  326. data/lib/hecks/runtime/refusal_wording.rb +23 -1
  327. data/lib/hecks/runtime/registry/verification.rb +163 -4
  328. data/lib/hecks/runtime/registry.rb +57 -3
  329. data/lib/hecks/runtime/remote_dispatcher.rb +31 -2
  330. data/lib/hecks/runtime/value/coercion.rb +23 -135
  331. data/lib/hecks/runtime/value/field_checks.rb +172 -0
  332. data/lib/hecks/runtime/value.rb +4 -1
  333. data/lib/hecks/runtime.rb +20 -6
  334. data/lib/hecks/rust_build/conformance.rb +158 -0
  335. data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
  336. data/lib/hecks/rust_build/coverage.rb +218 -0
  337. data/lib/hecks/rust_build/native_build.rb +124 -0
  338. data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
  339. data/lib/hecks/rust_build/project_rust.rb +332 -0
  340. data/lib/hecks/rust_build/wasm.rb +105 -0
  341. data/lib/hecks/rust_build/wasm_browser.rb +113 -0
  342. data/lib/hecks/rust_build.rb +144 -0
  343. data/lib/hecks/storehouse.rb +16 -16
  344. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +2 -2
  345. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +1 -1
  346. data/lib/hecks/three_zero/forms.yml +127 -0
  347. data/lib/hecks/three_zero.rb +12 -0
  348. data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
  349. data/lib/hecks/tools/comment_style.rb +1070 -0
  350. data/lib/hecks/tools/corpus_report.rb +87 -0
  351. data/lib/hecks/tools/deploy_recipe.rb +255 -0
  352. data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
  353. data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
  354. data/lib/hecks/tools/era_projection_backfill.rb +94 -0
  355. data/lib/hecks/tools/era_reattest.rb +146 -0
  356. data/lib/hecks/tools/evolve_run.rb +189 -0
  357. data/lib/hecks/tools/fuzz_sweep.rb +373 -0
  358. data/lib/hecks/tools/heki_compaction.rb +100 -0
  359. data/lib/hecks/tools/hoist_local_givens.rb +246 -0
  360. data/lib/hecks/tools/journal_compaction.rb +89 -0
  361. data/lib/hecks/tools/oidc_manifests.rb +70 -0
  362. data/lib/hecks/tools/query_ir_run.rb +81 -0
  363. data/lib/hecks/tools/regeneration_run.rb +166 -0
  364. data/lib/hecks/tools/rust_comment_style.rb +642 -0
  365. data/lib/hecks/tools/sequence_script.rb +62 -0
  366. data/lib/hecks/tools/tail_merge.rb +117 -0
  367. data/lib/hecks/tools/tenant_provisioning.rb +142 -0
  368. data/lib/hecks/tools/translation_audit.rb +208 -0
  369. data/lib/hecks/tools/translation_scaffold.rb +131 -0
  370. data/lib/hecks/tools.rb +86 -0
  371. data/lib/hecks/version.rb +1 -1
  372. data/lib/hecks.rb +32 -15
  373. data/qa/settings.yml +66 -0
  374. data/rust/.cargo/config.toml +6 -0
  375. data/rust/.gitignore +2 -0
  376. data/rust/Cargo.lock +54 -0
  377. data/rust/Cargo.toml +61 -0
  378. data/rust/build/.cargo/config.toml +3 -0
  379. data/rust/build/.gitignore +1 -0
  380. data/rust/build/Cargo.lock +7 -0
  381. data/rust/build/Cargo.toml +19 -0
  382. data/rust/build/src/build_artifact.rs +47 -0
  383. data/rust/build/src/cargo_sync.rs +248 -0
  384. data/rust/build/src/json.rs +421 -0
  385. data/rust/build/src/lineage_pass.rs +285 -0
  386. data/rust/build/src/main.rs +51 -0
  387. data/rust/build/src/optional_pass.rs +150 -0
  388. data/rust/build/src/pipeline.rs +264 -0
  389. data/rust/build/src/reserved_names.rs +76 -0
  390. data/rust/build/src/resolve.rs +124 -0
  391. data/rust/build/src/root.rs +25 -0
  392. data/rust/build/src/sidecars.rs +73 -0
  393. data/rust/build/src/subprocess.rs +51 -0
  394. data/rust/build/src/tmp.rs +31 -0
  395. data/rust/codegen/.cargo/config.toml +3 -0
  396. data/rust/codegen/.gitignore +1 -0
  397. data/rust/codegen/Cargo.lock +7 -0
  398. data/rust/codegen/Cargo.toml +20 -0
  399. data/rust/codegen/src/attr.rs +32 -0
  400. data/rust/codegen/src/bridging.rs +481 -0
  401. data/rust/codegen/src/commands.rs +1226 -0
  402. data/rust/codegen/src/constraints.rs +109 -0
  403. data/rust/codegen/src/dependency_planning.rs +209 -0
  404. data/rust/codegen/src/domain_generator.rs +1288 -0
  405. data/rust/codegen/src/exemplar.rs +230 -0
  406. data/rust/codegen/src/expr_emitter.rs +87 -0
  407. data/rust/codegen/src/fielded.rs +192 -0
  408. data/rust/codegen/src/hecks_naming.rs +33 -0
  409. data/rust/codegen/src/json.rs +335 -0
  410. data/rust/codegen/src/json_codec.rs +697 -0
  411. data/rust/codegen/src/literal.rs +207 -0
  412. data/rust/codegen/src/main.rs +290 -0
  413. data/rust/codegen/src/manifest.rs +227 -0
  414. data/rust/codegen/src/mutations.rs +784 -0
  415. data/rust/codegen/src/naming.rs +286 -0
  416. data/rust/codegen/src/ports.rs +208 -0
  417. data/rust/codegen/src/prelude.rs +116 -0
  418. data/rust/codegen/src/queries.rs +786 -0
  419. data/rust/codegen/src/reactions.rs +532 -0
  420. data/rust/codegen/src/read_models.rs +697 -0
  421. data/rust/codegen/src/reference_specs.rs +40 -0
  422. data/rust/codegen/src/registry.rs +966 -0
  423. data/rust/codegen/src/reserved_names.rs +76 -0
  424. data/rust/codegen/src/shared.rs +93 -0
  425. data/rust/codegen/src/skip_reason.rs +25 -0
  426. data/rust/codegen/src/types.rs +362 -0
  427. data/rust/host/.cargo/config.toml +3 -0
  428. data/rust/host/.gitignore +1 -0
  429. data/rust/host/Cargo.lock +4297 -0
  430. data/rust/host/Cargo.toml +61 -0
  431. data/rust/host/HECKS_RELEASE +1 -0
  432. data/rust/host/rds-ca-bundle.pem +2736 -0
  433. data/rust/host/src/api.rs +1596 -0
  434. data/rust/host/src/approval.rs +652 -0
  435. data/rust/host/src/auth.rs +1287 -0
  436. data/rust/host/src/bin/expr_harness.rs +46 -0
  437. data/rust/host/src/bin/lineage_harness.rs +119 -0
  438. data/rust/host/src/bin/mint_harness.rs +135 -0
  439. data/rust/host/src/checkout.rs +265 -0
  440. data/rust/host/src/dispatch.rs +1443 -0
  441. data/rust/host/src/expr_json.rs +1117 -0
  442. data/rust/host/src/field_hints.rs +52 -0
  443. data/rust/host/src/ir.rs +733 -0
  444. data/rust/host/src/journal.rs +874 -0
  445. data/rust/host/src/lambda_client.rs +378 -0
  446. data/rust/host/src/log.rs +147 -0
  447. data/rust/host/src/main.rs +474 -0
  448. data/rust/host/src/mint.rs +1865 -0
  449. data/rust/host/src/payments/keystore.rs +330 -0
  450. data/rust/host/src/payments/tests.rs +1400 -0
  451. data/rust/host/src/payments.rs +767 -0
  452. data/rust/host/src/presentation.rs +698 -0
  453. data/rust/host/src/presentation_write.rs +1431 -0
  454. data/rust/host/src/query_step.rs +149 -0
  455. data/rust/host/src/rate_limit.rs +904 -0
  456. data/rust/host/src/reference_transform.rs +241 -0
  457. data/rust/host/src/reference_validate.rs +338 -0
  458. data/rust/host/src/resend.rs +296 -0
  459. data/rust/host/src/secrets.rs +106 -0
  460. data/rust/host/src/server.rs +663 -0
  461. data/rust/host/src/storage_shape.rs +175 -0
  462. data/rust/host/src/test_pg.rs +40 -0
  463. data/rust/host/src/ui_schema.rs +1351 -0
  464. data/rust/host/src/wasm_runner.rs +191 -0
  465. data/rust/host/src/web/newsletter/cooldown.rs +126 -0
  466. data/rust/host/src/web/newsletter/tests.rs +104 -0
  467. data/rust/host/src/web/newsletter.rs +310 -0
  468. data/rust/host/src/web/newsletter_send.rs +357 -0
  469. data/rust/host/src/web/registrations.rs +1505 -0
  470. data/rust/host/src/web.rs +2937 -0
  471. data/rust/lsp/.gitignore +1 -0
  472. data/rust/lsp/Cargo.lock +7 -0
  473. data/rust/lsp/Cargo.toml +16 -0
  474. data/rust/lsp/README.md +133 -0
  475. data/rust/lsp/src/diagnostics.rs +165 -0
  476. data/rust/lsp/src/json.rs +385 -0
  477. data/rust/lsp/src/main.rs +399 -0
  478. data/rust/lsp/src/outline.rs +177 -0
  479. data/rust/lsp/src/rpc.rs +67 -0
  480. data/rust/parser/.cargo/config.toml +5 -0
  481. data/rust/parser/.gitignore +1 -0
  482. data/rust/parser/Cargo.lock +7 -0
  483. data/rust/parser/Cargo.toml +21 -0
  484. data/rust/parser/src/build/closed_sets.rs +27 -0
  485. data/rust/parser/src/build/identity.rs +276 -0
  486. data/rust/parser/src/build/mod.rs +12 -0
  487. data/rust/parser/src/build/naming.rs +141 -0
  488. data/rust/parser/src/build/pattern_subset.rs +161 -0
  489. data/rust/parser/src/build/query_derive.rs +72 -0
  490. data/rust/parser/src/build/query_inference.rs +269 -0
  491. data/rust/parser/src/build/query_options.rs +100 -0
  492. data/rust/parser/src/build/read_model.rs +94 -0
  493. data/rust/parser/src/build/references.rs +32 -0
  494. data/rust/parser/src/canonical.rs +114 -0
  495. data/rust/parser/src/diag.rs +55 -0
  496. data/rust/parser/src/emit.rs +953 -0
  497. data/rust/parser/src/expr/ast_json.rs +147 -0
  498. data/rust/parser/src/expr/evaluator.rs +81 -0
  499. data/rust/parser/src/expr/mod.rs +84 -0
  500. data/rust/parser/src/expr/resolver.rs +497 -0
  501. data/rust/parser/src/ir.rs +387 -0
  502. data/rust/parser/src/keywords.rs +585 -0
  503. data/rust/parser/src/lex.rs +662 -0
  504. data/rust/parser/src/main.rs +252 -0
  505. data/rust/parser/src/parse/aggregate.rs +612 -0
  506. data/rust/parser/src/parse/chapter.rs +517 -0
  507. data/rust/parser/src/parse/command.rs +637 -0
  508. data/rust/parser/src/parse/domain_port.rs +127 -0
  509. data/rust/parser/src/parse/entity.rs +527 -0
  510. data/rust/parser/src/parse/file.rs +67 -0
  511. data/rust/parser/src/parse/hecksagon.rs +231 -0
  512. data/rust/parser/src/parse/lifecycle.rs +162 -0
  513. data/rust/parser/src/parse/mod.rs +1262 -0
  514. data/rust/parser/src/parse/policy.rs +87 -0
  515. data/rust/parser/src/parse/process_manager.rs +305 -0
  516. data/rust/parser/src/parse/query.rs +100 -0
  517. data/rust/parser/src/parse/read_model.rs +214 -0
  518. data/rust/parser/src/parse/value_object.rs +250 -0
  519. data/rust/parser/src/ruby_value.rs +422 -0
  520. data/rust/project/bridging.rb +257 -0
  521. data/rust/project/commands.rb +877 -0
  522. data/rust/project/constraints.rb +86 -0
  523. data/rust/project/dependency_planning.rb +178 -0
  524. data/rust/project/domain_generator.rb +980 -0
  525. data/rust/project/exemplar.rb +148 -0
  526. data/rust/project/expr_emitter.rb +72 -0
  527. data/rust/project/fielded.rb +141 -0
  528. data/rust/project/json_codec.rb +641 -0
  529. data/rust/project/mutations.rb +721 -0
  530. data/rust/project/naming.rb +205 -0
  531. data/rust/project/ports.rb +65 -0
  532. data/rust/project/queries.rb +493 -0
  533. data/rust/project/reactions.rb +317 -0
  534. data/rust/project/read_models.rb +690 -0
  535. data/rust/project/reference_specs.rb +27 -0
  536. data/rust/project/registry.rb +396 -0
  537. data/rust/project/skip_reason.rb +29 -0
  538. data/rust/project/types.rb +225 -0
  539. data/rust/project/write_if_changed.rb +83 -0
  540. data/rust/project.rb +30 -0
  541. data/rust/project_rust_pipeline.rb +383 -0
  542. data/rust/src/exemplar/commands.rs +279 -0
  543. data/rust/src/exemplar/constraints.rs +18 -0
  544. data/rust/src/exemplar/fielded.rs +280 -0
  545. data/rust/src/exemplar/json.rs +277 -0
  546. data/rust/src/exemplar/mod.rs +17 -0
  547. data/rust/src/exemplar/mutations.rs +80 -0
  548. data/rust/src/exemplar/queries.rs +33 -0
  549. data/rust/src/exemplar/reactions.rs +88 -0
  550. data/rust/src/exemplar/read_models.rs +48 -0
  551. data/rust/src/exemplar/registry.rs +166 -0
  552. data/rust/src/exemplar/types.rs +44 -0
  553. data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
  554. data/rust/src/kernel/attribute_shapes/list.rs +18 -0
  555. data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
  556. data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
  557. data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
  558. data/rust/src/kernel/cli.rs +585 -0
  559. data/rust/src/kernel/dispatch.rs +1090 -0
  560. data/rust/src/kernel/expr.rs +458 -0
  561. data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
  562. data/rust/src/kernel/expression_operators/comparison.rs +48 -0
  563. data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
  564. data/rust/src/kernel/expression_operators/logical.rs +13 -0
  565. data/rust/src/kernel/expression_operators/membership.rs +18 -0
  566. data/rust/src/kernel/expression_operators/mod.rs +68 -0
  567. data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
  568. data/rust/src/kernel/expression_operators/positional.rs +126 -0
  569. data/rust/src/kernel/expression_operators/presence.rs +101 -0
  570. data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
  571. data/rust/src/kernel/expression_operators/sized.rs +35 -0
  572. data/rust/src/kernel/expression_operators/text.rs +79 -0
  573. data/rust/src/kernel/expression_operators/to_string.rs +18 -0
  574. data/rust/src/kernel/json.rs +779 -0
  575. data/rust/src/kernel/mod.rs +133 -0
  576. data/rust/src/kernel/named_query.rs +219 -0
  577. data/rust/src/kernel/orchestrate.rs +1810 -0
  578. data/rust/src/kernel/pattern.rs +307 -0
  579. data/rust/src/kernel/query_comparators.rs +206 -0
  580. data/rust/src/kernel/query_ordering.rs +103 -0
  581. data/rust/src/kernel/read_model.rs +914 -0
  582. data/rust/src/kernel/reference_lookup.rs +149 -0
  583. data/rust/src/kernel/repository.rs +468 -0
  584. data/rust/src/kernel/routing.rs +612 -0
  585. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
  586. data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
  587. data/rust/src/kernel/vocab/field_hint.rs +70 -0
  588. data/rust/src/kernel/vocab/mod.rs +20 -0
  589. data/rust/src/kernel/vocab/query_comparator.rs +65 -0
  590. data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
  591. data/rust/src/lib.rs +8 -0
  592. data/rust/src/main.rs +16 -0
  593. data/rust/web/.gitignore +1 -0
  594. data/rust/web/Cargo.lock +119 -0
  595. data/rust/web/Cargo.toml +18 -0
  596. data/rust/web/src/lib.rs +8 -0
  597. metadata +477 -12
  598. data/lib/hecks/facade/cli_door.rb +0 -96
  599. data/lib/hecks/facade/cli_runner.rb +0 -127
  600. data/lib/hecks/mcp_door_scope.rb +0 -95
@@ -0,0 +1,458 @@
1
+ //! Interprets the `Expr` AST — a structural port of Ruby's
2
+ //! `Evaluator`/`Resolver` (docs/implemented/guides/running-a-runtime.md).
3
+
4
+ use super::Refusal;
5
+ use crate::kernel::attribute_shapes::composite;
6
+ use crate::kernel::expression_operators::{self, comparison, OperatorCategory};
7
+
8
+ /// The dynamic runtime value an expression evaluates to — mirrors
9
+ /// Ruby's `Resolver#interpret`, plus `List(usize)`: a list field's length.
10
+ #[derive(Debug, Clone, PartialEq)]
11
+ pub enum Value {
12
+ Int(i64),
13
+ Float(f64),
14
+ Str(String),
15
+ Bool(bool),
16
+ List(usize),
17
+ Nil,
18
+ /// A materialised list — distinct from the length-only `List(usize)`
19
+ /// above. Produced by an array literal or `.split`; never used for
20
+ /// `.include?`'s haystack (`expr_emitter.rb`'s `emit_include` rewrites
21
+ /// that shape at codegen time instead).
22
+ Array(Vec<Value>),
23
+ }
24
+
25
+ impl Value {
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))
29
+ }
30
+ }
31
+
32
+ pub enum Field<'a> {
33
+ Value(Value),
34
+ Nested(&'a dyn Fielded),
35
+ }
36
+
37
+ /// The lookup surface every generated struct implements. A dotted
38
+ /// `Lookup` resolves its head segment here, then walks `Field::Nested`.
39
+ pub trait Fielded {
40
+ fn field(&self, name: &str) -> Option<Field<'_>>;
41
+
42
+ /// The bare (undotted) `Lookup` reading of this object — Ruby's
43
+ /// `unwrap_scalar` fallthrough. `DerefNode` overrides to answer its id.
44
+ fn as_scalar(&self) -> Option<Value> {
45
+ None
46
+ }
47
+
48
+ /// The elements of a list-typed field, for the enumeration operators
49
+ /// (`.any?`/`.all?`/`.none?`/`.find`). Default `None`, like `as_scalar`.
50
+ fn items(&self, _name: &str) -> Option<Vec<Field<'_>>> {
51
+ None
52
+ }
53
+ }
54
+
55
+ /// A block's own parameter, checked before `rest` (real args/instance) —
56
+ /// Ruby's `interpret_with_element` merge order. Nested blocks chain a
57
+ /// `Bound` inside a `Bound`, matching Ruby's nested `merge`.
58
+ pub struct Bound<'a> {
59
+ pub name: &'a str,
60
+ pub value: Field<'a>,
61
+ pub rest: &'a dyn Fielded,
62
+ }
63
+
64
+ impl<'a> Fielded for Bound<'a> {
65
+ fn field(&self, name: &str) -> Option<Field<'_>> {
66
+ if name == self.name {
67
+ return Some(match &self.value {
68
+ Field::Value(v) => Field::Value(v.clone()),
69
+ Field::Nested(obj) => Field::Nested(*obj),
70
+ });
71
+ }
72
+ self.rest.field(name)
73
+ }
74
+
75
+ fn items(&self, name: &str) -> Option<Vec<Field<'_>>> {
76
+ if name == self.name {
77
+ // A bound element is one member, never itself a list.
78
+ return None;
79
+ }
80
+ self.rest.items(name)
81
+ }
82
+ }
83
+
84
+ /// A `Fielded` with nothing in it — Ruby's own `attrs` is `{}` too, e.g.
85
+ /// a value object checking its own invariants against only its fields.
86
+ pub struct NoFields;
87
+ impl Fielded for NoFields {
88
+ fn field(&self, _name: &str) -> Option<Field<'_>> {
89
+ None
90
+ }
91
+ }
92
+
93
+ /// `ensures`'s own `attrs`, plus `old` (pre-mutation instance) merged in
94
+ /// and checked first — Ruby's `Hash#merge` precedence, `enforce_ensures`.
95
+ pub struct WithOld<'a> {
96
+ pub args: &'a dyn Fielded,
97
+ pub old: &'a dyn Fielded,
98
+ }
99
+
100
+ impl<'a> Fielded for WithOld<'a> {
101
+ fn field(&self, name: &str) -> Option<Field<'_>> {
102
+ if name == "old" {
103
+ return Some(Field::Nested(self.old));
104
+ }
105
+ self.args.field(name)
106
+ }
107
+
108
+ fn items(&self, name: &str) -> Option<Vec<Field<'_>>> {
109
+ if name == "old" {
110
+ return None;
111
+ }
112
+ self.args.items(name)
113
+ }
114
+ }
115
+
116
+ /// C2.3 (docs/semantics/bluebook-semantics.md): inside `ensures`, settled
117
+ /// state wins over a same-named argument (unlike `given`'s C2.2) — the
118
+ /// same drop `enforce_ensures` does, answered lazily per lookup.
119
+ pub struct StateFirst<'a> {
120
+ pub args: &'a dyn Fielded,
121
+ pub settled: &'a dyn Fielded,
122
+ }
123
+
124
+ impl<'a> Fielded for StateFirst<'a> {
125
+ fn field(&self, name: &str) -> Option<Field<'_>> {
126
+ if self.settled.field(name).is_some() {
127
+ return None;
128
+ }
129
+ self.args.field(name)
130
+ }
131
+
132
+ fn items(&self, name: &str) -> Option<Vec<Field<'_>>> {
133
+ if self.settled.field(name).is_some() {
134
+ return None;
135
+ }
136
+ self.args.items(name)
137
+ }
138
+ }
139
+
140
+ /// An entity command's owning record, exposed as `parent` on the args
141
+ /// side (`enforce_givens`/`enforce_ensures`), merged after `old` — Ruby's
142
+ /// own `merge` order gives `old` precedence.
143
+ pub struct WithParent<'a> {
144
+ pub args: &'a dyn Fielded,
145
+ pub parent: &'a dyn Fielded,
146
+ }
147
+
148
+ impl<'a> Fielded for WithParent<'a> {
149
+ fn field(&self, name: &str) -> Option<Field<'_>> {
150
+ if name == "parent" {
151
+ return Some(Field::Nested(self.parent));
152
+ }
153
+ self.args.field(name)
154
+ }
155
+
156
+ fn items(&self, name: &str) -> Option<Vec<Field<'_>>> {
157
+ if name == "parent" {
158
+ return None;
159
+ }
160
+ self.args.items(name)
161
+ }
162
+ }
163
+
164
+ // Re-exported here so generated call sites (`expr_emitter.rb` emits
165
+ // `crate::kernel::Comparison { .. }`) keep working unchanged.
166
+ pub use comparison::Comparison;
167
+
168
+ /// The three spellings of one Array-aggregation family, kept as one
169
+ /// node with a mode — matching Ruby's `BlockPredicate#mode`.
170
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
171
+ pub enum BlockMode {
172
+ All,
173
+ Any,
174
+ None,
175
+ }
176
+
177
+ impl BlockMode {
178
+ /// The operator's own spelling, for the "expects a list" wording
179
+ /// Ruby renders from `"#{node.mode}?"`.
180
+ pub fn ruby_name(&self) -> &'static str {
181
+ match self {
182
+ BlockMode::All => "all?",
183
+ BlockMode::Any => "any?",
184
+ BlockMode::None => "none?",
185
+ }
186
+ }
187
+ }
188
+
189
+ /// The full Evaluator + Resolver AST, one recursive enum — each variant
190
+ /// is exactly one Ruby node type. An unconstructed variant for one
191
+ /// domain is expected, not dead code to prune.
192
+ #[derive(Debug, Clone)]
193
+ #[allow(dead_code)]
194
+ pub enum Expr {
195
+ Or(Box<Expr>, Box<Expr>),
196
+ And(Box<Expr>, Box<Expr>),
197
+ Not(Box<Expr>),
198
+ Compare { op: Comparison, left: Box<Expr>, right: Box<Expr> },
199
+ Include { haystack: Box<Expr>, needle: Box<Expr> },
200
+ Int(i64),
201
+ Float(f64),
202
+ Str(String),
203
+ Bool(bool),
204
+ Nil,
205
+ Add(Box<Expr>, Box<Expr>),
206
+ SignTest { op: Comparison, receiver: Box<Expr> },
207
+ Empty(Box<Expr>),
208
+ ToS(Box<Expr>),
209
+ Modulo { receiver: Box<Expr>, divisor: Box<Expr> },
210
+ Size(Box<Expr>),
211
+ Lookup(&'static str),
212
+ /// `receiver.any?`/`.all?`/`.none? { |param| predicate }` —
213
+ /// evaluated once per element with `param` bound (see `Bound`).
214
+ BlockPredicate { mode: BlockMode, receiver: Box<Expr>, param: &'static str, predicate: Box<Expr> },
215
+ /// `receiver.find { |param| predicate }.a.b` — first accepted
216
+ /// element, projected through `path`, or nil when nothing matches.
217
+ Find { receiver: Box<Expr>, param: &'static str, predicate: Box<Expr>, path: &'static [&'static str] },
218
+ /// `[a, b, c]` — a leaf like `Int`/`Str`/`Nil` above, not an
219
+ /// operator, so `interpret` handles it directly, not via
220
+ /// `dispatch_operator`.
221
+ Array(Vec<Expr>),
222
+ /// `receiver.match?(/pattern/flags)` — `pattern` is a real
223
+ /// Ruby-Regexp source, taken as-is between the slashes.
224
+ MatchesRegex { receiver: Box<Expr>, pattern: String, flags: String },
225
+ /// `receiver.present?` / `.blank?` — one node for the pair,
226
+ /// `negated` distinguishing them (`.blank?` is `.present?` negated).
227
+ Presence { receiver: Box<Expr>, negated: bool },
228
+ /// `receiver.set?` / `.unset?` — narrower than `Presence`: only
229
+ /// `!receiver.is_nil()`. An assigned-but-empty value is `.set?`,
230
+ /// unlike `Presence`'s Rails-standard emptiness reading.
231
+ Assignment { receiver: Box<Expr>, negated: bool },
232
+ /// `receiver.split("SEP")` — `Resolver::Split`. Produces a real
233
+ /// `Value::Array`, not `Value::List` — see that variant's own header.
234
+ Split { receiver: Box<Expr>, separator: String },
235
+ /// `receiver.start_with?("prefix")` — `Resolver::StartsWith`.
236
+ StartsWith { receiver: Box<Expr>, substring: String },
237
+ /// `receiver.end_with?("suffix")` — `Resolver::EndsWith`.
238
+ EndsWith { receiver: Box<Expr>, substring: String },
239
+ /// `receiver.first` — `Resolver::First`.
240
+ First(Box<Expr>),
241
+ /// `receiver.last` — `Resolver::Last`.
242
+ Last(Box<Expr>),
243
+ }
244
+
245
+ /// `args`/`instance` from `Evaluator.call` — `args` is checked first for
246
+ /// every `Lookup`, matching `Resolver#fetch`. Pass `&NoFields` for `{}`.
247
+ pub struct EvalContext<'a> {
248
+ pub args: &'a dyn Fielded,
249
+ pub instance: &'a dyn Fielded,
250
+ }
251
+
252
+ /// Leaves (literals, `Lookup`) have no `OperatorCategory`, so they're
253
+ /// handled directly; everything else routes through `dispatch_operator`.
254
+ pub fn interpret(expr: &Expr, ctx: &EvalContext) -> Result<Value, Refusal> {
255
+ use Expr::*;
256
+ match expr {
257
+ Int(v) => Ok(Value::Int(*v)),
258
+ Float(v) => Ok(Value::Float(*v)),
259
+ Str(v) => Ok(Value::Str(v.clone())),
260
+ Bool(v) => Ok(Value::Bool(*v)),
261
+ Nil => Ok(Value::Nil),
262
+ Lookup(path) => lookup(path, ctx),
263
+ Array(elements) => Ok(Value::Array(elements.iter().map(|e| interpret(e, ctx)).collect::<Result<Vec<_>, _>>()?)),
264
+ _ => dispatch_operator(category_of(expr), expr, ctx),
265
+ }
266
+ }
267
+
268
+ /// Which `OperatorCategory` a non-leaf `Expr` variant belongs to — a
269
+ /// hand-written association `interpret` never calls for a leaf, hence
270
+ /// the `unreachable!` there rather than a wrong category.
271
+ fn category_of(expr: &Expr) -> OperatorCategory {
272
+ use Expr::*;
273
+ match expr {
274
+ Or(..) | And(..) | Not(..) => OperatorCategory::Logical,
275
+ Include { .. } => OperatorCategory::Membership,
276
+ Compare { .. } => OperatorCategory::Comparison,
277
+ Add(..) | Modulo { .. } => OperatorCategory::Arithmetic,
278
+ SignTest { .. } => OperatorCategory::SignTest,
279
+ Empty(..) | Size(..) => OperatorCategory::Sized,
280
+ ToS(..) => OperatorCategory::ToString,
281
+ BlockPredicate { .. } | Find { .. } => OperatorCategory::Enumeration,
282
+ MatchesRegex { .. } => OperatorCategory::PatternMatch,
283
+ Presence { .. } | Assignment { .. } => OperatorCategory::Presence,
284
+ Split { .. } | StartsWith { .. } | EndsWith { .. } => OperatorCategory::Text,
285
+ First(..) | Last(..) => OperatorCategory::Positional,
286
+ Int(..) | Float(..) | Str(..) | Bool(..) | Nil | Lookup(..) | Array(..) => {
287
+ unreachable!("interpret's own leaf arms handle these before category_of is ever called")
288
+ }
289
+ }
290
+ }
291
+
292
+ /// Exhaustive over `OperatorCategory`, no wildcard `_ =>` arm: adding or
293
+ /// removing a variant (hecks project_kernel_capabilities) stops this
294
+ /// compiling until a matching arm exists. Never add one back — it would
295
+ /// silently route a new category to the wrong operator file.
296
+ fn dispatch_operator(category: OperatorCategory, expr: &Expr, ctx: &EvalContext) -> Result<Value, Refusal> {
297
+ use expression_operators::*;
298
+ match category {
299
+ OperatorCategory::Logical => logical::interpret(expr, ctx),
300
+ OperatorCategory::Membership => membership::interpret(expr, ctx),
301
+ OperatorCategory::Comparison => comparison::interpret(expr, ctx),
302
+ OperatorCategory::Arithmetic => match expr {
303
+ Expr::Add(..) => arithmetic::add(expr, ctx),
304
+ Expr::Modulo { .. } => arithmetic::modulo(expr, ctx),
305
+ _ => Err(Refusal::TypeMismatch(format!("dispatch_operator(Arithmetic, ..) called with {expr:?} — a router bug"))),
306
+ },
307
+ OperatorCategory::SignTest => sign_test::interpret(expr, ctx),
308
+ OperatorCategory::Sized => match expr {
309
+ Expr::Empty(..) => sized::empty(expr, ctx),
310
+ Expr::Size(..) => sized::size(expr, ctx),
311
+ _ => Err(Refusal::TypeMismatch(format!("dispatch_operator(Sized, ..) called with {expr:?} — a router bug"))),
312
+ },
313
+ OperatorCategory::ToString => to_string::interpret(expr, ctx),
314
+ OperatorCategory::Enumeration => enumeration::interpret(expr, ctx),
315
+ OperatorCategory::PatternMatch => pattern_match::interpret(expr, ctx),
316
+ OperatorCategory::Presence => match expr {
317
+ Expr::Presence { .. } => presence::interpret(expr, ctx),
318
+ Expr::Assignment { .. } => presence::interpret_assignment(expr, ctx),
319
+ _ => Err(Refusal::TypeMismatch(format!("dispatch_operator(Presence, ..) called with {expr:?} — a router bug"))),
320
+ },
321
+ OperatorCategory::Text => text::interpret(expr, ctx),
322
+ OperatorCategory::Positional => positional::interpret(expr, ctx),
323
+ }
324
+ }
325
+
326
+ /// `Resolver#fetch`: the head segment checks `args` then `instance`;
327
+ /// later segments walk `composite::step`. Only the head segment can
328
+ /// refuse here — canonical text is validated at boot, so any refusal
329
+ /// past this point is a codegen bug, not a real business refusal.
330
+ fn lookup(path: &str, ctx: &EvalContext) -> Result<Value, Refusal> {
331
+ let mut segments = path.split('.');
332
+ let head = segments.next().unwrap();
333
+ let mut current = ctx
334
+ .args
335
+ .field(head)
336
+ .or_else(|| ctx.instance.field(head))
337
+ .ok_or_else(|| eval_error(format!("cannot resolve {head:?} — no such attribute or argument")))?;
338
+
339
+ for seg in segments {
340
+ current = composite::step(current, seg, head)?;
341
+ }
342
+
343
+ composite::finish(current, path)
344
+ }
345
+
346
+ /// The list-elements reading of a `Lookup` path — same head resolution
347
+ /// as `lookup`, then `Fielded::items` on the last segment (a list is
348
+ /// always the end of a path). `op` names the caller for error wording.
349
+ pub(crate) fn lookup_items<'a>(path: &str, ctx: &EvalContext<'a>, op: &str) -> Result<Vec<Field<'a>>, Refusal> {
350
+ let segments: Vec<&str> = path.split('.').collect();
351
+ let (head, rest) = segments.split_first().unwrap();
352
+
353
+ if rest.is_empty() {
354
+ // args first when the key is present there, state second — a
355
+ // non-list argument refuses rather than falling through to a
356
+ // same-named instance list.
357
+ let side: &'a dyn Fielded = if ctx.args.field(head).is_some() { ctx.args } else { ctx.instance };
358
+ return side
359
+ .items(head)
360
+ .ok_or_else(|| eval_error(format!("{op} expects a list, got {}", describe_field(side.field(head)))));
361
+ }
362
+
363
+ let (last, middle) = rest.split_last().unwrap();
364
+ let mut current = ctx
365
+ .args
366
+ .field(head)
367
+ .or_else(|| ctx.instance.field(head))
368
+ .ok_or_else(|| eval_error(format!("cannot resolve {head:?} — no such attribute or argument")))?;
369
+ for seg in middle {
370
+ current = composite::step(current, seg, head)?;
371
+ }
372
+ match current {
373
+ Field::Nested(obj) => obj
374
+ .items(last)
375
+ .ok_or_else(|| eval_error(format!("{op} expects a list, got {}", describe_field(obj.field(last))))),
376
+ Field::Value(v) => Err(eval_error(format!("{path} — cannot look up {last:?} on scalar {v:?}"))),
377
+ }
378
+ }
379
+
380
+ fn describe_field(field: Option<Field<'_>>) -> String {
381
+ match field {
382
+ None => "nothing".to_string(),
383
+ Some(Field::Value(v)) => format!("{v:?}"),
384
+ Some(Field::Nested(_)) => "an object".to_string(),
385
+ }
386
+ }
387
+
388
+ pub(crate) fn eval_error(message: String) -> Refusal {
389
+ Refusal::Fault(message)
390
+ }
391
+
392
+ #[cfg(test)]
393
+ mod lookup_tests {
394
+ use super::*;
395
+
396
+ // Shaped like a generated value object: one required boolean field,
397
+ // one optional — the exact shape `!previous_sessions.nil?` names
398
+ // (see `composite::step`'s own header).
399
+ struct Attendee {
400
+ previous_sessions: bool,
401
+ first_time: Option<bool>,
402
+ }
403
+ impl Fielded for Attendee {
404
+ fn field(&self, name: &str) -> Option<Field<'_>> {
405
+ match name {
406
+ "previous_sessions" => Some(Field::Value(Value::Bool(self.previous_sessions))),
407
+ "first_time" => Some(match self.first_time {
408
+ Some(b) => Field::Value(Value::Bool(b)),
409
+ None => Field::Value(Value::Nil),
410
+ }),
411
+ _ => None,
412
+ }
413
+ }
414
+ }
415
+
416
+ fn not_nil(field: &'static str, instance: &dyn Fielded) -> Value {
417
+ let expr = Expr::Not(Box::new(Expr::Lookup(field)));
418
+ let ctx = EvalContext { args: &NoFields, instance };
419
+ interpret(&expr, &ctx).expect("evaluates without refusing")
420
+ }
421
+
422
+ #[test]
423
+ fn a_present_true_boolean_answers_not_nil() {
424
+ // `!previous_sessions.nil?` — the live crash, `previous_sessions:
425
+ // true`: `.nil?` is not a dedicated node in this grammar, so this
426
+ // is really `Lookup("previous_sessions.nil?")` negated.
427
+ let attendee = Attendee { previous_sessions: true, first_time: None };
428
+ assert_eq!(not_nil("previous_sessions.nil?", &attendee), Value::Bool(true));
429
+ }
430
+
431
+ #[test]
432
+ fn a_present_false_boolean_also_answers_not_nil() {
433
+ // The other half of the live crash: `false` is exactly as
434
+ // present as `true` — `walk_path` never inspects the value,
435
+ // only whether the head attribute resolved at all.
436
+ let attendee = Attendee { previous_sessions: false, first_time: None };
437
+ assert_eq!(not_nil("previous_sessions.nil?", &attendee), Value::Bool(true));
438
+ }
439
+
440
+ #[test]
441
+ fn a_genuinely_unset_optional_field_still_answers_not_nil() {
442
+ // Matches Ruby parity: `walk_path` breaks to `nil` off any
443
+ // scalar before inspecting `.nil?`'s segment name, so `!x.nil?`
444
+ // reads `true` for any head attribute that resolved, set or not.
445
+ let attendee = Attendee { previous_sessions: true, first_time: None };
446
+ assert_eq!(not_nil("first_time.nil?", &attendee), Value::Bool(true));
447
+ }
448
+
449
+ #[test]
450
+ fn a_genuinely_missing_head_attribute_still_refuses() {
451
+ // An undeclared head attribute is still a codegen bug, not a
452
+ // legitimate absent value — see `lookup`'s own doc comment.
453
+ let attendee = Attendee { previous_sessions: true, first_time: None };
454
+ let expr = Expr::Not(Box::new(Expr::Lookup("not_a_real_attribute.nil?")));
455
+ let ctx = EvalContext { args: &NoFields, instance: &attendee };
456
+ assert!(interpret(&expr, &ctx).is_err());
457
+ }
458
+ }
@@ -0,0 +1,128 @@
1
+ //! `+` and `.modulo` (`Resolver::Addition`/`Modulo`); both require numeric operands.
2
+
3
+ use crate::kernel::attribute_shapes::scalar;
4
+ use crate::kernel::expr::{eval_error, interpret as eval, EvalContext, Expr, Value};
5
+ use crate::kernel::Refusal;
6
+
7
+ pub fn add(expr: &Expr, ctx: &EvalContext) -> Result<Value, Refusal> {
8
+ let Expr::Add(l, r) = expr else {
9
+ return Err(Refusal::TypeMismatch(format!("arithmetic::add called with a non-addition node {expr:?} — a router bug")));
10
+ };
11
+
12
+ sum(&eval(l, ctx)?, &eval(r, ctx)?)
13
+ }
14
+
15
+ pub fn modulo(expr: &Expr, ctx: &EvalContext) -> Result<Value, Refusal> {
16
+ let Expr::Modulo { receiver, divisor } = expr else {
17
+ return Err(Refusal::TypeMismatch(format!("arithmetic::modulo called with a non-modulo node {expr:?} — a router bug")));
18
+ };
19
+
20
+ let r = require_number(&eval(receiver, ctx)?, "modulo")?.trunc() as i64;
21
+ let d = require_number(&eval(divisor, ctx)?, "modulo")?.trunc() as i64;
22
+ if d == 0 {
23
+ return Err(eval_error("divided by 0".to_string()));
24
+ }
25
+ Ok(Value::Int(floored_mod(r, d)))
26
+ }
27
+
28
+ // Floored like Ruby's `Integer#%` (the result takes the divisor's sign); Rust's `%` truncates.
29
+ fn floored_mod(r: i64, d: i64) -> i64 {
30
+ // `i64::MIN % -1` overflows in Rust's `%`; every integer model answers 0 (C3.3).
31
+ if d == -1 {
32
+ return 0;
33
+ }
34
+ let raw = r % d;
35
+ if raw != 0 && (raw < 0) != (d < 0) { raw + d } else { raw }
36
+ }
37
+
38
+ #[cfg(test)]
39
+ mod tests {
40
+ use super::floored_mod;
41
+
42
+ // Expected values are real `ruby -e 'puts X % Y'` results.
43
+ #[test]
44
+ fn matches_ruby_integer_modulo_across_every_sign_combination() {
45
+ assert_eq!(floored_mod(7, 3), 1);
46
+ assert_eq!(floored_mod(-7, 3), 2);
47
+ assert_eq!(floored_mod(7, -3), -2);
48
+ assert_eq!(floored_mod(-7, -3), -1);
49
+ assert_eq!(floored_mod(0, 3), 0);
50
+ assert_eq!(floored_mod(6, 3), 0);
51
+ assert_eq!(floored_mod(-6, 3), 0);
52
+ }
53
+
54
+ // C3.3 — `i64::MIN % -1` answers 0, never panics.
55
+ #[test]
56
+ fn i64_min_modulo_minus_one_is_zero() {
57
+ assert_eq!(floored_mod(i64::MIN, -1), 0);
58
+ }
59
+ }
60
+
61
+ #[cfg(test)]
62
+ mod float_sum_tests {
63
+ use super::sum;
64
+ use crate::kernel::expr::Value;
65
+
66
+ // C3.4 — a Float sum that overflows to infinity faults rather than answering inf.
67
+ #[test]
68
+ fn refuses_a_float_sum_that_is_not_finite() {
69
+ let result = sum(&Value::Float(1.0e308), &Value::Float(1.0e308));
70
+ assert!(result.is_err(), "1e308 + 1e308 must fault, not answer inf");
71
+ }
72
+ }
73
+
74
+ #[cfg(test)]
75
+ mod sum_tests {
76
+ use super::sum;
77
+ use crate::kernel::expr::Value;
78
+
79
+ #[test]
80
+ fn adds_ordinary_ints() {
81
+ assert_eq!(sum(&Value::Int(2), &Value::Int(3)).unwrap(), Value::Int(5));
82
+ }
83
+
84
+ #[test]
85
+ fn mixed_int_float_promotes_to_float_like_ruby_coercion() {
86
+ assert_eq!(sum(&Value::Int(2), &Value::Float(3.5)).unwrap(), Value::Float(5.5));
87
+ }
88
+
89
+ // Ruby's `Integer#+` promotes to Bignum and never overflows; this kernel has no bignum, so
90
+ // overflow refuses instead of panicking or wrapping.
91
+ #[test]
92
+ fn refuses_cleanly_on_overflow_instead_of_panicking_or_wrapping() {
93
+ let result = sum(&Value::Int(i64::MAX), &Value::Int(1));
94
+ assert!(result.is_err(), "i64::MAX + 1 must refuse, not silently wrap to i64::MIN");
95
+ }
96
+
97
+ #[test]
98
+ fn refuses_cleanly_on_negative_overflow() {
99
+ let result = sum(&Value::Int(i64::MIN), &Value::Int(-1));
100
+ assert!(result.is_err(), "i64::MIN + -1 must refuse, not silently wrap to i64::MAX");
101
+ }
102
+
103
+ #[test]
104
+ fn does_not_refuse_right_at_the_boundary() {
105
+ assert_eq!(sum(&Value::Int(i64::MAX - 1), &Value::Int(1)).unwrap(), Value::Int(i64::MAX));
106
+ }
107
+ }
108
+
109
+ // Ruby's `Integer#+` promotes to Bignum; an `i64` cannot, so `checked_add` refuses on overflow
110
+ // instead of panicking (debug) or wrapping (release).
111
+ fn sum(lhs: &Value, rhs: &Value) -> Result<Value, Refusal> {
112
+ if let (Value::Int(l), Value::Int(r)) = (lhs, rhs) {
113
+ return l.checked_add(*r).map(Value::Int).ok_or_else(|| eval_error(format!("addition overflowed: {l} + {r} does not fit in a 64-bit integer")));
114
+ }
115
+ let l = require_number(lhs, "addition")?;
116
+ let r = require_number(rhs, "addition")?;
117
+ let sum = l + r;
118
+ // C3.4 — a non-finite Float sum faults like an Integer overflow.
119
+ if !sum.is_finite() {
120
+ return Err(eval_error(format!("addition overflowed: {l} + {r} is not a finite number")));
121
+ }
122
+ Ok(Value::Float(sum))
123
+ }
124
+
125
+ /// Shared with `sign_test.rs` — see this file's own header.
126
+ pub fn require_number(v: &Value, operation: &str) -> Result<f64, Refusal> {
127
+ scalar::numeric(v).ok_or_else(|| eval_error(format!("{operation} expects a number, got {v:?}")))
128
+ }
@@ -0,0 +1,48 @@
1
+ //! `>=`, `<=`, `<`, `>`, `==`, `!=` (`Evaluator.apply`), reduced to two primitives plus negation.
2
+
3
+ use crate::kernel::attribute_shapes::scalar;
4
+ use crate::kernel::expr::{eval_error, interpret as eval, EvalContext, Expr, Value};
5
+ use crate::kernel::Refusal;
6
+
7
+ /// The comparison algebra: `less_than` and `equal` OR'd together, negated when `negated`.
8
+ #[derive(Debug, Clone, Copy)]
9
+ pub struct Comparison {
10
+ pub less_than: bool,
11
+ pub equal: bool,
12
+ pub negated: bool,
13
+ }
14
+
15
+ pub fn interpret(expr: &Expr, ctx: &EvalContext) -> Result<Value, Refusal> {
16
+ let Expr::Compare { op, left, right } = expr else {
17
+ return Err(Refusal::TypeMismatch(format!("comparison::interpret called with a non-comparison node {expr:?} — a router bug")));
18
+ };
19
+
20
+ let l = eval(left, ctx)?;
21
+ let r = eval(right, ctx)?;
22
+ Ok(Value::Bool(apply(op, &l, &r)?))
23
+ }
24
+
25
+ /// Applies `op` to resolved values; `sign_test.rs` reuses it against the literal 0.
26
+ pub fn apply(op: &Comparison, lhs: &Value, rhs: &Value) -> Result<bool, Refusal> {
27
+ let lt = op.less_than && less_than(lhs, rhs)?;
28
+ let eq = op.equal && values_equal(lhs, rhs);
29
+ Ok(if op.negated { !(lt || eq) } else { lt || eq })
30
+ }
31
+
32
+ fn less_than(lhs: &Value, rhs: &Value) -> Result<bool, Refusal> {
33
+ match (scalar::numeric(lhs), scalar::numeric(rhs)) {
34
+ (Some(l), Some(r)) => Ok(l < r),
35
+ _ => match (lhs, rhs) {
36
+ (Value::Str(l), Value::Str(r)) => Ok(l < r),
37
+ _ => Err(eval_error(format!("comparison of {lhs:?} with {rhs:?} failed"))),
38
+ },
39
+ }
40
+ }
41
+
42
+ /// `pub(crate)` so `membership.rs` reuses the numeric-coerced equality for array haystacks.
43
+ pub(crate) fn values_equal(lhs: &Value, rhs: &Value) -> bool {
44
+ match (scalar::numeric(lhs), scalar::numeric(rhs)) {
45
+ (Some(l), Some(r)) => l == r,
46
+ _ => lhs == rhs,
47
+ }
48
+ }