hecks 2.8.0 → 3.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 (886) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +50 -7
  3. data/lib/hecks/adapters/driven/claude_code.rb +4 -104
  4. data/lib/hecks/adapters/driven/d1.rb +44 -237
  5. data/lib/hecks/adapters/driven/folder.rb +17 -76
  6. data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
  7. data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
  8. data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
  9. data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
  10. data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
  11. data/lib/hecks/adapters/driven/heki.rb +13 -42
  12. data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
  15. data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
  16. data/lib/hecks/adapters/driven/lambda.rb +10 -84
  17. data/lib/hecks/adapters/driven/local_storage.rb +5 -40
  18. data/lib/hecks/adapters/driven/memory.rb +32 -100
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +10 -32
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
  24. data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
  25. data/lib/hecks/adapters/driven/postgres.rb +149 -136
  26. data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
  27. data/lib/hecks/adapters/driven/prism.rb +48 -38
  28. data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
  29. data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
  30. data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
  31. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
  32. data/lib/hecks/adapters/driven/sqlite.rb +128 -65
  33. data/lib/hecks/adapters/driven/tenant_provisioner.rb +76 -62
  34. data/lib/hecks/adapters/driven.rb +5 -36
  35. data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
  36. data/lib/hecks/behaviors/dsl.rb +7 -25
  37. data/lib/hecks/behaviors/expectations.rb +32 -142
  38. data/lib/hecks/behaviors/ir.rb +7 -20
  39. data/lib/hecks/behaviors/rspec.rb +6 -17
  40. data/lib/hecks/behaviors/runner.rb +10 -34
  41. data/lib/hecks/behaviors.rb +4 -8
  42. data/lib/hecks/bench/cli.rb +62 -0
  43. data/lib/hecks/bench/environment.rb +71 -0
  44. data/lib/hecks/bench/postgres_probe.rb +30 -0
  45. data/lib/hecks/bench/report.rb +76 -0
  46. data/lib/hecks/bench/ruby_runner.rb +93 -0
  47. data/lib/hecks/bench/run.rb +55 -0
  48. data/lib/hecks/bench/rust_runner.rb +101 -0
  49. data/lib/hecks/bench/stats.rb +46 -0
  50. data/lib/hecks/bench/suite.rb +98 -0
  51. data/lib/hecks/bench/workload.rb +89 -0
  52. data/lib/hecks/bench.rb +17 -0
  53. data/lib/hecks/bluebook/aggregate.rb +9 -75
  54. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
  55. data/lib/hecks/bluebook/assembly/build.rb +4 -15
  56. data/lib/hecks/bluebook/assembly/contract.rb +15 -136
  57. data/lib/hecks/bluebook/assembly/contracts.rb +56 -174
  58. data/lib/hecks/bluebook/assembly/marks.rb +31 -223
  59. data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
  60. data/lib/hecks/bluebook/assembly.rb +6 -45
  61. data/lib/hecks/bluebook/attribute.rb +12 -61
  62. data/lib/hecks/bluebook/behaviour/aggregate.rb +39 -39
  63. data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
  64. data/lib/hecks/bluebook/behaviour/chapter.rb +14 -42
  65. data/lib/hecks/bluebook/behaviour/command.rb +14 -66
  66. data/lib/hecks/bluebook/behaviour/domain_port.rb +11 -20
  67. data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
  68. data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
  69. data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
  70. data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
  71. data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
  72. data/lib/hecks/bluebook/behaviour/read_model.rb +25 -31
  73. data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
  74. data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
  75. data/lib/hecks/bluebook/capabilities.rb +17 -49
  76. data/lib/hecks/bluebook/chapter.rb +16 -50
  77. data/lib/hecks/bluebook/command.rb +23 -81
  78. data/lib/hecks/bluebook/domain_port.rb +49 -67
  79. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
  80. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
  81. data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
  82. data/lib/hecks/bluebook/dsl/binding_proxy.rb +16 -69
  83. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +96 -708
  84. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +63 -169
  85. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +3 -1
  86. data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
  87. data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
  88. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +42 -89
  89. data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
  90. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
  91. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +148 -197
  92. data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
  93. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
  94. data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
  95. data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
  96. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
  97. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
  98. data/lib/hecks/bluebook/dsl/query_builder.rb +38 -43
  99. data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
  100. data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
  101. data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
  102. data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
  103. data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
  104. data/lib/hecks/bluebook/dsl/world_builder.rb +46 -111
  105. data/lib/hecks/bluebook/entity.rb +13 -71
  106. data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
  107. data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
  108. data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
  109. data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
  110. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
  111. data/lib/hecks/bluebook/expression/resolver.rb +39 -661
  112. data/lib/hecks/bluebook/hexagon.rb +18 -33
  113. data/lib/hecks/bluebook/lifecycle.rb +5 -16
  114. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
  115. data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
  116. data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
  117. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
  118. data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
  119. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +25 -176
  120. data/lib/hecks/bluebook/meta_validator/shapes.rb +53 -267
  121. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -306
  122. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
  123. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
  124. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
  125. data/lib/hecks/bluebook/meta_validator.rb +87 -352
  126. data/lib/hecks/bluebook/model_check/client_profile.rb +6 -21
  127. data/lib/hecks/bluebook/model_check.rb +122 -596
  128. data/lib/hecks/bluebook/pattern_subset.rb +49 -69
  129. data/lib/hecks/bluebook/process_manager.rb +13 -83
  130. data/lib/hecks/bluebook/project_loader.rb +3 -39
  131. data/lib/hecks/bluebook/project_register.rb +12 -60
  132. data/lib/hecks/bluebook/query.rb +27 -51
  133. data/lib/hecks/bluebook/read_model.rb +57 -81
  134. data/lib/hecks/bluebook/reference.rb +7 -41
  135. data/lib/hecks/bluebook/smoke_test.rb +9 -99
  136. data/lib/hecks/bluebook/synthesizer.rb +13 -62
  137. data/lib/hecks/bluebook/translation.rb +26 -62
  138. data/lib/hecks/bluebook/value_object.rb +11 -36
  139. data/lib/hecks/bluebook.rb +5 -22
  140. data/lib/hecks/cache_dir.rb +4 -23
  141. data/lib/hecks/canonical_json.rb +30 -0
  142. data/lib/hecks/chapters.rb +102 -0
  143. data/lib/hecks/cli/behaviors.rb +61 -0
  144. data/lib/hecks/cli/canonicalise.rb +25 -0
  145. data/lib/hecks/cli/check_engine_agreement.rb +32 -0
  146. data/lib/hecks/cli/check_era.rb +88 -0
  147. data/lib/hecks/cli/console.rb +66 -0
  148. data/lib/hecks/cli/deploy_template_diff.rb +40 -0
  149. data/lib/hecks/cli/doc_coverage.rb +32 -0
  150. data/lib/hecks/cli/document.rb +5 -23
  151. data/lib/hecks/cli/expression_projection.rb +41 -0
  152. data/lib/hecks/cli/follow.rb +88 -0
  153. data/lib/hecks/cli/history.rb +44 -0
  154. data/lib/hecks/cli/ir.rb +4 -6
  155. data/lib/hecks/cli/mcp.rb +5 -6
  156. data/lib/hecks/cli/model_check.rb +24 -88
  157. data/lib/hecks/cli/pattern_cases.rb +47 -0
  158. data/lib/hecks/cli/present.rb +39 -0
  159. data/lib/hecks/cli/project_cli.rb +223 -64
  160. data/lib/hecks/cli/project_diagrams.rb +4 -10
  161. data/lib/hecks/cli/project_glossary.rb +44 -0
  162. data/lib/hecks/cli/reference.rb +46 -0
  163. data/lib/hecks/cli/refresh_projections.rb +44 -0
  164. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
  165. data/lib/hecks/cli/release.rb +70 -0
  166. data/lib/hecks/cli/release_gem.rb +59 -0
  167. data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
  168. data/lib/hecks/cli/rspec_shard_files.rb +66 -0
  169. data/lib/hecks/cli/run.rb +15 -61
  170. data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
  171. data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
  172. data/lib/hecks/cli/shape.rb +70 -0
  173. data/lib/hecks/cli/smoke_http.rb +230 -0
  174. data/lib/hecks/cli/smoke_test.rb +7 -19
  175. data/lib/hecks/cli/spec_example.rb +27 -0
  176. data/lib/hecks/cli/statements.rb +32 -0
  177. data/lib/hecks/cli/stores.rb +4 -6
  178. data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
  179. data/lib/hecks/cli.rb +216 -73
  180. data/lib/hecks/codemod.rb +324 -0
  181. data/lib/hecks/construct.rb +8 -34
  182. data/lib/hecks/corpus.rb +423 -0
  183. data/lib/hecks/deploy/bluebook/deploy.bluebook +617 -158
  184. data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -11
  185. data/lib/hecks/deploy/oidc.json +82 -2
  186. data/lib/hecks/doc/reference.rb +351 -0
  187. data/lib/hecks/doors/cli_door.rb +196 -0
  188. data/lib/hecks/doors/cli_runner.rb +297 -0
  189. data/lib/hecks/{facade → doors}/command_request.rb +14 -34
  190. data/lib/hecks/doors/handle.rb +206 -0
  191. data/lib/hecks/doors/json_door.rb +132 -0
  192. data/lib/hecks/doors/launcher_options.rb +129 -0
  193. data/lib/hecks/{cli → doors}/mcp_door.rb +25 -120
  194. data/lib/hecks/doors/mcp_door_scope.rb +97 -0
  195. data/lib/hecks/doors/ruby_door/aggregate_door.rb +162 -0
  196. data/lib/hecks/doors/ruby_door/chapter.rb +72 -0
  197. data/lib/hecks/doors/ruby_door.rb +77 -0
  198. data/lib/hecks/doors.rb +61 -0
  199. data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
  200. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
  201. data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
  202. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
  203. data/lib/hecks/embryonaut_bluebook.rb +5 -72
  204. data/lib/hecks/engine_agreement.rb +156 -0
  205. data/lib/hecks/facade.rb +23 -51
  206. data/lib/hecks/forms/app.rb +21 -76
  207. data/lib/hecks/forms/banking_presentation.rb +50 -0
  208. data/lib/hecks/forms/command_form_renderer.rb +10 -29
  209. data/lib/hecks/forms/field_renderer.rb +17 -156
  210. data/lib/hecks/forms/field_shape.rb +31 -222
  211. data/lib/hecks/forms/html.rb +12 -47
  212. data/lib/hecks/forms/index_renderer.rb +1 -4
  213. data/lib/hecks/forms/page.rb +3 -9
  214. data/lib/hecks/forms/params.rb +14 -60
  215. data/lib/hecks/forms/port_argument.rb +2 -19
  216. data/lib/hecks/forms/query_form_renderer.rb +4 -79
  217. data/lib/hecks/forms/record_renderer.rb +8 -75
  218. data/lib/hecks/forms/record_table.rb +7 -50
  219. data/lib/hecks/forms/reference_options.rb +3 -30
  220. data/lib/hecks/forms/value_object_shape.rb +8 -35
  221. data/lib/hecks/forms.rb +5 -33
  222. data/lib/hecks/fqn.rb +5 -12
  223. data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
  224. data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
  225. data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
  226. data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
  227. data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
  228. data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
  229. data/lib/hecks/framework.rb +11 -80
  230. data/lib/hecks/freezer.rb +5 -39
  231. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
  232. data/lib/hecks/fuzzing/combination_miner.rb +86 -0
  233. data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
  234. data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
  235. data/lib/hecks/fuzzing/differential.rb +156 -0
  236. data/lib/hecks/fuzzing/domain_generator.rb +615 -0
  237. data/lib/hecks/fuzzing/era_boundary.rb +71 -0
  238. data/lib/hecks/fuzzing/form_census.rb +136 -0
  239. data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
  240. data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
  241. data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
  242. data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
  243. data/lib/hecks/fuzzing/outside_world.rb +45 -0
  244. data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
  245. data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
  246. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
  247. data/lib/hecks/fuzzing/properties/guards.rb +190 -0
  248. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
  249. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
  250. data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
  251. data/lib/hecks/fuzzing/properties/querying.rb +175 -0
  252. data/lib/hecks/fuzzing/properties.rb +162 -0
  253. data/lib/hecks/fuzzing/qa_settings.rb +123 -0
  254. data/lib/hecks/fuzzing/replay.rb +505 -0
  255. data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
  256. data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
  257. data/lib/hecks/fuzzing/self_consistency.rb +339 -0
  258. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
  259. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
  260. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
  261. data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
  262. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
  263. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
  264. data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
  265. data/lib/hecks/fuzzing/shrinker.rb +166 -0
  266. data/lib/hecks/fuzzing/structural_skips.rb +33 -0
  267. data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
  268. data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
  269. data/lib/hecks/fuzzing/value_generator.rb +216 -0
  270. data/lib/hecks/fuzzing.rb +23 -0
  271. data/lib/hecks/grammar/evolve.rb +336 -0
  272. data/lib/hecks/grammar/expression.bluebook +15 -40
  273. data/lib/hecks/grammar/translation.bluebook +3 -6
  274. data/lib/hecks/grammar.rb +16 -41
  275. data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
  276. data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
  277. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
  278. data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
  279. data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
  280. data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
  281. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
  282. data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
  283. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
  284. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
  285. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
  286. data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
  287. data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
  288. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
  289. data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
  290. data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
  291. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
  292. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
  293. data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
  294. data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
  295. data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
  296. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
  297. data/lib/hecks/hecks/adapters/git.adapter +7 -0
  298. data/lib/hecks/hecks/adapters/git.rb +102 -0
  299. data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
  300. data/lib/hecks/hecks/adapters/host_http.rb +65 -0
  301. data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
  302. data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
  303. data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
  304. data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
  305. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
  306. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
  307. data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
  308. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
  309. data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
  310. data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
  311. data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
  312. data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
  313. data/lib/hecks/hecks/adapters/local_files.rb +46 -0
  314. data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
  315. data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
  316. data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
  317. data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
  318. data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
  319. data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
  320. data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
  321. data/lib/hecks/hecks/adapters/shell.adapter +7 -0
  322. data/lib/hecks/hecks/adapters/shell.rb +49 -0
  323. data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
  324. data/lib/hecks/hecks/adapters/terminal.rb +74 -0
  325. data/lib/hecks/hecks/codebase.behaviors +172 -0
  326. data/lib/hecks/hecks/codebase.bluebook +2453 -0
  327. data/lib/hecks/hecks/context_map.hecksagon +13 -0
  328. data/lib/hecks/hecks/custodian.behaviors +179 -0
  329. data/lib/hecks/hecks/custodian.bluebook +1847 -0
  330. data/lib/hecks/hecks/deploy.behaviors +74 -0
  331. data/lib/hecks/hecks/deploy.world +12 -0
  332. data/lib/hecks/hecks/environments/memory.world +10 -0
  333. data/lib/hecks/hecks/governance.world +7 -0
  334. data/lib/hecks/hecks/hecks.behaviors +62 -0
  335. data/lib/hecks/hecks/hecks.bluebook +124 -0
  336. data/lib/hecks/hecks/hecks.hecksagon +390 -0
  337. data/lib/hecks/hecks/hecks.world +42 -0
  338. data/lib/hecks/ir.rb +14 -83
  339. data/lib/hecks/kernel_capabilities.rb +194 -0
  340. data/lib/hecks/language/adapter.bluebook +2 -7
  341. data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
  342. data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
  343. data/lib/hecks/language/bluebook/bluebook.bluebook +26 -83
  344. data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
  345. data/lib/hecks/language/bluebook/command.bluebook +27 -144
  346. data/lib/hecks/language/bluebook/entity.bluebook +24 -97
  347. data/lib/hecks/language/bluebook/policy.bluebook +16 -67
  348. data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
  349. data/lib/hecks/language/bluebook/projection.bluebook +126 -60
  350. data/lib/hecks/language/bluebook/query.bluebook +19 -43
  351. data/lib/hecks/language/bluebook/shape.bluebook +23 -73
  352. data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
  353. data/lib/hecks/language/bluebook/vocabulary.bluebook +83 -198
  354. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
  355. data/lib/hecks/language/hecksagon/domain_port.bluebook +14 -19
  356. data/lib/hecks/language/hecksagon/hecksagon.bluebook +26 -25
  357. data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
  358. data/lib/hecks/language/oidc.json +35 -0
  359. data/lib/hecks/language/port.bluebook +4 -17
  360. data/lib/hecks/language/translation/translation.bluebook +3 -9
  361. data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
  362. data/lib/hecks/language/world/wiring.bluebook +1 -3
  363. data/lib/hecks/language/world/world.bluebook +5 -7
  364. data/lib/hecks/literal.rb +8 -50
  365. data/lib/hecks/mcp_stdio_guard.rb +4 -41
  366. data/lib/hecks/naming.rb +29 -137
  367. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
  368. data/lib/hecks/persistence_legacy_fixture.rb +113 -0
  369. data/lib/hecks/ports/access_control.rb +7 -23
  370. data/lib/hecks/ports/agent/answers.rb +5 -23
  371. data/lib/hecks/ports/agent.rb +47 -172
  372. data/lib/hecks/ports/authentication.rb +12 -39
  373. data/lib/hecks/ports/authorization.rb +16 -43
  374. data/lib/hecks/ports/clock.rb +5 -45
  375. data/lib/hecks/ports/extraction.rb +7 -12
  376. data/lib/hecks/ports/identity_assignment.rb +8 -29
  377. data/lib/hecks/ports/identity_generation.rb +6 -29
  378. data/lib/hecks/ports/identity_resolution.rb +2 -10
  379. data/lib/hecks/ports/key_vault.rb +9 -45
  380. data/lib/hecks/ports/persistence/append_only.rb +61 -64
  381. data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
  382. data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
  383. data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
  384. data/lib/hecks/ports/persistence/plugin.rb +13 -42
  385. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +44 -144
  386. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
  387. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +20 -85
  388. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +6 -25
  389. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +18 -24
  390. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
  391. data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
  392. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
  393. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
  394. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
  395. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
  396. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
  397. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
  398. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +19 -76
  399. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
  400. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
  401. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
  402. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
  403. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
  404. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +43 -127
  405. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
  406. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +131 -555
  407. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
  408. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
  409. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
  410. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
  411. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
  412. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
  413. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
  414. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +50 -47
  415. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -124
  416. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
  417. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
  418. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
  419. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
  420. data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
  421. data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
  422. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
  423. data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
  424. data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
  425. data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
  426. data/lib/hecks/ports/persistence/state_codec.rb +47 -108
  427. data/lib/hecks/ports/persistence.rb +7 -14
  428. data/lib/hecks/ports/projection.rb +42 -84
  429. data/lib/hecks/ports/query/in_memory.rb +10 -58
  430. data/lib/hecks/ports/query/ordering.rb +11 -32
  431. data/lib/hecks/ports/query.rb +14 -37
  432. data/lib/hecks/ports.rb +2 -8
  433. data/lib/hecks/projection_files.rb +285 -0
  434. data/lib/hecks/projections/bootstrap_table.rb +12 -44
  435. data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
  436. data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
  437. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
  438. data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
  439. data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
  440. data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
  441. data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
  442. data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
  443. data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
  444. data/lib/hecks/projections/deploy/fargate.rb +40 -207
  445. data/lib/hecks/projections/deploy/lambda.rb +191 -708
  446. data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
  447. data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
  448. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  449. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
  450. data/lib/hecks/projections/deploy/preview/script.rb +2 -14
  451. data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
  452. data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
  453. data/lib/hecks/projections/deploy/preview/template.rb +3 -59
  454. data/lib/hecks/projections/deploy/preview.rb +2 -67
  455. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
  456. data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
  457. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
  458. data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
  459. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
  460. data/lib/hecks/projections/deploy/scripts.rb +4 -57
  461. data/lib/hecks/projections/deploy/shared.rb +48 -147
  462. data/lib/hecks/projections/deploy/smoke.rb +4 -61
  463. data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
  464. data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
  465. data/lib/hecks/projections/deploy/template_diff.rb +5 -57
  466. data/lib/hecks/projections/diagrams.rb +22 -584
  467. data/lib/hecks/projections/glossary/html.rb +14 -130
  468. data/lib/hecks/projections/glossary/markdown.rb +8 -69
  469. data/lib/hecks/projections/glossary/mermaid.rb +9 -66
  470. data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
  471. data/lib/hecks/projections/glossary/sentences.rb +14 -159
  472. data/lib/hecks/projections/glossary.rb +30 -222
  473. data/lib/hecks/projections/ir.rb +3 -10
  474. data/lib/hecks/projections/model/deviations.rb +16 -43
  475. data/lib/hecks/projections/model.rb +12 -51
  476. data/lib/hecks/projections/oidc.rb +12 -56
  477. data/lib/hecks/projections/parser_table.rb +13 -32
  478. data/lib/hecks/projections/reference.rb +4 -18
  479. data/lib/hecks/projections/rust_vocabulary.rb +13 -251
  480. data/lib/hecks/projections/shape.rb +3 -29
  481. data/lib/hecks/projections/statements.rb +7 -86
  482. data/lib/hecks/projections/vocabulary.rb +6 -22
  483. data/lib/hecks/projections.rb +2 -16
  484. data/lib/hecks/projector/cli_projector.rb +154 -400
  485. data/lib/hecks/projector/docs_projector.rb +10 -201
  486. data/lib/hecks/projector/exporter.rb +42 -134
  487. data/lib/hecks/projector/ir_projector.rb +2 -9
  488. data/lib/hecks/projector/narrate_projector.rb +15 -189
  489. data/lib/hecks/projector/target.rb +13 -76
  490. data/lib/hecks/projector.rb +30 -121
  491. data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
  492. data/lib/hecks/quality_control/adapters/agent.rb +58 -0
  493. data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
  494. data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
  495. data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
  496. data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
  497. data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
  498. data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
  499. data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
  500. data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
  501. data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
  502. data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
  503. data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
  504. data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
  505. data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
  506. data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
  507. data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
  508. data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
  509. data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
  510. data/lib/hecks/quality_control/cli/child.rb +45 -0
  511. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
  512. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
  513. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
  514. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
  515. data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
  516. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
  517. data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
  518. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
  519. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
  520. data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
  521. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
  522. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
  523. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
  524. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
  525. data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
  526. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
  527. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
  528. data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
  529. data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
  530. data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
  531. data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
  532. data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
  533. data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
  534. data/lib/hecks/query_ir.rb +346 -0
  535. data/lib/hecks/query_ir_mcp.rb +198 -0
  536. data/lib/hecks/query_specification/common/comparators.rb +5 -26
  537. data/lib/hecks/query_specification/common/comparison.rb +35 -143
  538. data/lib/hecks/query_specification/common/dsl.rb +8 -24
  539. data/lib/hecks/query_specification/common/null_policy.rb +20 -70
  540. data/lib/hecks/query_specification/common/options.rb +2 -6
  541. data/lib/hecks/query_specification/common/where_clause.rb +2 -10
  542. data/lib/hecks/query_specification/field_path.rb +37 -77
  543. data/lib/hecks/query_specification/hop_path.rb +12 -76
  544. data/lib/hecks/release/gem_pin.rb +2 -26
  545. data/lib/hecks/release/runner/ci_publisher.rb +5 -8
  546. data/lib/hecks/release/runner/clean_tree.rb +42 -0
  547. data/lib/hecks/release/runner/commands.rb +2 -4
  548. data/lib/hecks/release/runner/console.rb +6 -0
  549. data/lib/hecks/release/runner/gem_publisher.rb +15 -9
  550. data/lib/hecks/release/runner/npm_publisher.rb +9 -53
  551. data/lib/hecks/release/runner/preflight.rb +29 -20
  552. data/lib/hecks/release/runner/published.rb +8 -26
  553. data/lib/hecks/release/runner/tagger.rb +31 -23
  554. data/lib/hecks/release/runner.rb +60 -38
  555. data/lib/hecks/rendering.rb +4 -26
  556. data/lib/hecks/router/namespace_installer.rb +15 -39
  557. data/lib/hecks/router.rb +4 -8
  558. data/lib/hecks/runtime/adapter_lookup.rb +124 -0
  559. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  560. data/lib/hecks/runtime/boot_gates.rb +3 -18
  561. data/lib/hecks/runtime/caller.rb +19 -59
  562. data/lib/hecks/runtime/capability_graph.rb +5 -21
  563. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  564. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +27 -160
  565. data/lib/hecks/runtime/command_interpreter.rb +124 -306
  566. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  567. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  568. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  569. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  570. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  571. data/lib/hecks/runtime/dependency_planning.rb +50 -104
  572. data/lib/hecks/runtime/dispatcher.rb +134 -243
  573. data/lib/hecks/runtime/entity_element.rb +40 -462
  574. data/lib/hecks/runtime/entity_interpreter.rb +64 -219
  575. data/lib/hecks/runtime/errors.rb +32 -115
  576. data/lib/hecks/runtime/event.rb +3 -25
  577. data/lib/hecks/runtime/identity.rb +18 -154
  578. data/lib/hecks/runtime/instance.rb +18 -150
  579. data/lib/hecks/runtime/interpreting.rb +14 -70
  580. data/lib/hecks/runtime/invocation.rb +28 -164
  581. data/lib/hecks/runtime/loader.rb +109 -194
  582. data/lib/hecks/runtime/outbox.rb +39 -262
  583. data/lib/hecks/runtime/policy_interpreter.rb +43 -202
  584. data/lib/hecks/runtime/port_operation_interpreter.rb +28 -97
  585. data/lib/hecks/runtime/query_interpreter.rb +122 -187
  586. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  587. data/lib/hecks/runtime/reaction_outcome.rb +89 -0
  588. data/lib/hecks/runtime/read_model_interpreter.rb +117 -208
  589. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  590. data/lib/hecks/runtime/reference_hop.rb +8 -81
  591. data/lib/hecks/runtime/refusal_wording.rb +28 -87
  592. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  593. data/lib/hecks/runtime/registry/verification.rb +213 -227
  594. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  595. data/lib/hecks/runtime/registry.rb +156 -247
  596. data/lib/hecks/runtime/remote_dispatcher.rb +68 -154
  597. data/lib/hecks/runtime/routing.rb +4 -15
  598. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  599. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  600. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  601. data/lib/hecks/runtime/tenant_check.rb +6 -68
  602. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  603. data/lib/hecks/runtime/value/admission.rb +16 -67
  604. data/lib/hecks/runtime/value/coercion.rb +162 -760
  605. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  606. data/lib/hecks/runtime/value/field_checks.rb +172 -0
  607. data/lib/hecks/runtime/value.rb +15 -84
  608. data/lib/hecks/runtime.rb +35 -69
  609. data/lib/hecks/rust_build/conformance.rb +158 -0
  610. data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
  611. data/lib/hecks/rust_build/coverage.rb +218 -0
  612. data/lib/hecks/rust_build/native_build.rb +124 -0
  613. data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
  614. data/lib/hecks/rust_build/project_rust.rb +332 -0
  615. data/lib/hecks/rust_build/wasm.rb +105 -0
  616. data/lib/hecks/rust_build/wasm_browser.rb +113 -0
  617. data/lib/hecks/rust_build.rb +144 -0
  618. data/lib/hecks/storehouse.rb +155 -582
  619. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +10 -34
  620. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  621. data/lib/hecks/three_zero/forms.yml +127 -0
  622. data/lib/hecks/three_zero.rb +12 -0
  623. data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
  624. data/lib/hecks/tools/comment_style.rb +1070 -0
  625. data/lib/hecks/tools/corpus_report.rb +87 -0
  626. data/lib/hecks/tools/deploy_recipe.rb +255 -0
  627. data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
  628. data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
  629. data/lib/hecks/tools/era_projection_backfill.rb +94 -0
  630. data/lib/hecks/tools/era_reattest.rb +146 -0
  631. data/lib/hecks/tools/evolve_run.rb +189 -0
  632. data/lib/hecks/tools/fuzz_sweep.rb +373 -0
  633. data/lib/hecks/tools/heki_compaction.rb +100 -0
  634. data/lib/hecks/tools/hoist_local_givens.rb +246 -0
  635. data/lib/hecks/tools/journal_compaction.rb +89 -0
  636. data/lib/hecks/tools/oidc_manifests.rb +70 -0
  637. data/lib/hecks/tools/query_ir_run.rb +81 -0
  638. data/lib/hecks/tools/regeneration_run.rb +166 -0
  639. data/lib/hecks/tools/rust_comment_style.rb +642 -0
  640. data/lib/hecks/tools/sequence_script.rb +62 -0
  641. data/lib/hecks/tools/tail_merge.rb +117 -0
  642. data/lib/hecks/tools/tenant_provisioning.rb +142 -0
  643. data/lib/hecks/tools/translation_audit.rb +208 -0
  644. data/lib/hecks/tools/translation_scaffold.rb +131 -0
  645. data/lib/hecks/tools.rb +86 -0
  646. data/lib/hecks/vendoring/git_environment.rb +2 -8
  647. data/lib/hecks/vendoring/git_source.rb +4 -10
  648. data/lib/hecks/vendoring.rb +7 -46
  649. data/lib/hecks/version.rb +3 -14
  650. data/lib/hecks.rb +52 -90
  651. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  652. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  653. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  654. data/qa/settings.yml +66 -0
  655. data/rust/.cargo/config.toml +6 -0
  656. data/rust/.gitignore +2 -0
  657. data/rust/Cargo.lock +54 -0
  658. data/rust/Cargo.toml +61 -0
  659. data/rust/build/.cargo/config.toml +3 -0
  660. data/rust/build/.gitignore +1 -0
  661. data/rust/build/Cargo.lock +7 -0
  662. data/rust/build/Cargo.toml +19 -0
  663. data/rust/build/src/build_artifact.rs +47 -0
  664. data/rust/build/src/cargo_sync.rs +248 -0
  665. data/rust/build/src/json.rs +421 -0
  666. data/rust/build/src/lineage_pass.rs +285 -0
  667. data/rust/build/src/main.rs +51 -0
  668. data/rust/build/src/optional_pass.rs +150 -0
  669. data/rust/build/src/pipeline.rs +264 -0
  670. data/rust/build/src/reserved_names.rs +76 -0
  671. data/rust/build/src/resolve.rs +124 -0
  672. data/rust/build/src/root.rs +25 -0
  673. data/rust/build/src/sidecars.rs +73 -0
  674. data/rust/build/src/subprocess.rs +51 -0
  675. data/rust/build/src/tmp.rs +31 -0
  676. data/rust/codegen/.cargo/config.toml +3 -0
  677. data/rust/codegen/.gitignore +1 -0
  678. data/rust/codegen/Cargo.lock +7 -0
  679. data/rust/codegen/Cargo.toml +20 -0
  680. data/rust/codegen/src/attr.rs +32 -0
  681. data/rust/codegen/src/bridging.rs +481 -0
  682. data/rust/codegen/src/commands.rs +1226 -0
  683. data/rust/codegen/src/constraints.rs +109 -0
  684. data/rust/codegen/src/dependency_planning.rs +209 -0
  685. data/rust/codegen/src/domain_generator.rs +1288 -0
  686. data/rust/codegen/src/exemplar.rs +230 -0
  687. data/rust/codegen/src/expr_emitter.rs +87 -0
  688. data/rust/codegen/src/fielded.rs +192 -0
  689. data/rust/codegen/src/hecks_naming.rs +33 -0
  690. data/rust/codegen/src/json.rs +335 -0
  691. data/rust/codegen/src/json_codec.rs +697 -0
  692. data/rust/codegen/src/literal.rs +207 -0
  693. data/rust/codegen/src/main.rs +290 -0
  694. data/rust/codegen/src/manifest.rs +227 -0
  695. data/rust/codegen/src/mutations.rs +784 -0
  696. data/rust/codegen/src/naming.rs +286 -0
  697. data/rust/codegen/src/ports.rs +208 -0
  698. data/rust/codegen/src/prelude.rs +116 -0
  699. data/rust/codegen/src/queries.rs +786 -0
  700. data/rust/codegen/src/reactions.rs +532 -0
  701. data/rust/codegen/src/read_models.rs +697 -0
  702. data/rust/codegen/src/reference_specs.rs +40 -0
  703. data/rust/codegen/src/registry.rs +966 -0
  704. data/rust/codegen/src/reserved_names.rs +76 -0
  705. data/rust/codegen/src/shared.rs +93 -0
  706. data/rust/codegen/src/skip_reason.rs +25 -0
  707. data/rust/codegen/src/types.rs +362 -0
  708. data/rust/host/.cargo/config.toml +3 -0
  709. data/rust/host/.gitignore +1 -0
  710. data/rust/host/Cargo.lock +4297 -0
  711. data/rust/host/Cargo.toml +61 -0
  712. data/rust/host/HECKS_RELEASE +1 -0
  713. data/rust/host/rds-ca-bundle.pem +2736 -0
  714. data/rust/host/src/api.rs +1596 -0
  715. data/rust/host/src/approval.rs +652 -0
  716. data/rust/host/src/auth.rs +1287 -0
  717. data/rust/host/src/bin/expr_harness.rs +46 -0
  718. data/rust/host/src/bin/lineage_harness.rs +119 -0
  719. data/rust/host/src/bin/mint_harness.rs +135 -0
  720. data/rust/host/src/checkout.rs +265 -0
  721. data/rust/host/src/dispatch.rs +1443 -0
  722. data/rust/host/src/expr_json.rs +1117 -0
  723. data/rust/host/src/field_hints.rs +52 -0
  724. data/rust/host/src/ir.rs +733 -0
  725. data/rust/host/src/journal.rs +874 -0
  726. data/rust/host/src/lambda_client.rs +378 -0
  727. data/rust/host/src/log.rs +147 -0
  728. data/rust/host/src/main.rs +474 -0
  729. data/rust/host/src/mint.rs +1865 -0
  730. data/rust/host/src/payments/keystore.rs +330 -0
  731. data/rust/host/src/payments/tests.rs +1400 -0
  732. data/rust/host/src/payments.rs +767 -0
  733. data/rust/host/src/presentation.rs +698 -0
  734. data/rust/host/src/presentation_write.rs +1431 -0
  735. data/rust/host/src/query_step.rs +149 -0
  736. data/rust/host/src/rate_limit.rs +904 -0
  737. data/rust/host/src/reference_transform.rs +241 -0
  738. data/rust/host/src/reference_validate.rs +338 -0
  739. data/rust/host/src/resend.rs +296 -0
  740. data/rust/host/src/secrets.rs +106 -0
  741. data/rust/host/src/server.rs +663 -0
  742. data/rust/host/src/storage_shape.rs +175 -0
  743. data/rust/host/src/test_pg.rs +40 -0
  744. data/rust/host/src/ui_schema.rs +1351 -0
  745. data/rust/host/src/wasm_runner.rs +191 -0
  746. data/rust/host/src/web/newsletter/cooldown.rs +126 -0
  747. data/rust/host/src/web/newsletter/tests.rs +104 -0
  748. data/rust/host/src/web/newsletter.rs +310 -0
  749. data/rust/host/src/web/newsletter_send.rs +357 -0
  750. data/rust/host/src/web/registrations.rs +1505 -0
  751. data/rust/host/src/web.rs +2937 -0
  752. data/rust/lsp/.gitignore +1 -0
  753. data/rust/lsp/Cargo.lock +7 -0
  754. data/rust/lsp/Cargo.toml +16 -0
  755. data/rust/lsp/README.md +133 -0
  756. data/rust/lsp/src/diagnostics.rs +165 -0
  757. data/rust/lsp/src/json.rs +385 -0
  758. data/rust/lsp/src/main.rs +399 -0
  759. data/rust/lsp/src/outline.rs +177 -0
  760. data/rust/lsp/src/rpc.rs +67 -0
  761. data/rust/parser/.cargo/config.toml +5 -0
  762. data/rust/parser/.gitignore +1 -0
  763. data/rust/parser/Cargo.lock +7 -0
  764. data/rust/parser/Cargo.toml +21 -0
  765. data/rust/parser/src/build/closed_sets.rs +27 -0
  766. data/rust/parser/src/build/identity.rs +276 -0
  767. data/rust/parser/src/build/mod.rs +12 -0
  768. data/rust/parser/src/build/naming.rs +141 -0
  769. data/rust/parser/src/build/pattern_subset.rs +161 -0
  770. data/rust/parser/src/build/query_derive.rs +72 -0
  771. data/rust/parser/src/build/query_inference.rs +269 -0
  772. data/rust/parser/src/build/query_options.rs +100 -0
  773. data/rust/parser/src/build/read_model.rs +94 -0
  774. data/rust/parser/src/build/references.rs +32 -0
  775. data/rust/parser/src/canonical.rs +114 -0
  776. data/rust/parser/src/diag.rs +55 -0
  777. data/rust/parser/src/emit.rs +953 -0
  778. data/rust/parser/src/expr/ast_json.rs +147 -0
  779. data/rust/parser/src/expr/evaluator.rs +81 -0
  780. data/rust/parser/src/expr/mod.rs +84 -0
  781. data/rust/parser/src/expr/resolver.rs +497 -0
  782. data/rust/parser/src/ir.rs +387 -0
  783. data/rust/parser/src/keywords.rs +585 -0
  784. data/rust/parser/src/lex.rs +662 -0
  785. data/rust/parser/src/main.rs +252 -0
  786. data/rust/parser/src/parse/aggregate.rs +612 -0
  787. data/rust/parser/src/parse/chapter.rs +517 -0
  788. data/rust/parser/src/parse/command.rs +637 -0
  789. data/rust/parser/src/parse/domain_port.rs +127 -0
  790. data/rust/parser/src/parse/entity.rs +527 -0
  791. data/rust/parser/src/parse/file.rs +67 -0
  792. data/rust/parser/src/parse/hecksagon.rs +231 -0
  793. data/rust/parser/src/parse/lifecycle.rs +162 -0
  794. data/rust/parser/src/parse/mod.rs +1262 -0
  795. data/rust/parser/src/parse/policy.rs +87 -0
  796. data/rust/parser/src/parse/process_manager.rs +305 -0
  797. data/rust/parser/src/parse/query.rs +100 -0
  798. data/rust/parser/src/parse/read_model.rs +214 -0
  799. data/rust/parser/src/parse/value_object.rs +250 -0
  800. data/rust/parser/src/ruby_value.rs +422 -0
  801. data/rust/project/bridging.rb +257 -0
  802. data/rust/project/commands.rb +877 -0
  803. data/rust/project/constraints.rb +86 -0
  804. data/rust/project/dependency_planning.rb +178 -0
  805. data/rust/project/domain_generator.rb +980 -0
  806. data/rust/project/exemplar.rb +148 -0
  807. data/rust/project/expr_emitter.rb +72 -0
  808. data/rust/project/fielded.rb +141 -0
  809. data/rust/project/json_codec.rb +641 -0
  810. data/rust/project/mutations.rb +721 -0
  811. data/rust/project/naming.rb +205 -0
  812. data/rust/project/ports.rb +65 -0
  813. data/rust/project/queries.rb +493 -0
  814. data/rust/project/reactions.rb +317 -0
  815. data/rust/project/read_models.rb +690 -0
  816. data/rust/project/reference_specs.rb +27 -0
  817. data/rust/project/registry.rb +396 -0
  818. data/rust/project/skip_reason.rb +29 -0
  819. data/rust/project/types.rb +225 -0
  820. data/rust/project/write_if_changed.rb +83 -0
  821. data/rust/project.rb +30 -0
  822. data/rust/project_rust_pipeline.rb +383 -0
  823. data/rust/src/exemplar/commands.rs +279 -0
  824. data/rust/src/exemplar/constraints.rs +18 -0
  825. data/rust/src/exemplar/fielded.rs +280 -0
  826. data/rust/src/exemplar/json.rs +277 -0
  827. data/rust/src/exemplar/mod.rs +17 -0
  828. data/rust/src/exemplar/mutations.rs +80 -0
  829. data/rust/src/exemplar/queries.rs +33 -0
  830. data/rust/src/exemplar/reactions.rs +88 -0
  831. data/rust/src/exemplar/read_models.rs +48 -0
  832. data/rust/src/exemplar/registry.rs +166 -0
  833. data/rust/src/exemplar/types.rs +44 -0
  834. data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
  835. data/rust/src/kernel/attribute_shapes/list.rs +18 -0
  836. data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
  837. data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
  838. data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
  839. data/rust/src/kernel/cli.rs +585 -0
  840. data/rust/src/kernel/dispatch.rs +1090 -0
  841. data/rust/src/kernel/expr.rs +458 -0
  842. data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
  843. data/rust/src/kernel/expression_operators/comparison.rs +48 -0
  844. data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
  845. data/rust/src/kernel/expression_operators/logical.rs +13 -0
  846. data/rust/src/kernel/expression_operators/membership.rs +18 -0
  847. data/rust/src/kernel/expression_operators/mod.rs +68 -0
  848. data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
  849. data/rust/src/kernel/expression_operators/positional.rs +126 -0
  850. data/rust/src/kernel/expression_operators/presence.rs +101 -0
  851. data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
  852. data/rust/src/kernel/expression_operators/sized.rs +35 -0
  853. data/rust/src/kernel/expression_operators/text.rs +79 -0
  854. data/rust/src/kernel/expression_operators/to_string.rs +18 -0
  855. data/rust/src/kernel/json.rs +779 -0
  856. data/rust/src/kernel/mod.rs +133 -0
  857. data/rust/src/kernel/named_query.rs +219 -0
  858. data/rust/src/kernel/orchestrate.rs +1810 -0
  859. data/rust/src/kernel/pattern.rs +307 -0
  860. data/rust/src/kernel/query_comparators.rs +206 -0
  861. data/rust/src/kernel/query_ordering.rs +103 -0
  862. data/rust/src/kernel/read_model.rs +914 -0
  863. data/rust/src/kernel/reference_lookup.rs +149 -0
  864. data/rust/src/kernel/repository.rs +468 -0
  865. data/rust/src/kernel/routing.rs +612 -0
  866. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
  867. data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
  868. data/rust/src/kernel/vocab/field_hint.rs +70 -0
  869. data/rust/src/kernel/vocab/mod.rs +20 -0
  870. data/rust/src/kernel/vocab/query_comparator.rs +65 -0
  871. data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
  872. data/rust/src/lib.rs +8 -0
  873. data/rust/src/main.rs +16 -0
  874. data/rust/web/.gitignore +1 -0
  875. data/rust/web/Cargo.lock +119 -0
  876. data/rust/web/Cargo.toml +18 -0
  877. data/rust/web/src/lib.rs +8 -0
  878. metadata +478 -12
  879. data/lib/hecks/facade/cli_door.rb +0 -178
  880. data/lib/hecks/facade/cli_runner.rb +0 -271
  881. data/lib/hecks/facade/handle.rb +0 -293
  882. data/lib/hecks/facade/json_door.rb +0 -247
  883. data/lib/hecks/facade/surface/aggregate_door.rb +0 -222
  884. data/lib/hecks/facade/surface/chapter.rb +0 -116
  885. data/lib/hecks/facade/surface.rb +0 -61
  886. data/lib/hecks/mcp_door_scope.rb +0 -176
@@ -8,53 +8,23 @@ module Hecks
8
8
  module Expression
9
9
  class EvaluationError < StandardError; end
10
10
 
11
- # The dotted/arithmetic leaf grammar a predicate's `Resolve` node
12
- # bottoms out in — attribute lookups, literals, arithmetic, and the
13
- # growing set of vendored method-call suffixes (`.match?`,
14
- # `.present?`/`.blank?`, `.set?`/`.unset?`, `.split`, `.first`/
15
- # `.last`, `.all?`/`.any?`/`.none?`/`.find`, ...) this file and its
16
- # `resolver/block_predicates.rb` sibling parse and interpret. Each
17
- # leaf's `parse` is a pure function of its own string, cached at the
18
- # `Evaluator` layer; `interpret` reads state/attrs fresh each call.
11
+ # The dotted/arithmetic leaf grammar a predicate's `Resolve` node bottoms out in.
12
+ # `parse` is a pure function of its string; `interpret` reads state/attrs fresh.
19
13
  module Resolver
20
14
  SIGN_TESTS = Hecks::Vocabulary.fetch("SignTest")
21
15
 
22
- # Which Comparison operator each sign test is sugar for, against the
23
- # literal 0 — Vocabulary::SignTest's own compares_via
24
- # (language/bluebook/vocabulary.bluebook), read off the generated
25
- # table rather than typed a second time.
16
+ # Comparison operator each sign test stands for, against the literal 0.
26
17
  SIGN_TEST_OPERATORS = Hecks::Vocabulary.rows("SignTest")
27
18
  .to_h { |row| [row["name"], row["compares_via"]] }
28
19
  .freeze
29
20
 
30
- # The leaf grammar an expression's dotted/arithmetic side parses into.
31
- # Which node a string produces is a pure function of the string, so
32
- # parse() runs once per distinct leaf (folded into Evaluator's own
33
- # ast_cache — see evaluator.rb) and interpret() reads state/attrs
34
- # fresh on every call. Only Lookup — the true leaf — ever touches
35
- # state/attrs.
21
+ # Leaf nodes of the grammar. Only Lookup touches state/attrs.
36
22
  IntegerLiteral = Struct.new(:value, keyword_init: true)
37
23
  FloatLiteral = Struct.new(:value, keyword_init: true)
38
24
  StringLiteral = Struct.new(:value, keyword_init: true)
39
25
  BoolLiteral = Struct.new(:value, keyword_init: true)
40
- # `["active", "suspended"]` — a literal set, the haystack half of
41
- # an `.include?`. Vocabulary::IncludeHaystack has always admitted
42
- # Array (and Evaluator#includes? has always had a `when Array`
43
- # arm), but nothing could produce one: there was no array-literal
44
- # node, so `["a", "b"].include?(x)` fell through to Lookup and
45
- # refused with `cannot resolve "[\"a\", \"b\"]"`. A declared
46
- # capability with no way to spell it.
47
26
  ArrayLiteral = Struct.new(:elements, keyword_init: true)
48
- # A plain class, not `Struct.new(keyword_init: true)` — every
49
- # sibling leaf node here carries at least one field, but this
50
- # one carries none by nature (a nil literal has no data to
51
- # hold), and `Struct.new` with zero member names ahead of
52
- # `keyword_init:` is real, live Ruby-version-dependent
53
- # behavior: works on 3.3, raises "wrong number of arguments
54
- # (given 0, expected 1+)" on 3.2 (caught deploying to Lambda's
55
- # own ruby3.2 runtime). `.new`/`case ... when NilLiteral` below
56
- # are the only two things this type is ever used for, and a
57
- # bare class answers both identically.
27
+ # A plain class: `Struct.new(keyword_init: true)` with no members raises on Ruby 3.2.
58
28
  NilLiteral = Class.new
59
29
  Addition = Struct.new(:left, :right, keyword_init: true)
60
30
  SignTest = Struct.new(:operator, :test, :receiver, keyword_init: true)
@@ -64,186 +34,39 @@ module Hecks
64
34
  Size = Struct.new(:receiver, keyword_init: true)
65
35
  Lookup = Struct.new(:path, keyword_init: true)
66
36
 
67
- # Update 2026-08-27: every "vendored addition, not (yet) upstream
68
- # hecks" comment on this file's own MatchesRegex/Presence/Split/
69
- # First/Last/StartsWith/EndsWith below (plus ArrayLiteral above)
70
- # described a real gap, found the hard way, in the history each
71
- # comment tells — and that history stays exactly as written,
72
- # on purpose. What changed is the present tense claim "not (yet)
73
- # upstream": a review of this exact migration found these eight
74
- # symbols had working Ruby parse/interpret arms but had never
75
- # gone through Propose -> Render -> Admit
76
- # (lib/hecks/grammar/expression_operators.json) the way every
77
- # other operator here has — a closed-vocabulary guard
78
- # (spec/operator_conformance_spec.rb) built entirely over tables
79
- # structurally could not see hand-coded Struct/parse/interpret
80
- # additions, so eight operators ran in Ruby, admitted nowhere,
81
- # invisible to the one guard whose whole job was "reads in every
82
- # target." All eight are now ledger-admitted for real, with full
83
- # Rust kernel parity (rust/src/kernel/expression_operators/
84
- # {pattern_match,presence,text,positional}.rs) — the two-tier
85
- # gap is closed, not merely tracked.
86
- #
87
- # `receiver.match?(/pattern/)` -- vendored addition, not (yet)
88
- # upstream hecks (migration plan task 8): confirmed the
89
- # single most impactful corpus-wide dispatch-time gap of the
90
- # whole migration -- `.match?(regex)` appears in nearly every
91
- # value_object's format-validation rule across every corpus
92
- # this migration touched (email/phone/ISO-8601-timestamp/zip
93
- # patterns, dozens of files), and had no parse support at all:
94
- # it fell all the way through to the `Lookup` catch-all below,
95
- # which tried to split the entire ".match?(/\A\d{5}\z/)" text
96
- # on "." as if it were a dotted attribute path, and crashed with
97
- # an opaque "no implicit conversion of Symbol into Integer"
98
- # somewhere downstream -- confirmed live via a real dispatch,
99
- # not validate (validate never evaluates a predicate body).
100
- # `receiver` still needs its own parse (it may itself be a
101
- # dotted lookup, e.g. `some_field.match?(...)`), the pattern
102
- # text is taken as-is between the slashes (a Ruby Regexp literal,
103
- # not the evaluator's own mini-grammar -- there is nothing to
104
- # recurse into).
105
37
  MatchesRegex = Struct.new(:receiver, :pattern, :flags, keyword_init: true)
106
38
 
107
- # `.present?`/`.blank?` -- vendored addition, not (yet) upstream
108
- # hecks (migration plan task 8): the second-most pervasive
109
- # gap this pass found, right behind `.match?` -- confirmed real
110
- # via the same "no such attribute" Lookup-fallback crash, since
111
- # neither suffix existed in this leaf grammar at all. Rails-
112
- # standard semantics (blank = nil, or responds_to?(:empty?) &&
113
- # empty? ; present = !blank), not just `!nil?` -- matches what
114
- # every corpus author actually means by `.present?` on a string
115
- # field that could legitimately be "" rather than absent.
116
39
  Presence = Struct.new(:receiver, :negated, keyword_init: true)
117
40
 
118
- # `.set?`/`.unset?` -- sibling of `.present?`/`.blank?` immediately
119
- # above, added for a deliberately narrower question, spelled out in
120
- # the name so choosing between the two pairs is a choice, not a
121
- # trap: `.present?` means "not EMPTY" (Rails-standard -- nil/false,
122
- # and an empty String/Array/Hash, are blank; nothing else is),
123
- # which quietly answers "was this ever assigned" and "does the
124
- # assigned value happen to be empty" as the same question. For an
125
- # optional field whose only legitimate unset state is nil, that
126
- # conflation is a real trap -- confirmed live: a corpus author
127
- # reaching for `superseded_by.blank?` to guard an optional
128
- # reference burned real time before landing on "just don't guard
129
- # it," because `.blank?` cannot be asked to mean ONLY "was this
130
- # never assigned." `.set?`/`.unset?` ask exactly that, and nothing
131
- # else: `!receiver.nil?`, full stop -- an assigned-but-empty
132
- # String/Array/Hash (or a VO wrapping one) is `.set?`, not
133
- # `.unset?`, unlike `.blank?`'s own reading of the identical value.
41
+ # `.set?`/`.unset?` ask only `!nil?`; unlike `.blank?`, an assigned empty value is set.
134
42
  Assignment = Struct.new(:receiver, :negated, keyword_init: true)
135
43
 
136
- # `receiver.split("SEP")` -- vendored addition, not (yet) upstream
137
- # hecks (migration plan task 9): confirmed the single
138
- # highest-value new finding of the real-dispatch smoke sweep --
139
- # the bus's own core `Phrase` value object (dispatch/lexicon/
140
- # query/command_bus.bluebook, all four storehouse-kernel files,
141
- # byte-identical text) validates its four-segment shape with
142
- # `value.split("::").length == 4 && value.split("::").all? { |s|
143
- # s.length > 0 }` -- `.split(` matched none of this grammar's
144
- # known suffixes, so it fell through to the `Lookup` catch-all,
145
- # which split the raw expression text on "." (not the runtime
146
- # value) and crashed with `TypeError: no implicit conversion of
147
- # Symbol into Integer` the moment `String#[]` was handed a
148
- # Symbol segment -- meaning every command taking a Phrase was
149
- # entirely undispatchable, confirmed live via `Lexicon::Lexicon.
150
- # Lookup`/`Query::Query.Run`, not inferred. Produces a real
151
- # Array, deliberately not a scalar -- `.length`/`.all?` compose
152
- # with it below the same way they compose with any other
153
- # receiver, because `parse` recurses on the receiver text and
154
- # the existing `Size` node (`.length`/`.size`) already treats
155
- # "whatever `interpret` returns" as its receiver's value, not a
156
- # fixed type. Separator is taken literally between the quotes,
157
- # same "no sub-grammar to recurse into" precedent `MatchesRegex`
158
- # already set for its own `/pattern/` text above.
159
44
  Split = Struct.new(:receiver, :separator, keyword_init: true)
160
45
 
161
- # `receiver.last` -- vendored addition, not (yet) upstream
162
- # hecks (migration plan task 9), same pass as `Split` above
163
- # and built for the same `Phrase`/`Path` value objects --
164
- # `Query::Phrase`'s own invariant chains `.split("::").last.
165
- # match?(...)` to check the fourth segment's casing. Same shape
166
- # as `.length`/`.size` (Size) and `.empty?` (Empty) -- a plain
167
- # receiver-in, scalar-out accessor, no sub-grammar of its own.
168
46
  Last = Struct.new(:receiver, keyword_init: true)
169
47
 
170
- # `receiver.first` -- sibling addition to `Last` immediately
171
- # above, same shape, same vendored-not-upstream status. Added
172
- # for the shipping domain's `legs.first` (an itinerary's
173
- # departure leg), the exact mirror of `legs.last` (its arrival
174
- # leg) that domain already leaned on -- `Last`'s own duck-typed-
175
- # on-`respond_to?` reasoning applies unchanged, so `first_of`
176
- # below is `last_of` with the one method swapped, not a new
177
- # design.
178
48
  First = Struct.new(:receiver, keyword_init: true)
179
49
 
180
- # `receiver.start_with?("prefix")` / `receiver.end_with?("suffix")`
181
- # -- vendored addition, not (yet) upstream hecks (migration
182
- # plan task 9), the sibling gap the `Split`/`Last`/`BlockPredicate`
183
- # pass above flagged and deliberately left unfixed --
184
- # `Query::Query.Run`/`Dispatch::Dispatch.Route`/`CommandBus::
185
- # CommandBus.Route`'s shared `Params` value object validates its
186
- # JSON-object shape with `value.start_with?("{") && value.
187
- # end_with?("}")` (dispatch/query/command_bus.bluebook, all three
188
- # storehouse-kernel files, byte-identical text) -- `.start_with?(`/
189
- # `.end_with?(` matched none of this grammar's known suffixes, so
190
- # both fell through to the `Lookup` catch-all and crashed with the
191
- # same `TypeError: no implicit conversion of Symbol into
192
- # Integer` shape any unsupported suffix produces here, confirmed
193
- # live via a real dispatch (not validate), not inferred. Two
194
- # separate node types rather than one `mode:`-keyed struct (the `BlockPredicate`/
195
- # `SignTest` precedent) -- `start_with?`/`end_with?` aren't two
196
- # spellings of the same test the way `all?`/`any?`/`none?` are (one
197
- # Array-aggregation family) or `positive?`/`negative?`/`zero?` are
198
- # (one comparison-against-0 family) ; they check different ends of
199
- # the same string and share nothing but their
200
- # receiver-plus-literal-argument shape. `substring` is taken
201
- # literally between the quotes, same "no sub-grammar to recurse
202
- # into" precedent `Split`'s `separator`/`MatchesRegex`'s `pattern`
203
- # already set.
204
50
  StartsWith = Struct.new(:receiver, :substring, keyword_init: true)
205
51
  EndsWith = Struct.new(:receiver, :substring, keyword_init: true)
206
52
 
207
53
  module_function
208
54
 
209
- # Parses and interprets `expr` in one step, without going through
210
- # `Evaluator`'s own boolean/comparison grammar or its `ast_cache` —
211
- # for a caller that wants this leaf grammar's own raw value (an
212
- # Integer, a String, an Array, ...), not a boolean.
55
+ # Parses and interprets `expr` in one step, bypassing `Evaluator`'s boolean grammar.
213
56
  #
214
- # @param expr [String] the dotted/arithmetic leaf expression to
215
- # resolve
216
- # @param state [Hash{Symbol => Object}] the stored attribute values
217
- # an unqualified name may resolve against
218
- # @param attrs [Hash{Symbol => Object}] the call's own argument
219
- # values, checked before `state`
220
- # @return [Object] whatever `expr` resolves to: an Integer, Float,
221
- # String, `true`/`false`, `nil`, or Array, depending on `expr`'s
222
- # own leaf grammar
223
- # @raise [EvaluationError] if `expr` names an attribute or argument
224
- # neither `attrs` nor `state` has, or if an operation in `expr`
225
- # is applied to a value of the wrong type
57
+ # @param expr [String] the dotted/arithmetic leaf expression
58
+ # @param state [Hash{Symbol => Object}] stored attribute values
59
+ # @param attrs [Hash{Symbol => Object}] call arguments, checked before `state`
60
+ # @return [Object] the resolved Integer, Float, String, boolean, nil, or Array
61
+ # @raise [EvaluationError] on an unknown name or a wrongly typed operand
226
62
  def resolve(expr, state, attrs)
227
63
  interpret(parse(expr), state, attrs)
228
64
  end
229
65
 
230
- # A grammar's own dispatch table — one `return Node.new(...) if
231
- # expr =~ /pattern/` per leaf production, in a fixed precedence
232
- # order (`.length` before the generic suffixes, sign tests before
233
- # the rest, etc. — see this file's own history comments on why
234
- # several of these were added in exactly this position). Each
235
- # branch is already one line; splitting the table into smaller
236
- # methods would not shrink any single check, only hide the
237
- # precedence order this method's own line-by-line sequence is.
66
+ # Branch order is the precedence order (`.length` first, sign tests before the rest).
238
67
  #
239
- # @param expr [String] the leaf expression text to parse
240
- # @return [Object] the parsed leaf node — one of this module's own
241
- # Struct types (`IntegerLiteral`, `FloatLiteral`, `StringLiteral`,
242
- # `BoolLiteral`, `NilLiteral`, `ArrayLiteral`, `Addition`,
243
- # `SignTest`, `Empty`, `ToS`, `Modulo`, `Size`, `MatchesRegex`,
244
- # `Presence`, `Assignment`, `Split`, `First`, `Last`,
245
- # `StartsWith`, `EndsWith`, `BlockPredicate`, `Find`), chosen by
246
- # `expr`'s own shape, or `Lookup` when nothing else matches
68
+ # @param expr [String] the leaf expression text
69
+ # @return [Object] the parsed leaf node, or `Lookup` when nothing else matches
247
70
  # rubocop:disable-next Metrics/AbcSize
248
71
  # rubocop:disable-next Metrics/CyclomaticComplexity
249
72
  # rubocop:disable-next Metrics/MethodLength
@@ -313,13 +136,6 @@ module Hecks
313
136
  Lookup.new(path: expr)
314
137
  end
315
138
 
316
- # Builds the `SignTest` node for a matched `.positive?`/
317
- # `.negative?`/`.zero?`-style suffix.
318
- #
319
- # @param parts [Array(String, String)] the `[receiver_text,
320
- # test_name]` pair `match_suffix` returns
321
- # @return [SignTest] the parsed sign-test node, its `receiver`
322
- # itself parsed
323
139
  def sign_test_node(parts)
324
140
  receiver, test = parts
325
141
  symbol = SIGN_TEST_OPERATORS.fetch(test)
@@ -327,28 +143,10 @@ module Hecks
327
143
  SignTest.new(operator: operator, test: test, receiver: parse(receiver))
328
144
  end
329
145
 
330
- # `parse`'s own dispatch table, mirrored: one `when` per leaf node
331
- # type it can produce, each already delegating to a small named
332
- # helper (add, apply_sign_test, emptiness_of, ...) — the case
333
- # itself is the closed, declared set this method exists to
334
- # exhaust; the `else` backstop's own comment explains why a
335
- # missing arm is a bug this method is built to make loud, not
336
- # quiet.
146
+ # Evaluates a node `parse` produced against `state` and `attrs`.
337
147
  #
338
- # @param node [Object] a node `parse` produced (one of this
339
- # module's own Struct types) or built directly by a caller
340
- # @param state [Hash{Symbol => Object}] the stored attribute values
341
- # a `Lookup` node may resolve against
342
- # @param attrs [Hash{Symbol => Object}] the call's own argument
343
- # values, checked before `state`
344
- # @return [Object] the value `node` resolves to: an Integer, Float,
345
- # String, `true`/`false`, `nil`, or Array, depending on `node`'s
346
- # own type
347
- # @raise [EvaluationError] if `node` is not one of the handled
348
- # types, or if a helper it dispatches to (`add`, `apply_sign_test`,
349
- # `matches_regex?`, `split_value`, `last_of`, `first_of`,
350
- # `starts_with?`, `ends_with?`, `apply_modulo`, `size_of`,
351
- # `emptiness_of`, `lookup`) refuses its operand
148
+ # @return [Object] the value the node resolves to
149
+ # @raise [EvaluationError] if the node type is unhandled or an operand is refused
352
150
  # rubocop:disable-next Metrics/AbcSize
353
151
  # rubocop:disable-next Metrics/CyclomaticComplexity
354
152
  # rubocop:disable-next Metrics/MethodLength
@@ -394,69 +192,24 @@ module Hecks
394
192
  when Lookup
395
193
  lookup(node.path, state, attrs)
396
194
  else
397
- # Every leaf node `parse` can produce has a `when` above —
398
- # a backstop against the day this grammar grows a new leaf
399
- # type (this file's own history: MatchesRegex/Presence/
400
- # Split/Last/First/Find/StartsWith/EndsWith/BlockPredicate
401
- # were each added exactly this way, and each one — before it had an
402
- # `interpret` arm — fell all the way through to the
403
- # `Lookup` catch-all in `parse` and crashed downstream with
404
- # an opaque type error, never here). A missing arm here
405
- # would instead return bare `nil` silently, the one wrong-
406
- # answer shape this leaf grammar has otherwise never
407
- # allowed.
195
+ # A missing arm must raise; silently returning nil is the one wrong answer
196
+ # this grammar never allows.
408
197
  raise EvaluationError, "no interpreter handles #{node.class} — add a case before parse can produce it"
409
198
  end
410
199
  end
411
200
 
412
- # `.present?`/`.blank?` -- vendored addition, see the
413
- # `Presence` struct's own comment above. `nil` and `false` are
414
- # blank ; a String/Array/Hash is blank when empty, not merely
415
- # falsy -- a VO-wrapped field that is assigned (`{value: "x"}`,
416
- # `Value#to_h`'d first) is present regardless of what its own
417
- # inner value holds, matching how every VO-typed field in this
418
- # corpus is actually shaped once set at all.
419
- #
420
- # @param value [Object] the already-interpreted receiver value
421
- # @return [Boolean] `true` for `nil`, `false`, or an empty
422
- # String/Array/Hash (a to_h-able value is converted first);
423
- # `false` for anything else
424
201
  def blank?(value)
425
202
  return true if value.nil? || value == false
426
203
 
427
- # Duck-typed, not `value.is_a?(Runtime::Value)` -- this module
428
- # is `Bluebook::Expression`, a different namespace tree from
429
- # `Runtime::Value` entirely, and reaching across for a single
430
- # class check is the exact cross-module coupling that already
431
- # broke `Modulo`'s own `match_call` reference elsewhere in
432
- # this file (found live while building this fix, not assumed).
433
- value = value.to_h if value.respond_to?(:to_h) && !value.is_a?(Hash)
204
+ # Duck-typed: reaching across to Runtime::Value would couple the namespaces. A list
205
+ # is judged by its own emptiness, never coerced: `[0].to_h` would raise TypeError.
206
+ value = value.to_h if value.respond_to?(:to_h) && !value.is_a?(Hash) && !value.is_a?(Array)
434
207
  case value
435
208
  when String, Array, Hash then value.empty?
436
209
  else false
437
210
  end
438
211
  end
439
212
 
440
- # `receiver.match?(/pattern/)` -- vendored addition, see the
441
- # `MatchesRegex` struct's own comment above. `receiver_value` is
442
- # coerced to a plain String first -- inlined here rather than
443
- # calling `Evaluator#string_of` (a different module_function
444
- # module; not actually in scope from inside Resolver despite
445
- # `Modulo`'s own parse rule above calling a same-named
446
- # `match_call` that has the identical cross-module problem --
447
- # found live while building this, not assumed).
448
- #
449
- # @param receiver_value [String, Symbol, Integer, Float, nil] the
450
- # already-interpreted receiver value
451
- # @param pattern [String] the regex source, exactly as it appeared
452
- # between the `/.../` delimiters
453
- # @param flags [String] the suffix flag letters after the closing
454
- # `/`; `"i"` enables `Regexp::IGNORECASE`, `"m"` `MULTILINE`,
455
- # `"x"` `EXTENDED`
456
- # @return [Boolean] whether `pattern` matches `receiver_value`'s
457
- # string form
458
- # @raise [EvaluationError] if `receiver_value` is not a scalar, or
459
- # `pattern` is not a valid regex
460
213
  def matches_regex?(receiver_value, pattern, flags)
461
214
  text = case receiver_value
462
215
  when String, Symbol, Integer, Float then receiver_value.to_s
@@ -472,25 +225,10 @@ module Hecks
472
225
 
473
226
  Regexp.new(pattern, options).match?(text)
474
227
  rescue RegexpError => e
475
- # M9: a malformed pattern between the slashes (an unclosed
476
- # character class, say) is a defect in the expression text
477
- # itself, exactly the same category of author mistake an
478
- # unresolvable attribute name already refuses for — `Regexp.new`
479
- # raising a raw `RegexpError` crossed this sublanguage's own
480
- # refusal boundary the same way the `ZeroDivisionError`/
481
- # `TypeError` cases elsewhere in this file did.
228
+ # A malformed pattern is an author mistake; refuse it as EvaluationError.
482
229
  raise EvaluationError, "match? given an invalid pattern #{pattern.inspect} — #{e.message}"
483
230
  end
484
231
 
485
- # The elements of a bracketed literal, or nil if this isn't one.
486
- # Splits on top-level commas only — quote-aware and depth-aware,
487
- # the same discipline `split_addition` already applies, so a
488
- # nested array or a comma inside a string element stays whole.
489
- #
490
- # @param expr [String] the leaf expression text
491
- # @return [Array<String>, nil] the unparsed source text of each
492
- # element, in order (`[]` for `"[]"`), or `nil` when `expr` is
493
- # not bracketed
494
232
  def array_elements(expr)
495
233
  return nil unless expr.start_with?("[") && expr.end_with?("]")
496
234
 
@@ -523,24 +261,8 @@ module Hecks
523
261
  elements.reject(&:empty?)
524
262
  end
525
263
 
526
- # Braces count toward depth, exactly as parens do — a `+` inside a
527
- # block predicate's own `{ |x| ... }` body is not this expression's
528
- # own addition. `parse` tries addition before `parse_block_opener`,
529
- # so a paren-only depth count split
530
- # kings.any? { |k| k.square.file == to.file + 1 && ... }
531
- # at that inner `+`, turning the whole expression into a nonsense
532
- # Addition whose left operand then walked "any? { |k| k" into an
533
- # Array as an attribute path — "TypeError: no implicit conversion
534
- # of Symbol into Integer", the same signature every unsupported
535
- # construct raises, which is what let this hide. Found live in a
536
- # downstream chess domain's castling given; the evaluator's own
537
- # top_level_index has counted braces since its own version of this
538
- # exact lesson.
539
- #
540
- # @param expr [String] the leaf expression text
541
- # @return [Array(String, String), nil] the `[left, right]` operand
542
- # text around the first top-level `+`, or `nil` when `expr` has
543
- # none
264
+ # Braces and brackets count toward depth like parens, so a `+` inside a block body
265
+ # or an array element is not this expression's own addition.
544
266
  def split_addition(expr)
545
267
  depth = 0
546
268
  quote = nil
@@ -550,17 +272,7 @@ module Hecks
550
272
  quote = nil if char == quote
551
273
  elsif ['"', "'"].include?(char)
552
274
  quote = char
553
- # `[`/`]` -- the identical lesson this method's own `(`/`{`
554
- # comment already names, a third time (found live via the
555
- # type-directed bounded-exhaustive expression generator,
556
- # Phase 7 of the equivalence-gap plan): `ArrayLiteral` can
557
- # appear as a general sub-expression, not only as
558
- # `.include?`'s own haystack, so without counting `[`/`]`
559
- # toward depth here too, an array element containing its own
560
- # top-level `+` (`[0, 0 + 0]`) reads as this expression's own
561
- # addition split point -- tearing the whole receiver before
562
- # `.all?`/`.any?`/etc. in half before `parse_block_opener`
563
- # ever sees it as one atomic leaf.
275
+ # `[`/`]` count too: an array element may hold its own top-level `+`.
564
276
  elsif ["(", "{", "["].include?(char)
565
277
  depth += 1
566
278
  elsif [")", "}", "]"].include?(char)
@@ -572,23 +284,9 @@ module Hecks
572
284
  nil
573
285
  end
574
286
 
575
- # Integer is signed 64-bit (C3.3, docs/semantics/bluebook-semantics.md)
576
- # — Ruby's own Integer would promote past ±2^63-1 silently; the
577
- # language does not, so a sum outside that range is an evaluation
578
- # fault, worded exactly as the Rust kernel's `checked_add` words
579
- # it. A Float sum that is not finite is the same fault (C3.4).
287
+ # Integer is signed 64-bit; a sum outside the range is an evaluation fault (C3.3, C3.4).
580
288
  INT64_RANGE = (-(2**63))..((2**63) - 1)
581
289
 
582
- # Adds two already-interpreted numeric operands, refusing an
583
- # out-of-range result rather than letting it wrap or promote
584
- # silently.
585
- #
586
- # @param left [Integer, Float] the left operand
587
- # @param right [Integer, Float] the right operand
588
- # @return [Integer, Float] `left + right`
589
- # @raise [EvaluationError] if either operand is not numeric, or the
590
- # Integer sum falls outside signed 64-bit range, or the Float sum
591
- # is not finite
592
290
  def add(left, right)
593
291
  lhs = require_number(left, "addition")
594
292
  rhs = require_number(right, "addition")
@@ -603,12 +301,6 @@ module Hecks
603
301
  raise EvaluationError, "addition overflowed: #{lhs} + #{rhs} is not a finite number"
604
302
  end
605
303
 
606
- # Reports whether `expr` is a whole string literal — `"` or `'`
607
- # delimited, both ends.
608
- #
609
- # @param expr [String] the leaf expression text
610
- # @return [Boolean] whether `expr` opens and closes with matching
611
- # quote characters
612
304
  def quoted?(expr)
613
305
  return false if expr.length < 2
614
306
 
@@ -616,135 +308,54 @@ module Hecks
616
308
  (expr.start_with?("'") && expr.end_with?("'"))
617
309
  end
618
310
 
619
- # Declared the same way in Vocabulary::SizedType
620
- # (language/bluebook/vocabulary.bluebook) — spec/vocabulary_conformance_spec
621
- # holds this equal to the language. Shared by .size and .empty?,
622
- # which admit the same set for the same reason.
311
+ # Held equal to Vocabulary::SizedType by spec/vocabulary_conformance_spec.
623
312
  SIZED_TYPES = Hecks::Vocabulary.fetch("SizedType")
624
313
 
625
- # Counts `value`'s elements or characters, for `.length`/`.size`.
626
- #
627
- # @param value [Array, String, Hash] the already-interpreted
628
- # receiver value
629
- # @return [Integer] the number of elements (Array/Hash) or
630
- # characters (String) `value` holds
631
- # @raise [EvaluationError] if `value` is not an Array, String, or
632
- # Hash
633
314
  def size_of(value)
634
315
  return value.size if value.is_a?(Array) || value.is_a?(String) || value.is_a?(Hash)
635
316
 
636
317
  raise EvaluationError, "size expects a list or string, got #{describe(value)}"
637
318
  end
638
319
 
639
- # Reports whether `value` holds no elements or characters, for
640
- # `.empty?`.
641
- #
642
- # @param value [Array, String, Hash] the already-interpreted
643
- # receiver value
644
- # @return [Boolean] whether `value` holds no elements or characters
645
- # @raise [EvaluationError] if `value` is not an Array, String, or
646
- # Hash
647
320
  def emptiness_of(value)
648
321
  return value.empty? if value.is_a?(Array) || value.is_a?(String) || value.is_a?(Hash)
649
322
 
650
323
  raise EvaluationError, "empty? expects a list or string, got #{describe(value)}"
651
324
  end
652
325
 
653
- # Splits `value` on `separator`, for `.split("SEP")` -- vendored
654
- # addition, see the `Split` struct's own comment above. Only a
655
- # String receiver makes sense to split -- unlike `.length`/`.size`/
656
- # `.empty?`, which are already meaningful over Array/Hash too,
657
- # `.split` is a String-only method in the corpus's own usage
658
- # (every occurrence found this pass splits a Phrase's own string
659
- # value).
660
- #
661
- # @param value [String] the already-interpreted receiver value
662
- # @param separator [String] the literal separator text
663
- # @return [Array<String>] `value` split on `separator`, Ruby's own
664
- # `String#split` semantics
665
- # @raise [EvaluationError] if `value` is not a String
666
326
  def split_value(value, separator)
667
327
  raise EvaluationError, "split expects a string, got #{describe(value)}" unless value.is_a?(String)
668
328
 
669
329
  value.split(separator)
670
330
  end
671
331
 
672
- # Returns `value`'s last element, for `.last` -- vendored
673
- # addition, see the `Last` struct's own comment above. Duck-typed
674
- # on `respond_to?(:last)` rather than hard-coding Array -- the one
675
- # corpus usage found this pass (`Query::Phrase`'s
676
- # `.split("::").last`) always receives a `Split`-produced Array,
677
- # but nothing about `.last` itself is Array-specific, and this
678
- # matches `Empty`/`Size`'s own duck-typed-over-a-known-set
679
- # precedent without inventing a narrower rule than the method
680
- # needs.
681
- #
682
- # @param value [#last] the already-interpreted receiver value
683
- # @return [Object] `value.last`
684
- # @raise [EvaluationError] if `value` does not respond to `:last`
685
332
  def last_of(value)
686
333
  return value.last if value.respond_to?(:last)
687
334
 
688
335
  raise EvaluationError, "last expects a list, got #{describe(value)}"
689
336
  end
690
337
 
691
- # Returns `value`'s first element, for `.first` -- see the
692
- # `First` struct's own comment above. `last_of` with the one
693
- # method swapped, same duck-typed reasoning.
694
- #
695
- # @param value [#first] the already-interpreted receiver value
696
- # @return [Object] `value.first`
697
- # @raise [EvaluationError] if `value` does not respond to `:first`
698
338
  def first_of(value)
699
339
  return value.first if value.respond_to?(:first)
700
340
 
701
341
  raise EvaluationError, "first expects a list, got #{describe(value)}"
702
342
  end
703
343
 
704
- # Reports whether `value` starts with `substring`, for
705
- # `.start_with?("prefix")` -- vendored addition, see the
706
- # `StartsWith` struct's own comment above. String-only, same
707
- # reasoning as `.split` above -- every corpus usage found this
708
- # pass (`Params`'s own JSON-object-shape invariant) receives a
709
- # plain String field.
710
- #
711
- # @param value [String] the already-interpreted receiver value
712
- # @param substring [String] the literal prefix text
713
- # @return [Boolean] whether `value` starts with `substring`
714
- # @raise [EvaluationError] if `value` is not a String
715
344
  def starts_with?(value, substring)
716
345
  raise EvaluationError, "start_with? expects a string, got #{describe(value)}" unless value.is_a?(String)
717
346
 
718
347
  value.start_with?(substring)
719
348
  end
720
349
 
721
- # Reports whether `value` ends with `substring`, for
722
- # `.end_with?("suffix")` -- vendored addition, see the `EndsWith`
723
- # struct's own comment above. Same String-only reasoning as
724
- # `start_with?` immediately above.
725
- #
726
- # @param value [String] the already-interpreted receiver value
727
- # @param substring [String] the literal suffix text
728
- # @return [Boolean] whether `value` ends with `substring`
729
- # @raise [EvaluationError] if `value` is not a String
730
350
  def ends_with?(value, substring)
731
351
  raise EvaluationError, "end_with? expects a string, got #{describe(value)}" unless value.is_a?(String)
732
352
 
733
353
  value.end_with?(substring)
734
354
  end
735
355
 
736
- # Declared the same way in Vocabulary::ToStringType
737
- # (language/bluebook/vocabulary.bluebook) — spec/vocabulary_conformance_spec
738
- # holds this equal to the language.
356
+ # Held equal to Vocabulary::ToStringType by spec/vocabulary_conformance_spec.
739
357
  TO_STRING_TYPES = Hecks::Vocabulary.fetch("ToStringType")
740
358
 
741
- # Coerces `value` to its string form, for `.to_s`.
742
- #
743
- # @param value [String, Integer, Float, Boolean, nil] the
744
- # already-interpreted receiver value
745
- # @return [String] `value` unchanged if already a String,
746
- # `value.to_s` for a numeric or boolean, or `""` for `nil`
747
- # @raise [EvaluationError] if `value` is not one of the above
748
359
  def string_of(value)
749
360
  case value
750
361
  when String then value
@@ -755,16 +366,6 @@ module Hecks
755
366
  end
756
367
  end
757
368
 
758
- # Splits `expr` at the first of `suffixes` it ends with — the
759
- # shared lookup behind every `.positive?`/`.negative?`/`.zero?`-
760
- # style sign test.
761
- #
762
- # @param expr [String] the leaf expression text
763
- # @param suffixes [Array<String>] the admitted suffix names
764
- # (without the leading `.`), tried in order
765
- # @return [Array(String, String), nil] the `[receiver_text,
766
- # matched_suffix]` pair for the first matching suffix, or `nil`
767
- # if none matches
768
369
  def match_suffix(expr, suffixes)
769
370
  suffixes.each do |suffix|
770
371
  marker = ".#{suffix}"
@@ -773,14 +374,6 @@ module Hecks
773
374
  nil
774
375
  end
775
376
 
776
- # Applies `node`'s comparison (`positive?`/`negative?`/`zero?`)
777
- # against the literal `0`, reusing `Evaluator.apply`'s own
778
- # comparison primitives rather than re-deriving them by hand.
779
- #
780
- # @param node [SignTest] the sign-test node being evaluated
781
- # @param value [Object] the already-interpreted receiver value
782
- # @return [Boolean] whether `value` passes `node`'s own sign test
783
- # @raise [EvaluationError] if `value` is not numeric
784
377
  def apply_sign_test(node, value)
785
378
  number = numeric(value)
786
379
  raise EvaluationError, "#{node.test} expects a number, got #{describe(value)}" unless number
@@ -788,58 +381,8 @@ module Hecks
788
381
  Evaluator.apply(node.operator, number, 0)
789
382
  end
790
383
 
791
- # Found live via the type-directed bounded-exhaustive expression
792
- # generator (Phase 7, equivalence-gap plan — spec/
793
- # bounded_exhaustive_expression_spec.rb): `.modulo(`'s own
794
- # argument position accepts any numeric sub-expression, including
795
- # another `.modulo(...)` call — `0.modulo(num_b.modulo(-1))` is
796
- # perfectly well-typed — but `expr.rindex(marker)` finds the
797
- # rightmost (innermost) `.modulo(` in the whole string, not the
798
- # outermost one a nested call needs split at. For that expression
799
- # it found the inner `.modulo(` (inside `num_b.modulo(-1)`) and
800
- # split there, producing a receiver of `"0.modulo(num_b"` and a
801
- # divisor of `"-1)"` — both garbage, both re-parsed as bogus
802
- # `Lookup` paths, both then refusing with "cannot resolve" — a
803
- # silent misparse that happened to fail safe into a real
804
- # `EvaluationError` rather than a raw crash, which is exactly why
805
- # this had gone unnoticed: nothing before this generator existed
806
- # ever fed `.modulo` a nested `.modulo` call, random fuzzing
807
- # essentially never manufactures that specific shape by chance,
808
- # and the resulting refusal looks like an ordinary, correct one
809
- # unless you already know every name this generator's own
810
- # synthetic state declares (real corpus authors would see this as
811
- # a mysterious "cannot resolve" on text they never wrote).
812
- #
813
- # Fixed the same way `split_addition`/`Evaluator.top_level_index`
814
- # already handle nested `(`/`{` elsewhere in this exact file:
815
- # find the first (leftmost, outermost) occurrence of the marker,
816
- # then track paren/quote depth from there to find its own
817
- # matching close — not just strip the string's own trailing `)`
818
- # and hope it belongs to this call.
819
- # Not just the first occurrence, either — `.modulo` also chains
820
- # (`x.modulo(a).modulo(b)`, the receiver of the outer call itself
821
- # ending in a `.modulo(...)` call), a second real shape the
822
- # leftmost-occurrence-only version of this fix still mis-parsed:
823
- # the first `.modulo(`'s own matching close paren lands mid-
824
- # string (right after `a)`, before the second `.modulo(b)`), so
825
- # it correctly fails the "reaches the end" check below and must
826
- # be tried again at the next occurrence rather than giving up.
827
- # Trying occurrences strictly left to right and taking the first
828
- # one whose matching close reaches the string's last character
829
- # handles both shapes with the same rule: for nesting
830
- # (`.modulo(x.modulo(y))`), the leftmost (outer) occurrence's own
831
- # paren-depth tracking already walks straight through the inner
832
- # call to the true final `)`; for chaining, the leftmost
833
- # occurrence's close lands short and is rejected, so the next
834
- # occurrence (the true outermost call) is tried instead.
835
- #
836
- # @param expr [String] the leaf expression text
837
- # @param marker [String] the call-opening text to search for, such
838
- # as `".modulo("`
839
- # @return [Array(String, String), nil] the `[receiver_text,
840
- # argument_text]` pair for the outermost occurrence of `marker`
841
- # whose matching close paren reaches `expr`'s last character, or
842
- # `nil` if none does
384
+ # Finds the outermost `marker` call whose matching `)` ends `expr`. `rindex` would split
385
+ # nested `.modulo(` calls at the inner one, and a first match alone mis-parses chains.
843
386
  def match_call(expr, marker)
844
387
  start = 0
845
388
  while (index = expr.index(marker, start))
@@ -851,17 +394,6 @@ module Hecks
851
394
  nil
852
395
  end
853
396
 
854
- # Finds the index of the `)` that closes the `(` already consumed
855
- # by the caller. `matching_brace` (resolver/block_predicates.rb)'s
856
- # own twin, one bracket pair over: `start` is the index just past
857
- # the opening `(` already consumed by the caller (depth starts at
858
- # 1, not 0, for the same reason).
859
- #
860
- # @param expr [String] the source text to scan
861
- # @param start [Integer] the index just past the already-consumed
862
- # opening `(`
863
- # @return [Integer, nil] the index of the matching `)`, or `nil` if
864
- # `expr` has no balanced close from `start` onward
865
397
  def matching_paren(expr, start)
866
398
  depth = 1
867
399
  quote = nil
@@ -883,32 +415,7 @@ module Hecks
883
415
  nil
884
416
  end
885
417
 
886
- # Both operands are coerced to a real Integer/Float before the
887
- # zero-check, and the check reads the coerced divisor — not the
888
- # raw `divisor_value` (which might not even respond to `.zero?`,
889
- # a String for instance) and not a `.to_i`-truncated stand-in for
890
- # it either. The old order checked a truncated `divisor.to_i`
891
- # after already validating the untruncated value wasn't zero, so
892
- # a divisor merely small (`0.3`, truncating to `0`) sailed past
893
- # the guard and then blew up `Integer#%` with a raw
894
- # `ZeroDivisionError` the moment it reached zero anyway.
895
- #
896
- # The modulo itself is plain `%` on the coerced values, matching
897
- # `add`'s own no-truncation precedent just above — Ruby's native
898
- # `%` already handles every Integer/Float combination correctly
899
- # (promoting to Float when either side is one), so rounding both
900
- # operands down to Integer first was pure data loss with no
901
- # purpose: `7.5.modulo(2.5)` silently became `7 % 2` (`1`)
902
- # instead of the real `0.0`.
903
- #
904
- # @param receiver_value [Object] the already-interpreted receiver
905
- # value
906
- # @param divisor_value [Object] the already-interpreted divisor
907
- # value
908
- # @return [Integer, Float] `receiver % divisor`, on the coerced
909
- # numeric operands
910
- # @raise [EvaluationError] if either operand is not numeric, or the
911
- # coerced divisor is zero
418
+ # The zero-check reads the coerced divisor, not the raw value or a `to_i` truncation.
912
419
  def apply_modulo(receiver_value, divisor_value)
913
420
  receiver = require_number(receiver_value, "modulo")
914
421
  divisor = require_number(divisor_value, "modulo")
@@ -917,20 +424,6 @@ module Hecks
917
424
  receiver % divisor
918
425
  end
919
426
 
920
- # Resolves a `Lookup` node's dotted path against `attrs` (checked
921
- # first) and `state`, unwrapping a single-field value object's
922
- # result to its own scalar.
923
- #
924
- # @param expr [String] the dotted (or bare) attribute path, such as
925
- # `"customer.status"`
926
- # @param state [Hash{Symbol => Object}] the stored attribute values
927
- # the path's root name may resolve against
928
- # @param attrs [Hash{Symbol => Object}] the call's own argument
929
- # values, checked before `state`
930
- # @return [Object] the resolved value, unwrapped by `unwrap_scalar`
931
- # @raise [EvaluationError] if the path's root name is in neither
932
- # `attrs` nor `state`, or a later segment cannot be read from the
933
- # value it navigates onto
934
427
  def lookup(expr, state, attrs)
935
428
  return unwrap_scalar(fetch(expr, state, attrs)) unless expr.include?(".")
936
429
 
@@ -938,25 +431,8 @@ module Hecks
938
431
  unwrap_scalar(walk_path(fetch(head, state, attrs), rest))
939
432
  end
940
433
 
941
- # Walks a list of dotted segments through an already-resolved
942
- # Hash-like value — extracted from `lookup`'s own reduce so
943
- # `found_of` (the `Find` node's own path projection) can walk a
944
- # `.find { ... }`-produced element the identical way `lookup`
945
- # walks a plain attribute path, rather than duplicating the
946
- # symbol-or-string key step twice in this file. `key?` decides
947
- # which spelling answers — a bare `||` between the two would
948
- # treat a genuinely-held `false` the same as an absent key and
949
- # fall through to the other spelling, landing on `nil`.
950
- #
951
- # @param value [Object] the already-resolved starting value; `nil`
952
- # once any segment lands on a value that does not respond to
953
- # `#[]`
954
- # @param segments [Array<String>] the dotted path's remaining
955
- # segments, in order
956
- # @return [Object, nil] the value reached by walking every segment,
957
- # or `nil` if a segment lands on a value with no `#[]`
958
- # @raise [EvaluationError] if a segment cannot be read from an
959
- # Array-like value (a non-Integer/Range index)
434
+ # Walks dotted segments through a Hash-like value. `key?` picks the symbol or string
435
+ # spelling so a held `false` is not mistaken for an absent key.
960
436
  def walk_path(value, segments)
961
437
  segments.reduce(value) do |current, segment|
962
438
  break nil unless current.respond_to?(:[])
@@ -968,15 +444,7 @@ module Hecks
968
444
  begin
969
445
  current[segment]
970
446
  rescue TypeError
971
- # M9 (docs/audits/2026-08-10-main-bug-audit.md): a dotted
972
- # path can walk onto an Array (e.g. the result of `.split`,
973
- # or a `list_of` attribute) — Array#[] demands an
974
- # Integer/Range and raises a raw TypeError for a String
975
- # segment ("no implicit conversion of String into
976
- # Integer"). This rescue catches it and re-raises as this
977
- # sublanguage's own refusal ("this predicate doesn't apply
978
- # here"), rather than letting the raw TypeError cross this
979
- # sublanguage's own refusal boundary and crash the runtime.
447
+ # `Array#[]` raises a raw TypeError for a String segment; refuse it instead.
980
448
  raise EvaluationError,
981
449
  "cannot read #{segment.inspect} from #{describe(current)}"
982
450
  end
@@ -984,64 +452,9 @@ module Hecks
984
452
  end
985
453
  end
986
454
 
987
- # Unwraps a single-field value object to its own scalar, so
988
- # `field == "literal"` -- vendored addition, not (yet) upstream
989
- # hecks (migration plan task 8): the third-most pervasive
990
- # dispatch-time gap this pass found, same family as `.match?`/
991
- # `.present?` above -- a lookup of a single-field
992
- # scalar-convenience value object (the exact shape `Value.
993
- # from_identifier`/`Value::Coercion#fields_for`'s own single-
994
- # field auto-unwrap already treats as "this VO IS its scalar"
995
- # everywhere else in this runtime) came back as the `Value`
996
- # wrapper itself, never unwrapped for reading -- so `Value#==`
997
- # (which only ever equals another `Value` instance) silently
998
- # refused every `guarantees "..." do status == "active" end` /
999
- # `expects "..." do trash_day.present? end`-shaped bare
1000
- # comparison against a raw literal. Confirmed corpus-wide, not
1001
- # one file's mistake: bin-buddy alone has this exact `field ==
1002
- # "literal"` shape in plan.bluebook, service_task.bluebook,
1003
- # route.bluebook, and subscription.bluebook, all equally silent
1004
- # until a real dispatch (never validate) exercised the
1005
- # predicate.
1006
- #
1007
- # Unwraps both the bare (undotted) case and a dotted lookup's own
1008
- # terminal value: a dotted lookup that navigates through an
1009
- # entity/list element to a nested field (`leg.voyage`, where
1010
- # `voyage` is itself a single-field VO) lands on the same
1011
- # unwrapped-`Value` shape a bare lookup does, and hits the
1012
- # identical silent `Value#==` failure without the same
1013
- # "this VO IS its scalar" treatment — only the intermediate hops
1014
- # need raw `#[]` addressing to keep navigating. `unwrap_scalar` is
1015
- # idempotent on an already-raw scalar (a String/Integer doesn't
1016
- # respond to `#to_h`), so this is safe for `field.value`-shaped
1017
- # dotted lookups too — they already return a raw scalar and stay
1018
- # unaffected.
1019
- #
1020
- # Gated on the sole attribute's *count*, never its *name*: gating
1021
- # on a literal `:value` key would be a lie of omission for
1022
- # `Money{amount}` and every other single-field value object whose
1023
- # author picked a domain name for the field — the same
1024
- # "this VO IS its scalar" reading
1025
- # ([[feedback_name_the_scalar_field]],
1026
- # `Behaviour::ValueObject#sole_attribute`) applies regardless of
1027
- # what the sole field happens to be called, and a name gate would
1028
- # make a bare `balance > 0` work for a `Balance{value}` while
1029
- # silently comparing a whole VO for a `Balance{amount}`. A
1030
- # declared `Runtime::Value` reads its own `sole_attribute` (the
1031
- # declaration's answer, not the stored hash's); any other
1032
- # to_h-able (a Struct, a bespoke wrapper with no declaration to
1033
- # consult) keeps the `{value: X}`-only unwrap, so nothing that
1034
- # never was a value object gains a surprise unwrapping.
1035
- # `rust/src/kernel/json.rs`'s `impl Fielded for Json` mirrors the
1036
- # count-only reading on the Rust side — change them in lockstep
1037
- # or rust_conformance diverges.
1038
- #
1039
- # @param value [Object] a resolved lookup or path-walk result
1040
- # @return [Object] `value` itself if it is a Hash, Array, or has no
1041
- # `#to_h`; a declared `Runtime::Value`'s sole attribute's own
1042
- # value, read via `sole.name`, if it has exactly one attribute;
1043
- # `value[:value]` for any other to_h-able with exactly one key
1044
- # named `:value`; `value` unchanged otherwise
455
+ # Unwraps a single-field value object to its scalar so `field == "literal"` works.
456
+ # Gated on the field count, not its name. Mirrors `impl Fielded for Json` in
457
+ # rust/src/kernel/json.rs; change both together.
1045
458
  def unwrap_scalar(value)
1046
459
  return value unless value.respond_to?(:to_h) && !value.is_a?(Hash) && !value.is_a?(Array)
1047
460
 
@@ -1054,17 +467,6 @@ module Hecks
1054
467
  hash.size == 1 && hash.key?(:value) ? hash[:value] : value
1055
468
  end
1056
469
 
1057
- # Resolves a single unqualified name — `attrs` first, `state`
1058
- # second.
1059
- #
1060
- # @param name [String, Symbol] the attribute or argument name
1061
- # @param state [Hash{Symbol => Object}, #[]] the stored attribute
1062
- # values
1063
- # @param attrs [Hash{Symbol => Object}] the call's own argument
1064
- # values, checked first
1065
- # @return [Object] the value held under `name`
1066
- # @raise [EvaluationError] if `name` is in neither `attrs` nor
1067
- # `state`
1068
470
  def fetch(name, state, attrs)
1069
471
  key = name.to_sym
1070
472
  return attrs[key] if attrs.key?(key)
@@ -1073,45 +475,21 @@ module Hecks
1073
475
  raise EvaluationError, "cannot resolve #{name.inspect} — no such attribute or argument"
1074
476
  end
1075
477
 
1076
- # Reports whether `state` holds `key`, duck-typed for a `state`
1077
- # that does not respond to `:key?`.
1078
- #
1079
- # @param state [Hash{Symbol => Object}, #[]] the stored attribute
1080
- # values
1081
- # @param key [Symbol] the attribute name to look for
1082
- # @return [Boolean] `state.key?(key)` when `state` responds to
1083
- # `:key?`; otherwise whether `state[key]` is non-nil
1084
478
  def known?(state, key)
1085
479
  return state.key?(key) if state.respond_to?(:key?)
1086
480
 
1087
481
  !state[key].nil?
1088
482
  end
1089
483
 
1090
- # Returns `value` if it is already numeric, or `nil` if it is not.
1091
- #
1092
- # @param value [Object] the value to check
1093
- # @return [Integer, Float, nil] `value` unchanged if it is an
1094
- # Integer or Float, otherwise `nil`
1095
484
  def numeric(value)
1096
485
  value if value.is_a?(Integer) || value.is_a?(Float)
1097
486
  end
1098
487
 
1099
- # Coerces `value` to a number or refuses it.
1100
- #
1101
- # @param value [Object] the value to check
1102
- # @param operation [String] the operation name, for the refusal
1103
- # message
1104
- # @return [Integer, Float] `value` unchanged
1105
- # @raise [EvaluationError] if `value` is not an Integer or Float
1106
488
  def require_number(value, operation)
1107
489
  numeric(value) ||
1108
490
  raise(EvaluationError, "#{operation} expects a number, got #{describe(value)}")
1109
491
  end
1110
492
 
1111
- # Renders `value` the way it should read inside a refusal message.
1112
- #
1113
- # @param value [Object] the value to render
1114
- # @return [String] `Rendering.describe(value)`
1115
493
  def describe(value) = Rendering.describe(value)
1116
494
  end
1117
495
  end