hecks 2.9.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (600) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +51 -6
  3. data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
  4. data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
  5. data/lib/hecks/adapters/driven/lambda.rb +1 -1
  6. data/lib/hecks/adapters/driven/memory.rb +16 -3
  7. data/lib/hecks/adapters/driven/postgres/reconnect.rb +6 -2
  8. data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
  9. data/lib/hecks/adapters/driven/postgres.rb +13 -4
  10. data/lib/hecks/adapters/driven/prism.rb +41 -8
  11. data/lib/hecks/adapters/driven/tenant_provisioner.rb +73 -16
  12. data/lib/hecks/behaviors/expectations.rb +1 -1
  13. data/lib/hecks/behaviors.rb +1 -1
  14. data/lib/hecks/bench/cli.rb +62 -0
  15. data/lib/hecks/bench/environment.rb +71 -0
  16. data/lib/hecks/bench/postgres_probe.rb +30 -0
  17. data/lib/hecks/bench/report.rb +76 -0
  18. data/lib/hecks/bench/ruby_runner.rb +93 -0
  19. data/lib/hecks/bench/run.rb +55 -0
  20. data/lib/hecks/bench/rust_runner.rb +101 -0
  21. data/lib/hecks/bench/stats.rb +46 -0
  22. data/lib/hecks/bench/suite.rb +98 -0
  23. data/lib/hecks/bench/workload.rb +89 -0
  24. data/lib/hecks/bench.rb +17 -0
  25. data/lib/hecks/bluebook/assembly/contracts.rb +2 -0
  26. data/lib/hecks/bluebook/attribute.rb +1 -1
  27. data/lib/hecks/bluebook/behaviour/aggregate.rb +30 -1
  28. data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -0
  29. data/lib/hecks/bluebook/chapter.rb +6 -2
  30. data/lib/hecks/bluebook/domain_port.rb +36 -5
  31. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +10 -0
  32. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +8 -0
  33. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -1
  34. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +31 -3
  35. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +122 -3
  36. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +1 -1
  37. data/lib/hecks/bluebook/dsl/query_builder.rb +27 -1
  38. data/lib/hecks/bluebook/dsl/rule_reference.rb +1 -1
  39. data/lib/hecks/bluebook/dsl/world_builder.rb +29 -2
  40. data/lib/hecks/bluebook/expression/canonical_form.rb +1 -1
  41. data/lib/hecks/bluebook/expression/resolver.rb +3 -2
  42. data/lib/hecks/bluebook/hexagon.rb +13 -5
  43. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -0
  44. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +4 -1
  45. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
  46. data/lib/hecks/bluebook/meta_validator.rb +9 -4
  47. data/lib/hecks/bluebook/model_check.rb +23 -0
  48. data/lib/hecks/bluebook/pattern_subset.rb +38 -0
  49. data/lib/hecks/bluebook/query.rb +20 -5
  50. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  51. data/lib/hecks/canonical_json.rb +30 -0
  52. data/lib/hecks/chapters.rb +102 -0
  53. data/lib/hecks/cli/behaviors.rb +61 -0
  54. data/lib/hecks/cli/canonicalise.rb +25 -0
  55. data/lib/hecks/cli/check_engine_agreement.rb +32 -0
  56. data/lib/hecks/cli/check_era.rb +88 -0
  57. data/lib/hecks/cli/console.rb +66 -0
  58. data/lib/hecks/cli/deploy_template_diff.rb +40 -0
  59. data/lib/hecks/cli/doc_coverage.rb +32 -0
  60. data/lib/hecks/cli/document.rb +2 -2
  61. data/lib/hecks/cli/expression_projection.rb +41 -0
  62. data/lib/hecks/cli/follow.rb +88 -0
  63. data/lib/hecks/cli/history.rb +44 -0
  64. data/lib/hecks/cli/ir.rb +1 -1
  65. data/lib/hecks/cli/mcp.rb +3 -3
  66. data/lib/hecks/cli/model_check.rb +15 -8
  67. data/lib/hecks/cli/pattern_cases.rb +47 -0
  68. data/lib/hecks/cli/present.rb +39 -0
  69. data/lib/hecks/cli/project_cli.rb +224 -32
  70. data/lib/hecks/cli/project_diagrams.rb +4 -3
  71. data/lib/hecks/cli/project_glossary.rb +44 -0
  72. data/lib/hecks/cli/reference.rb +46 -0
  73. data/lib/hecks/cli/refresh_projections.rb +44 -0
  74. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
  75. data/lib/hecks/cli/release.rb +70 -0
  76. data/lib/hecks/cli/release_gem.rb +59 -0
  77. data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
  78. data/lib/hecks/cli/rspec_shard_files.rb +66 -0
  79. data/lib/hecks/cli/run.rb +3 -3
  80. data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
  81. data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
  82. data/lib/hecks/cli/shape.rb +70 -0
  83. data/lib/hecks/cli/smoke_http.rb +230 -0
  84. data/lib/hecks/cli/smoke_test.rb +5 -3
  85. data/lib/hecks/cli/spec_example.rb +27 -0
  86. data/lib/hecks/cli/statements.rb +32 -0
  87. data/lib/hecks/cli/stores.rb +4 -5
  88. data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
  89. data/lib/hecks/cli.rb +201 -52
  90. data/lib/hecks/codemod.rb +324 -0
  91. data/lib/hecks/corpus.rb +423 -0
  92. data/lib/hecks/deploy/bluebook/deploy.bluebook +591 -23
  93. data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -4
  94. data/lib/hecks/deploy/oidc.json +82 -2
  95. data/lib/hecks/doc/reference.rb +351 -0
  96. data/lib/hecks/doors/cli_door.rb +196 -0
  97. data/lib/hecks/doors/cli_runner.rb +297 -0
  98. data/lib/hecks/{facade → doors}/command_request.rb +1 -1
  99. data/lib/hecks/{facade → doors}/handle.rb +2 -2
  100. data/lib/hecks/{facade → doors}/json_door.rb +4 -4
  101. data/lib/hecks/doors/launcher_options.rb +129 -0
  102. data/lib/hecks/{cli → doors}/mcp_door.rb +7 -7
  103. data/lib/hecks/doors/mcp_door_scope.rb +97 -0
  104. data/lib/hecks/{facade/surface → doors/ruby_door}/aggregate_door.rb +8 -3
  105. data/lib/hecks/{facade/surface → doors/ruby_door}/chapter.rb +3 -3
  106. data/lib/hecks/{facade/surface.rb → doors/ruby_door.rb} +36 -5
  107. data/lib/hecks/doors.rb +61 -0
  108. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +1 -1
  109. data/lib/hecks/embryonaut_bluebook.rb +1 -1
  110. data/lib/hecks/engine_agreement.rb +156 -0
  111. data/lib/hecks/facade.rb +24 -37
  112. data/lib/hecks/forms/app.rb +6 -6
  113. data/lib/hecks/forms/banking_presentation.rb +50 -0
  114. data/lib/hecks/forms/field_shape.rb +2 -1
  115. data/lib/hecks/forms/port_argument.rb +1 -1
  116. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
  117. data/lib/hecks/fuzzing/combination_miner.rb +86 -0
  118. data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
  119. data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
  120. data/lib/hecks/fuzzing/differential.rb +156 -0
  121. data/lib/hecks/fuzzing/domain_generator.rb +615 -0
  122. data/lib/hecks/fuzzing/era_boundary.rb +71 -0
  123. data/lib/hecks/fuzzing/form_census.rb +136 -0
  124. data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
  125. data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
  126. data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
  127. data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
  128. data/lib/hecks/fuzzing/outside_world.rb +45 -0
  129. data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
  130. data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
  131. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
  132. data/lib/hecks/fuzzing/properties/guards.rb +190 -0
  133. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
  134. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
  135. data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
  136. data/lib/hecks/fuzzing/properties/querying.rb +175 -0
  137. data/lib/hecks/fuzzing/properties.rb +162 -0
  138. data/lib/hecks/fuzzing/qa_settings.rb +123 -0
  139. data/lib/hecks/fuzzing/replay.rb +505 -0
  140. data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
  141. data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
  142. data/lib/hecks/fuzzing/self_consistency.rb +339 -0
  143. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
  144. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
  145. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
  146. data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
  147. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
  148. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
  149. data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
  150. data/lib/hecks/fuzzing/shrinker.rb +166 -0
  151. data/lib/hecks/fuzzing/structural_skips.rb +33 -0
  152. data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
  153. data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
  154. data/lib/hecks/fuzzing/value_generator.rb +216 -0
  155. data/lib/hecks/fuzzing.rb +23 -0
  156. data/lib/hecks/grammar/evolve.rb +336 -0
  157. data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
  158. data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
  159. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
  160. data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
  161. data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
  162. data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
  163. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
  164. data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
  165. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
  166. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
  167. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
  168. data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
  169. data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
  170. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
  171. data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
  172. data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
  173. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
  174. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
  175. data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
  176. data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
  177. data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
  178. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
  179. data/lib/hecks/hecks/adapters/git.adapter +7 -0
  180. data/lib/hecks/hecks/adapters/git.rb +102 -0
  181. data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
  182. data/lib/hecks/hecks/adapters/host_http.rb +65 -0
  183. data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
  184. data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
  185. data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
  186. data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
  187. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
  188. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
  189. data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
  190. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
  191. data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
  192. data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
  193. data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
  194. data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
  195. data/lib/hecks/hecks/adapters/local_files.rb +46 -0
  196. data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
  197. data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
  198. data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
  199. data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
  200. data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
  201. data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
  202. data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
  203. data/lib/hecks/hecks/adapters/shell.adapter +7 -0
  204. data/lib/hecks/hecks/adapters/shell.rb +49 -0
  205. data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
  206. data/lib/hecks/hecks/adapters/terminal.rb +74 -0
  207. data/lib/hecks/hecks/codebase.behaviors +172 -0
  208. data/lib/hecks/hecks/codebase.bluebook +2453 -0
  209. data/lib/hecks/hecks/context_map.hecksagon +13 -0
  210. data/lib/hecks/hecks/custodian.behaviors +179 -0
  211. data/lib/hecks/hecks/custodian.bluebook +1847 -0
  212. data/lib/hecks/hecks/deploy.behaviors +74 -0
  213. data/lib/hecks/hecks/deploy.world +12 -0
  214. data/lib/hecks/hecks/environments/memory.world +10 -0
  215. data/lib/hecks/hecks/governance.world +7 -0
  216. data/lib/hecks/hecks/hecks.behaviors +62 -0
  217. data/lib/hecks/hecks/hecks.bluebook +124 -0
  218. data/lib/hecks/hecks/hecks.hecksagon +390 -0
  219. data/lib/hecks/hecks/hecks.world +42 -0
  220. data/lib/hecks/kernel_capabilities.rb +194 -0
  221. data/lib/hecks/language/bluebook/bluebook.bluebook +11 -0
  222. data/lib/hecks/language/bluebook/query.bluebook +7 -0
  223. data/lib/hecks/language/bluebook/vocabulary.bluebook +4 -4
  224. data/lib/hecks/language/hecksagon/domain_port.bluebook +10 -0
  225. data/lib/hecks/language/hecksagon/hecksagon.bluebook +20 -0
  226. data/lib/hecks/mcp_stdio_guard.rb +2 -2
  227. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
  228. data/lib/hecks/persistence_legacy_fixture.rb +113 -0
  229. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +8 -5
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +4 -1
  231. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +15 -2
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +1 -1
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +1 -1
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +32 -15
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +15 -5
  237. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
  238. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +1 -1
  239. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +43 -24
  240. data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
  241. data/lib/hecks/ports/persistence/state_codec.rb +24 -5
  242. data/lib/hecks/projection_files.rb +285 -0
  243. data/lib/hecks/projections/bootstrap_table.rb +1 -1
  244. data/lib/hecks/projections/deploy/fargate.rb +5 -5
  245. data/lib/hecks/projections/deploy/lambda.rb +43 -32
  246. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  247. data/lib/hecks/projections/deploy/preview/template.rb +1 -1
  248. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
  249. data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
  250. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
  251. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
  252. data/lib/hecks/projections/deploy/shared.rb +1 -1
  253. data/lib/hecks/projections/diagrams.rb +3 -3
  254. data/lib/hecks/projections/model/deviations.rb +1 -1
  255. data/lib/hecks/projections/parser_table.rb +4 -4
  256. data/lib/hecks/projections/reference.rb +2 -3
  257. data/lib/hecks/projections/rust_vocabulary.rb +1 -1
  258. data/lib/hecks/projector/cli_projector.rb +106 -37
  259. data/lib/hecks/projector.rb +1 -1
  260. data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
  261. data/lib/hecks/quality_control/adapters/agent.rb +58 -0
  262. data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
  263. data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
  264. data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
  265. data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
  266. data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
  267. data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
  268. data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
  269. data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
  270. data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
  271. data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
  272. data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
  273. data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
  274. data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
  275. data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
  276. data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
  277. data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
  278. data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
  279. data/lib/hecks/quality_control/cli/child.rb +45 -0
  280. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
  281. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
  282. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
  283. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
  284. data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
  285. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
  286. data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
  287. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
  288. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
  289. data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
  290. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
  291. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
  292. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
  293. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
  294. data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
  295. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
  296. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
  297. data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
  298. data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
  299. data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
  300. data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
  301. data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
  302. data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
  303. data/lib/hecks/query_ir.rb +346 -0
  304. data/lib/hecks/query_ir_mcp.rb +198 -0
  305. data/lib/hecks/release/runner/ci_publisher.rb +2 -2
  306. data/lib/hecks/release/runner/clean_tree.rb +42 -0
  307. data/lib/hecks/release/runner/console.rb +6 -0
  308. data/lib/hecks/release/runner/gem_publisher.rb +14 -7
  309. data/lib/hecks/release/runner/npm_publisher.rb +6 -36
  310. data/lib/hecks/release/runner/preflight.rb +26 -2
  311. data/lib/hecks/release/runner/published.rb +8 -26
  312. data/lib/hecks/release/runner/tagger.rb +30 -15
  313. data/lib/hecks/release/runner.rb +57 -6
  314. data/lib/hecks/router/namespace_installer.rb +3 -3
  315. data/lib/hecks/runtime/adapter_lookup.rb +124 -0
  316. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +4 -0
  317. data/lib/hecks/runtime/dependency_planning.rb +20 -2
  318. data/lib/hecks/runtime/dispatcher.rb +53 -3
  319. data/lib/hecks/runtime/interpreting.rb +3 -0
  320. data/lib/hecks/runtime/loader.rb +65 -12
  321. data/lib/hecks/runtime/outbox.rb +1 -1
  322. data/lib/hecks/runtime/policy_interpreter.rb +5 -2
  323. data/lib/hecks/runtime/port_operation_interpreter.rb +2 -10
  324. data/lib/hecks/runtime/query_interpreter.rb +77 -0
  325. data/lib/hecks/runtime/reaction_outcome.rb +89 -0
  326. data/lib/hecks/runtime/refusal_wording.rb +23 -1
  327. data/lib/hecks/runtime/registry/verification.rb +163 -4
  328. data/lib/hecks/runtime/registry.rb +57 -3
  329. data/lib/hecks/runtime/remote_dispatcher.rb +31 -2
  330. data/lib/hecks/runtime/value/coercion.rb +23 -135
  331. data/lib/hecks/runtime/value/field_checks.rb +172 -0
  332. data/lib/hecks/runtime/value.rb +4 -1
  333. data/lib/hecks/runtime.rb +20 -6
  334. data/lib/hecks/rust_build/conformance.rb +158 -0
  335. data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
  336. data/lib/hecks/rust_build/coverage.rb +218 -0
  337. data/lib/hecks/rust_build/native_build.rb +124 -0
  338. data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
  339. data/lib/hecks/rust_build/project_rust.rb +332 -0
  340. data/lib/hecks/rust_build/wasm.rb +105 -0
  341. data/lib/hecks/rust_build/wasm_browser.rb +113 -0
  342. data/lib/hecks/rust_build.rb +144 -0
  343. data/lib/hecks/storehouse.rb +16 -16
  344. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +2 -2
  345. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +1 -1
  346. data/lib/hecks/three_zero/forms.yml +127 -0
  347. data/lib/hecks/three_zero.rb +12 -0
  348. data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
  349. data/lib/hecks/tools/comment_style.rb +1070 -0
  350. data/lib/hecks/tools/corpus_report.rb +87 -0
  351. data/lib/hecks/tools/deploy_recipe.rb +255 -0
  352. data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
  353. data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
  354. data/lib/hecks/tools/era_projection_backfill.rb +94 -0
  355. data/lib/hecks/tools/era_reattest.rb +146 -0
  356. data/lib/hecks/tools/evolve_run.rb +189 -0
  357. data/lib/hecks/tools/fuzz_sweep.rb +373 -0
  358. data/lib/hecks/tools/heki_compaction.rb +100 -0
  359. data/lib/hecks/tools/hoist_local_givens.rb +246 -0
  360. data/lib/hecks/tools/journal_compaction.rb +89 -0
  361. data/lib/hecks/tools/oidc_manifests.rb +70 -0
  362. data/lib/hecks/tools/query_ir_run.rb +81 -0
  363. data/lib/hecks/tools/regeneration_run.rb +166 -0
  364. data/lib/hecks/tools/rust_comment_style.rb +642 -0
  365. data/lib/hecks/tools/sequence_script.rb +62 -0
  366. data/lib/hecks/tools/tail_merge.rb +117 -0
  367. data/lib/hecks/tools/tenant_provisioning.rb +142 -0
  368. data/lib/hecks/tools/translation_audit.rb +208 -0
  369. data/lib/hecks/tools/translation_scaffold.rb +131 -0
  370. data/lib/hecks/tools.rb +86 -0
  371. data/lib/hecks/version.rb +1 -1
  372. data/lib/hecks.rb +32 -15
  373. data/qa/settings.yml +66 -0
  374. data/rust/.cargo/config.toml +6 -0
  375. data/rust/.gitignore +2 -0
  376. data/rust/Cargo.lock +54 -0
  377. data/rust/Cargo.toml +61 -0
  378. data/rust/build/.cargo/config.toml +3 -0
  379. data/rust/build/.gitignore +1 -0
  380. data/rust/build/Cargo.lock +7 -0
  381. data/rust/build/Cargo.toml +19 -0
  382. data/rust/build/src/build_artifact.rs +47 -0
  383. data/rust/build/src/cargo_sync.rs +248 -0
  384. data/rust/build/src/json.rs +421 -0
  385. data/rust/build/src/lineage_pass.rs +285 -0
  386. data/rust/build/src/main.rs +51 -0
  387. data/rust/build/src/optional_pass.rs +150 -0
  388. data/rust/build/src/pipeline.rs +264 -0
  389. data/rust/build/src/reserved_names.rs +76 -0
  390. data/rust/build/src/resolve.rs +124 -0
  391. data/rust/build/src/root.rs +25 -0
  392. data/rust/build/src/sidecars.rs +73 -0
  393. data/rust/build/src/subprocess.rs +51 -0
  394. data/rust/build/src/tmp.rs +31 -0
  395. data/rust/codegen/.cargo/config.toml +3 -0
  396. data/rust/codegen/.gitignore +1 -0
  397. data/rust/codegen/Cargo.lock +7 -0
  398. data/rust/codegen/Cargo.toml +20 -0
  399. data/rust/codegen/src/attr.rs +32 -0
  400. data/rust/codegen/src/bridging.rs +481 -0
  401. data/rust/codegen/src/commands.rs +1226 -0
  402. data/rust/codegen/src/constraints.rs +109 -0
  403. data/rust/codegen/src/dependency_planning.rs +209 -0
  404. data/rust/codegen/src/domain_generator.rs +1288 -0
  405. data/rust/codegen/src/exemplar.rs +230 -0
  406. data/rust/codegen/src/expr_emitter.rs +87 -0
  407. data/rust/codegen/src/fielded.rs +192 -0
  408. data/rust/codegen/src/hecks_naming.rs +33 -0
  409. data/rust/codegen/src/json.rs +335 -0
  410. data/rust/codegen/src/json_codec.rs +697 -0
  411. data/rust/codegen/src/literal.rs +207 -0
  412. data/rust/codegen/src/main.rs +290 -0
  413. data/rust/codegen/src/manifest.rs +227 -0
  414. data/rust/codegen/src/mutations.rs +784 -0
  415. data/rust/codegen/src/naming.rs +286 -0
  416. data/rust/codegen/src/ports.rs +208 -0
  417. data/rust/codegen/src/prelude.rs +116 -0
  418. data/rust/codegen/src/queries.rs +786 -0
  419. data/rust/codegen/src/reactions.rs +532 -0
  420. data/rust/codegen/src/read_models.rs +697 -0
  421. data/rust/codegen/src/reference_specs.rs +40 -0
  422. data/rust/codegen/src/registry.rs +966 -0
  423. data/rust/codegen/src/reserved_names.rs +76 -0
  424. data/rust/codegen/src/shared.rs +93 -0
  425. data/rust/codegen/src/skip_reason.rs +25 -0
  426. data/rust/codegen/src/types.rs +362 -0
  427. data/rust/host/.cargo/config.toml +3 -0
  428. data/rust/host/.gitignore +1 -0
  429. data/rust/host/Cargo.lock +4297 -0
  430. data/rust/host/Cargo.toml +61 -0
  431. data/rust/host/HECKS_RELEASE +1 -0
  432. data/rust/host/rds-ca-bundle.pem +2736 -0
  433. data/rust/host/src/api.rs +1596 -0
  434. data/rust/host/src/approval.rs +652 -0
  435. data/rust/host/src/auth.rs +1287 -0
  436. data/rust/host/src/bin/expr_harness.rs +46 -0
  437. data/rust/host/src/bin/lineage_harness.rs +119 -0
  438. data/rust/host/src/bin/mint_harness.rs +135 -0
  439. data/rust/host/src/checkout.rs +265 -0
  440. data/rust/host/src/dispatch.rs +1443 -0
  441. data/rust/host/src/expr_json.rs +1117 -0
  442. data/rust/host/src/field_hints.rs +52 -0
  443. data/rust/host/src/ir.rs +733 -0
  444. data/rust/host/src/journal.rs +874 -0
  445. data/rust/host/src/lambda_client.rs +378 -0
  446. data/rust/host/src/log.rs +147 -0
  447. data/rust/host/src/main.rs +474 -0
  448. data/rust/host/src/mint.rs +1865 -0
  449. data/rust/host/src/payments/keystore.rs +330 -0
  450. data/rust/host/src/payments/tests.rs +1400 -0
  451. data/rust/host/src/payments.rs +767 -0
  452. data/rust/host/src/presentation.rs +698 -0
  453. data/rust/host/src/presentation_write.rs +1431 -0
  454. data/rust/host/src/query_step.rs +149 -0
  455. data/rust/host/src/rate_limit.rs +904 -0
  456. data/rust/host/src/reference_transform.rs +241 -0
  457. data/rust/host/src/reference_validate.rs +338 -0
  458. data/rust/host/src/resend.rs +296 -0
  459. data/rust/host/src/secrets.rs +106 -0
  460. data/rust/host/src/server.rs +663 -0
  461. data/rust/host/src/storage_shape.rs +175 -0
  462. data/rust/host/src/test_pg.rs +40 -0
  463. data/rust/host/src/ui_schema.rs +1351 -0
  464. data/rust/host/src/wasm_runner.rs +191 -0
  465. data/rust/host/src/web/newsletter/cooldown.rs +126 -0
  466. data/rust/host/src/web/newsletter/tests.rs +104 -0
  467. data/rust/host/src/web/newsletter.rs +310 -0
  468. data/rust/host/src/web/newsletter_send.rs +357 -0
  469. data/rust/host/src/web/registrations.rs +1505 -0
  470. data/rust/host/src/web.rs +2937 -0
  471. data/rust/lsp/.gitignore +1 -0
  472. data/rust/lsp/Cargo.lock +7 -0
  473. data/rust/lsp/Cargo.toml +16 -0
  474. data/rust/lsp/README.md +133 -0
  475. data/rust/lsp/src/diagnostics.rs +165 -0
  476. data/rust/lsp/src/json.rs +385 -0
  477. data/rust/lsp/src/main.rs +399 -0
  478. data/rust/lsp/src/outline.rs +177 -0
  479. data/rust/lsp/src/rpc.rs +67 -0
  480. data/rust/parser/.cargo/config.toml +5 -0
  481. data/rust/parser/.gitignore +1 -0
  482. data/rust/parser/Cargo.lock +7 -0
  483. data/rust/parser/Cargo.toml +21 -0
  484. data/rust/parser/src/build/closed_sets.rs +27 -0
  485. data/rust/parser/src/build/identity.rs +276 -0
  486. data/rust/parser/src/build/mod.rs +12 -0
  487. data/rust/parser/src/build/naming.rs +141 -0
  488. data/rust/parser/src/build/pattern_subset.rs +161 -0
  489. data/rust/parser/src/build/query_derive.rs +72 -0
  490. data/rust/parser/src/build/query_inference.rs +269 -0
  491. data/rust/parser/src/build/query_options.rs +100 -0
  492. data/rust/parser/src/build/read_model.rs +94 -0
  493. data/rust/parser/src/build/references.rs +32 -0
  494. data/rust/parser/src/canonical.rs +114 -0
  495. data/rust/parser/src/diag.rs +55 -0
  496. data/rust/parser/src/emit.rs +953 -0
  497. data/rust/parser/src/expr/ast_json.rs +147 -0
  498. data/rust/parser/src/expr/evaluator.rs +81 -0
  499. data/rust/parser/src/expr/mod.rs +84 -0
  500. data/rust/parser/src/expr/resolver.rs +497 -0
  501. data/rust/parser/src/ir.rs +387 -0
  502. data/rust/parser/src/keywords.rs +585 -0
  503. data/rust/parser/src/lex.rs +662 -0
  504. data/rust/parser/src/main.rs +252 -0
  505. data/rust/parser/src/parse/aggregate.rs +612 -0
  506. data/rust/parser/src/parse/chapter.rs +517 -0
  507. data/rust/parser/src/parse/command.rs +637 -0
  508. data/rust/parser/src/parse/domain_port.rs +127 -0
  509. data/rust/parser/src/parse/entity.rs +527 -0
  510. data/rust/parser/src/parse/file.rs +67 -0
  511. data/rust/parser/src/parse/hecksagon.rs +231 -0
  512. data/rust/parser/src/parse/lifecycle.rs +162 -0
  513. data/rust/parser/src/parse/mod.rs +1262 -0
  514. data/rust/parser/src/parse/policy.rs +87 -0
  515. data/rust/parser/src/parse/process_manager.rs +305 -0
  516. data/rust/parser/src/parse/query.rs +100 -0
  517. data/rust/parser/src/parse/read_model.rs +214 -0
  518. data/rust/parser/src/parse/value_object.rs +250 -0
  519. data/rust/parser/src/ruby_value.rs +422 -0
  520. data/rust/project/bridging.rb +257 -0
  521. data/rust/project/commands.rb +877 -0
  522. data/rust/project/constraints.rb +86 -0
  523. data/rust/project/dependency_planning.rb +178 -0
  524. data/rust/project/domain_generator.rb +980 -0
  525. data/rust/project/exemplar.rb +148 -0
  526. data/rust/project/expr_emitter.rb +72 -0
  527. data/rust/project/fielded.rb +141 -0
  528. data/rust/project/json_codec.rb +641 -0
  529. data/rust/project/mutations.rb +721 -0
  530. data/rust/project/naming.rb +205 -0
  531. data/rust/project/ports.rb +65 -0
  532. data/rust/project/queries.rb +493 -0
  533. data/rust/project/reactions.rb +317 -0
  534. data/rust/project/read_models.rb +690 -0
  535. data/rust/project/reference_specs.rb +27 -0
  536. data/rust/project/registry.rb +396 -0
  537. data/rust/project/skip_reason.rb +29 -0
  538. data/rust/project/types.rb +225 -0
  539. data/rust/project/write_if_changed.rb +83 -0
  540. data/rust/project.rb +30 -0
  541. data/rust/project_rust_pipeline.rb +383 -0
  542. data/rust/src/exemplar/commands.rs +279 -0
  543. data/rust/src/exemplar/constraints.rs +18 -0
  544. data/rust/src/exemplar/fielded.rs +280 -0
  545. data/rust/src/exemplar/json.rs +277 -0
  546. data/rust/src/exemplar/mod.rs +17 -0
  547. data/rust/src/exemplar/mutations.rs +80 -0
  548. data/rust/src/exemplar/queries.rs +33 -0
  549. data/rust/src/exemplar/reactions.rs +88 -0
  550. data/rust/src/exemplar/read_models.rs +48 -0
  551. data/rust/src/exemplar/registry.rs +166 -0
  552. data/rust/src/exemplar/types.rs +44 -0
  553. data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
  554. data/rust/src/kernel/attribute_shapes/list.rs +18 -0
  555. data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
  556. data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
  557. data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
  558. data/rust/src/kernel/cli.rs +585 -0
  559. data/rust/src/kernel/dispatch.rs +1090 -0
  560. data/rust/src/kernel/expr.rs +458 -0
  561. data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
  562. data/rust/src/kernel/expression_operators/comparison.rs +48 -0
  563. data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
  564. data/rust/src/kernel/expression_operators/logical.rs +13 -0
  565. data/rust/src/kernel/expression_operators/membership.rs +18 -0
  566. data/rust/src/kernel/expression_operators/mod.rs +68 -0
  567. data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
  568. data/rust/src/kernel/expression_operators/positional.rs +126 -0
  569. data/rust/src/kernel/expression_operators/presence.rs +101 -0
  570. data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
  571. data/rust/src/kernel/expression_operators/sized.rs +35 -0
  572. data/rust/src/kernel/expression_operators/text.rs +79 -0
  573. data/rust/src/kernel/expression_operators/to_string.rs +18 -0
  574. data/rust/src/kernel/json.rs +779 -0
  575. data/rust/src/kernel/mod.rs +133 -0
  576. data/rust/src/kernel/named_query.rs +219 -0
  577. data/rust/src/kernel/orchestrate.rs +1810 -0
  578. data/rust/src/kernel/pattern.rs +307 -0
  579. data/rust/src/kernel/query_comparators.rs +206 -0
  580. data/rust/src/kernel/query_ordering.rs +103 -0
  581. data/rust/src/kernel/read_model.rs +914 -0
  582. data/rust/src/kernel/reference_lookup.rs +149 -0
  583. data/rust/src/kernel/repository.rs +468 -0
  584. data/rust/src/kernel/routing.rs +612 -0
  585. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
  586. data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
  587. data/rust/src/kernel/vocab/field_hint.rs +70 -0
  588. data/rust/src/kernel/vocab/mod.rs +20 -0
  589. data/rust/src/kernel/vocab/query_comparator.rs +65 -0
  590. data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
  591. data/rust/src/lib.rs +8 -0
  592. data/rust/src/main.rs +16 -0
  593. data/rust/web/.gitignore +1 -0
  594. data/rust/web/Cargo.lock +119 -0
  595. data/rust/web/Cargo.toml +18 -0
  596. data/rust/web/src/lib.rs +8 -0
  597. metadata +477 -12
  598. data/lib/hecks/facade/cli_door.rb +0 -96
  599. data/lib/hecks/facade/cli_runner.rb +0 -127
  600. data/lib/hecks/mcp_door_scope.rb +0 -95
@@ -0,0 +1,383 @@
1
+ require "json"
2
+ require "fileutils"
3
+ require "open3"
4
+ require "tmpdir"
5
+
6
+ # Opt-in, all-Rust equivalent of hecks project_rust's default path, selected
7
+ # when HECKS_PARSER=rust and HECKS_CODEGEN=rust; never boots a bluebook DSL.
8
+ module RustProjectPipeline
9
+ ROOT = File.expand_path("..", __dir__).freeze
10
+ PARSER_DIR = File.join(ROOT, "rust/parser").freeze
11
+ CODEGEN_DIR = File.join(ROOT, "rust/codegen").freeze
12
+ PARSER_BIN = File.join(PARSER_DIR, "target/debug/hecks-parse").freeze
13
+ CODEGEN_BIN = File.join(CODEGEN_DIR, "target/debug/hecks-codegen").freeze
14
+
15
+ module_function
16
+
17
+ def call(domain)
18
+ ensure_binaries_built!
19
+
20
+ target_mod_name = File.basename(domain)
21
+ # Becomes a directory name, Rust module identifier, and Cargo feature
22
+ # name below (see Projector.valid_domain_mod_name?).
23
+ unless RustProjection::Projector.valid_domain_mod_name?(target_mod_name)
24
+ abort "hecks project_rust (Rust path): domain name #{target_mod_name.inspect} (from #{domain.inspect}) can't be " \
25
+ "used as-is — it has to double as a Rust module identifier and a Cargo feature name, and this one is " \
26
+ "either not a plain lowercase identifier, is a Rust keyword, or collides with a reserved Cargo.toml " \
27
+ "key (#{RustProjection::Projector::CARGO_RESERVED_DOMAIN_NAMES.join(', ')}). Rename the domain directory."
28
+ end
29
+ bluebook_paths = Dir.glob(File.join(domain, "bluebook", "*.bluebook")).sort
30
+ abort "hecks project_rust (Rust path): #{domain} has no .bluebook files" if bluebook_paths.empty?
31
+ hecksagon_path = File.join(domain, "bluebook", "#{target_mod_name}.hecksagon")
32
+ hecksagon_path = nil unless File.exist?(hecksagon_path)
33
+
34
+ target_chapter_name = header_chapter_name(bluebook_paths.first)
35
+ target_bluebooks = bluebook_paths.select { |path| header_chapter_name(path) == target_chapter_name }
36
+
37
+ uses_framework_names, uses_embryonaut_bluebook_names =
38
+ if hecksagon_path
39
+ resolved = JSON.parse(run_capture!(PARSER_BIN, "resolve", "--chapter", target_chapter_name, hecksagon_path))
40
+ [resolved.fetch("uses_framework"), resolved.fetch("uses_embryonaut_bluebook")]
41
+ else
42
+ [[], []]
43
+ end
44
+
45
+ target_files = target_bluebooks + [hecksagon_path].compact
46
+ target_ir_text = derive_append_optionals(run_capture!(PARSER_BIN, "chapter", "--chapter", target_chapter_name, *target_files))
47
+ # Only the target gets a lineage sidecar — matches the default path,
48
+ # which never sets one on a framework chapter either.
49
+ target_ir_text = derive_lineage(target_ir_text, hecksagon_path, sibling_world_path(domain, target_mod_name))
50
+
51
+ # Mirrors hecks project_rust's own Framework.load! resolution. A framework
52
+ # member has no .hecksagon of its own, only its .bluebook.
53
+ chapters = uses_framework_names.map do |fw_name|
54
+ fw_path = Hecks::Framework.members.fetch(fw_name) do
55
+ abort "hecks project_rust (Rust path): uses_framework #{fw_name.inspect} names no known framework member — known: #{Hecks::Framework.members.keys.sort.join(', ')}"
56
+ end
57
+ fw_chapter_name = header_chapter_name(fw_path)
58
+ unless fw_chapter_name == fw_name
59
+ abort "hecks project_rust (Rust path): #{fw_path} declares chapter #{fw_chapter_name.inspect}, but uses_framework named #{fw_name.inspect}"
60
+ end
61
+ fw_ir_text = derive_append_optionals(run_capture!(PARSER_BIN, "chapter", "--chapter", fw_chapter_name, fw_path))
62
+ {
63
+ mod_name: fw_name.downcase,
64
+ source_label: "#{domain} (uses_framework #{fw_name.inspect})",
65
+ ir_text: fw_ir_text
66
+ }
67
+ end
68
+
69
+ # Mirrors EmbryonautBluebook.load!: every .bluebook file directly under
70
+ # vendor/embryonaut_bluebooks/<name>/bluebook/, sorted. A vendored
71
+ # package has no .hecksagon of its own either, just its .bluebook(s).
72
+ chapters += uses_embryonaut_bluebook_names.map do |pkg_name|
73
+ pkg_dir = File.join(domain, "vendor", "embryonaut_bluebooks", pkg_name.to_s, "bluebook")
74
+ pkg_files = Dir.glob(File.join(pkg_dir, "*.bluebook")).sort
75
+ if pkg_files.empty?
76
+ abort "hecks project_rust (Rust path): uses_embryonaut_bluebook #{pkg_name.inspect} names no vendored " \
77
+ "bluebook at #{pkg_dir} — run hecks vendor #{pkg_name}"
78
+ end
79
+ pkg_chapter_name = header_chapter_name(pkg_files.first)
80
+ expected_chapter_name = Hecks::Naming.pascal(pkg_name.to_s)
81
+ unless pkg_chapter_name == expected_chapter_name
82
+ abort "hecks project_rust (Rust path): #{pkg_files.first} declares chapter #{pkg_chapter_name.inspect}, but " \
83
+ "uses_embryonaut_bluebook #{pkg_name.inspect} expects #{expected_chapter_name.inspect}"
84
+ end
85
+ pkg_bluebooks = pkg_files.select { |path| header_chapter_name(path) == pkg_chapter_name }
86
+ pkg_ir_text = derive_append_optionals(run_capture!(PARSER_BIN, "chapter", "--chapter", pkg_chapter_name, *pkg_bluebooks))
87
+ {
88
+ # Derived from the declared chapter name, not pkg_name.downcase
89
+ # directly — the two diverge once a package name has an underscore.
90
+ mod_name: expected_chapter_name.downcase,
91
+ source_label: "#{domain} (uses_embryonaut_bluebook #{pkg_name.inspect})",
92
+ ir_text: pkg_ir_text
93
+ }
94
+ end
95
+
96
+ # Framework- and embryonaut_bluebook-derived chapters can collide on
97
+ # mod_name (chapters here is a plain Array, not a Hash), so guard
98
+ # explicitly against two entries overwriting each other's sidecars.
99
+ chapters.group_by { |c| c[:mod_name] }.each_value do |group|
100
+ next if group.size == 1
101
+
102
+ abort "hecks project_rust (Rust path): #{group.map { |c| c[:source_label] }.join(' and ')} both resolve to the " \
103
+ "same generated module #{group.first[:mod_name].inspect} — attach #{target_chapter_name} to at most one " \
104
+ "of them"
105
+ end
106
+
107
+ # The self-hosted grammar's own "Bluebook" chapter, read without
108
+ # booting anything.
109
+ meta_files = Hecks::Bluebook::MetaValidator::GRAMMAR_FILES
110
+ meta_ir_text = derive_append_optionals(run_capture!(PARSER_BIN, "chapter", "--chapter", "Bluebook", *meta_files))
111
+
112
+ # `HECKS_RUST_DIR` picks the crate to project into, as it does for hecks project_rust's
113
+ # Ruby path; unset, it is this repository's own `rust/`.
114
+ rust_dir = ENV.fetch("HECKS_RUST_DIR", File.expand_path("../rust", __dir__))
115
+ out_root = File.join(rust_dir, "src/generated")
116
+ FileUtils.mkdir_p(out_root)
117
+ # Only this run's own directories are cleared — other domains coexist
118
+ # on disk.
119
+ FileUtils.rm_rf(File.join(out_root, "meta"))
120
+ FileUtils.rm_rf(File.join(out_root, "active"))
121
+ FileUtils.rm_rf(File.join(out_root, target_mod_name))
122
+ chapters.each { |c| FileUtils.rm_rf(File.join(out_root, c[:mod_name])) }
123
+
124
+ Dir.mktmpdir("hecks-codegen-full") do |tmp|
125
+ meta_ir_path = File.join(tmp, "meta_ir.json")
126
+ File.write(meta_ir_path, meta_ir_text)
127
+ meta_source_label = "the self-hosted language (lib/hecks/language/bluebook)"
128
+ run!(CODEGEN_BIN, "full", out_root, "meta", meta_source_label, meta_ir_path)
129
+ write_sidecars!(File.join(out_root, "meta"), meta_ir_text, meta_source_label)
130
+
131
+ target_ir_path = File.join(tmp, "target_ir.json")
132
+ File.write(target_ir_path, target_ir_text)
133
+
134
+ codegen_args = [out_root, target_mod_name, domain, target_ir_path]
135
+ chapters.each_with_index do |c, i|
136
+ ir_path = File.join(tmp, "chapter_#{i}_ir.json")
137
+ File.write(ir_path, c[:ir_text])
138
+ codegen_args.concat([c[:mod_name], c[:source_label], ir_path])
139
+ end
140
+ run!(CODEGEN_BIN, "full", *codegen_args)
141
+ end
142
+
143
+ write_sidecars!(File.join(out_root, target_mod_name), target_ir_text, domain)
144
+ chapters.each { |c| write_sidecars!(File.join(out_root, c[:mod_name]), c[:ir_text], c[:source_label]) }
145
+
146
+ sync_mod_and_cargo!(out_root, target_mod_name)
147
+ end
148
+
149
+ # Applies the same append-optional-field derivation domain_generator.rb
150
+ # applies in Ruby — hecks-parse's own ir.json has not had it applied yet.
151
+ def derive_append_optionals(ir_text)
152
+ ir = JSON.parse(ir_text, symbolize_names: true)
153
+ ir[:aggregates].each do |aggregate|
154
+ value_objects_by_name = aggregate[:value_objects].to_h { |vo| [vo[:name], vo] }
155
+ RustProjection::Projector.mark_append_optional_fields!(aggregate, value_objects_by_name)
156
+ end
157
+ JSON.pretty_generate(ir)
158
+ end
159
+
160
+ # Text-based equivalent of Exporter.lineage — no Runtime::Registry boot.
161
+ def derive_lineage(ir_text, hecksagon_path, world_path = nil)
162
+ ir = JSON.parse(ir_text, symbolize_names: true)
163
+ binds = hecksagon_path ? persistence_binds(hecksagon_path) : {}
164
+ fallback = (world_path && default_adapter_name(world_path, ir[:name])) || "Memory"
165
+ capable_adapters = lineage_capable_adapter_names
166
+
167
+ capable = ir[:aggregates].select do |aggregate|
168
+ adapter_name = binds.fetch(aggregate[:name], fallback)
169
+ capable_adapters.include?(adapter_name)
170
+ end
171
+
172
+ ir[:lineage] = {
173
+ capable_aggregates: capable.map { |aggregate| { name: aggregate[:name], storage_name: Hecks::Naming.snake(aggregate[:name]) } }
174
+ }
175
+ JSON.pretty_generate(ir)
176
+ end
177
+
178
+ # Matches only role-less persisted_by binds — the sole authoritative kind.
179
+ def persistence_binds(hecksagon_path)
180
+ text = File.read(hecksagon_path)
181
+ binds = {}
182
+ text.each_line do |line|
183
+ next if line =~ /\brole:/
184
+ next unless line =~ /(?:\w+(?:::\w+)*::)?(\w+)\.persisted_by\(\s*"([^"]+)"/
185
+
186
+ binds[Regexp.last_match(1)] = Regexp.last_match(2)
187
+ end
188
+ binds
189
+ end
190
+
191
+ # The target's own `.world` file, if it has one — named for the domain the
192
+ # same way its `.hecksagon` is.
193
+ def sibling_world_path(domain, target_mod_name)
194
+ path = File.join(domain, "bluebook", "#{target_mod_name}.world")
195
+ path if File.exist?(path)
196
+ end
197
+
198
+ # A .world file may hold several worlds (one per chapter) — only lines
199
+ # inside the target chapter's own Hecks.world block count, and the last
200
+ # default_adapter there wins, matching the Ruby builder.
201
+ def default_adapter_name(world_path, chapter)
202
+ in_target_world = false
203
+ found = nil
204
+ File.foreach(world_path) do |line|
205
+ if (opened = line[/\AHecks\.world[^"]*"([^"]*)"/, 1])
206
+ in_target_world = opened == chapter
207
+ elsif in_target_world && (name = line[/\A\s*default_adapter\s+"([^"]*)"/, 1])
208
+ found = name
209
+ end
210
+ end
211
+ found
212
+ end
213
+
214
+ # Read off adapter source directly, so a new lineage-capable adapter
215
+ # needs no change here.
216
+ def lineage_capable_adapter_names
217
+ Dir[File.join(ROOT, "lib/hecks/adapters/driven/*.rb")].filter_map do |path|
218
+ text = File.read(path)
219
+ next unless text.match?(/lineage_capable\?\s*=\s*true/)
220
+
221
+ text[/^\s*class\s+(\w+)/, 1]
222
+ end.compact
223
+ end
224
+
225
+ # The declared chapter name off a .bluebook file's own header line.
226
+ def header_chapter_name(path)
227
+ line = File.foreach(path).find { |l| l =~ /\A\s*Hecks\.bluebook\s+"([^"]+)"/ }
228
+ (line && Regexp.last_match(1)) or abort "hecks project_rust (Rust path): #{path} has no 'Hecks.bluebook \"Name\"' header"
229
+ end
230
+
231
+ # Writes ir.json byte-identical to hecks-parse's own output — never
232
+ # reparsed or reformatted, to avoid a non-byte-exact diff for no reason.
233
+ def write_sidecars!(mod_dir, ir_text, source_label)
234
+ FileUtils.mkdir_p(mod_dir)
235
+
236
+ ir_json_path = File.join(mod_dir, "ir.json")
237
+ File.write(ir_json_path, ir_text)
238
+ puts "wrote #{ir_json_path}"
239
+
240
+ metadata_path = File.join(mod_dir, "metadata.rs")
241
+ File.open(metadata_path, "w") do |f|
242
+ f.puts "// GENERATED by hecks project_rust — #{source_label}'s own canonical IR,"
243
+ f.puts "// embedded for runtime self-description. Not read by any dispatch"
244
+ f.puts "// function in this module — introspection only."
245
+ f.puts "pub const IR_JSON: &str = #{RustProjection::Projector.rust_string_literal(ir_text)};"
246
+ end
247
+ puts "wrote #{metadata_path}"
248
+ end
249
+
250
+ def ensure_binaries_built!
251
+ build!(PARSER_DIR)
252
+ build!(CODEGEN_DIR)
253
+ end
254
+
255
+ def build!(crate_dir)
256
+ ok = system("cargo", "build", chdir: crate_dir, out: $stdout, err: $stderr)
257
+ ok or abort "hecks project_rust (Rust path): cargo build failed in #{crate_dir} — run it there directly to see why"
258
+ end
259
+
260
+ def run_capture!(*cmd)
261
+ out, err, status = Open3.capture3(*cmd)
262
+ status.success? or abort "hecks project_rust (Rust path): #{cmd.join(' ')} failed:\n#{err}"
263
+ out
264
+ end
265
+
266
+ def run!(*cmd)
267
+ ok = system(*cmd)
268
+ ok or abort "hecks project_rust (Rust path): #{cmd.join(' ')} failed"
269
+ end
270
+
271
+ # Duplicates hecks project_rust's own default-path tail (root mod.rs +
272
+ # Cargo.toml feature sync) rather than extracting a shared method, so
273
+ # the default path's own body stays byte-for-byte untouched.
274
+ def sync_mod_and_cargo!(out_root, target_mod_name)
275
+ all_dirs = Dir.children(out_root).select { |name| File.directory?(File.join(out_root, name)) }.sort
276
+ domains = all_dirs.select { |name| File.exist?(File.join(out_root, name, "merged.rs")) }
277
+
278
+ root_mod_path = File.join(out_root, "mod.rs")
279
+ File.open(root_mod_path, "w") do |f|
280
+ f.puts "// GENERATED by hecks project_rust — re-run it to refresh this list."
281
+ f.puts "//"
282
+ f.puts "// BUG#25 — every DOMAIN (an entry in `domains`, below: a directory"
283
+ f.puts "// carrying its own merged.rs and therefore its own Cargo feature) is"
284
+ f.puts "// declared behind that SAME feature, so a broken or absent domain's"
285
+ f.puts "// generated code is never even compiled into a build that selects a"
286
+ f.puts "// DIFFERENT domain — `cargo build --features waybill` used to pull in"
287
+ f.puts "// every OTHER domain's `pub mod` unconditionally, so one domain's own"
288
+ f.puts "// non-compiling generated code (a `has_many` field, before this fix)"
289
+ f.puts "// broke every feature's build, not just its own. A shared FRAMEWORK"
290
+ f.puts "// chapter (governance/identity — no merged.rs of its own, so absent"
291
+ f.puts "// from `domains`) stays unconditional: it has no single feature of its"
292
+ f.puts "// own to gate behind, and is compiled in by whichever domain(s) attach"
293
+ f.puts "// it via `uses_framework`."
294
+ all_dirs.each do |name|
295
+ f.puts "#[cfg(feature = #{name.inspect})]" if domains.include?(name)
296
+ f.puts "pub mod #{name};"
297
+ end
298
+ f.puts
299
+ f.puts "// `active` is whichever ONE domain's own merged Store/dispatch table"
300
+ f.puts "// is selected by Cargo feature (rust/Cargo.toml, kept in sync with this"
301
+ f.puts "// list by hecks project_rust) — kernel/cli.rs imports"
302
+ f.puts "// `crate::generated::active::{dispatch_by_name, Store, ...}` unchanged"
303
+ f.puts "// no matter which domain that resolves to."
304
+ f.puts "//"
305
+ f.puts "// DOMAIN FEATURES ARE MUTUALLY EXCLUSIVE (R5) — Cargo has no native"
306
+ f.puts "// concept of that, and `default = [#{target_mod_name.inspect}]` in"
307
+ f.puts "// Cargo.toml stays enabled unless a build passes"
308
+ f.puts "// `--no-default-features`, so a plain `cargo build --features banking`"
309
+ f.puts "// used to enable BOTH the default domain's feature and banking's,"
310
+ f.puts "// producing two `pub use ... as active;` imports and a hard E0252."
311
+ f.puts "// Fixed two ways below, entirely mechanically from `domains`/"
312
+ f.puts "// `target_mod_name` (this run's new default) — never hand-maintained:"
313
+ f.puts "//"
314
+ f.puts "// 1. THE DEFAULT DOMAIN'S OWN re-export backs off whenever any OTHER"
315
+ f.puts "// domain feature is also enabled, so it never contends for `active`"
316
+ f.puts "// by accident — `cargo build --features banking` (still implicitly"
317
+ f.puts "// carrying the default feature) now resolves to banking alone,"
318
+ f.puts "// same as if `--no-default-features` had been passed explicitly."
319
+ f.puts "// 2. Two NON-default domain features enabled TOGETHER (a genuine,"
320
+ f.puts "// not-accidental conflict — nothing makes that combination"
321
+ f.puts "// meaningful) still fail the build, but with an explicit"
322
+ f.puts "// `compile_error!` naming both features instead of a cryptic"
323
+ f.puts "// \"defined multiple times\"."
324
+ domains.each do |name|
325
+ other_domains = domains - [name]
326
+ if name == target_mod_name && !other_domains.empty?
327
+ guard = other_domains.map { |o| "feature = #{o.inspect}" }.join(", ")
328
+ f.puts "#[cfg(all(feature = #{name.inspect}, not(any(#{guard}))))]"
329
+ else
330
+ f.puts "#[cfg(feature = #{name.inspect})]"
331
+ end
332
+ f.puts "pub use #{name}::merged as active;"
333
+ end
334
+ non_default_domains = domains - [target_mod_name]
335
+ if non_default_domains.size > 1
336
+ f.puts
337
+ f.puts "// Explicit conflict guard — see point 2 above. Only fires when TWO"
338
+ f.puts "// non-default domain features are both turned on; the default"
339
+ f.puts "// domain's own feature never reaches here (point 1 already excludes"
340
+ f.puts "// it whenever any other domain feature is present)."
341
+ non_default_domains.combination(2).each do |a, b|
342
+ f.puts "#[cfg(all(feature = #{a.inspect}, feature = #{b.inspect}))]"
343
+ f.puts "compile_error!(\"domain features are mutually exclusive — enable only one of: #{domains.join(', ')} (both #{a} and #{b} are enabled)\");"
344
+ end
345
+ end
346
+ end
347
+ puts "wrote #{root_mod_path}"
348
+
349
+ # The crate's own Cargo.toml, two levels above src/generated — the one `HECKS_RUST_DIR` chose.
350
+ cargo_toml_path = File.expand_path("../../Cargo.toml", out_root)
351
+ cargo_toml = File.read(cargo_toml_path)
352
+ unless cargo_toml.include?("[features]")
353
+ cargo_toml = cargo_toml.sub(/\A/, <<~TOML)
354
+ # ONE FEATURE PER GENERATED DOMAIN — selects which domain's own
355
+ # generated::<domain>::merged module `generated::active` re-exports
356
+ # (rust/src/generated/mod.rs). Kept in sync by hecks project_rust: a
357
+ # feature is added here whenever a new domain is generated, never
358
+ # removed (the domain's own generated/<name>/ directory is the
359
+ # source of truth for whether it still exists). `default` tracks
360
+ # whichever domain was generated MOST RECENTLY, so a bare
361
+ # `cargo build`/`cargo test` keeps behaving exactly as it always
362
+ # has; any other still-generated domain stays reachable via
363
+ # `--no-default-features --features <name>`.
364
+ [features]
365
+ default = []
366
+
367
+ TOML
368
+ end
369
+ # Scoped to the [features] table only — checking against the whole
370
+ # file false-positives against [package]/[lib] keys sharing a word
371
+ # with a domain name (version, edition, name, path, ...).
372
+ features_table = cargo_toml[/^\[features\](?:\n(?!\[).*)*$/] || ""
373
+ domains.each do |name|
374
+ next if features_table =~ /^#{Regexp.escape(name)}\s*=/
375
+
376
+ cargo_toml = cargo_toml.sub(/^default\s*=.*$/) { "#{Regexp.last_match(0)}\n#{name} = []" }
377
+ features_table = cargo_toml[/^\[features\](?:\n(?!\[).*)*$/] || ""
378
+ end
379
+ cargo_toml = cargo_toml.sub(/^default\s*=.*$/, "default = [#{target_mod_name.inspect}]")
380
+ File.write(cargo_toml_path, cargo_toml)
381
+ puts "wrote #{cargo_toml_path} (default feature: #{target_mod_name})"
382
+ end
383
+ end
@@ -0,0 +1,279 @@
1
+ //! Exemplar shapes for rust/project/commands.rb (see mod.rs).
2
+ //!
3
+ //! `dispatch_fn` proves the outer `pub fn .. -> DispatchResult { .. dispatch(..) .. }` wrapper.
4
+ #![allow(dead_code, unused_variables)]
5
+
6
+ // Minimal element satisfying `dispatch_entity`'s bounds (`Fielded + Clone`).
7
+ #[derive(Debug, Clone, PartialEq)]
8
+ pub struct TmplElement {
9
+ tmpl_field: i64,
10
+ }
11
+ impl crate::kernel::Fielded for TmplElement {
12
+ fn field(&self, name: &str) -> Option<crate::kernel::Field<'_>> {
13
+ None
14
+ }
15
+ }
16
+ impl TmplElement {
17
+ fn identity(&self) -> String {
18
+ String::new()
19
+ }
20
+ fn extract_id(v: &crate::kernel::Json) -> Result<String, crate::kernel::Refusal> {
21
+ let _ = v;
22
+ Ok(String::new())
23
+ }
24
+ // Generated entities carry both `extract_id` and `extract_id_lenient`.
25
+ fn extract_id_lenient(v: &crate::kernel::Json) -> Result<String, crate::kernel::Refusal> {
26
+ let _ = v;
27
+ Ok(String::new())
28
+ }
29
+ fn extract_wants(v: &crate::kernel::Json) -> String {
30
+ let _ = v;
31
+ String::new()
32
+ }
33
+ }
34
+
35
+ #[derive(Debug, Clone, PartialEq)]
36
+ pub struct TmplTargetArgs {
37
+ tmpl_field: i64,
38
+ }
39
+ impl crate::kernel::Fielded for TmplTargetArgs {
40
+ fn field(&self, name: &str) -> Option<crate::kernel::Field<'_>> {
41
+ None
42
+ }
43
+ }
44
+ impl TmplTargetArgs {
45
+ fn to_json(&self) -> crate::kernel::Json {
46
+ crate::kernel::Json::Null
47
+ }
48
+ fn from_json(v: &crate::kernel::Json) -> Result<Self, crate::kernel::Refusal> {
49
+ let _ = v;
50
+ Ok(TmplTargetArgs { tmpl_field: 0 })
51
+ }
52
+ }
53
+
54
+ // Stand-in for the aggregate record a generated `dispatch_*` fn targets.
55
+ #[derive(Debug, Clone, PartialEq)]
56
+ pub struct TmplRecord {
57
+ tmpl_field: i64,
58
+ tmpl_list_field: Vec<TmplElement>,
59
+ }
60
+ impl crate::kernel::Fielded for TmplRecord {
61
+ fn field(&self, name: &str) -> Option<crate::kernel::Field<'_>> {
62
+ None
63
+ }
64
+ }
65
+ impl crate::kernel::ToJson for TmplRecord {
66
+ fn to_json(&self) -> crate::kernel::Json {
67
+ crate::kernel::Json::Null
68
+ }
69
+ }
70
+ impl crate::kernel::SetProjectedField for TmplRecord {
71
+ fn set_projected_field(&mut self, name: &'static str, value: Option<String>) {
72
+ let _ = (name, value);
73
+ }
74
+ }
75
+
76
+ #[derive(Debug, Clone, PartialEq)]
77
+ pub struct TmplArgs {
78
+ tmpl_field: i64,
79
+ }
80
+ impl crate::kernel::Fielded for TmplArgs {
81
+ fn field(&self, name: &str) -> Option<crate::kernel::Field<'_>> {
82
+ None
83
+ }
84
+ }
85
+ impl TmplArgs {
86
+ fn to_json(&self) -> crate::kernel::Json {
87
+ crate::kernel::Json::Null
88
+ }
89
+ }
90
+
91
+ fn tmpl_hydrate_placeholder() -> crate::kernel::Hydrate<'static, TmplRecord> {
92
+ crate::kernel::Hydrate::Act { id: String::new() }
93
+ }
94
+
95
+ fn tmpl_mutation_lines_placeholder(record: &mut TmplRecord) {}
96
+
97
+ fn tmpl_seed_projections_placeholder() -> Vec<(&'static str, Option<String>)> {
98
+ Vec::new()
99
+ }
100
+
101
+ // TMPL:dispatch_fn BEGIN
102
+ pub fn dispatch_tmpl(
103
+ repo: &mut impl crate::kernel::Repository<TmplRecord>, id: &str, args: TmplArgs, mutations: &mut Vec<crate::kernel::MutationRecord>, tenant_boundary_check: Result<(), crate::kernel::Refusal>,
104
+ ) -> crate::kernel::DispatchResult<TmplRecord> {
105
+ tmpl_invariant_check_placeholder()?;
106
+ let tmpl_eval_fielded = tmpl_with_references_placeholder();
107
+ let tmpl_seed_projections = tmpl_seed_projections_placeholder();
108
+ tmpl_prelude_placeholder();
109
+ crate::kernel::dispatch(
110
+ repo,
111
+ tmpl_hydrate_placeholder(),
112
+ "TmplCmdName",
113
+ "TmplQualifiedName",
114
+ "TmplAggregateName",
115
+ "TmplIdentityReading",
116
+ &tmpl_eval_fielded,
117
+ &[
118
+ tmpl_given_spec_placeholder(),
119
+ ],
120
+ tmpl_transition_placeholder(),
121
+ |record| {
122
+ tmpl_mutation_lines_placeholder(record);
123
+ Ok(())
124
+ },
125
+ &[
126
+ tmpl_ensures_spec_placeholder(),
127
+ ],
128
+ &tmpl_invariants_placeholder(),
129
+ &[tmpl_emit_placeholder()],
130
+ args.to_json(),
131
+ mutations,
132
+ tmpl_seed_projections,
133
+ tenant_boundary_check,
134
+ )
135
+ }
136
+ // TMPL:dispatch_fn END
137
+
138
+ fn tmpl_invariant_check_placeholder() -> Result<(), crate::kernel::Refusal> {
139
+ Ok(())
140
+ }
141
+ // Empty for ordinary commands; a delegating command substitutes `delegate_prelude`.
142
+ fn tmpl_prelude_placeholder() {}
143
+ // Plain `TmplArgs` stands in for `kernel::WithReferences` so this exemplar compiles alone.
144
+ fn tmpl_with_references_placeholder() -> TmplArgs {
145
+ TmplArgs { tmpl_field: 0 }
146
+ }
147
+ fn tmpl_given_spec_placeholder() -> crate::kernel::GivenSpec {
148
+ crate::kernel::GivenSpec { description: "", expr: crate::kernel::Expr::Bool(true), corrects_event: None }
149
+ }
150
+ fn tmpl_transition_placeholder() -> Option<crate::kernel::TransitionCheck> {
151
+ None
152
+ }
153
+ fn tmpl_ensures_spec_placeholder() -> crate::kernel::EnsuresSpec {
154
+ crate::kernel::EnsuresSpec { description: "", expr: crate::kernel::Expr::Bool(true) }
155
+ }
156
+ fn tmpl_invariants_placeholder() -> crate::kernel::InvariantSet {
157
+ crate::kernel::InvariantSet { aggregate: vec![], entities: vec![] }
158
+ }
159
+ fn tmpl_emit_placeholder() -> &'static str {
160
+ ""
161
+ }
162
+
163
+ // `delegates_to "Entity.Command"`: the door runs `apply_entity_command` inside its closure.
164
+ // `delegate_prelude` fills the prelude slot; `delegate_apply` fills the mutation slot.
165
+ // The inner `|record|` deliberately shadows the door's: entity `sets` only reach the element.
166
+ fn tmpl_aliases_placeholder() -> (&'static str, &'static str) {
167
+ ("", "")
168
+ }
169
+
170
+ fn tmpl_delegate_prelude_host(
171
+ args: TmplArgs,
172
+ command_deref: Vec<(&'static str, crate::kernel::DerefNode)>,
173
+ owner_deref: Vec<(&'static str, crate::kernel::DerefNode)>,
174
+ ) -> Result<(), crate::kernel::Refusal> {
175
+ // TMPL:delegate_prelude BEGIN
176
+ let delegate_facts = args.to_json().with_aliases(&[tmpl_aliases_placeholder()]);
177
+ let target_args = TmplTargetArgs::from_json(&delegate_facts)?;
178
+ let target_with_references = crate::kernel::WithReferences { command_deref: &command_deref, args: &target_args, owner_deref: &owner_deref };
179
+ // TMPL:delegate_prelude END
180
+ let _ = target_with_references;
181
+ Ok(())
182
+ }
183
+
184
+ // `element_id` extraction must run after `dispatch`'s hydrate, so it is spliced into the
185
+ // mutation closure rather than the prelude. It uses `extract_id_lenient` because a blank mapped
186
+ // identity is a non-match, not a refusal.
187
+ fn tmpl_delegate_element_host(delegate_facts: crate::kernel::Json) -> Result<(), crate::kernel::Refusal> {
188
+ // TMPL:delegate_element BEGIN
189
+ let element_id = TmplElement::extract_id_lenient(&delegate_facts)?;
190
+ let element_wants = TmplElement::extract_wants(&delegate_facts);
191
+ // TMPL:delegate_element END
192
+ let _ = (element_id, element_wants);
193
+ Ok(())
194
+ }
195
+
196
+ fn tmpl_delegate_apply_host(
197
+ record: &mut TmplRecord,
198
+ id: &str,
199
+ element_id: String,
200
+ element_wants: String,
201
+ target_with_references: TmplArgs,
202
+ ) -> Result<(), crate::kernel::Refusal> {
203
+ // TMPL:delegate_apply BEGIN
204
+ crate::kernel::apply_entity_command(
205
+ record,
206
+ id,
207
+ |r: &TmplRecord| &r.tmpl_list_field,
208
+ |r: &mut TmplRecord| &mut r.tmpl_list_field,
209
+ |el: &TmplElement| el.identity() == element_id,
210
+ "TmplQualifiedCommandName",
211
+ "TmplQualifiedName",
212
+ "TmplAggregateName",
213
+ "TmplEntityName",
214
+ "TmplEntityIdentityReading",
215
+ &element_wants,
216
+ &target_with_references,
217
+ &[
218
+ tmpl_given_spec_placeholder(),
219
+ ],
220
+ tmpl_transition_placeholder(),
221
+ |record| {
222
+ tmpl_entity_mutation_lines_placeholder(record);
223
+ Ok(())
224
+ },
225
+ &[
226
+ tmpl_ensures_spec_placeholder(),
227
+ ],
228
+ true,
229
+ )?;
230
+ // TMPL:delegate_apply END
231
+ Ok(())
232
+ }
233
+
234
+ // `entity_dispatch_fn`: `dispatch_fn`'s sibling wrapping `kernel::dispatch_entity`, which
235
+ // addresses one element by `identity()` and never creates.
236
+ fn tmpl_entity_mutation_lines_placeholder(record: &mut TmplElement) {}
237
+
238
+ // TMPL:entity_dispatch_fn BEGIN
239
+ pub fn dispatch_entity_tmpl(
240
+ repo: &mut impl crate::kernel::Repository<TmplRecord>, parent_id: &str, element_id: &str, element_wants: &str, args: TmplArgs,
241
+ mutations: &mut Vec<crate::kernel::MutationRecord>, tmpl_deref_params_placeholder: (),
242
+ ) -> crate::kernel::DispatchResult<TmplRecord> {
243
+ tmpl_invariant_check_placeholder()?;
244
+ let tmpl_eval_fielded = tmpl_with_references_placeholder();
245
+ let tmpl_seed_projections = tmpl_seed_projections_placeholder();
246
+
247
+ crate::kernel::dispatch_entity(
248
+ repo,
249
+ parent_id,
250
+ |r: &TmplRecord| &r.tmpl_list_field,
251
+ |r: &mut TmplRecord| &mut r.tmpl_list_field,
252
+ |el: &TmplElement| el.identity() == element_id,
253
+ "TmplQualifiedCommandName",
254
+ "TmplQualifiedName",
255
+ "TmplAggregateName",
256
+ "TmplParentIdentityReading",
257
+ "TmplEntityName",
258
+ "TmplEntityIdentityReading",
259
+ element_wants,
260
+ &tmpl_eval_fielded,
261
+ &[
262
+ tmpl_given_spec_placeholder(),
263
+ ],
264
+ tmpl_transition_placeholder(),
265
+ |record| {
266
+ tmpl_entity_mutation_lines_placeholder(record);
267
+ Ok(())
268
+ },
269
+ &[
270
+ tmpl_ensures_spec_placeholder(),
271
+ ],
272
+ &tmpl_invariants_placeholder(),
273
+ &[tmpl_emit_placeholder()],
274
+ args.to_json(),
275
+ mutations,
276
+ tmpl_seed_projections,
277
+ )
278
+ }
279
+ // TMPL:entity_dispatch_fn END
@@ -0,0 +1,18 @@
1
+ //! Exemplar shapes for rust/project/constraints.rb (see mod.rs).
2
+ //!
3
+ //! Both shapes are single-line refusal checks spliced into a `Result`-returning body.
4
+ #![allow(dead_code, unused_variables)]
5
+
6
+ fn tmpl_admits_check_host(tmpl_scalar: String) -> Result<(), crate::kernel::Refusal> {
7
+ // TMPL:admits_check BEGIN
8
+ if !["tmpl_member_a", "tmpl_member_b"].contains(&tmpl_scalar.as_str()) { return Err(crate::kernel::Refusal::InvariantViolation(crate::kernel::refusal_wording::InvariantViolationAdmitsDeclaredSetArgs { name: "tmpl_admits_name", admits: "tmpl_admits_target", admitted: &["tmpl_member_a", "tmpl_member_b"], offered: format!("{:?}", tmpl_scalar).as_str() }.render_args())); }
9
+ // TMPL:admits_check END
10
+ Ok(())
11
+ }
12
+
13
+ fn tmpl_pattern_check_host(tmpl_scalar: String) -> Result<(), crate::kernel::Refusal> {
14
+ // TMPL:pattern_check BEGIN
15
+ if !crate::kernel::pattern::matches("tmpl_pattern_text", &tmpl_scalar) { return Err(crate::kernel::Refusal::TypeMismatch(crate::kernel::refusal_wording::TypeMismatchPatternMismatchArgs { r#type: "tmpl_pattern_owner", field: "tmpl_pattern_field", pattern: "tmpl_pattern_text", offered: format!("{:?}", tmpl_scalar).as_str() }.render_args())); }
16
+ // TMPL:pattern_check END
17
+ Ok(())
18
+ }