hecks 2.9.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 (600) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +51 -6
  3. data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
  4. data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
  5. data/lib/hecks/adapters/driven/lambda.rb +1 -1
  6. data/lib/hecks/adapters/driven/memory.rb +16 -3
  7. data/lib/hecks/adapters/driven/postgres/reconnect.rb +6 -2
  8. data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
  9. data/lib/hecks/adapters/driven/postgres.rb +4 -2
  10. data/lib/hecks/adapters/driven/prism.rb +41 -8
  11. data/lib/hecks/adapters/driven/tenant_provisioner.rb +73 -16
  12. data/lib/hecks/behaviors/expectations.rb +1 -1
  13. data/lib/hecks/behaviors.rb +1 -1
  14. data/lib/hecks/bench/cli.rb +62 -0
  15. data/lib/hecks/bench/environment.rb +71 -0
  16. data/lib/hecks/bench/postgres_probe.rb +30 -0
  17. data/lib/hecks/bench/report.rb +76 -0
  18. data/lib/hecks/bench/ruby_runner.rb +93 -0
  19. data/lib/hecks/bench/run.rb +55 -0
  20. data/lib/hecks/bench/rust_runner.rb +101 -0
  21. data/lib/hecks/bench/stats.rb +46 -0
  22. data/lib/hecks/bench/suite.rb +98 -0
  23. data/lib/hecks/bench/workload.rb +89 -0
  24. data/lib/hecks/bench.rb +17 -0
  25. data/lib/hecks/bluebook/assembly/contracts.rb +2 -0
  26. data/lib/hecks/bluebook/attribute.rb +1 -1
  27. data/lib/hecks/bluebook/behaviour/aggregate.rb +30 -1
  28. data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -0
  29. data/lib/hecks/bluebook/chapter.rb +6 -2
  30. data/lib/hecks/bluebook/domain_port.rb +36 -5
  31. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +10 -0
  32. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +8 -0
  33. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -1
  34. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +31 -3
  35. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +122 -3
  36. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +1 -1
  37. data/lib/hecks/bluebook/dsl/query_builder.rb +27 -1
  38. data/lib/hecks/bluebook/dsl/rule_reference.rb +1 -1
  39. data/lib/hecks/bluebook/dsl/world_builder.rb +29 -2
  40. data/lib/hecks/bluebook/expression/canonical_form.rb +1 -1
  41. data/lib/hecks/bluebook/expression/resolver.rb +3 -2
  42. data/lib/hecks/bluebook/hexagon.rb +13 -5
  43. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -0
  44. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +4 -1
  45. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
  46. data/lib/hecks/bluebook/meta_validator.rb +9 -4
  47. data/lib/hecks/bluebook/model_check.rb +23 -0
  48. data/lib/hecks/bluebook/pattern_subset.rb +38 -0
  49. data/lib/hecks/bluebook/query.rb +20 -5
  50. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  51. data/lib/hecks/canonical_json.rb +30 -0
  52. data/lib/hecks/chapters.rb +102 -0
  53. data/lib/hecks/cli/behaviors.rb +61 -0
  54. data/lib/hecks/cli/canonicalise.rb +25 -0
  55. data/lib/hecks/cli/check_engine_agreement.rb +32 -0
  56. data/lib/hecks/cli/check_era.rb +88 -0
  57. data/lib/hecks/cli/console.rb +66 -0
  58. data/lib/hecks/cli/deploy_template_diff.rb +40 -0
  59. data/lib/hecks/cli/doc_coverage.rb +32 -0
  60. data/lib/hecks/cli/document.rb +2 -2
  61. data/lib/hecks/cli/expression_projection.rb +41 -0
  62. data/lib/hecks/cli/follow.rb +88 -0
  63. data/lib/hecks/cli/history.rb +44 -0
  64. data/lib/hecks/cli/ir.rb +1 -1
  65. data/lib/hecks/cli/mcp.rb +3 -3
  66. data/lib/hecks/cli/model_check.rb +7 -4
  67. data/lib/hecks/cli/pattern_cases.rb +47 -0
  68. data/lib/hecks/cli/present.rb +39 -0
  69. data/lib/hecks/cli/project_cli.rb +224 -32
  70. data/lib/hecks/cli/project_diagrams.rb +4 -3
  71. data/lib/hecks/cli/project_glossary.rb +44 -0
  72. data/lib/hecks/cli/reference.rb +46 -0
  73. data/lib/hecks/cli/refresh_projections.rb +44 -0
  74. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
  75. data/lib/hecks/cli/release.rb +70 -0
  76. data/lib/hecks/cli/release_gem.rb +59 -0
  77. data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
  78. data/lib/hecks/cli/rspec_shard_files.rb +66 -0
  79. data/lib/hecks/cli/run.rb +3 -3
  80. data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
  81. data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
  82. data/lib/hecks/cli/shape.rb +70 -0
  83. data/lib/hecks/cli/smoke_http.rb +230 -0
  84. data/lib/hecks/cli/smoke_test.rb +5 -3
  85. data/lib/hecks/cli/spec_example.rb +27 -0
  86. data/lib/hecks/cli/statements.rb +32 -0
  87. data/lib/hecks/cli/stores.rb +4 -5
  88. data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
  89. data/lib/hecks/cli.rb +201 -52
  90. data/lib/hecks/codemod.rb +324 -0
  91. data/lib/hecks/corpus.rb +423 -0
  92. data/lib/hecks/deploy/bluebook/deploy.bluebook +591 -23
  93. data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -4
  94. data/lib/hecks/deploy/oidc.json +82 -2
  95. data/lib/hecks/doc/reference.rb +351 -0
  96. data/lib/hecks/doors/cli_door.rb +196 -0
  97. data/lib/hecks/doors/cli_runner.rb +297 -0
  98. data/lib/hecks/{facade → doors}/command_request.rb +1 -1
  99. data/lib/hecks/{facade → doors}/handle.rb +2 -2
  100. data/lib/hecks/{facade → doors}/json_door.rb +4 -4
  101. data/lib/hecks/doors/launcher_options.rb +129 -0
  102. data/lib/hecks/{cli → doors}/mcp_door.rb +7 -7
  103. data/lib/hecks/doors/mcp_door_scope.rb +97 -0
  104. data/lib/hecks/{facade/surface → doors/ruby_door}/aggregate_door.rb +8 -3
  105. data/lib/hecks/{facade/surface → doors/ruby_door}/chapter.rb +3 -3
  106. data/lib/hecks/{facade/surface.rb → doors/ruby_door.rb} +36 -5
  107. data/lib/hecks/doors.rb +61 -0
  108. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +1 -1
  109. data/lib/hecks/embryonaut_bluebook.rb +1 -1
  110. data/lib/hecks/engine_agreement.rb +156 -0
  111. data/lib/hecks/facade.rb +24 -37
  112. data/lib/hecks/forms/app.rb +6 -6
  113. data/lib/hecks/forms/banking_presentation.rb +50 -0
  114. data/lib/hecks/forms/field_shape.rb +2 -1
  115. data/lib/hecks/forms/port_argument.rb +1 -1
  116. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
  117. data/lib/hecks/fuzzing/combination_miner.rb +86 -0
  118. data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
  119. data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
  120. data/lib/hecks/fuzzing/differential.rb +156 -0
  121. data/lib/hecks/fuzzing/domain_generator.rb +615 -0
  122. data/lib/hecks/fuzzing/era_boundary.rb +71 -0
  123. data/lib/hecks/fuzzing/form_census.rb +136 -0
  124. data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
  125. data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
  126. data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
  127. data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
  128. data/lib/hecks/fuzzing/outside_world.rb +45 -0
  129. data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
  130. data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
  131. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
  132. data/lib/hecks/fuzzing/properties/guards.rb +190 -0
  133. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
  134. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
  135. data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
  136. data/lib/hecks/fuzzing/properties/querying.rb +175 -0
  137. data/lib/hecks/fuzzing/properties.rb +162 -0
  138. data/lib/hecks/fuzzing/qa_settings.rb +123 -0
  139. data/lib/hecks/fuzzing/replay.rb +505 -0
  140. data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
  141. data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
  142. data/lib/hecks/fuzzing/self_consistency.rb +339 -0
  143. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
  144. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
  145. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
  146. data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
  147. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
  148. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
  149. data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
  150. data/lib/hecks/fuzzing/shrinker.rb +166 -0
  151. data/lib/hecks/fuzzing/structural_skips.rb +33 -0
  152. data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
  153. data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
  154. data/lib/hecks/fuzzing/value_generator.rb +216 -0
  155. data/lib/hecks/fuzzing.rb +23 -0
  156. data/lib/hecks/grammar/evolve.rb +336 -0
  157. data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
  158. data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
  159. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
  160. data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
  161. data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
  162. data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
  163. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
  164. data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
  165. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
  166. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
  167. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
  168. data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
  169. data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
  170. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
  171. data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
  172. data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
  173. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
  174. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
  175. data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
  176. data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
  177. data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
  178. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
  179. data/lib/hecks/hecks/adapters/git.adapter +7 -0
  180. data/lib/hecks/hecks/adapters/git.rb +102 -0
  181. data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
  182. data/lib/hecks/hecks/adapters/host_http.rb +65 -0
  183. data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
  184. data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
  185. data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
  186. data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
  187. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
  188. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
  189. data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
  190. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
  191. data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
  192. data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
  193. data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
  194. data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
  195. data/lib/hecks/hecks/adapters/local_files.rb +46 -0
  196. data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
  197. data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
  198. data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
  199. data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
  200. data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
  201. data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
  202. data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
  203. data/lib/hecks/hecks/adapters/shell.adapter +7 -0
  204. data/lib/hecks/hecks/adapters/shell.rb +49 -0
  205. data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
  206. data/lib/hecks/hecks/adapters/terminal.rb +74 -0
  207. data/lib/hecks/hecks/codebase.behaviors +172 -0
  208. data/lib/hecks/hecks/codebase.bluebook +2453 -0
  209. data/lib/hecks/hecks/context_map.hecksagon +13 -0
  210. data/lib/hecks/hecks/custodian.behaviors +179 -0
  211. data/lib/hecks/hecks/custodian.bluebook +1847 -0
  212. data/lib/hecks/hecks/deploy.behaviors +74 -0
  213. data/lib/hecks/hecks/deploy.world +12 -0
  214. data/lib/hecks/hecks/environments/memory.world +10 -0
  215. data/lib/hecks/hecks/governance.world +7 -0
  216. data/lib/hecks/hecks/hecks.behaviors +62 -0
  217. data/lib/hecks/hecks/hecks.bluebook +124 -0
  218. data/lib/hecks/hecks/hecks.hecksagon +390 -0
  219. data/lib/hecks/hecks/hecks.world +42 -0
  220. data/lib/hecks/kernel_capabilities.rb +194 -0
  221. data/lib/hecks/language/bluebook/bluebook.bluebook +11 -0
  222. data/lib/hecks/language/bluebook/query.bluebook +7 -0
  223. data/lib/hecks/language/bluebook/vocabulary.bluebook +4 -4
  224. data/lib/hecks/language/hecksagon/domain_port.bluebook +10 -0
  225. data/lib/hecks/language/hecksagon/hecksagon.bluebook +20 -0
  226. data/lib/hecks/mcp_stdio_guard.rb +2 -2
  227. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
  228. data/lib/hecks/persistence_legacy_fixture.rb +113 -0
  229. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +8 -5
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +4 -1
  231. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +15 -2
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +1 -1
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +1 -1
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +32 -15
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +6 -3
  237. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
  238. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +1 -1
  239. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +43 -24
  240. data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
  241. data/lib/hecks/ports/persistence/state_codec.rb +24 -5
  242. data/lib/hecks/projection_files.rb +285 -0
  243. data/lib/hecks/projections/bootstrap_table.rb +1 -1
  244. data/lib/hecks/projections/deploy/fargate.rb +5 -5
  245. data/lib/hecks/projections/deploy/lambda.rb +43 -32
  246. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  247. data/lib/hecks/projections/deploy/preview/template.rb +1 -1
  248. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
  249. data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
  250. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
  251. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
  252. data/lib/hecks/projections/deploy/shared.rb +1 -1
  253. data/lib/hecks/projections/diagrams.rb +3 -3
  254. data/lib/hecks/projections/model/deviations.rb +1 -1
  255. data/lib/hecks/projections/parser_table.rb +4 -4
  256. data/lib/hecks/projections/reference.rb +2 -3
  257. data/lib/hecks/projections/rust_vocabulary.rb +1 -1
  258. data/lib/hecks/projector/cli_projector.rb +106 -37
  259. data/lib/hecks/projector.rb +1 -1
  260. data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
  261. data/lib/hecks/quality_control/adapters/agent.rb +58 -0
  262. data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
  263. data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
  264. data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
  265. data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
  266. data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
  267. data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
  268. data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
  269. data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
  270. data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
  271. data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
  272. data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
  273. data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
  274. data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
  275. data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
  276. data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
  277. data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
  278. data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
  279. data/lib/hecks/quality_control/cli/child.rb +45 -0
  280. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
  281. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
  282. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
  283. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
  284. data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
  285. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
  286. data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
  287. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
  288. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
  289. data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
  290. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
  291. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
  292. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
  293. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
  294. data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
  295. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
  296. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
  297. data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
  298. data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
  299. data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
  300. data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
  301. data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
  302. data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
  303. data/lib/hecks/query_ir.rb +346 -0
  304. data/lib/hecks/query_ir_mcp.rb +198 -0
  305. data/lib/hecks/release/runner/ci_publisher.rb +2 -2
  306. data/lib/hecks/release/runner/clean_tree.rb +42 -0
  307. data/lib/hecks/release/runner/console.rb +6 -0
  308. data/lib/hecks/release/runner/gem_publisher.rb +14 -7
  309. data/lib/hecks/release/runner/npm_publisher.rb +6 -36
  310. data/lib/hecks/release/runner/preflight.rb +26 -2
  311. data/lib/hecks/release/runner/published.rb +8 -26
  312. data/lib/hecks/release/runner/tagger.rb +30 -15
  313. data/lib/hecks/release/runner.rb +57 -6
  314. data/lib/hecks/router/namespace_installer.rb +3 -3
  315. data/lib/hecks/runtime/adapter_lookup.rb +124 -0
  316. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +4 -0
  317. data/lib/hecks/runtime/dependency_planning.rb +20 -2
  318. data/lib/hecks/runtime/dispatcher.rb +53 -3
  319. data/lib/hecks/runtime/interpreting.rb +3 -0
  320. data/lib/hecks/runtime/loader.rb +65 -12
  321. data/lib/hecks/runtime/outbox.rb +1 -1
  322. data/lib/hecks/runtime/policy_interpreter.rb +5 -2
  323. data/lib/hecks/runtime/port_operation_interpreter.rb +2 -10
  324. data/lib/hecks/runtime/query_interpreter.rb +77 -0
  325. data/lib/hecks/runtime/reaction_outcome.rb +89 -0
  326. data/lib/hecks/runtime/refusal_wording.rb +23 -1
  327. data/lib/hecks/runtime/registry/verification.rb +163 -4
  328. data/lib/hecks/runtime/registry.rb +57 -3
  329. data/lib/hecks/runtime/remote_dispatcher.rb +31 -2
  330. data/lib/hecks/runtime/value/coercion.rb +23 -135
  331. data/lib/hecks/runtime/value/field_checks.rb +172 -0
  332. data/lib/hecks/runtime/value.rb +4 -1
  333. data/lib/hecks/runtime.rb +20 -6
  334. data/lib/hecks/rust_build/conformance.rb +158 -0
  335. data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
  336. data/lib/hecks/rust_build/coverage.rb +218 -0
  337. data/lib/hecks/rust_build/native_build.rb +124 -0
  338. data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
  339. data/lib/hecks/rust_build/project_rust.rb +332 -0
  340. data/lib/hecks/rust_build/wasm.rb +105 -0
  341. data/lib/hecks/rust_build/wasm_browser.rb +113 -0
  342. data/lib/hecks/rust_build.rb +144 -0
  343. data/lib/hecks/storehouse.rb +16 -16
  344. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +2 -2
  345. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +1 -1
  346. data/lib/hecks/three_zero/forms.yml +127 -0
  347. data/lib/hecks/three_zero.rb +12 -0
  348. data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
  349. data/lib/hecks/tools/comment_style.rb +1070 -0
  350. data/lib/hecks/tools/corpus_report.rb +87 -0
  351. data/lib/hecks/tools/deploy_recipe.rb +255 -0
  352. data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
  353. data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
  354. data/lib/hecks/tools/era_projection_backfill.rb +94 -0
  355. data/lib/hecks/tools/era_reattest.rb +146 -0
  356. data/lib/hecks/tools/evolve_run.rb +189 -0
  357. data/lib/hecks/tools/fuzz_sweep.rb +373 -0
  358. data/lib/hecks/tools/heki_compaction.rb +100 -0
  359. data/lib/hecks/tools/hoist_local_givens.rb +246 -0
  360. data/lib/hecks/tools/journal_compaction.rb +89 -0
  361. data/lib/hecks/tools/oidc_manifests.rb +70 -0
  362. data/lib/hecks/tools/query_ir_run.rb +81 -0
  363. data/lib/hecks/tools/regeneration_run.rb +166 -0
  364. data/lib/hecks/tools/rust_comment_style.rb +642 -0
  365. data/lib/hecks/tools/sequence_script.rb +62 -0
  366. data/lib/hecks/tools/tail_merge.rb +117 -0
  367. data/lib/hecks/tools/tenant_provisioning.rb +142 -0
  368. data/lib/hecks/tools/translation_audit.rb +208 -0
  369. data/lib/hecks/tools/translation_scaffold.rb +131 -0
  370. data/lib/hecks/tools.rb +86 -0
  371. data/lib/hecks/version.rb +1 -1
  372. data/lib/hecks.rb +32 -15
  373. data/qa/settings.yml +66 -0
  374. data/rust/.cargo/config.toml +6 -0
  375. data/rust/.gitignore +2 -0
  376. data/rust/Cargo.lock +54 -0
  377. data/rust/Cargo.toml +61 -0
  378. data/rust/build/.cargo/config.toml +3 -0
  379. data/rust/build/.gitignore +1 -0
  380. data/rust/build/Cargo.lock +7 -0
  381. data/rust/build/Cargo.toml +19 -0
  382. data/rust/build/src/build_artifact.rs +47 -0
  383. data/rust/build/src/cargo_sync.rs +248 -0
  384. data/rust/build/src/json.rs +421 -0
  385. data/rust/build/src/lineage_pass.rs +285 -0
  386. data/rust/build/src/main.rs +51 -0
  387. data/rust/build/src/optional_pass.rs +150 -0
  388. data/rust/build/src/pipeline.rs +264 -0
  389. data/rust/build/src/reserved_names.rs +76 -0
  390. data/rust/build/src/resolve.rs +124 -0
  391. data/rust/build/src/root.rs +25 -0
  392. data/rust/build/src/sidecars.rs +73 -0
  393. data/rust/build/src/subprocess.rs +51 -0
  394. data/rust/build/src/tmp.rs +31 -0
  395. data/rust/codegen/.cargo/config.toml +3 -0
  396. data/rust/codegen/.gitignore +1 -0
  397. data/rust/codegen/Cargo.lock +7 -0
  398. data/rust/codegen/Cargo.toml +20 -0
  399. data/rust/codegen/src/attr.rs +32 -0
  400. data/rust/codegen/src/bridging.rs +481 -0
  401. data/rust/codegen/src/commands.rs +1226 -0
  402. data/rust/codegen/src/constraints.rs +109 -0
  403. data/rust/codegen/src/dependency_planning.rs +209 -0
  404. data/rust/codegen/src/domain_generator.rs +1288 -0
  405. data/rust/codegen/src/exemplar.rs +230 -0
  406. data/rust/codegen/src/expr_emitter.rs +87 -0
  407. data/rust/codegen/src/fielded.rs +192 -0
  408. data/rust/codegen/src/hecks_naming.rs +33 -0
  409. data/rust/codegen/src/json.rs +335 -0
  410. data/rust/codegen/src/json_codec.rs +697 -0
  411. data/rust/codegen/src/literal.rs +207 -0
  412. data/rust/codegen/src/main.rs +290 -0
  413. data/rust/codegen/src/manifest.rs +227 -0
  414. data/rust/codegen/src/mutations.rs +784 -0
  415. data/rust/codegen/src/naming.rs +286 -0
  416. data/rust/codegen/src/ports.rs +208 -0
  417. data/rust/codegen/src/prelude.rs +116 -0
  418. data/rust/codegen/src/queries.rs +786 -0
  419. data/rust/codegen/src/reactions.rs +532 -0
  420. data/rust/codegen/src/read_models.rs +697 -0
  421. data/rust/codegen/src/reference_specs.rs +40 -0
  422. data/rust/codegen/src/registry.rs +966 -0
  423. data/rust/codegen/src/reserved_names.rs +76 -0
  424. data/rust/codegen/src/shared.rs +93 -0
  425. data/rust/codegen/src/skip_reason.rs +25 -0
  426. data/rust/codegen/src/types.rs +362 -0
  427. data/rust/host/.cargo/config.toml +3 -0
  428. data/rust/host/.gitignore +1 -0
  429. data/rust/host/Cargo.lock +4297 -0
  430. data/rust/host/Cargo.toml +61 -0
  431. data/rust/host/HECKS_RELEASE +1 -0
  432. data/rust/host/rds-ca-bundle.pem +2736 -0
  433. data/rust/host/src/api.rs +1596 -0
  434. data/rust/host/src/approval.rs +652 -0
  435. data/rust/host/src/auth.rs +1287 -0
  436. data/rust/host/src/bin/expr_harness.rs +46 -0
  437. data/rust/host/src/bin/lineage_harness.rs +119 -0
  438. data/rust/host/src/bin/mint_harness.rs +135 -0
  439. data/rust/host/src/checkout.rs +265 -0
  440. data/rust/host/src/dispatch.rs +1443 -0
  441. data/rust/host/src/expr_json.rs +1117 -0
  442. data/rust/host/src/field_hints.rs +52 -0
  443. data/rust/host/src/ir.rs +733 -0
  444. data/rust/host/src/journal.rs +874 -0
  445. data/rust/host/src/lambda_client.rs +378 -0
  446. data/rust/host/src/log.rs +147 -0
  447. data/rust/host/src/main.rs +474 -0
  448. data/rust/host/src/mint.rs +1865 -0
  449. data/rust/host/src/payments/keystore.rs +330 -0
  450. data/rust/host/src/payments/tests.rs +1400 -0
  451. data/rust/host/src/payments.rs +767 -0
  452. data/rust/host/src/presentation.rs +698 -0
  453. data/rust/host/src/presentation_write.rs +1431 -0
  454. data/rust/host/src/query_step.rs +149 -0
  455. data/rust/host/src/rate_limit.rs +904 -0
  456. data/rust/host/src/reference_transform.rs +241 -0
  457. data/rust/host/src/reference_validate.rs +338 -0
  458. data/rust/host/src/resend.rs +296 -0
  459. data/rust/host/src/secrets.rs +106 -0
  460. data/rust/host/src/server.rs +663 -0
  461. data/rust/host/src/storage_shape.rs +175 -0
  462. data/rust/host/src/test_pg.rs +40 -0
  463. data/rust/host/src/ui_schema.rs +1351 -0
  464. data/rust/host/src/wasm_runner.rs +191 -0
  465. data/rust/host/src/web/newsletter/cooldown.rs +126 -0
  466. data/rust/host/src/web/newsletter/tests.rs +104 -0
  467. data/rust/host/src/web/newsletter.rs +310 -0
  468. data/rust/host/src/web/newsletter_send.rs +357 -0
  469. data/rust/host/src/web/registrations.rs +1505 -0
  470. data/rust/host/src/web.rs +2937 -0
  471. data/rust/lsp/.gitignore +1 -0
  472. data/rust/lsp/Cargo.lock +7 -0
  473. data/rust/lsp/Cargo.toml +16 -0
  474. data/rust/lsp/README.md +133 -0
  475. data/rust/lsp/src/diagnostics.rs +165 -0
  476. data/rust/lsp/src/json.rs +385 -0
  477. data/rust/lsp/src/main.rs +399 -0
  478. data/rust/lsp/src/outline.rs +177 -0
  479. data/rust/lsp/src/rpc.rs +67 -0
  480. data/rust/parser/.cargo/config.toml +5 -0
  481. data/rust/parser/.gitignore +1 -0
  482. data/rust/parser/Cargo.lock +7 -0
  483. data/rust/parser/Cargo.toml +21 -0
  484. data/rust/parser/src/build/closed_sets.rs +27 -0
  485. data/rust/parser/src/build/identity.rs +276 -0
  486. data/rust/parser/src/build/mod.rs +12 -0
  487. data/rust/parser/src/build/naming.rs +141 -0
  488. data/rust/parser/src/build/pattern_subset.rs +161 -0
  489. data/rust/parser/src/build/query_derive.rs +72 -0
  490. data/rust/parser/src/build/query_inference.rs +269 -0
  491. data/rust/parser/src/build/query_options.rs +100 -0
  492. data/rust/parser/src/build/read_model.rs +94 -0
  493. data/rust/parser/src/build/references.rs +32 -0
  494. data/rust/parser/src/canonical.rs +114 -0
  495. data/rust/parser/src/diag.rs +55 -0
  496. data/rust/parser/src/emit.rs +953 -0
  497. data/rust/parser/src/expr/ast_json.rs +147 -0
  498. data/rust/parser/src/expr/evaluator.rs +81 -0
  499. data/rust/parser/src/expr/mod.rs +84 -0
  500. data/rust/parser/src/expr/resolver.rs +497 -0
  501. data/rust/parser/src/ir.rs +387 -0
  502. data/rust/parser/src/keywords.rs +585 -0
  503. data/rust/parser/src/lex.rs +662 -0
  504. data/rust/parser/src/main.rs +252 -0
  505. data/rust/parser/src/parse/aggregate.rs +612 -0
  506. data/rust/parser/src/parse/chapter.rs +517 -0
  507. data/rust/parser/src/parse/command.rs +637 -0
  508. data/rust/parser/src/parse/domain_port.rs +127 -0
  509. data/rust/parser/src/parse/entity.rs +527 -0
  510. data/rust/parser/src/parse/file.rs +67 -0
  511. data/rust/parser/src/parse/hecksagon.rs +231 -0
  512. data/rust/parser/src/parse/lifecycle.rs +162 -0
  513. data/rust/parser/src/parse/mod.rs +1262 -0
  514. data/rust/parser/src/parse/policy.rs +87 -0
  515. data/rust/parser/src/parse/process_manager.rs +305 -0
  516. data/rust/parser/src/parse/query.rs +100 -0
  517. data/rust/parser/src/parse/read_model.rs +214 -0
  518. data/rust/parser/src/parse/value_object.rs +250 -0
  519. data/rust/parser/src/ruby_value.rs +422 -0
  520. data/rust/project/bridging.rb +257 -0
  521. data/rust/project/commands.rb +877 -0
  522. data/rust/project/constraints.rb +86 -0
  523. data/rust/project/dependency_planning.rb +178 -0
  524. data/rust/project/domain_generator.rb +980 -0
  525. data/rust/project/exemplar.rb +148 -0
  526. data/rust/project/expr_emitter.rb +72 -0
  527. data/rust/project/fielded.rb +141 -0
  528. data/rust/project/json_codec.rb +641 -0
  529. data/rust/project/mutations.rb +721 -0
  530. data/rust/project/naming.rb +205 -0
  531. data/rust/project/ports.rb +65 -0
  532. data/rust/project/queries.rb +493 -0
  533. data/rust/project/reactions.rb +317 -0
  534. data/rust/project/read_models.rb +690 -0
  535. data/rust/project/reference_specs.rb +27 -0
  536. data/rust/project/registry.rb +396 -0
  537. data/rust/project/skip_reason.rb +29 -0
  538. data/rust/project/types.rb +225 -0
  539. data/rust/project/write_if_changed.rb +83 -0
  540. data/rust/project.rb +30 -0
  541. data/rust/project_rust_pipeline.rb +383 -0
  542. data/rust/src/exemplar/commands.rs +279 -0
  543. data/rust/src/exemplar/constraints.rs +18 -0
  544. data/rust/src/exemplar/fielded.rs +280 -0
  545. data/rust/src/exemplar/json.rs +277 -0
  546. data/rust/src/exemplar/mod.rs +17 -0
  547. data/rust/src/exemplar/mutations.rs +80 -0
  548. data/rust/src/exemplar/queries.rs +33 -0
  549. data/rust/src/exemplar/reactions.rs +88 -0
  550. data/rust/src/exemplar/read_models.rs +48 -0
  551. data/rust/src/exemplar/registry.rs +166 -0
  552. data/rust/src/exemplar/types.rs +44 -0
  553. data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
  554. data/rust/src/kernel/attribute_shapes/list.rs +18 -0
  555. data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
  556. data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
  557. data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
  558. data/rust/src/kernel/cli.rs +585 -0
  559. data/rust/src/kernel/dispatch.rs +1090 -0
  560. data/rust/src/kernel/expr.rs +458 -0
  561. data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
  562. data/rust/src/kernel/expression_operators/comparison.rs +48 -0
  563. data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
  564. data/rust/src/kernel/expression_operators/logical.rs +13 -0
  565. data/rust/src/kernel/expression_operators/membership.rs +18 -0
  566. data/rust/src/kernel/expression_operators/mod.rs +68 -0
  567. data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
  568. data/rust/src/kernel/expression_operators/positional.rs +126 -0
  569. data/rust/src/kernel/expression_operators/presence.rs +101 -0
  570. data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
  571. data/rust/src/kernel/expression_operators/sized.rs +35 -0
  572. data/rust/src/kernel/expression_operators/text.rs +79 -0
  573. data/rust/src/kernel/expression_operators/to_string.rs +18 -0
  574. data/rust/src/kernel/json.rs +779 -0
  575. data/rust/src/kernel/mod.rs +133 -0
  576. data/rust/src/kernel/named_query.rs +219 -0
  577. data/rust/src/kernel/orchestrate.rs +1810 -0
  578. data/rust/src/kernel/pattern.rs +307 -0
  579. data/rust/src/kernel/query_comparators.rs +206 -0
  580. data/rust/src/kernel/query_ordering.rs +103 -0
  581. data/rust/src/kernel/read_model.rs +914 -0
  582. data/rust/src/kernel/reference_lookup.rs +149 -0
  583. data/rust/src/kernel/repository.rs +468 -0
  584. data/rust/src/kernel/routing.rs +612 -0
  585. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
  586. data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
  587. data/rust/src/kernel/vocab/field_hint.rs +70 -0
  588. data/rust/src/kernel/vocab/mod.rs +20 -0
  589. data/rust/src/kernel/vocab/query_comparator.rs +65 -0
  590. data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
  591. data/rust/src/lib.rs +8 -0
  592. data/rust/src/main.rs +16 -0
  593. data/rust/web/.gitignore +1 -0
  594. data/rust/web/Cargo.lock +119 -0
  595. data/rust/web/Cargo.toml +18 -0
  596. data/rust/web/src/lib.rs +8 -0
  597. metadata +477 -12
  598. data/lib/hecks/facade/cli_door.rb +0 -96
  599. data/lib/hecks/facade/cli_runner.rb +0 -127
  600. data/lib/hecks/mcp_door_scope.rb +0 -95
@@ -0,0 +1,422 @@
1
+ //! Mirror of `Hecks::Literal` (lib/hecks/literal.rb): the pinned wire spelling of a captured
2
+ //! Ruby literal, independent of the Ruby version's `Hash#inspect` format.
3
+
4
+ /// A captured Ruby literal.
5
+ #[derive(Debug, Clone, PartialEq)]
6
+ pub enum Value {
7
+ Nil,
8
+ Bool(bool),
9
+ Int(i64),
10
+ Float(f64),
11
+ Str(String),
12
+ Symbol(String),
13
+ Hash(Vec<(String, Value)>),
14
+ Array(Vec<Value>),
15
+ /// A bare word `Literal.read` tolerates; kept apart from `Str` so `render` adds no quotes.
16
+ Bare(String),
17
+ }
18
+
19
+ pub fn render(value: &Value) -> String {
20
+ match value {
21
+ Value::Nil => "nil".to_string(),
22
+ Value::Bool(b) => b.to_string(),
23
+ Value::Int(n) => n.to_string(),
24
+ Value::Float(f) => format_ruby_float(*f),
25
+ Value::Symbol(name) => format!(":{name}"),
26
+ Value::Str(text) => quote(text),
27
+ Value::Bare(text) => text.clone(),
28
+ Value::Hash(pairs) => {
29
+ let body = pairs
30
+ .iter()
31
+ .map(|(key, held)| format!("{key}: {}", render(held)))
32
+ .collect::<Vec<_>>()
33
+ .join(", ");
34
+ format!("{{{body}}}")
35
+ }
36
+ Value::Array(items) => {
37
+ let body = items.iter().map(render).collect::<Vec<_>>().join(", ");
38
+ format!("[{body}]")
39
+ }
40
+ }
41
+ }
42
+
43
+ /// Ruby's `Float#to_s` always keeps a digit after the point (`1.0`); Rust prints `1`.
44
+ fn format_ruby_float(value: f64) -> String {
45
+ let text = format!("{value}");
46
+ if text.contains('.') || text.contains('e') || text.contains("inf") || text.contains("NaN") {
47
+ text
48
+ } else {
49
+ format!("{text}.0")
50
+ }
51
+ }
52
+
53
+ pub fn quote(text: &str) -> String {
54
+ let mut out = String::with_capacity(text.len() + 2);
55
+ out.push('"');
56
+ for ch in text.chars() {
57
+ match ch {
58
+ '"' => out.push_str("\\\""),
59
+ '\\' => out.push_str("\\\\"),
60
+ other => out.push(other),
61
+ }
62
+ }
63
+ out.push('"');
64
+ out
65
+ }
66
+
67
+ /// Ruby's `#to_s` of a `Value`: no colon on a Symbol, no quotes on a String.
68
+ /// For where Ruby calls `.to_s` rather than `Literal.render`, e.g. `IR::ValueObject#to_h`.
69
+ pub fn to_s(value: &Value) -> String {
70
+ match value {
71
+ Value::Nil => String::new(),
72
+ Value::Bool(b) => b.to_string(),
73
+ Value::Int(n) => n.to_string(),
74
+ Value::Float(f) => format_ruby_float(*f),
75
+ Value::Symbol(name) => name.clone(),
76
+ Value::Str(text) => text.clone(),
77
+ Value::Bare(text) => text.clone(),
78
+ // Ruby's `Hash#to_s`/`Array#to_s` is version-dependent; use the pinned `render` form.
79
+ Value::Hash(_) | Value::Array(_) => render(value),
80
+ }
81
+ }
82
+
83
+ /// Unescapes the inner text of a quoted symbol in bluebook source, `:"a.b"` -> `a.b`.
84
+ /// `rest` is the text after the leading `:`, already confirmed to start and end with `"`.
85
+ pub fn unquote_for_symbol(rest: &str) -> String {
86
+ unquote(rest)
87
+ }
88
+
89
+ pub fn read(text: &str) -> Value {
90
+ let raw = text.trim();
91
+ if raw.is_empty() || raw == "nil" {
92
+ return Value::Nil;
93
+ }
94
+ if raw == "true" {
95
+ return Value::Bool(true);
96
+ }
97
+ if raw == "false" {
98
+ return Value::Bool(false);
99
+ }
100
+ if is_integer(raw) {
101
+ if let Ok(n) = raw.parse::<i64>() {
102
+ return Value::Int(n);
103
+ }
104
+ }
105
+ if is_float(raw) {
106
+ if let Ok(f) = raw.parse::<f64>() {
107
+ return Value::Float(f);
108
+ }
109
+ }
110
+ if let Some(name) = raw.strip_prefix(':') {
111
+ return Value::Symbol(name.to_string());
112
+ }
113
+ // Adjacent literals (`"a" "b"`) concatenate in Ruby; check before the single-literal
114
+ // branches, which would slice `a" "b` out of the middle.
115
+ if raw.starts_with('"') || raw.starts_with('\'') {
116
+ if let Some((joined, consumed)) = scan_adjacent_strings(raw) {
117
+ if consumed == raw.chars().count() {
118
+ return Value::Str(joined);
119
+ }
120
+ }
121
+ }
122
+ if is_quoted(raw) {
123
+ return Value::Str(unquote(raw));
124
+ }
125
+ // Single-quoted literals appear in source (e.g. regex patterns) though `Literal.render`
126
+ // never emits them. Only `\\` and `\'` are escapes, so this is not `unquote`.
127
+ if is_single_quoted(raw) {
128
+ return Value::Str(unquote_single(raw));
129
+ }
130
+ if raw.starts_with('{') && raw.ends_with('}') {
131
+ return read_hash(raw);
132
+ }
133
+ if raw.starts_with('[') && raw.ends_with(']') {
134
+ return read_array(raw);
135
+ }
136
+ Value::Bare(raw.to_string())
137
+ }
138
+
139
+ fn is_integer(raw: &str) -> bool {
140
+ let body = raw.strip_prefix('-').unwrap_or(raw);
141
+ !body.is_empty() && body.chars().all(|c| c.is_ascii_digit())
142
+ }
143
+
144
+ fn is_float(raw: &str) -> bool {
145
+ let body = raw.strip_prefix('-').unwrap_or(raw);
146
+ let Some((int_part, frac_part)) = body.split_once('.') else {
147
+ return false;
148
+ };
149
+ !int_part.is_empty()
150
+ && !frac_part.is_empty()
151
+ && int_part.chars().all(|c| c.is_ascii_digit())
152
+ && frac_part.chars().all(|c| c.is_ascii_digit())
153
+ }
154
+
155
+ fn is_quoted(raw: &str) -> bool {
156
+ raw.len() >= 2 && raw.starts_with('"') && raw.ends_with('"')
157
+ }
158
+
159
+ fn is_single_quoted(raw: &str) -> bool {
160
+ raw.len() >= 2 && raw.starts_with('\'') && raw.ends_with('\'')
161
+ }
162
+
163
+ /// Unescapes a Ruby single-quoted body: only `\\` and `\'` are escapes, other backslashes stay.
164
+ fn unquote_single(raw: &str) -> String {
165
+ unescape_single_quoted_inner(&raw[1..raw.len() - 1])
166
+ }
167
+
168
+ fn unescape_single_quoted_inner(inner: &str) -> String {
169
+ let mut out = String::with_capacity(inner.len());
170
+ let mut chars = inner.chars().peekable();
171
+ while let Some(ch) = chars.next() {
172
+ if ch == '\\' {
173
+ match chars.peek() {
174
+ Some('\\') => {
175
+ out.push('\\');
176
+ chars.next();
177
+ }
178
+ Some('\'') => {
179
+ out.push('\'');
180
+ chars.next();
181
+ }
182
+ _ => out.push('\\'),
183
+ }
184
+ } else {
185
+ out.push(ch);
186
+ }
187
+ }
188
+ out
189
+ }
190
+
191
+ fn unquote(raw: &str) -> String {
192
+ unescape_double_quoted_inner(&raw[1..raw.len() - 1])
193
+ }
194
+
195
+ fn unescape_double_quoted_inner(inner: &str) -> String {
196
+ let mut out = String::with_capacity(inner.len());
197
+ let mut chars = inner.chars();
198
+ while let Some(ch) = chars.next() {
199
+ if ch == '\\' {
200
+ if let Some(next) = chars.next() {
201
+ out.push(next);
202
+ }
203
+ } else {
204
+ out.push(ch);
205
+ }
206
+ }
207
+ out
208
+ }
209
+
210
+ /// Scans adjacent quoted literals (either quote style, whitespace-separated) from index 0.
211
+ /// Returns their unescaped concatenation and the chars consumed, or `None` if `raw` does not
212
+ /// start with a closed literal. Callers compare `consumed` to the full length to tell a whole
213
+ /// string expression from trailing text.
214
+ fn scan_adjacent_strings(raw: &str) -> Option<(String, usize)> {
215
+ let chars: Vec<char> = raw.chars().collect();
216
+ let mut i = 0usize;
217
+ let mut out = String::new();
218
+ let mut matched_any = false;
219
+
220
+ loop {
221
+ if matched_any {
222
+ let mut j = i;
223
+ while j < chars.len() && chars[j].is_whitespace() {
224
+ j += 1;
225
+ }
226
+ if j >= chars.len() || (chars[j] != '"' && chars[j] != '\'') {
227
+ break;
228
+ }
229
+ i = j;
230
+ }
231
+
232
+ // Defensive: return `None` rather than panic on an empty or odd `raw`.
233
+ let Some(&quote) = chars.get(i).filter(|c| **c == '"' || **c == '\'') else {
234
+ return None;
235
+ };
236
+
237
+ let start = i;
238
+ i += 1;
239
+ let mut escaping = false;
240
+ let mut closed = false;
241
+ while i < chars.len() {
242
+ let ch = chars[i];
243
+ if escaping {
244
+ escaping = false;
245
+ i += 1;
246
+ continue;
247
+ }
248
+ if ch == '\\' {
249
+ escaping = true;
250
+ i += 1;
251
+ continue;
252
+ }
253
+ if ch == quote {
254
+ i += 1;
255
+ closed = true;
256
+ break;
257
+ }
258
+ i += 1;
259
+ }
260
+ if !closed {
261
+ return None;
262
+ }
263
+
264
+ let inner: String = chars[start + 1..i - 1].iter().collect();
265
+ out.push_str(&if quote == '"' {
266
+ unescape_double_quoted_inner(&inner)
267
+ } else {
268
+ unescape_single_quoted_inner(&inner)
269
+ });
270
+ matched_any = true;
271
+ }
272
+
273
+ Some((out, i))
274
+ }
275
+
276
+ fn read_hash(raw: &str) -> Value {
277
+ let inner = &raw[1..raw.len() - 1];
278
+ let pairs = split_items(inner)
279
+ .into_iter()
280
+ .map(|item| {
281
+ let (key, held) = item.split_once(':').unwrap_or((item.as_str(), ""));
282
+ (key.trim().to_string(), read(held.trim()))
283
+ })
284
+ .collect();
285
+ Value::Hash(pairs)
286
+ }
287
+
288
+ fn read_array(raw: &str) -> Value {
289
+ let inner = &raw[1..raw.len() - 1];
290
+ Value::Array(
291
+ split_items(inner)
292
+ .into_iter()
293
+ .map(|item| read(item.trim()))
294
+ .collect(),
295
+ )
296
+ }
297
+
298
+ /// Splits on commas outside quotes and nested braces, brackets and parens.
299
+ ///
300
+ /// Unlike `Hecks::Literal.split_items` it also tracks parens, so a nested call such as
301
+ /// `one_of("a", "b")` inside an argument list stays in one segment.
302
+ pub fn split_items(body: &str) -> Vec<String> {
303
+ let mut items = Vec::new();
304
+ let mut current = String::new();
305
+ let mut depth: i32 = 0;
306
+ let mut quoting = false;
307
+ let mut escaping = false;
308
+
309
+ for ch in body.chars() {
310
+ current.push(ch);
311
+ if escaping {
312
+ escaping = false;
313
+ continue;
314
+ }
315
+ if quoting && ch == '\\' {
316
+ escaping = true;
317
+ continue;
318
+ }
319
+ if ch == '"' {
320
+ quoting = !quoting;
321
+ continue;
322
+ }
323
+ if quoting {
324
+ continue;
325
+ }
326
+ if ch == '{' || ch == '[' || ch == '(' {
327
+ depth += 1;
328
+ }
329
+ if ch == '}' || ch == ']' || ch == ')' {
330
+ depth -= 1;
331
+ }
332
+ if ch == ',' && depth == 0 {
333
+ current.pop();
334
+ items.push(current.clone());
335
+ current.clear();
336
+ }
337
+ }
338
+ items.push(current);
339
+ items
340
+ .into_iter()
341
+ .map(|s| s.trim().to_string())
342
+ .filter(|s| !s.is_empty())
343
+ .collect()
344
+ }
345
+
346
+ #[cfg(test)]
347
+ mod tests {
348
+ use super::*;
349
+
350
+ #[test]
351
+ fn renders_the_pinned_spellings() {
352
+ assert_eq!(render(&Value::Nil), "nil");
353
+ assert_eq!(render(&Value::Bool(true)), "true");
354
+ assert_eq!(render(&Value::Bool(false)), "false");
355
+ assert_eq!(render(&Value::Symbol("amount".to_string())), ":amount");
356
+ assert_eq!(render(&Value::Int(0)), "0");
357
+ assert_eq!(render(&Value::Float(0.0)), "0.0");
358
+ assert_eq!(render(&Value::Str("credit".to_string())), "\"credit\"");
359
+ assert_eq!(
360
+ render(&Value::Str("a\"b\\c".to_string())),
361
+ "\"a\\\"b\\\\c\""
362
+ );
363
+ assert_eq!(
364
+ render(&Value::Hash(vec![(
365
+ "value".to_string(),
366
+ Value::Str("credit".to_string())
367
+ )])),
368
+ "{value: \"credit\"}"
369
+ );
370
+ assert_eq!(
371
+ render(&Value::Array(vec![Value::Int(1), Value::Int(2)])),
372
+ "[1, 2]"
373
+ );
374
+ }
375
+
376
+ #[test]
377
+ fn reads_back_what_render_wrote() {
378
+ assert_eq!(read("nil"), Value::Nil);
379
+ assert_eq!(read("true"), Value::Bool(true));
380
+ assert_eq!(read(":amount"), Value::Symbol("amount".to_string()));
381
+ assert_eq!(read("0"), Value::Int(0));
382
+ assert_eq!(read("0.0"), Value::Float(0.0));
383
+ assert_eq!(read("\"credit\""), Value::Str("credit".to_string()));
384
+ assert_eq!(
385
+ read("{value: \"credit\"}"),
386
+ Value::Hash(vec![(
387
+ "value".to_string(),
388
+ Value::Str("credit".to_string())
389
+ )])
390
+ );
391
+ assert_eq!(
392
+ read("[1, 2]"),
393
+ Value::Array(vec![Value::Int(1), Value::Int(2)])
394
+ );
395
+ // a bare word never rendered stays a string reading, not nil/symbol
396
+ assert_eq!(read("open"), Value::Bare("open".to_string()));
397
+ }
398
+
399
+ #[test]
400
+ fn splits_only_top_level_commas() {
401
+ assert_eq!(
402
+ split_items("\"a, b\", 1"),
403
+ vec!["\"a, b\"".to_string(), "1".to_string()]
404
+ );
405
+ assert_eq!(
406
+ split_items("{a: 1, b: 2}, 3"),
407
+ vec!["{a: 1, b: 2}".to_string(), "3".to_string()]
408
+ );
409
+ }
410
+
411
+ #[test]
412
+ fn concatenates_three_adjacent_literals_mixing_quote_styles() {
413
+ assert_eq!(read("\"a\" 'b' \"c\""), Value::Str("abc".to_string()));
414
+ }
415
+
416
+ #[test]
417
+ fn does_not_treat_a_quoted_string_followed_by_other_text_as_concatenation() {
418
+ // `"a" foo` is not two adjacent literals: `scan_adjacent_strings` consumes less than
419
+ // the whole input, so `read` falls through to `Value::Bare`.
420
+ assert_eq!(read("\"a\" foo"), Value::Bare("\"a\" foo".to_string()));
421
+ }
422
+ }
@@ -0,0 +1,257 @@
1
+ module RustProjection
2
+ module Projector
3
+ module_function
4
+
5
+ # `use` statements for value-object types this aggregate's commands
6
+ # reference by name but don't declare locally (same chapter only).
7
+ def cross_aggregate_vo_imports(aggregate, domain_value_object_owner, mod_name)
8
+ local_names = aggregate[:value_objects].map { |vo| vo[:name] }.to_set
9
+ attrs = (aggregate[:commands] + aggregate[:entities].flat_map { |e| e[:commands] } +
10
+ aggregate[:ports].flat_map { |p| p[:operations] })
11
+ .flat_map { |c| c[:attributes] }
12
+
13
+ # A locally-declared type is never "foreign", even if some other
14
+ # aggregate owns a VO of the same name domain-wide.
15
+ foreign_types = attrs.map { |a| a[:type] }.uniq.reject { |type| local_names.include?(type) }
16
+ foreign_types.filter_map { |type| domain_value_object_owner[type] && [type, domain_value_object_owner[type]] }
17
+ .sort_by { |(type, owner)| [owner, type] }
18
+ .map { |(type, owner)| "use crate::generated::#{mod_name}::#{owner.downcase}::#{rust_ident(type)};" }
19
+ end
20
+
21
+ # Whether target_vo's fields can all be filled from source_vo's fields
22
+ # (matched by name) or the target's own defaults.
23
+ def vo_field_bridgeable?(source_vo, target_vo)
24
+ return false unless source_vo && target_vo
25
+ return closed_sets_bridgeable?(source_vo, target_vo) if source_vo[:closed_set] || target_vo[:closed_set]
26
+
27
+ target_vo[:attributes].all? do |t_attr|
28
+ source_vo[:attributes].any? { |s_attr| s_attr[:name] == t_attr[:name] } || !t_attr[:default].nil?
29
+ end
30
+ end
31
+
32
+ # Source closed-set members mappable to target members, or nil unless
33
+ # every source member is also a target member (so the bridge can't fail).
34
+ def closed_set_bridge_members(source_vo, target_vo)
35
+ return nil unless source_vo[:closed_set] && target_vo[:closed_set]
36
+ return nil unless source_vo[:attributes].size == 1 && target_vo[:attributes].size == 1
37
+
38
+ target_values = target_vo[:members].map { |row| row.to_h.values.first.to_s }
39
+ source_rows = source_vo[:members]
40
+ return nil unless source_rows.all? { |row| target_values.include?(row.to_h.values.first.to_s) }
41
+
42
+ source_rows
43
+ end
44
+
45
+ def closed_sets_bridgeable?(source_vo, target_vo)
46
+ !closed_set_bridge_members(source_vo, target_vo).nil?
47
+ end
48
+
49
+ def vo_field_rhs(source_expr, source_vo, target_type, value_objects_by_name)
50
+ target_vo = value_objects_by_name[target_type]
51
+ if (rows = closed_set_bridge_members(source_vo, target_vo))
52
+ arms = rows.map { |row| "#{rust_ident(source_vo[:name])}::#{closed_set_variant(row)} => #{rust_ident(target_type)}::#{closed_set_variant(row)}" }
53
+ return "match &#{source_expr} { #{arms.join(', ')} }"
54
+ end
55
+
56
+ fields = target_vo[:attributes].map do |t_attr|
57
+ field = rust_ident_field(t_attr[:name])
58
+ if source_vo[:attributes].any? { |s_attr| s_attr[:name] == t_attr[:name] }
59
+ "#{field}: #{source_expr}.#{field}.clone()"
60
+ else
61
+ "#{field}: #{literal_rhs(t_attr[:default])}"
62
+ end
63
+ end
64
+ "#{rust_ident(target_type)} { #{fields.join(', ')} }"
65
+ end
66
+
67
+ # Whether a value can move from source_type to target_type. Shared
68
+ # with `value_rhs`, which performs the bridge this checks for.
69
+ def bridgeable_value_types?(source_type, target_type, value_objects_by_name)
70
+ return true if source_type == target_type
71
+ # Compares Rust representations, not declared type names: a
72
+ # `Reference<T>` and a bare `String` are both just `String`.
73
+ return true if effective_scalar_type(source_type) && effective_scalar_type(source_type) == effective_scalar_type(target_type)
74
+
75
+ source_vo = value_objects_by_name[source_type]
76
+ target_vo = value_objects_by_name[target_type]
77
+ return vo_field_bridgeable?(source_vo, target_vo) if target_vo
78
+
79
+ # A single-field source VO unwraps into a bare scalar target when
80
+ # that one field's own Rust representation already matches it.
81
+ return false unless source_vo && !source_vo[:closed_set] && source_vo[:attributes].size == 1
82
+
83
+ unwrapped_type = source_vo[:attributes].first[:type]
84
+ unwrapped_type == target_type ||
85
+ (effective_scalar_type(unwrapped_type) && effective_scalar_type(unwrapped_type) == effective_scalar_type(target_type))
86
+ end
87
+
88
+ def value_rhs(source_expr, source_type, target_type, value_objects_by_name)
89
+ return "#{source_expr}.clone()" if source_type == target_type
90
+ return "#{source_expr}.clone()" if effective_scalar_type(source_type) && effective_scalar_type(source_type) == effective_scalar_type(target_type)
91
+
92
+ source_vo = value_objects_by_name[source_type]
93
+ target_vo = value_objects_by_name[target_type]
94
+ return vo_field_rhs(source_expr, source_vo, target_type, value_objects_by_name) if target_vo
95
+
96
+ raise "unsupported coercion #{source_type} -> #{target_type} — bridgeable_value_types? should have caught this" unless source_vo && !source_vo[:closed_set] && source_vo[:attributes].size == 1
97
+
98
+ "#{source_expr}.#{rust_ident_field(source_vo[:attributes].first[:name])}.clone()"
99
+ end
100
+
101
+ # Whether a list `:set` needs per-element rebuilding, rather than one
102
+ # `.clone()`, because source and target elements differ in representation.
103
+ def list_bridge_requires_element_mapping?(source_type, target_type)
104
+ return false if source_type == target_type
105
+
106
+ !(effective_scalar_type(source_type) && effective_scalar_type(source_type) == effective_scalar_type(target_type))
107
+ end
108
+
109
+ # `value_rhs`, mapped over each element of a list `:set` whose source
110
+ # and target element types differ.
111
+ def list_value_rhs(source_expr, source_type, target_type, value_objects_by_name)
112
+ "#{source_expr}.iter().map(|item| #{value_rhs('item', source_type, target_type, value_objects_by_name)}).collect()"
113
+ end
114
+
115
+ def literal_set_bridgeable?(value, target_type, value_objects_by_name)
116
+ return literal_hash_bridgeable?(value, target_type, value_objects_by_name) if value.is_a?(Hash) && target_type
117
+ return false if value.is_a?(Hash)
118
+ return false unless value.is_a?(String) || value.is_a?(Numeric) || value == true || value == false
119
+ return true unless target_type && value_objects_by_name[target_type]
120
+
121
+ # A scalar literal into a single-field VO stands for that field's
122
+ # value (mirrors `Value.for_attribute`'s runtime rewrap).
123
+ sole = sole_field_of(target_type, value_objects_by_name)
124
+ return false unless sole
125
+
126
+ literal_hash_bridgeable?({ sole => value }, target_type, value_objects_by_name)
127
+ end
128
+
129
+ # The right-hand side for a literal into target_type: a Hash, a
130
+ # scalar rewrapped into a single-field VO, or the scalar itself.
131
+ def literal_rhs_for(value, target_type, value_objects_by_name)
132
+ return literal_hash_rhs(value, target_type, value_objects_by_name) if value.is_a?(Hash)
133
+
134
+ sole = target_type && value_objects_by_name[target_type] && sole_field_of(target_type, value_objects_by_name)
135
+ return literal_hash_rhs({ sole => value }, target_type, value_objects_by_name) if sole
136
+
137
+ literal_rhs(value)
138
+ end
139
+
140
+ # Whether a literal Hash supplies every field a target VO (or one
141
+ # of its closed-set members) needs.
142
+ def literal_hash_bridgeable?(hash, target_type, value_objects_by_name)
143
+ vo = value_objects_by_name[target_type]
144
+ return false unless vo
145
+
146
+ if vo[:closed_set]
147
+ vo[:members].any? { |member| member.all? { |field, value| [hash[field.to_sym], hash[field.to_s]].include?(value) } }
148
+ else
149
+ vo[:attributes].all? { |attr| hash.key?(attr[:name].to_sym) || hash.key?(attr[:name].to_s) }
150
+ end
151
+ end
152
+
153
+ def literal_hash_rhs(hash, target_type, value_objects_by_name)
154
+ vo = value_objects_by_name[target_type]
155
+ raise "unsupported literal hash source #{hash.inspect} -> #{target_type} — literal_hash_bridgeable? should have caught this" unless vo
156
+
157
+ if vo[:closed_set]
158
+ row = vo[:members].find { |member| member.all? { |field, value| [hash[field.to_sym], hash[field.to_s]].include?(value) } }
159
+ raise "literal #{hash.inspect} matches no member of #{target_type} — literal_hash_bridgeable? should have caught this" unless row
160
+
161
+ "#{rust_ident(target_type)}::#{closed_set_variant(row)}"
162
+ else
163
+ fields = vo[:attributes].map do |attr|
164
+ key = [attr[:name].to_sym, attr[:name].to_s].find { |k| hash.key?(k) }
165
+ raise "literal #{hash.inspect} missing field #{attr[:name]} for #{target_type} — literal_hash_bridgeable? should have caught this" unless key
166
+
167
+ "#{rust_ident_field(attr[:name])}: #{literal_rhs(hash[key])}"
168
+ end
169
+ "#{rust_ident(target_type)} { #{fields.join(', ')} }"
170
+ end
171
+ end
172
+
173
+ def integer_field_of(vo)
174
+ return nil unless vo && !vo[:closed_set]
175
+
176
+ attr = vo[:attributes].find { |a| a[:type] == "Integer" }
177
+ attr && attr[:name]
178
+ end
179
+
180
+ # The attribute and its one Integer field an `:increment`/`:decrement`
181
+ # touches, or nil unless the target is a single-field integer VO.
182
+ def arithmetic_target_field(mutation, aggregate, value_objects_by_name)
183
+ target_attr = aggregate[:attributes].find { |a| a[:name].to_s == mutation[:target].to_s }
184
+ return nil unless target_attr && !target_attr[:list]
185
+
186
+ field = integer_field_of(value_objects_by_name[target_attr[:type]])
187
+ field && [target_attr, field]
188
+ end
189
+
190
+ # The amount side of an `:increment`/`:decrement` as a raw Rust
191
+ # integer expression. Returns nil, not raise, when nothing bridges.
192
+ def arithmetic_amount_expr(source, command, value_objects_by_name, target_integer_field)
193
+ if source[:kind] == "literal"
194
+ value = source[:value]
195
+ return literal_rhs(value) if value.is_a?(Integer)
196
+ return nil unless value.is_a?(Hash)
197
+
198
+ key = [target_integer_field.to_sym, target_integer_field.to_s].find { |k| value.key?(k) }
199
+ key && value[key].is_a?(Integer) ? literal_rhs(value[key]) : nil
200
+ elsif source[:kind] == "argument"
201
+ arg_attr = command[:attributes].find { |a| a[:name].to_s == source[:name] }
202
+ return nil unless arg_attr
203
+ return "args.#{rust_ident_field(arg_attr[:name])}" if arg_attr[:type] == "Integer"
204
+
205
+ field = integer_field_of(value_objects_by_name[arg_attr[:type]])
206
+ field && "args.#{rust_ident_field(arg_attr[:name])}.#{rust_ident_field(field)}"
207
+ end
208
+ end
209
+
210
+ # `clamp:`'s literal `[min, max]` bounds, or nil for anything else
211
+ # (non-literal source, wrong length, non-Integer bound).
212
+ def clamp_bounds_ints(source)
213
+ return nil unless source[:kind] == "literal"
214
+
215
+ value = source[:value]
216
+ return nil unless value.is_a?(Array) && value.size == 2 && value.all? { |v| v.is_a?(Integer) }
217
+
218
+ value
219
+ end
220
+
221
+ # The default for an attribute a creating command's arguments don't
222
+ # mention: its own default, or one built from its VO's field defaults.
223
+ def creation_default_rhs(attr, value_objects_by_name)
224
+ default = attr[:default]
225
+ unless default.nil?
226
+ return literal_rhs(default) unless default.is_a?(Hash)
227
+
228
+ # Completes a partial Hash default with the target VO's own
229
+ # per-field defaults first; `literal_hash_rhs` requires every
230
+ # field present.
231
+ return literal_hash_rhs(complete_hash_default(default, attr[:type], value_objects_by_name), attr[:type], value_objects_by_name)
232
+ end
233
+
234
+ vo = value_objects_by_name[attr[:type]]
235
+ return nil unless vo && !vo[:closed_set] && vo[:attributes].all? { |f| !f[:default].nil? }
236
+
237
+ fields = vo[:attributes].map { |f| "#{rust_ident_field(f[:name])}: #{literal_rhs(f[:default])}" }
238
+ "#{rust_ident(attr[:type])} { #{fields.join(', ')} }"
239
+ end
240
+
241
+ # Fields present in `hash`, plus any the target VO defaults on its
242
+ # own; a field with neither is left out, not set to nil.
243
+ def complete_hash_default(hash, target_type, value_objects_by_name)
244
+ vo = value_objects_by_name[target_type]
245
+ return hash unless vo && !vo[:closed_set]
246
+
247
+ vo[:attributes].each_with_object({}) do |field, completed|
248
+ key = [field[:name].to_sym, field[:name].to_s].find { |k| hash.key?(k) }
249
+ if key
250
+ completed[field[:name].to_sym] = hash[key]
251
+ elsif !field[:default].nil?
252
+ completed[field[:name].to_sym] = field[:default]
253
+ end
254
+ end
255
+ end
256
+ end
257
+ end