hecks 2.8.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (886) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +50 -7
  3. data/lib/hecks/adapters/driven/claude_code.rb +4 -104
  4. data/lib/hecks/adapters/driven/d1.rb +44 -237
  5. data/lib/hecks/adapters/driven/folder.rb +17 -76
  6. data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
  7. data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
  8. data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
  9. data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
  10. data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
  11. data/lib/hecks/adapters/driven/heki.rb +13 -42
  12. data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
  15. data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
  16. data/lib/hecks/adapters/driven/lambda.rb +10 -84
  17. data/lib/hecks/adapters/driven/local_storage.rb +5 -40
  18. data/lib/hecks/adapters/driven/memory.rb +32 -100
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +10 -32
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
  24. data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
  25. data/lib/hecks/adapters/driven/postgres.rb +149 -136
  26. data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
  27. data/lib/hecks/adapters/driven/prism.rb +48 -38
  28. data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
  29. data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
  30. data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
  31. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
  32. data/lib/hecks/adapters/driven/sqlite.rb +128 -65
  33. data/lib/hecks/adapters/driven/tenant_provisioner.rb +76 -62
  34. data/lib/hecks/adapters/driven.rb +5 -36
  35. data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
  36. data/lib/hecks/behaviors/dsl.rb +7 -25
  37. data/lib/hecks/behaviors/expectations.rb +32 -142
  38. data/lib/hecks/behaviors/ir.rb +7 -20
  39. data/lib/hecks/behaviors/rspec.rb +6 -17
  40. data/lib/hecks/behaviors/runner.rb +10 -34
  41. data/lib/hecks/behaviors.rb +4 -8
  42. data/lib/hecks/bench/cli.rb +62 -0
  43. data/lib/hecks/bench/environment.rb +71 -0
  44. data/lib/hecks/bench/postgres_probe.rb +30 -0
  45. data/lib/hecks/bench/report.rb +76 -0
  46. data/lib/hecks/bench/ruby_runner.rb +93 -0
  47. data/lib/hecks/bench/run.rb +55 -0
  48. data/lib/hecks/bench/rust_runner.rb +101 -0
  49. data/lib/hecks/bench/stats.rb +46 -0
  50. data/lib/hecks/bench/suite.rb +98 -0
  51. data/lib/hecks/bench/workload.rb +89 -0
  52. data/lib/hecks/bench.rb +17 -0
  53. data/lib/hecks/bluebook/aggregate.rb +9 -75
  54. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
  55. data/lib/hecks/bluebook/assembly/build.rb +4 -15
  56. data/lib/hecks/bluebook/assembly/contract.rb +15 -136
  57. data/lib/hecks/bluebook/assembly/contracts.rb +56 -174
  58. data/lib/hecks/bluebook/assembly/marks.rb +31 -223
  59. data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
  60. data/lib/hecks/bluebook/assembly.rb +6 -45
  61. data/lib/hecks/bluebook/attribute.rb +12 -61
  62. data/lib/hecks/bluebook/behaviour/aggregate.rb +39 -39
  63. data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
  64. data/lib/hecks/bluebook/behaviour/chapter.rb +14 -42
  65. data/lib/hecks/bluebook/behaviour/command.rb +14 -66
  66. data/lib/hecks/bluebook/behaviour/domain_port.rb +11 -20
  67. data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
  68. data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
  69. data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
  70. data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
  71. data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
  72. data/lib/hecks/bluebook/behaviour/read_model.rb +25 -31
  73. data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
  74. data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
  75. data/lib/hecks/bluebook/capabilities.rb +17 -49
  76. data/lib/hecks/bluebook/chapter.rb +16 -50
  77. data/lib/hecks/bluebook/command.rb +23 -81
  78. data/lib/hecks/bluebook/domain_port.rb +49 -67
  79. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
  80. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
  81. data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
  82. data/lib/hecks/bluebook/dsl/binding_proxy.rb +16 -69
  83. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +96 -708
  84. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +63 -169
  85. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +3 -1
  86. data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
  87. data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
  88. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +42 -89
  89. data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
  90. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
  91. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +148 -197
  92. data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
  93. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
  94. data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
  95. data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
  96. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
  97. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
  98. data/lib/hecks/bluebook/dsl/query_builder.rb +38 -43
  99. data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
  100. data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
  101. data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
  102. data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
  103. data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
  104. data/lib/hecks/bluebook/dsl/world_builder.rb +46 -111
  105. data/lib/hecks/bluebook/entity.rb +13 -71
  106. data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
  107. data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
  108. data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
  109. data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
  110. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
  111. data/lib/hecks/bluebook/expression/resolver.rb +39 -661
  112. data/lib/hecks/bluebook/hexagon.rb +18 -33
  113. data/lib/hecks/bluebook/lifecycle.rb +5 -16
  114. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
  115. data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
  116. data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
  117. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
  118. data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
  119. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +25 -176
  120. data/lib/hecks/bluebook/meta_validator/shapes.rb +53 -267
  121. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -306
  122. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
  123. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
  124. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
  125. data/lib/hecks/bluebook/meta_validator.rb +87 -352
  126. data/lib/hecks/bluebook/model_check/client_profile.rb +6 -21
  127. data/lib/hecks/bluebook/model_check.rb +122 -596
  128. data/lib/hecks/bluebook/pattern_subset.rb +49 -69
  129. data/lib/hecks/bluebook/process_manager.rb +13 -83
  130. data/lib/hecks/bluebook/project_loader.rb +3 -39
  131. data/lib/hecks/bluebook/project_register.rb +12 -60
  132. data/lib/hecks/bluebook/query.rb +27 -51
  133. data/lib/hecks/bluebook/read_model.rb +57 -81
  134. data/lib/hecks/bluebook/reference.rb +7 -41
  135. data/lib/hecks/bluebook/smoke_test.rb +9 -99
  136. data/lib/hecks/bluebook/synthesizer.rb +13 -62
  137. data/lib/hecks/bluebook/translation.rb +26 -62
  138. data/lib/hecks/bluebook/value_object.rb +11 -36
  139. data/lib/hecks/bluebook.rb +5 -22
  140. data/lib/hecks/cache_dir.rb +4 -23
  141. data/lib/hecks/canonical_json.rb +30 -0
  142. data/lib/hecks/chapters.rb +102 -0
  143. data/lib/hecks/cli/behaviors.rb +61 -0
  144. data/lib/hecks/cli/canonicalise.rb +25 -0
  145. data/lib/hecks/cli/check_engine_agreement.rb +32 -0
  146. data/lib/hecks/cli/check_era.rb +88 -0
  147. data/lib/hecks/cli/console.rb +66 -0
  148. data/lib/hecks/cli/deploy_template_diff.rb +40 -0
  149. data/lib/hecks/cli/doc_coverage.rb +32 -0
  150. data/lib/hecks/cli/document.rb +5 -23
  151. data/lib/hecks/cli/expression_projection.rb +41 -0
  152. data/lib/hecks/cli/follow.rb +88 -0
  153. data/lib/hecks/cli/history.rb +44 -0
  154. data/lib/hecks/cli/ir.rb +4 -6
  155. data/lib/hecks/cli/mcp.rb +5 -6
  156. data/lib/hecks/cli/model_check.rb +24 -88
  157. data/lib/hecks/cli/pattern_cases.rb +47 -0
  158. data/lib/hecks/cli/present.rb +39 -0
  159. data/lib/hecks/cli/project_cli.rb +223 -64
  160. data/lib/hecks/cli/project_diagrams.rb +4 -10
  161. data/lib/hecks/cli/project_glossary.rb +44 -0
  162. data/lib/hecks/cli/reference.rb +46 -0
  163. data/lib/hecks/cli/refresh_projections.rb +44 -0
  164. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
  165. data/lib/hecks/cli/release.rb +70 -0
  166. data/lib/hecks/cli/release_gem.rb +59 -0
  167. data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
  168. data/lib/hecks/cli/rspec_shard_files.rb +66 -0
  169. data/lib/hecks/cli/run.rb +15 -61
  170. data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
  171. data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
  172. data/lib/hecks/cli/shape.rb +70 -0
  173. data/lib/hecks/cli/smoke_http.rb +230 -0
  174. data/lib/hecks/cli/smoke_test.rb +7 -19
  175. data/lib/hecks/cli/spec_example.rb +27 -0
  176. data/lib/hecks/cli/statements.rb +32 -0
  177. data/lib/hecks/cli/stores.rb +4 -6
  178. data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
  179. data/lib/hecks/cli.rb +216 -73
  180. data/lib/hecks/codemod.rb +324 -0
  181. data/lib/hecks/construct.rb +8 -34
  182. data/lib/hecks/corpus.rb +423 -0
  183. data/lib/hecks/deploy/bluebook/deploy.bluebook +617 -158
  184. data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -11
  185. data/lib/hecks/deploy/oidc.json +82 -2
  186. data/lib/hecks/doc/reference.rb +351 -0
  187. data/lib/hecks/doors/cli_door.rb +196 -0
  188. data/lib/hecks/doors/cli_runner.rb +297 -0
  189. data/lib/hecks/{facade → doors}/command_request.rb +14 -34
  190. data/lib/hecks/doors/handle.rb +206 -0
  191. data/lib/hecks/doors/json_door.rb +132 -0
  192. data/lib/hecks/doors/launcher_options.rb +129 -0
  193. data/lib/hecks/{cli → doors}/mcp_door.rb +25 -120
  194. data/lib/hecks/doors/mcp_door_scope.rb +97 -0
  195. data/lib/hecks/doors/ruby_door/aggregate_door.rb +162 -0
  196. data/lib/hecks/doors/ruby_door/chapter.rb +72 -0
  197. data/lib/hecks/doors/ruby_door.rb +77 -0
  198. data/lib/hecks/doors.rb +61 -0
  199. data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
  200. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
  201. data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
  202. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
  203. data/lib/hecks/embryonaut_bluebook.rb +5 -72
  204. data/lib/hecks/engine_agreement.rb +156 -0
  205. data/lib/hecks/facade.rb +23 -51
  206. data/lib/hecks/forms/app.rb +21 -76
  207. data/lib/hecks/forms/banking_presentation.rb +50 -0
  208. data/lib/hecks/forms/command_form_renderer.rb +10 -29
  209. data/lib/hecks/forms/field_renderer.rb +17 -156
  210. data/lib/hecks/forms/field_shape.rb +31 -222
  211. data/lib/hecks/forms/html.rb +12 -47
  212. data/lib/hecks/forms/index_renderer.rb +1 -4
  213. data/lib/hecks/forms/page.rb +3 -9
  214. data/lib/hecks/forms/params.rb +14 -60
  215. data/lib/hecks/forms/port_argument.rb +2 -19
  216. data/lib/hecks/forms/query_form_renderer.rb +4 -79
  217. data/lib/hecks/forms/record_renderer.rb +8 -75
  218. data/lib/hecks/forms/record_table.rb +7 -50
  219. data/lib/hecks/forms/reference_options.rb +3 -30
  220. data/lib/hecks/forms/value_object_shape.rb +8 -35
  221. data/lib/hecks/forms.rb +5 -33
  222. data/lib/hecks/fqn.rb +5 -12
  223. data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
  224. data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
  225. data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
  226. data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
  227. data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
  228. data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
  229. data/lib/hecks/framework.rb +11 -80
  230. data/lib/hecks/freezer.rb +5 -39
  231. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
  232. data/lib/hecks/fuzzing/combination_miner.rb +86 -0
  233. data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
  234. data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
  235. data/lib/hecks/fuzzing/differential.rb +156 -0
  236. data/lib/hecks/fuzzing/domain_generator.rb +615 -0
  237. data/lib/hecks/fuzzing/era_boundary.rb +71 -0
  238. data/lib/hecks/fuzzing/form_census.rb +136 -0
  239. data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
  240. data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
  241. data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
  242. data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
  243. data/lib/hecks/fuzzing/outside_world.rb +45 -0
  244. data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
  245. data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
  246. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
  247. data/lib/hecks/fuzzing/properties/guards.rb +190 -0
  248. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
  249. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
  250. data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
  251. data/lib/hecks/fuzzing/properties/querying.rb +175 -0
  252. data/lib/hecks/fuzzing/properties.rb +162 -0
  253. data/lib/hecks/fuzzing/qa_settings.rb +123 -0
  254. data/lib/hecks/fuzzing/replay.rb +505 -0
  255. data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
  256. data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
  257. data/lib/hecks/fuzzing/self_consistency.rb +339 -0
  258. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
  259. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
  260. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
  261. data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
  262. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
  263. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
  264. data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
  265. data/lib/hecks/fuzzing/shrinker.rb +166 -0
  266. data/lib/hecks/fuzzing/structural_skips.rb +33 -0
  267. data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
  268. data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
  269. data/lib/hecks/fuzzing/value_generator.rb +216 -0
  270. data/lib/hecks/fuzzing.rb +23 -0
  271. data/lib/hecks/grammar/evolve.rb +336 -0
  272. data/lib/hecks/grammar/expression.bluebook +15 -40
  273. data/lib/hecks/grammar/translation.bluebook +3 -6
  274. data/lib/hecks/grammar.rb +16 -41
  275. data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
  276. data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
  277. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
  278. data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
  279. data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
  280. data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
  281. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
  282. data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
  283. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
  284. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
  285. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
  286. data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
  287. data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
  288. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
  289. data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
  290. data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
  291. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
  292. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
  293. data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
  294. data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
  295. data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
  296. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
  297. data/lib/hecks/hecks/adapters/git.adapter +7 -0
  298. data/lib/hecks/hecks/adapters/git.rb +102 -0
  299. data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
  300. data/lib/hecks/hecks/adapters/host_http.rb +65 -0
  301. data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
  302. data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
  303. data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
  304. data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
  305. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
  306. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
  307. data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
  308. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
  309. data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
  310. data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
  311. data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
  312. data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
  313. data/lib/hecks/hecks/adapters/local_files.rb +46 -0
  314. data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
  315. data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
  316. data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
  317. data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
  318. data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
  319. data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
  320. data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
  321. data/lib/hecks/hecks/adapters/shell.adapter +7 -0
  322. data/lib/hecks/hecks/adapters/shell.rb +49 -0
  323. data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
  324. data/lib/hecks/hecks/adapters/terminal.rb +74 -0
  325. data/lib/hecks/hecks/codebase.behaviors +172 -0
  326. data/lib/hecks/hecks/codebase.bluebook +2453 -0
  327. data/lib/hecks/hecks/context_map.hecksagon +13 -0
  328. data/lib/hecks/hecks/custodian.behaviors +179 -0
  329. data/lib/hecks/hecks/custodian.bluebook +1847 -0
  330. data/lib/hecks/hecks/deploy.behaviors +74 -0
  331. data/lib/hecks/hecks/deploy.world +12 -0
  332. data/lib/hecks/hecks/environments/memory.world +10 -0
  333. data/lib/hecks/hecks/governance.world +7 -0
  334. data/lib/hecks/hecks/hecks.behaviors +62 -0
  335. data/lib/hecks/hecks/hecks.bluebook +124 -0
  336. data/lib/hecks/hecks/hecks.hecksagon +390 -0
  337. data/lib/hecks/hecks/hecks.world +42 -0
  338. data/lib/hecks/ir.rb +14 -83
  339. data/lib/hecks/kernel_capabilities.rb +194 -0
  340. data/lib/hecks/language/adapter.bluebook +2 -7
  341. data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
  342. data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
  343. data/lib/hecks/language/bluebook/bluebook.bluebook +26 -83
  344. data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
  345. data/lib/hecks/language/bluebook/command.bluebook +27 -144
  346. data/lib/hecks/language/bluebook/entity.bluebook +24 -97
  347. data/lib/hecks/language/bluebook/policy.bluebook +16 -67
  348. data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
  349. data/lib/hecks/language/bluebook/projection.bluebook +126 -60
  350. data/lib/hecks/language/bluebook/query.bluebook +19 -43
  351. data/lib/hecks/language/bluebook/shape.bluebook +23 -73
  352. data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
  353. data/lib/hecks/language/bluebook/vocabulary.bluebook +83 -198
  354. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
  355. data/lib/hecks/language/hecksagon/domain_port.bluebook +14 -19
  356. data/lib/hecks/language/hecksagon/hecksagon.bluebook +26 -25
  357. data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
  358. data/lib/hecks/language/oidc.json +35 -0
  359. data/lib/hecks/language/port.bluebook +4 -17
  360. data/lib/hecks/language/translation/translation.bluebook +3 -9
  361. data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
  362. data/lib/hecks/language/world/wiring.bluebook +1 -3
  363. data/lib/hecks/language/world/world.bluebook +5 -7
  364. data/lib/hecks/literal.rb +8 -50
  365. data/lib/hecks/mcp_stdio_guard.rb +4 -41
  366. data/lib/hecks/naming.rb +29 -137
  367. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
  368. data/lib/hecks/persistence_legacy_fixture.rb +113 -0
  369. data/lib/hecks/ports/access_control.rb +7 -23
  370. data/lib/hecks/ports/agent/answers.rb +5 -23
  371. data/lib/hecks/ports/agent.rb +47 -172
  372. data/lib/hecks/ports/authentication.rb +12 -39
  373. data/lib/hecks/ports/authorization.rb +16 -43
  374. data/lib/hecks/ports/clock.rb +5 -45
  375. data/lib/hecks/ports/extraction.rb +7 -12
  376. data/lib/hecks/ports/identity_assignment.rb +8 -29
  377. data/lib/hecks/ports/identity_generation.rb +6 -29
  378. data/lib/hecks/ports/identity_resolution.rb +2 -10
  379. data/lib/hecks/ports/key_vault.rb +9 -45
  380. data/lib/hecks/ports/persistence/append_only.rb +61 -64
  381. data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
  382. data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
  383. data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
  384. data/lib/hecks/ports/persistence/plugin.rb +13 -42
  385. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +44 -144
  386. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
  387. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +20 -85
  388. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +6 -25
  389. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +18 -24
  390. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
  391. data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
  392. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
  393. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
  394. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
  395. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
  396. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
  397. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
  398. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +19 -76
  399. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
  400. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
  401. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
  402. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
  403. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
  404. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +43 -127
  405. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
  406. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +131 -555
  407. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
  408. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
  409. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
  410. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
  411. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
  412. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
  413. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
  414. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +50 -47
  415. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -124
  416. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
  417. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
  418. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
  419. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
  420. data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
  421. data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
  422. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
  423. data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
  424. data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
  425. data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
  426. data/lib/hecks/ports/persistence/state_codec.rb +47 -108
  427. data/lib/hecks/ports/persistence.rb +7 -14
  428. data/lib/hecks/ports/projection.rb +42 -84
  429. data/lib/hecks/ports/query/in_memory.rb +10 -58
  430. data/lib/hecks/ports/query/ordering.rb +11 -32
  431. data/lib/hecks/ports/query.rb +14 -37
  432. data/lib/hecks/ports.rb +2 -8
  433. data/lib/hecks/projection_files.rb +285 -0
  434. data/lib/hecks/projections/bootstrap_table.rb +12 -44
  435. data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
  436. data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
  437. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
  438. data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
  439. data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
  440. data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
  441. data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
  442. data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
  443. data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
  444. data/lib/hecks/projections/deploy/fargate.rb +40 -207
  445. data/lib/hecks/projections/deploy/lambda.rb +191 -708
  446. data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
  447. data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
  448. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  449. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
  450. data/lib/hecks/projections/deploy/preview/script.rb +2 -14
  451. data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
  452. data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
  453. data/lib/hecks/projections/deploy/preview/template.rb +3 -59
  454. data/lib/hecks/projections/deploy/preview.rb +2 -67
  455. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
  456. data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
  457. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
  458. data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
  459. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
  460. data/lib/hecks/projections/deploy/scripts.rb +4 -57
  461. data/lib/hecks/projections/deploy/shared.rb +48 -147
  462. data/lib/hecks/projections/deploy/smoke.rb +4 -61
  463. data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
  464. data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
  465. data/lib/hecks/projections/deploy/template_diff.rb +5 -57
  466. data/lib/hecks/projections/diagrams.rb +22 -584
  467. data/lib/hecks/projections/glossary/html.rb +14 -130
  468. data/lib/hecks/projections/glossary/markdown.rb +8 -69
  469. data/lib/hecks/projections/glossary/mermaid.rb +9 -66
  470. data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
  471. data/lib/hecks/projections/glossary/sentences.rb +14 -159
  472. data/lib/hecks/projections/glossary.rb +30 -222
  473. data/lib/hecks/projections/ir.rb +3 -10
  474. data/lib/hecks/projections/model/deviations.rb +16 -43
  475. data/lib/hecks/projections/model.rb +12 -51
  476. data/lib/hecks/projections/oidc.rb +12 -56
  477. data/lib/hecks/projections/parser_table.rb +13 -32
  478. data/lib/hecks/projections/reference.rb +4 -18
  479. data/lib/hecks/projections/rust_vocabulary.rb +13 -251
  480. data/lib/hecks/projections/shape.rb +3 -29
  481. data/lib/hecks/projections/statements.rb +7 -86
  482. data/lib/hecks/projections/vocabulary.rb +6 -22
  483. data/lib/hecks/projections.rb +2 -16
  484. data/lib/hecks/projector/cli_projector.rb +154 -400
  485. data/lib/hecks/projector/docs_projector.rb +10 -201
  486. data/lib/hecks/projector/exporter.rb +42 -134
  487. data/lib/hecks/projector/ir_projector.rb +2 -9
  488. data/lib/hecks/projector/narrate_projector.rb +15 -189
  489. data/lib/hecks/projector/target.rb +13 -76
  490. data/lib/hecks/projector.rb +30 -121
  491. data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
  492. data/lib/hecks/quality_control/adapters/agent.rb +58 -0
  493. data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
  494. data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
  495. data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
  496. data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
  497. data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
  498. data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
  499. data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
  500. data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
  501. data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
  502. data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
  503. data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
  504. data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
  505. data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
  506. data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
  507. data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
  508. data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
  509. data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
  510. data/lib/hecks/quality_control/cli/child.rb +45 -0
  511. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
  512. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
  513. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
  514. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
  515. data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
  516. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
  517. data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
  518. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
  519. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
  520. data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
  521. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
  522. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
  523. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
  524. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
  525. data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
  526. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
  527. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
  528. data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
  529. data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
  530. data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
  531. data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
  532. data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
  533. data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
  534. data/lib/hecks/query_ir.rb +346 -0
  535. data/lib/hecks/query_ir_mcp.rb +198 -0
  536. data/lib/hecks/query_specification/common/comparators.rb +5 -26
  537. data/lib/hecks/query_specification/common/comparison.rb +35 -143
  538. data/lib/hecks/query_specification/common/dsl.rb +8 -24
  539. data/lib/hecks/query_specification/common/null_policy.rb +20 -70
  540. data/lib/hecks/query_specification/common/options.rb +2 -6
  541. data/lib/hecks/query_specification/common/where_clause.rb +2 -10
  542. data/lib/hecks/query_specification/field_path.rb +37 -77
  543. data/lib/hecks/query_specification/hop_path.rb +12 -76
  544. data/lib/hecks/release/gem_pin.rb +2 -26
  545. data/lib/hecks/release/runner/ci_publisher.rb +5 -8
  546. data/lib/hecks/release/runner/clean_tree.rb +42 -0
  547. data/lib/hecks/release/runner/commands.rb +2 -4
  548. data/lib/hecks/release/runner/console.rb +6 -0
  549. data/lib/hecks/release/runner/gem_publisher.rb +15 -9
  550. data/lib/hecks/release/runner/npm_publisher.rb +9 -53
  551. data/lib/hecks/release/runner/preflight.rb +29 -20
  552. data/lib/hecks/release/runner/published.rb +8 -26
  553. data/lib/hecks/release/runner/tagger.rb +31 -23
  554. data/lib/hecks/release/runner.rb +60 -38
  555. data/lib/hecks/rendering.rb +4 -26
  556. data/lib/hecks/router/namespace_installer.rb +15 -39
  557. data/lib/hecks/router.rb +4 -8
  558. data/lib/hecks/runtime/adapter_lookup.rb +124 -0
  559. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  560. data/lib/hecks/runtime/boot_gates.rb +3 -18
  561. data/lib/hecks/runtime/caller.rb +19 -59
  562. data/lib/hecks/runtime/capability_graph.rb +5 -21
  563. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  564. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +27 -160
  565. data/lib/hecks/runtime/command_interpreter.rb +124 -306
  566. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  567. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  568. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  569. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  570. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  571. data/lib/hecks/runtime/dependency_planning.rb +50 -104
  572. data/lib/hecks/runtime/dispatcher.rb +134 -243
  573. data/lib/hecks/runtime/entity_element.rb +40 -462
  574. data/lib/hecks/runtime/entity_interpreter.rb +64 -219
  575. data/lib/hecks/runtime/errors.rb +32 -115
  576. data/lib/hecks/runtime/event.rb +3 -25
  577. data/lib/hecks/runtime/identity.rb +18 -154
  578. data/lib/hecks/runtime/instance.rb +18 -150
  579. data/lib/hecks/runtime/interpreting.rb +14 -70
  580. data/lib/hecks/runtime/invocation.rb +28 -164
  581. data/lib/hecks/runtime/loader.rb +109 -194
  582. data/lib/hecks/runtime/outbox.rb +39 -262
  583. data/lib/hecks/runtime/policy_interpreter.rb +43 -202
  584. data/lib/hecks/runtime/port_operation_interpreter.rb +28 -97
  585. data/lib/hecks/runtime/query_interpreter.rb +122 -187
  586. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  587. data/lib/hecks/runtime/reaction_outcome.rb +89 -0
  588. data/lib/hecks/runtime/read_model_interpreter.rb +117 -208
  589. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  590. data/lib/hecks/runtime/reference_hop.rb +8 -81
  591. data/lib/hecks/runtime/refusal_wording.rb +28 -87
  592. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  593. data/lib/hecks/runtime/registry/verification.rb +213 -227
  594. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  595. data/lib/hecks/runtime/registry.rb +156 -247
  596. data/lib/hecks/runtime/remote_dispatcher.rb +68 -154
  597. data/lib/hecks/runtime/routing.rb +4 -15
  598. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  599. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  600. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  601. data/lib/hecks/runtime/tenant_check.rb +6 -68
  602. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  603. data/lib/hecks/runtime/value/admission.rb +16 -67
  604. data/lib/hecks/runtime/value/coercion.rb +162 -760
  605. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  606. data/lib/hecks/runtime/value/field_checks.rb +172 -0
  607. data/lib/hecks/runtime/value.rb +15 -84
  608. data/lib/hecks/runtime.rb +35 -69
  609. data/lib/hecks/rust_build/conformance.rb +158 -0
  610. data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
  611. data/lib/hecks/rust_build/coverage.rb +218 -0
  612. data/lib/hecks/rust_build/native_build.rb +124 -0
  613. data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
  614. data/lib/hecks/rust_build/project_rust.rb +332 -0
  615. data/lib/hecks/rust_build/wasm.rb +105 -0
  616. data/lib/hecks/rust_build/wasm_browser.rb +113 -0
  617. data/lib/hecks/rust_build.rb +144 -0
  618. data/lib/hecks/storehouse.rb +155 -582
  619. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +10 -34
  620. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  621. data/lib/hecks/three_zero/forms.yml +127 -0
  622. data/lib/hecks/three_zero.rb +12 -0
  623. data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
  624. data/lib/hecks/tools/comment_style.rb +1070 -0
  625. data/lib/hecks/tools/corpus_report.rb +87 -0
  626. data/lib/hecks/tools/deploy_recipe.rb +255 -0
  627. data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
  628. data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
  629. data/lib/hecks/tools/era_projection_backfill.rb +94 -0
  630. data/lib/hecks/tools/era_reattest.rb +146 -0
  631. data/lib/hecks/tools/evolve_run.rb +189 -0
  632. data/lib/hecks/tools/fuzz_sweep.rb +373 -0
  633. data/lib/hecks/tools/heki_compaction.rb +100 -0
  634. data/lib/hecks/tools/hoist_local_givens.rb +246 -0
  635. data/lib/hecks/tools/journal_compaction.rb +89 -0
  636. data/lib/hecks/tools/oidc_manifests.rb +70 -0
  637. data/lib/hecks/tools/query_ir_run.rb +81 -0
  638. data/lib/hecks/tools/regeneration_run.rb +166 -0
  639. data/lib/hecks/tools/rust_comment_style.rb +642 -0
  640. data/lib/hecks/tools/sequence_script.rb +62 -0
  641. data/lib/hecks/tools/tail_merge.rb +117 -0
  642. data/lib/hecks/tools/tenant_provisioning.rb +142 -0
  643. data/lib/hecks/tools/translation_audit.rb +208 -0
  644. data/lib/hecks/tools/translation_scaffold.rb +131 -0
  645. data/lib/hecks/tools.rb +86 -0
  646. data/lib/hecks/vendoring/git_environment.rb +2 -8
  647. data/lib/hecks/vendoring/git_source.rb +4 -10
  648. data/lib/hecks/vendoring.rb +7 -46
  649. data/lib/hecks/version.rb +3 -14
  650. data/lib/hecks.rb +52 -90
  651. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  652. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  653. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  654. data/qa/settings.yml +66 -0
  655. data/rust/.cargo/config.toml +6 -0
  656. data/rust/.gitignore +2 -0
  657. data/rust/Cargo.lock +54 -0
  658. data/rust/Cargo.toml +61 -0
  659. data/rust/build/.cargo/config.toml +3 -0
  660. data/rust/build/.gitignore +1 -0
  661. data/rust/build/Cargo.lock +7 -0
  662. data/rust/build/Cargo.toml +19 -0
  663. data/rust/build/src/build_artifact.rs +47 -0
  664. data/rust/build/src/cargo_sync.rs +248 -0
  665. data/rust/build/src/json.rs +421 -0
  666. data/rust/build/src/lineage_pass.rs +285 -0
  667. data/rust/build/src/main.rs +51 -0
  668. data/rust/build/src/optional_pass.rs +150 -0
  669. data/rust/build/src/pipeline.rs +264 -0
  670. data/rust/build/src/reserved_names.rs +76 -0
  671. data/rust/build/src/resolve.rs +124 -0
  672. data/rust/build/src/root.rs +25 -0
  673. data/rust/build/src/sidecars.rs +73 -0
  674. data/rust/build/src/subprocess.rs +51 -0
  675. data/rust/build/src/tmp.rs +31 -0
  676. data/rust/codegen/.cargo/config.toml +3 -0
  677. data/rust/codegen/.gitignore +1 -0
  678. data/rust/codegen/Cargo.lock +7 -0
  679. data/rust/codegen/Cargo.toml +20 -0
  680. data/rust/codegen/src/attr.rs +32 -0
  681. data/rust/codegen/src/bridging.rs +481 -0
  682. data/rust/codegen/src/commands.rs +1226 -0
  683. data/rust/codegen/src/constraints.rs +109 -0
  684. data/rust/codegen/src/dependency_planning.rs +209 -0
  685. data/rust/codegen/src/domain_generator.rs +1288 -0
  686. data/rust/codegen/src/exemplar.rs +230 -0
  687. data/rust/codegen/src/expr_emitter.rs +87 -0
  688. data/rust/codegen/src/fielded.rs +192 -0
  689. data/rust/codegen/src/hecks_naming.rs +33 -0
  690. data/rust/codegen/src/json.rs +335 -0
  691. data/rust/codegen/src/json_codec.rs +697 -0
  692. data/rust/codegen/src/literal.rs +207 -0
  693. data/rust/codegen/src/main.rs +290 -0
  694. data/rust/codegen/src/manifest.rs +227 -0
  695. data/rust/codegen/src/mutations.rs +784 -0
  696. data/rust/codegen/src/naming.rs +286 -0
  697. data/rust/codegen/src/ports.rs +208 -0
  698. data/rust/codegen/src/prelude.rs +116 -0
  699. data/rust/codegen/src/queries.rs +786 -0
  700. data/rust/codegen/src/reactions.rs +532 -0
  701. data/rust/codegen/src/read_models.rs +697 -0
  702. data/rust/codegen/src/reference_specs.rs +40 -0
  703. data/rust/codegen/src/registry.rs +966 -0
  704. data/rust/codegen/src/reserved_names.rs +76 -0
  705. data/rust/codegen/src/shared.rs +93 -0
  706. data/rust/codegen/src/skip_reason.rs +25 -0
  707. data/rust/codegen/src/types.rs +362 -0
  708. data/rust/host/.cargo/config.toml +3 -0
  709. data/rust/host/.gitignore +1 -0
  710. data/rust/host/Cargo.lock +4297 -0
  711. data/rust/host/Cargo.toml +61 -0
  712. data/rust/host/HECKS_RELEASE +1 -0
  713. data/rust/host/rds-ca-bundle.pem +2736 -0
  714. data/rust/host/src/api.rs +1596 -0
  715. data/rust/host/src/approval.rs +652 -0
  716. data/rust/host/src/auth.rs +1287 -0
  717. data/rust/host/src/bin/expr_harness.rs +46 -0
  718. data/rust/host/src/bin/lineage_harness.rs +119 -0
  719. data/rust/host/src/bin/mint_harness.rs +135 -0
  720. data/rust/host/src/checkout.rs +265 -0
  721. data/rust/host/src/dispatch.rs +1443 -0
  722. data/rust/host/src/expr_json.rs +1117 -0
  723. data/rust/host/src/field_hints.rs +52 -0
  724. data/rust/host/src/ir.rs +733 -0
  725. data/rust/host/src/journal.rs +874 -0
  726. data/rust/host/src/lambda_client.rs +378 -0
  727. data/rust/host/src/log.rs +147 -0
  728. data/rust/host/src/main.rs +474 -0
  729. data/rust/host/src/mint.rs +1865 -0
  730. data/rust/host/src/payments/keystore.rs +330 -0
  731. data/rust/host/src/payments/tests.rs +1400 -0
  732. data/rust/host/src/payments.rs +767 -0
  733. data/rust/host/src/presentation.rs +698 -0
  734. data/rust/host/src/presentation_write.rs +1431 -0
  735. data/rust/host/src/query_step.rs +149 -0
  736. data/rust/host/src/rate_limit.rs +904 -0
  737. data/rust/host/src/reference_transform.rs +241 -0
  738. data/rust/host/src/reference_validate.rs +338 -0
  739. data/rust/host/src/resend.rs +296 -0
  740. data/rust/host/src/secrets.rs +106 -0
  741. data/rust/host/src/server.rs +663 -0
  742. data/rust/host/src/storage_shape.rs +175 -0
  743. data/rust/host/src/test_pg.rs +40 -0
  744. data/rust/host/src/ui_schema.rs +1351 -0
  745. data/rust/host/src/wasm_runner.rs +191 -0
  746. data/rust/host/src/web/newsletter/cooldown.rs +126 -0
  747. data/rust/host/src/web/newsletter/tests.rs +104 -0
  748. data/rust/host/src/web/newsletter.rs +310 -0
  749. data/rust/host/src/web/newsletter_send.rs +357 -0
  750. data/rust/host/src/web/registrations.rs +1505 -0
  751. data/rust/host/src/web.rs +2937 -0
  752. data/rust/lsp/.gitignore +1 -0
  753. data/rust/lsp/Cargo.lock +7 -0
  754. data/rust/lsp/Cargo.toml +16 -0
  755. data/rust/lsp/README.md +133 -0
  756. data/rust/lsp/src/diagnostics.rs +165 -0
  757. data/rust/lsp/src/json.rs +385 -0
  758. data/rust/lsp/src/main.rs +399 -0
  759. data/rust/lsp/src/outline.rs +177 -0
  760. data/rust/lsp/src/rpc.rs +67 -0
  761. data/rust/parser/.cargo/config.toml +5 -0
  762. data/rust/parser/.gitignore +1 -0
  763. data/rust/parser/Cargo.lock +7 -0
  764. data/rust/parser/Cargo.toml +21 -0
  765. data/rust/parser/src/build/closed_sets.rs +27 -0
  766. data/rust/parser/src/build/identity.rs +276 -0
  767. data/rust/parser/src/build/mod.rs +12 -0
  768. data/rust/parser/src/build/naming.rs +141 -0
  769. data/rust/parser/src/build/pattern_subset.rs +161 -0
  770. data/rust/parser/src/build/query_derive.rs +72 -0
  771. data/rust/parser/src/build/query_inference.rs +269 -0
  772. data/rust/parser/src/build/query_options.rs +100 -0
  773. data/rust/parser/src/build/read_model.rs +94 -0
  774. data/rust/parser/src/build/references.rs +32 -0
  775. data/rust/parser/src/canonical.rs +114 -0
  776. data/rust/parser/src/diag.rs +55 -0
  777. data/rust/parser/src/emit.rs +953 -0
  778. data/rust/parser/src/expr/ast_json.rs +147 -0
  779. data/rust/parser/src/expr/evaluator.rs +81 -0
  780. data/rust/parser/src/expr/mod.rs +84 -0
  781. data/rust/parser/src/expr/resolver.rs +497 -0
  782. data/rust/parser/src/ir.rs +387 -0
  783. data/rust/parser/src/keywords.rs +585 -0
  784. data/rust/parser/src/lex.rs +662 -0
  785. data/rust/parser/src/main.rs +252 -0
  786. data/rust/parser/src/parse/aggregate.rs +612 -0
  787. data/rust/parser/src/parse/chapter.rs +517 -0
  788. data/rust/parser/src/parse/command.rs +637 -0
  789. data/rust/parser/src/parse/domain_port.rs +127 -0
  790. data/rust/parser/src/parse/entity.rs +527 -0
  791. data/rust/parser/src/parse/file.rs +67 -0
  792. data/rust/parser/src/parse/hecksagon.rs +231 -0
  793. data/rust/parser/src/parse/lifecycle.rs +162 -0
  794. data/rust/parser/src/parse/mod.rs +1262 -0
  795. data/rust/parser/src/parse/policy.rs +87 -0
  796. data/rust/parser/src/parse/process_manager.rs +305 -0
  797. data/rust/parser/src/parse/query.rs +100 -0
  798. data/rust/parser/src/parse/read_model.rs +214 -0
  799. data/rust/parser/src/parse/value_object.rs +250 -0
  800. data/rust/parser/src/ruby_value.rs +422 -0
  801. data/rust/project/bridging.rb +257 -0
  802. data/rust/project/commands.rb +877 -0
  803. data/rust/project/constraints.rb +86 -0
  804. data/rust/project/dependency_planning.rb +178 -0
  805. data/rust/project/domain_generator.rb +980 -0
  806. data/rust/project/exemplar.rb +148 -0
  807. data/rust/project/expr_emitter.rb +72 -0
  808. data/rust/project/fielded.rb +141 -0
  809. data/rust/project/json_codec.rb +641 -0
  810. data/rust/project/mutations.rb +721 -0
  811. data/rust/project/naming.rb +205 -0
  812. data/rust/project/ports.rb +65 -0
  813. data/rust/project/queries.rb +493 -0
  814. data/rust/project/reactions.rb +317 -0
  815. data/rust/project/read_models.rb +690 -0
  816. data/rust/project/reference_specs.rb +27 -0
  817. data/rust/project/registry.rb +396 -0
  818. data/rust/project/skip_reason.rb +29 -0
  819. data/rust/project/types.rb +225 -0
  820. data/rust/project/write_if_changed.rb +83 -0
  821. data/rust/project.rb +30 -0
  822. data/rust/project_rust_pipeline.rb +383 -0
  823. data/rust/src/exemplar/commands.rs +279 -0
  824. data/rust/src/exemplar/constraints.rs +18 -0
  825. data/rust/src/exemplar/fielded.rs +280 -0
  826. data/rust/src/exemplar/json.rs +277 -0
  827. data/rust/src/exemplar/mod.rs +17 -0
  828. data/rust/src/exemplar/mutations.rs +80 -0
  829. data/rust/src/exemplar/queries.rs +33 -0
  830. data/rust/src/exemplar/reactions.rs +88 -0
  831. data/rust/src/exemplar/read_models.rs +48 -0
  832. data/rust/src/exemplar/registry.rs +166 -0
  833. data/rust/src/exemplar/types.rs +44 -0
  834. data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
  835. data/rust/src/kernel/attribute_shapes/list.rs +18 -0
  836. data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
  837. data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
  838. data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
  839. data/rust/src/kernel/cli.rs +585 -0
  840. data/rust/src/kernel/dispatch.rs +1090 -0
  841. data/rust/src/kernel/expr.rs +458 -0
  842. data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
  843. data/rust/src/kernel/expression_operators/comparison.rs +48 -0
  844. data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
  845. data/rust/src/kernel/expression_operators/logical.rs +13 -0
  846. data/rust/src/kernel/expression_operators/membership.rs +18 -0
  847. data/rust/src/kernel/expression_operators/mod.rs +68 -0
  848. data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
  849. data/rust/src/kernel/expression_operators/positional.rs +126 -0
  850. data/rust/src/kernel/expression_operators/presence.rs +101 -0
  851. data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
  852. data/rust/src/kernel/expression_operators/sized.rs +35 -0
  853. data/rust/src/kernel/expression_operators/text.rs +79 -0
  854. data/rust/src/kernel/expression_operators/to_string.rs +18 -0
  855. data/rust/src/kernel/json.rs +779 -0
  856. data/rust/src/kernel/mod.rs +133 -0
  857. data/rust/src/kernel/named_query.rs +219 -0
  858. data/rust/src/kernel/orchestrate.rs +1810 -0
  859. data/rust/src/kernel/pattern.rs +307 -0
  860. data/rust/src/kernel/query_comparators.rs +206 -0
  861. data/rust/src/kernel/query_ordering.rs +103 -0
  862. data/rust/src/kernel/read_model.rs +914 -0
  863. data/rust/src/kernel/reference_lookup.rs +149 -0
  864. data/rust/src/kernel/repository.rs +468 -0
  865. data/rust/src/kernel/routing.rs +612 -0
  866. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
  867. data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
  868. data/rust/src/kernel/vocab/field_hint.rs +70 -0
  869. data/rust/src/kernel/vocab/mod.rs +20 -0
  870. data/rust/src/kernel/vocab/query_comparator.rs +65 -0
  871. data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
  872. data/rust/src/lib.rs +8 -0
  873. data/rust/src/main.rs +16 -0
  874. data/rust/web/.gitignore +1 -0
  875. data/rust/web/Cargo.lock +119 -0
  876. data/rust/web/Cargo.toml +18 -0
  877. data/rust/web/src/lib.rs +8 -0
  878. metadata +478 -12
  879. data/lib/hecks/facade/cli_door.rb +0 -178
  880. data/lib/hecks/facade/cli_runner.rb +0 -271
  881. data/lib/hecks/facade/handle.rb +0 -293
  882. data/lib/hecks/facade/json_door.rb +0 -247
  883. data/lib/hecks/facade/surface/aggregate_door.rb +0 -222
  884. data/lib/hecks/facade/surface/chapter.rb +0 -116
  885. data/lib/hecks/facade/surface.rb +0 -61
  886. data/lib/hecks/mcp_door_scope.rb +0 -176
@@ -0,0 +1,966 @@
1
+ //! Command router emitter, ported from `rust/project/registry.rb`.
2
+ //! The entry structs mirror the Hashes `domain_generator.rb` accumulates while walking the IR.
3
+
4
+ use crate::exemplar::Exemplar;
5
+ use crate::naming;
6
+ use crate::reference_specs::{self, ReferenceSpec};
7
+
8
+ #[derive(Clone)]
9
+ pub struct ReferenceCheck {
10
+ pub field: String,
11
+ pub optional: bool,
12
+ /// `Some(element key expression)` for a list (`has_many`) check.
13
+ pub list_item: Option<String>,
14
+ pub target_mod: String,
15
+ pub target_name: String,
16
+ pub heads: String,
17
+ }
18
+
19
+ /// One write-side tenant boundary check (`domain_generator.rb#tenant_boundary_checks`), typed.
20
+ pub struct TenantBoundaryCheck {
21
+ pub reference_field: String,
22
+ pub target_mod: String,
23
+ pub target_name: String,
24
+ pub aggregate_name: String,
25
+ pub own_tenant_field: String,
26
+ pub own_accessor: String,
27
+ pub target_tenant_field: String,
28
+ pub target_accessor: String,
29
+ }
30
+
31
+ pub struct CommandEntry {
32
+ pub verb: String,
33
+ pub name: String,
34
+ pub fn_name: String,
35
+ pub args_struct: String,
36
+ pub creates: bool,
37
+ pub identity_extra_params: Vec<String>,
38
+ pub reference_checks: Vec<ReferenceCheck>,
39
+ /// Write-side tenant boundary checks run before dispatch.
40
+ pub tenant_boundary_checks: Vec<TenantBoundaryCheck>,
41
+ /// Reference-typed attributes, the specs behind `command_deref`.
42
+ pub reference_specs: Vec<ReferenceSpec>,
43
+ /// Declared attribute names; read by `reactions.rs#emit_command_attributes_table`.
44
+ pub attributes: Vec<String>,
45
+ /// Value-object invariant/admits/pattern checks as ready-to-splice lines, run before the
46
+ /// role and reference checks. `commands.rs::emit_command` repeats them in the dispatch fn.
47
+ pub invariant_check_lines: Vec<String>,
48
+ pub role: Option<String>,
49
+ }
50
+
51
+ pub struct EntityCommandEntry {
52
+ pub verb: String,
53
+ pub name: String,
54
+ pub entity_record: String,
55
+ pub fn_name: String,
56
+ pub args_struct: String,
57
+ pub reference_checks: Vec<ReferenceCheck>,
58
+ pub reference_specs: Vec<ReferenceSpec>,
59
+ /// Declared attribute names; see `CommandEntry`.
60
+ pub attributes: Vec<String>,
61
+ /// See `CommandEntry::invariant_check_lines`.
62
+ pub invariant_check_lines: Vec<String>,
63
+ pub role: Option<String>,
64
+ /// Carried for shape parity with `domain_generator.rb`'s `entity_commands`; unread here.
65
+ pub entity_name: String,
66
+ pub entity_identity_reading: String,
67
+ }
68
+
69
+ /// A command owned by an entity nested two levels deep (`Aggregate.Entity.Entity.Command`).
70
+ /// Adds the innermost entity's name and identity reading to `EntityCommandEntry`'s fields.
71
+ pub struct NestedEntityCommandEntry {
72
+ pub verb: String,
73
+ pub name: String,
74
+ pub entity_record: String,
75
+ pub nested_record: String,
76
+ pub fn_name: String,
77
+ pub args_struct: String,
78
+ pub reference_checks: Vec<ReferenceCheck>,
79
+ pub reference_specs: Vec<ReferenceSpec>,
80
+ pub attributes: Vec<String>,
81
+ pub invariant_check_lines: Vec<String>,
82
+ pub role: Option<String>,
83
+ pub entity_name: String,
84
+ pub entity_identity_reading: String,
85
+ pub nested_name: String,
86
+ pub nested_identity_reading: String,
87
+ /// Whether the router gets a flat-args (`None`) fallback for this command; true only when both
88
+ /// hops' identity is `extract_id`-supported.
89
+ pub unrouted_supported: bool,
90
+ }
91
+
92
+ pub struct PortEntry {
93
+ pub verb: String,
94
+ pub name: String,
95
+ pub fn_name: String,
96
+ pub args_struct: String,
97
+ pub reference_checks: Vec<ReferenceCheck>,
98
+ /// Self-reference field from older port IR; new operations take the owner from the route.
99
+ pub legacy_receiver_field: Option<String>,
100
+ /// Receiver field of `to:`-declared operations; a real attribute, kept in the payload.
101
+ pub to_receiver_field: Option<String>,
102
+ }
103
+
104
+ /// A nested entity's name and identity paths, from `domain_generator.rb`'s `entities:` field.
105
+ /// `reactions.rs#emit_entity_identity_head_table` reads the single-component case.
106
+ pub struct EntityIdentityEntry {
107
+ pub name: String,
108
+ pub identified_by: Vec<String>,
109
+ }
110
+
111
+ pub struct AggregateEntry {
112
+ pub name: String,
113
+ pub module_name: String,
114
+ pub record: String,
115
+ pub commands: Vec<CommandEntry>,
116
+ pub entity_commands: Vec<EntityCommandEntry>,
117
+ pub nested_entity_commands: Vec<NestedEntityCommandEntry>,
118
+ pub ports: Vec<PortEntry>,
119
+ pub chapter_mod: String,
120
+ pub domain_name: String,
121
+ /// Reference-typed attributes; this aggregate's row in `REFERENCE_TABLE`.
122
+ pub reference_specs: Vec<ReferenceSpec>,
123
+ /// Declared identity paths; `reactions.rs#emit_identity_head_table` reads one-component ones.
124
+ pub identified_by: Vec<String>,
125
+ /// Nested entities, read by `reactions.rs#emit_entity_identity_head_table`.
126
+ pub entities: Vec<EntityIdentityEntry>,
127
+ }
128
+
129
+ pub fn emit_role_check(
130
+ exemplar: &Exemplar,
131
+ role: Option<&str>,
132
+ command_name: &str,
133
+ ) -> Option<String> {
134
+ let role = role?;
135
+ Some(exemplar.render(
136
+ "role_check",
137
+ &[
138
+ ("\"TmplRole\"", naming::ruby_inspect_string(role)),
139
+ (
140
+ "\"TmplCommandName\"",
141
+ naming::ruby_inspect_string(command_name),
142
+ ),
143
+ ],
144
+ ))
145
+ }
146
+
147
+ /// Port of `rust/project/registry.rb#emit_tenant_boundary_check`.
148
+ /// Hand-built: the target accessor differs for a single-attribute value object and a bare scalar.
149
+ pub fn emit_tenant_boundary_check(check: &TenantBoundaryCheck) -> String {
150
+ let ref_ident = naming::rust_ident_field(&check.reference_field);
151
+ let own_expr = format!("args.{}.clone()", naming::rust_ident_field(&check.own_accessor));
152
+ let target_expr = match check.target_accessor.split_once('.') {
153
+ Some((head, inner)) => format!(
154
+ "record.{}.as_ref().map(|v| v.{}.clone())",
155
+ naming::rust_ident_field(head),
156
+ naming::rust_ident_field(inner)
157
+ ),
158
+ None => format!("record.{}.clone()", naming::rust_ident_field(&check.target_accessor)),
159
+ };
160
+
161
+ let mut out = String::new();
162
+ out.push_str(&format!("if let Some(record) = store.{}.find(&args.{ref_ident}) {{ ", check.target_mod));
163
+ out.push_str(&format!("if let Some(target_tenant) = {target_expr} {{ "));
164
+ out.push_str(&format!("let own_tenant = {own_expr}; "));
165
+ out.push_str("if target_tenant != own_tenant { ");
166
+ out.push_str(
167
+ "return Err(crate::kernel::Refusal::Unauthorized(crate::kernel::refusal_wording::UnauthorizedCrossTenantReferenceArgs { ",
168
+ );
169
+ out.push_str(&format!("aggregate: {}, ", naming::ruby_inspect_string(&check.aggregate_name)));
170
+ out.push_str(&format!("field: {}, ", naming::ruby_inspect_string(&check.own_tenant_field)));
171
+ out.push_str("tenant: &format!(\"{:?}\", own_tenant), ");
172
+ out.push_str(&format!("attribute: {}, ", naming::ruby_inspect_string(&check.reference_field)));
173
+ out.push_str(&format!("target: {}, ", naming::ruby_inspect_string(&check.target_name)));
174
+ out.push_str(&format!("target_field: {}, ", naming::ruby_inspect_string(&check.target_tenant_field)));
175
+ out.push_str("other: &format!(\"{:?}\", target_tenant)");
176
+ out.push_str(" }.render_args())); ");
177
+ out.push_str("} } }");
178
+ out
179
+ }
180
+
181
+ /// `kernel::ArgumentGates` literal for one command (`registry.rb#emit_argument_gates_literal`).
182
+ /// The last two fields are closures because they need `store`, which exists only at the router.
183
+ fn emit_argument_gates_literal(args_path: &str, invariant_check_lines: &[String], role_line: Option<String>, reference_lines: &[String]) -> String {
184
+ let mut normalize = vec![format!("let args = {args_path}::from_json(v)?;")];
185
+ normalize.extend(invariant_check_lines.iter().map(|l| squeeze(l)));
186
+ normalize.push("Ok(args)".to_string());
187
+
188
+ let role = match role_line.filter(|l| !l.is_empty()) {
189
+ Some(line) => format!("&|| {{ {} Ok(()) }}", squeeze(&line)),
190
+ None => "&|| Ok(())".to_string(),
191
+ };
192
+ let references: Vec<String> = reference_lines.iter().filter(|l| !l.is_empty()).map(|l| squeeze(l)).collect();
193
+ let references = if references.is_empty() {
194
+ format!("&|_args: &{args_path}| Ok(())")
195
+ } else {
196
+ format!("&|args: &{args_path}| {{ {} Ok(()) }}", references.join(" "))
197
+ };
198
+
199
+ format!(
200
+ "crate::kernel::ArgumentGates {{ decode_arguments: &{args_path}::decode_arguments, \
201
+ refuse_unknown_arguments: &{args_path}::refuse_unknown_arguments, \
202
+ refuse_absent_arguments: &{args_path}::refuse_absent_arguments, \
203
+ normalize_args: &|v: &crate::kernel::Json| {{ {} }}, refuse_role_mismatch: {role}, resolve_references: {references} }}",
204
+ normalize.join(" ")
205
+ )
206
+ }
207
+
208
+ /// Joins a multi-line emitted check onto one line so it can sit inside a closure expression.
209
+ fn squeeze(text: &str) -> String {
210
+ text.lines().map(str::trim).filter(|l| !l.is_empty()).collect::<Vec<_>>().join(" ")
211
+ }
212
+
213
+ pub fn emit_reference_check(exemplar: &Exemplar, check: &ReferenceCheck) -> String {
214
+ let ident = naming::rust_ident_field(&check.field);
215
+ if let Some(list_item) = &check.list_item {
216
+ exemplar.render(
217
+ "reference_check_list",
218
+ &[
219
+ (
220
+ "\"TmplTarget\"",
221
+ naming::ruby_inspect_string(&check.target_name),
222
+ ),
223
+ ("\"tmpl_heads\"", naming::ruby_inspect_string(&check.heads)),
224
+ ("tmpl_target_mod", check.target_mod.clone()),
225
+ ("tmpl_list_field", ident),
226
+ ("&item.tmpl_element_field", list_item.clone()),
227
+ ],
228
+ )
229
+ } else if check.optional {
230
+ exemplar.render(
231
+ "reference_check_optional",
232
+ &[
233
+ (
234
+ "\"TmplTarget\"",
235
+ naming::ruby_inspect_string(&check.target_name),
236
+ ),
237
+ ("\"tmpl_heads\"", naming::ruby_inspect_string(&check.heads)),
238
+ ("tmpl_target_mod", check.target_mod.clone()),
239
+ ("tmpl_optional_field", ident),
240
+ ],
241
+ )
242
+ } else {
243
+ exemplar.render(
244
+ "reference_check_required",
245
+ &[
246
+ (
247
+ "\"TmplTarget\"",
248
+ naming::ruby_inspect_string(&check.target_name),
249
+ ),
250
+ ("\"tmpl_heads\"", naming::ruby_inspect_string(&check.heads)),
251
+ ("tmpl_target_mod", check.target_mod.clone()),
252
+ ("tmpl_field", ident),
253
+ ],
254
+ )
255
+ }
256
+ }
257
+
258
+ fn chapter_path(a: &AggregateEntry) -> String {
259
+ format!("crate::generated::{}::{}", a.chapter_mod, a.module_name)
260
+ }
261
+
262
+ pub fn emit_registry(exemplar: &Exemplar, aggregates: &[AggregateEntry]) -> String {
263
+ let store_fields: Vec<String> = aggregates
264
+ .iter()
265
+ .map(|a| {
266
+ format!(
267
+ " pub {}: crate::kernel::InMemoryRepository<{}::{}>,",
268
+ a.module_name,
269
+ chapter_path(a),
270
+ a.record
271
+ )
272
+ })
273
+ .collect();
274
+ let store_inits: Vec<String> = aggregates
275
+ .iter()
276
+ .map(|a| {
277
+ format!(
278
+ " {}: crate::kernel::InMemoryRepository::new(),",
279
+ a.module_name
280
+ )
281
+ })
282
+ .collect();
283
+
284
+ let dump_arms: Vec<String> = aggregates
285
+ .iter()
286
+ .map(|a| {
287
+ let prefix = format!("{}::{}#", a.domain_name, a.name);
288
+ format!(
289
+ "for (id, record) in self.{}.entries() {{\n instances.push((format!(\"{{}}{{}}\", {}, id), record.to_json()));\n}}",
290
+ a.module_name,
291
+ naming::ruby_inspect_string(&prefix)
292
+ )
293
+ })
294
+ .collect();
295
+
296
+ let seed_arms: Vec<String> = aggregates
297
+ .iter()
298
+ .map(|a| {
299
+ let mod_path = chapter_path(a);
300
+ let prefix = format!("{}::{}#", a.domain_name, a.name);
301
+ format!(
302
+ "if let Some(id) = key.strip_prefix({}) {{\n store.{}.save(id, {mod_path}::{}::from_json(value)?);\n continue;\n}}",
303
+ naming::ruby_inspect_string(&prefix),
304
+ a.module_name,
305
+ a.record
306
+ )
307
+ })
308
+ .collect();
309
+
310
+ let query_arms: Vec<String> = aggregates
311
+ .iter()
312
+ .map(|a| {
313
+ let prefix = format!("{}::{}", a.domain_name, a.name);
314
+ format!(
315
+ "if aggregate == {} {{\n return Some(self.{}.json_entries(|record| record.to_json()).map(|(id, json)| (id.clone(), json.clone())).collect());\n}}",
316
+ naming::ruby_inspect_string(&prefix),
317
+ a.module_name
318
+ )
319
+ })
320
+ .collect();
321
+
322
+ let scan_each_arms: Vec<String> = aggregates
323
+ .iter()
324
+ .map(|a| {
325
+ let prefix = format!("{}::{}", a.domain_name, a.name);
326
+ format!(
327
+ "if aggregate == {} {{\n for (id, json) in self.{}.json_entries(|record| record.to_json()) {{\n visit(id, json);\n }}\n return true;\n}}",
328
+ naming::ruby_inspect_string(&prefix),
329
+ a.module_name
330
+ )
331
+ })
332
+ .collect();
333
+
334
+ let mut aggregate_arms: Vec<String> = Vec::new();
335
+ for a in aggregates {
336
+ let mod_path = chapter_path(a);
337
+ for c in &a.commands {
338
+ let extra_names = &c.identity_extra_params;
339
+ let extra_idents: Vec<String> = extra_names
340
+ .iter()
341
+ .map(|n| naming::rust_ident_field(n))
342
+ .collect();
343
+ let extra_lines: Vec<String> = extra_names
344
+ .iter()
345
+ .zip(extra_idents.iter())
346
+ .map(|(name, ident)| {
347
+ let msg = format!("{} creates a {} — pass {name}", c.verb, a.record);
348
+ format!(
349
+ "let {ident} = facts_json.dig({}).ok_or_else(|| crate::kernel::Refusal::NotFound({}.to_string()))?.to_id_component()?;",
350
+ naming::ruby_inspect_string(name),
351
+ naming::ruby_inspect_string(&msg)
352
+ )
353
+ })
354
+ .collect();
355
+ let extra_pass: String = extra_idents
356
+ .iter()
357
+ .map(|ident| format!("&{ident}, "))
358
+ .collect();
359
+
360
+ // A creating command's dispatch fn takes `route` so it can tell create from find.
361
+ let dispatch_call = format!(
362
+ "{mod_path}::dispatch_{}(&mut store.{}, {}args, mutations, owner_deref, command_deref, tenant_boundary_check)",
363
+ c.fn_name,
364
+ a.module_name,
365
+ if c.creates { format!("route, {extra_pass}") } else { "&id, ".to_string() }
366
+ );
367
+ // `extract_id` has no command context to render `NotFoundActingNoIdentity`, so its Err
368
+ // is wrapped here into the wording Ruby's `acting_no_identity` refusal produces.
369
+ let not_found_expr = |command: &str, aggregate: &str, identity: &str| {
370
+ format!(
371
+ "crate::kernel::Refusal::NotFound(crate::kernel::refusal_wording::NotFoundActingNoIdentityArgs {{ command: {}, aggregate: {}, identity: {} }}.render_args())",
372
+ naming::ruby_inspect_string(command),
373
+ naming::ruby_inspect_string(aggregate),
374
+ naming::ruby_inspect_string(identity)
375
+ )
376
+ };
377
+ // Route depth is checked eagerly. With an explicit `with:` Ruby validates facts before
378
+ // the route, so `args_line` picks the order at runtime. Identity follows every gate.
379
+ let route_precheck_line =
380
+ "if let Some(route) = route { route.require_depth(0)?; }".to_string();
381
+ let id_line = if c.creates {
382
+ String::new()
383
+ } else {
384
+ let not_found = not_found_expr(&c.name, &a.record, &a.identified_by.join(", "));
385
+ format!(
386
+ "let id = match route {{ Some(route) => route.aggregate().to_string(), None => {mod_path}::{}::extract_id(facts_json).map_err(|_| {not_found})?, }};",
387
+ a.record
388
+ )
389
+ };
390
+ // The argument gates go to the kernel, which runs them in `AggregateStep::ORDER`.
391
+ let gates_expr = format!(
392
+ "crate::kernel::decode_aggregate_arguments(facts_json, &{})?",
393
+ emit_argument_gates_literal(
394
+ &format!("{mod_path}::{}", c.args_struct),
395
+ &c.invariant_check_lines,
396
+ emit_role_check(exemplar, c.role.as_deref(), &c.name),
397
+ &c
398
+ .reference_checks
399
+ .iter()
400
+ .map(|check| emit_reference_check(exemplar, check))
401
+ .collect::<Vec<_>>()
402
+ )
403
+ );
404
+ // One `if` expression, so exactly one of the two orders runs per call.
405
+ let args_line = format!(
406
+ "let args = if invocation.explicit_with() {{ let args = {gates_expr}; {route_precheck_line} args }} \
407
+ else {{ {route_precheck_line} {gates_expr} }};"
408
+ );
409
+
410
+ // Checks come from `ir.json` at codegen time; none means `Ok(())`, else one closure.
411
+ let tenant_boundary_check_bodies: Vec<String> = c
412
+ .tenant_boundary_checks
413
+ .iter()
414
+ .map(emit_tenant_boundary_check)
415
+ .collect();
416
+ let tenant_boundary_check_line = if tenant_boundary_check_bodies.is_empty() {
417
+ "let tenant_boundary_check: Result<(), crate::kernel::Refusal> = Ok(());".to_string()
418
+ } else {
419
+ format!(
420
+ "let tenant_boundary_check: Result<(), crate::kernel::Refusal> = \
421
+ (|| -> Result<(), crate::kernel::Refusal> {{ {} Ok(()) }})();",
422
+ tenant_boundary_check_bodies.join(" ")
423
+ )
424
+ };
425
+
426
+ // Resolved before the `&mut store.<mod>` borrow below; it needs `store` whole.
427
+ let owner_deref_expr = if c.creates {
428
+ "Vec::new()".to_string()
429
+ } else {
430
+ format!(
431
+ "crate::kernel::owner_deref(&*store, REFERENCE_TABLE, {}, &id)",
432
+ naming::ruby_inspect_string(&format!("{}::{}", a.domain_name, a.name))
433
+ )
434
+ };
435
+ let deref_lines = vec![
436
+ format!("let owner_deref = {owner_deref_expr};"),
437
+ format!(
438
+ "let command_deref = crate::kernel::command_deref(&*store, REFERENCE_TABLE, {}, &args);",
439
+ reference_specs::emit_reference_specs_literal(&c.reference_specs)
440
+ ),
441
+ ];
442
+
443
+ let mut body: Vec<String> = vec![
444
+ "let invocation = crate::kernel::CommandInvocation::from_json(args_json)?;"
445
+ .to_string(),
446
+ "let route = invocation.route();".to_string(),
447
+ "let facts_json = invocation.facts();".to_string(),
448
+ ];
449
+ body.push(args_line);
450
+ // Identity after the gates; `extra_lines` read identity-extra heads, so they follow.
451
+ if !id_line.is_empty() {
452
+ body.push(id_line);
453
+ }
454
+ body.extend(extra_lines);
455
+ body.push(tenant_boundary_check_line);
456
+ body.extend(deref_lines);
457
+ body.push(
458
+ "let payload = crate::kernel::Json::overlay(facts_json, &args.to_json());"
459
+ .to_string(),
460
+ );
461
+ body.push(format!(
462
+ "{dispatch_call}.map(|(_, events)| stamp_payload(events, &payload))"
463
+ ));
464
+
465
+ aggregate_arms.push(format!(
466
+ " {} => {{\n{}\n }}",
467
+ naming::ruby_inspect_string(&c.verb),
468
+ body.iter()
469
+ .map(|l| format!(" {l}"))
470
+ .collect::<Vec<_>>()
471
+ .join("\n")
472
+ ));
473
+ }
474
+ }
475
+
476
+ let mut entity_arms: Vec<String> = Vec::new();
477
+ for a in aggregates {
478
+ let mod_path = chapter_path(a);
479
+ for c in &a.entity_commands {
480
+ // The argument gates; `kernel::decode_entity_arguments` walks `EntityStep::ORDER`.
481
+ let gates_line = format!(
482
+ "let args = crate::kernel::decode_entity_arguments(facts_json, &{})?;",
483
+ emit_argument_gates_literal(
484
+ &format!("{mod_path}::{}", c.args_struct),
485
+ &c.invariant_check_lines,
486
+ emit_role_check(exemplar, c.role.as_deref(), &c.name),
487
+ &c
488
+ .reference_checks
489
+ .iter()
490
+ .map(|check| emit_reference_check(exemplar, check))
491
+ .collect::<Vec<_>>()
492
+ )
493
+ );
494
+ let dispatch_call = format!(
495
+ "{mod_path}::dispatch_entity_{}(&mut store.{}, &parent_id, &element_id, &element_wants, args, mutations, owner_deref, command_deref).map(|(_, events)| stamp_payload(events, &payload))",
496
+ c.fn_name, a.module_name
497
+ );
498
+
499
+ // Gates run before identity for routed and unrouted calls alike; the eager
500
+ // `route.require_depth(1)?` makes a wrong-depth `to:` refuse first, as in Ruby.
501
+ // A missing identity is wrapped as NotFound, as in the aggregate arm's `id_line`.
502
+ let entity_parent_no_identity_message = format!(
503
+ "{} acts on a {}'s {} — pass {}:",
504
+ c.name, a.record, c.entity_name, a.identified_by.join(", ")
505
+ );
506
+ let entity_element_no_identity_message = format!(
507
+ "{} acts on one {} — pass {}:",
508
+ c.name, c.entity_name, c.entity_identity_reading
509
+ );
510
+ // `element_id` uses `extract_id_lenient` (absent vs blank element identity);
511
+ // `parent_id` stays strict.
512
+ let body_entity_match = format!(
513
+ "let (parent_id, element_id, element_wants) = match route {{ Some(route) => {{ let element_id = route.entities()[0].clone(); (route.aggregate().to_string(), element_id.clone(), element_id) }}, None => {{ let parent_id = {mod_path}::{}::extract_id(facts_json).map_err(|_| crate::kernel::Refusal::NotFound({}.to_string()))?; let element_id = {mod_path}::{}::extract_id_lenient(facts_json).map_err(|_| crate::kernel::Refusal::NotFound({}.to_string()))?; let element_wants = {mod_path}::{}::extract_wants(facts_json); (parent_id, element_id, element_wants) }}, }};",
514
+ a.record,
515
+ naming::ruby_inspect_string(&entity_parent_no_identity_message),
516
+ c.entity_record,
517
+ naming::ruby_inspect_string(&entity_element_no_identity_message),
518
+ c.entity_record
519
+ );
520
+
521
+ let mut body: Vec<String> = vec![
522
+ "let invocation = crate::kernel::CommandInvocation::from_json(args_json)?;".to_string(),
523
+ "let route = invocation.route();".to_string(),
524
+ "let facts_json = invocation.facts();".to_string(),
525
+ "if let Some(route) = route { route.require_depth(1)?; }".to_string(),
526
+ gates_line,
527
+ body_entity_match,
528
+ ];
529
+ // `owner_deref` dereferences the parent's reference fields off `parent_id`, so
530
+ // `seeded_projections` can re-seed them on every entity-command save. `command_deref`
531
+ // also carries the parent's dereferenced state under one "parent" key.
532
+ body.push(format!(
533
+ "let owner_deref = crate::kernel::owner_deref(&*store, REFERENCE_TABLE, {}, &parent_id);",
534
+ naming::ruby_inspect_string(&format!("{}::{}", a.domain_name, a.name))
535
+ ));
536
+ body.push(format!(
537
+ "let mut command_deref = crate::kernel::command_deref(&*store, REFERENCE_TABLE, {}, &args);",
538
+ reference_specs::emit_reference_specs_literal(&c.reference_specs)
539
+ ));
540
+ body.push(format!(
541
+ "if let Some(parent_node) = crate::kernel::parent_deref(&*store, REFERENCE_TABLE, {}, &parent_id) {{ command_deref.push((\"parent\", parent_node)); }}",
542
+ naming::ruby_inspect_string(&format!("{}::{}", a.domain_name, a.name))
543
+ ));
544
+ body.push(
545
+ "let payload = crate::kernel::Json::overlay(facts_json, &args.to_json());"
546
+ .to_string(),
547
+ );
548
+ body.push(dispatch_call);
549
+
550
+ entity_arms.push(format!(
551
+ " {} => {{\n{}\n }}",
552
+ naming::ruby_inspect_string(&c.verb),
553
+ body.iter()
554
+ .map(|l| format!(" {l}"))
555
+ .collect::<Vec<_>>()
556
+ .join("\n")
557
+ ));
558
+ }
559
+ }
560
+
561
+ // Commands owned by an entity nested two levels deep. `c.unrouted_supported` selects a binding
562
+ // that accepts a route or flat args; otherwise the command is routed-only.
563
+ let mut nested_entity_arms: Vec<String> = Vec::new();
564
+ for a in aggregates {
565
+ let mod_path = chapter_path(a);
566
+ for c in &a.nested_entity_commands {
567
+ // The argument gates; see the entity arm's `gates_line`.
568
+ let gates_line = format!(
569
+ "let args = crate::kernel::decode_entity_arguments(facts_json, &{})?;",
570
+ emit_argument_gates_literal(
571
+ &format!("{mod_path}::{}", c.args_struct),
572
+ &c.invariant_check_lines,
573
+ emit_role_check(exemplar, c.role.as_deref(), &c.name),
574
+ &c
575
+ .reference_checks
576
+ .iter()
577
+ .map(|check| emit_reference_check(exemplar, check))
578
+ .collect::<Vec<_>>()
579
+ )
580
+ );
581
+ let dispatch_call = format!(
582
+ "{mod_path}::dispatch_entity_{}(&mut store.{}, &parent_id, &hop1_id, &hop1_wants, &hop2_id, &hop2_wants, args, mutations, owner_deref, command_deref).map(|(_, events)| stamp_payload(events, &payload))",
583
+ c.fn_name, a.module_name
584
+ );
585
+
586
+ // Gates before identity, as in `entity_arms`; `route.require_depth(2)?` refuses a
587
+ // wrong-depth route first. A missing identity wraps into a per-hop NotFound message.
588
+ let entity_parent_no_identity_message = format!(
589
+ "{} acts on a {}'s {}.{} — pass {}:",
590
+ c.name, a.record, c.entity_name, c.nested_name, a.identified_by.join(", ")
591
+ );
592
+ let hop1_no_identity_message = format!(
593
+ "{} acts on one {} — pass {}:",
594
+ c.name, c.entity_name, c.entity_identity_reading
595
+ );
596
+ let hop2_no_identity_message = format!(
597
+ "{} acts on one {} — pass {}:",
598
+ c.name, c.nested_name, c.nested_identity_reading
599
+ );
600
+ // Both hops resolve through `extract_id_lenient`; `parent_id` stays strict.
601
+ let route_binding = if c.unrouted_supported {
602
+ format!(
603
+ "let (parent_id, hop1_id, hop1_wants, hop2_id, hop2_wants) = match route {{ Some(route) => {{ let hop1_id = route.entities()[0].clone(); let hop2_id = route.entities()[1].clone(); (route.aggregate().to_string(), hop1_id.clone(), hop1_id, hop2_id.clone(), hop2_id) }}, None => {{ let parent_id = {mod_path}::{}::extract_id(facts_json).map_err(|_| crate::kernel::Refusal::NotFound({}.to_string()))?; let hop1_id = {mod_path}::{}::extract_id_lenient(facts_json).map_err(|_| crate::kernel::Refusal::NotFound({}.to_string()))?; let hop1_wants = {mod_path}::{}::extract_wants(facts_json); let hop2_id = {mod_path}::{}::extract_id_lenient(facts_json).map_err(|_| crate::kernel::Refusal::NotFound({}.to_string()))?; let hop2_wants = {mod_path}::{}::extract_wants(facts_json); (parent_id, hop1_id, hop1_wants, hop2_id, hop2_wants) }}, }};",
604
+ a.record,
605
+ naming::ruby_inspect_string(&entity_parent_no_identity_message),
606
+ c.entity_record,
607
+ naming::ruby_inspect_string(&hop1_no_identity_message),
608
+ c.entity_record,
609
+ c.nested_record,
610
+ naming::ruby_inspect_string(&hop2_no_identity_message),
611
+ c.nested_record
612
+ )
613
+ } else {
614
+ let route_error = format!(
615
+ "{} addresses an entity nested two levels deep — requires an explicit to: {{ aggregate:, entities: [...] }} route",
616
+ c.verb
617
+ );
618
+ format!(
619
+ "let route = route.ok_or_else(|| crate::kernel::Refusal::TypeMismatch({}.to_string()))?; let parent_id = route.aggregate().to_string(); let hop1_id = route.entities()[0].clone(); let hop2_id = route.entities()[1].clone(); let hop1_wants = hop1_id.clone(); let hop2_wants = hop2_id.clone();",
620
+ naming::ruby_inspect_string(&route_error)
621
+ )
622
+ };
623
+
624
+ let mut body: Vec<String> = vec![
625
+ "let invocation = crate::kernel::CommandInvocation::from_json(args_json)?;".to_string(),
626
+ "let route = invocation.route();".to_string(),
627
+ "let facts_json = invocation.facts();".to_string(),
628
+ ];
629
+ body.push("if let Some(route) = route { route.require_depth(2)?; }".to_string());
630
+ body.push(gates_line);
631
+ body.push(route_binding);
632
+ // `owner_deref` uses the top-level aggregate's reference fields, since projection
633
+ // seeding scopes to it, not to the nested entity.
634
+ body.push(format!(
635
+ "let owner_deref = crate::kernel::owner_deref(&*store, REFERENCE_TABLE, {}, &parent_id);",
636
+ naming::ruby_inspect_string(&format!("{}::{}", a.domain_name, a.name))
637
+ ));
638
+ body.push(format!(
639
+ "let command_deref = crate::kernel::command_deref(&*store, REFERENCE_TABLE, {}, &args);",
640
+ reference_specs::emit_reference_specs_literal(&c.reference_specs)
641
+ ));
642
+ body.push(
643
+ "let payload = crate::kernel::Json::overlay(facts_json, &args.to_json());"
644
+ .to_string(),
645
+ );
646
+ body.push(dispatch_call);
647
+
648
+ nested_entity_arms.push(format!(
649
+ " {} => {{\n{}\n }}",
650
+ naming::ruby_inspect_string(&c.verb),
651
+ body.iter()
652
+ .map(|l| format!(" {l}"))
653
+ .collect::<Vec<_>>()
654
+ .join("\n")
655
+ ));
656
+ }
657
+ }
658
+
659
+ let mut port_arms: Vec<String> = Vec::new();
660
+ for a in aggregates {
661
+ let mod_path = chapter_path(a);
662
+ for p in &a.ports {
663
+ let reference_lines: Vec<String> = p
664
+ .reference_checks
665
+ .iter()
666
+ .map(|check| emit_reference_check(exemplar, check))
667
+ .collect();
668
+ let dispatch_call = format!("{mod_path}::dispatch_operation_{}(&id, args).map(|events| stamp_payload(events, &payload))", p.fn_name);
669
+ let legacy_receiver = p
670
+ .legacy_receiver_field
671
+ .as_deref()
672
+ .map(naming::ruby_inspect_string)
673
+ .map(|field| format!("Some({field})"))
674
+ .unwrap_or_else(|| "None".to_string());
675
+ let to_receiver = p
676
+ .to_receiver_field
677
+ .as_deref()
678
+ .map(naming::ruby_inspect_string)
679
+ .map(|field| format!("Some({field})"))
680
+ .unwrap_or_else(|| "None".to_string());
681
+
682
+ let mut body: Vec<String> = vec![
683
+ "let invocation = crate::kernel::CommandInvocation::from_json(args_json)?;".to_string(),
684
+ format!("let (id, port_facts) = invocation.split_aggregate_receiver({legacy_receiver}, {to_receiver})?;"),
685
+ "let facts_json = &port_facts;".to_string(),
686
+ format!(
687
+ "let _instance = store.{}.find(&id).ok_or_else(|| crate::kernel::Refusal::NotFound(format!(\"{} {{:?}} does not exist\", id)))?;",
688
+ a.module_name, a.name
689
+ ),
690
+ format!("let args = {mod_path}::{}::from_json(facts_json)?;", p.args_struct),
691
+ ];
692
+ body.extend(reference_lines);
693
+ body.push(
694
+ "let payload = crate::kernel::Json::overlay(facts_json, &args.to_json());"
695
+ .to_string(),
696
+ );
697
+ body.push(dispatch_call);
698
+
699
+ port_arms.push(format!(
700
+ " {} => {{\n{}\n }}",
701
+ naming::ruby_inspect_string(&p.verb),
702
+ body.iter()
703
+ .map(|l| format!(" {l}"))
704
+ .collect::<Vec<_>>()
705
+ .join("\n")
706
+ ));
707
+ }
708
+ }
709
+
710
+ let mut dispatch_arms = aggregate_arms;
711
+ dispatch_arms.extend(entity_arms);
712
+ dispatch_arms.extend(nested_entity_arms);
713
+ dispatch_arms.extend(port_arms);
714
+
715
+ let header = "// GENERATED by hecks project_rust — the JSON command router\n// `kernel::cli` dispatches every step through. Do not hand-edit —\n// re-run hecks project_rust instead.\n#![allow(dead_code, unused_variables)]\n\n// `Repository::save` (from_seed, below) is a TRAIT method —\n// `InMemoryRepository`'s own inherent methods (entries(), used\n// by instances()) need no import, but save() does.\nuse crate::kernel::Repository;\n\n";
716
+
717
+ let body = exemplar.render(
718
+ "registry_file",
719
+ &[
720
+ ("TmplStore2", "Store".to_string()),
721
+ (
722
+ " pub tmpl_field: crate::kernel::InMemoryRepository<i64>,",
723
+ store_fields.join("\n"),
724
+ ),
725
+ (
726
+ " tmpl_field: tmpl_store_fields_placeholder(),",
727
+ store_inits.join("\n"),
728
+ ),
729
+ ("tmpl_dump_arm_placeholder();", dump_arms.join("\n")),
730
+ ("tmpl_seed_arm_placeholder();", seed_arms.join("\n")),
731
+ ("tmpl_query_arm_placeholder();", query_arms.join("\n")),
732
+ ("tmpl_scan_each_arm_placeholder();", scan_each_arms.join("\n")),
733
+ (
734
+ "\"tmpl_verb\" => { tmpl_dispatch_arm_placeholder() }",
735
+ dispatch_arms.join("\n"),
736
+ ),
737
+ ],
738
+ );
739
+
740
+ format!("{header}{body}")
741
+ }
742
+
743
+ /// Port of `rust/project/registry.rb#emit_reference_table`.
744
+ pub fn emit_reference_table(aggregates: &[AggregateEntry]) -> String {
745
+ let rows: Vec<String> = aggregates
746
+ .iter()
747
+ .map(|a| {
748
+ let qualified = format!("{}::{}", a.domain_name, a.name);
749
+ format!(
750
+ " ({}, {}),",
751
+ naming::ruby_inspect_string(&qualified),
752
+ reference_specs::emit_reference_specs_literal(&a.reference_specs)
753
+ )
754
+ })
755
+ .collect();
756
+
757
+ format!(
758
+ "pub static REFERENCE_TABLE: crate::kernel::ReferenceTable = &[\n{}\n];\n",
759
+ rows.join("\n")
760
+ )
761
+ }
762
+
763
+ /// Port of `rust/project/registry.rb#emit_reference_lookup`.
764
+ pub fn emit_reference_lookup(aggregates: &[AggregateEntry]) -> String {
765
+ let arms: Vec<String> = aggregates
766
+ .iter()
767
+ .map(|a| {
768
+ let prefix = format!("{}::{}", a.domain_name, a.name);
769
+ format!(
770
+ "if target == {} {{\n return self.{}.find(id).map(|r| Box::new(r) as Box<dyn crate::kernel::Fielded>);\n}}",
771
+ naming::ruby_inspect_string(&prefix),
772
+ a.module_name
773
+ )
774
+ })
775
+ .collect();
776
+
777
+ format!(
778
+ "{}\nimpl crate::kernel::ReferenceLookup for Store {{\n fn find_fielded(&self, target: &str, id: &str) -> Option<Box<dyn crate::kernel::Fielded>> {{\n{}\n None\n }}\n}}\n",
779
+ emit_reference_table(aggregates),
780
+ arms.join("\n")
781
+ )
782
+ }
783
+
784
+ #[cfg(test)]
785
+ mod tests {
786
+ use super::*;
787
+
788
+ #[test]
789
+ fn generated_router_separates_routes_and_supports_compound_create_facts() {
790
+ let aggregate = AggregateEntry {
791
+ name: "SafeDepositBox".to_string(),
792
+ module_name: "safedepositbox".to_string(),
793
+ record: "SafeDepositBox".to_string(),
794
+ commands: vec![
795
+ CommandEntry {
796
+ verb: "Banking::SafeDepositBox.Rent".to_string(),
797
+ name: "Rent".to_string(),
798
+ fn_name: "rent".to_string(),
799
+ args_struct: "RentArgs".to_string(),
800
+ creates: true,
801
+ identity_extra_params: vec![
802
+ "branch_code".to_string(),
803
+ "box_number".to_string(),
804
+ ],
805
+ reference_checks: Vec::new(),
806
+ tenant_boundary_checks: Vec::new(),
807
+ reference_specs: Vec::new(),
808
+ attributes: Vec::new(),
809
+ invariant_check_lines: Vec::new(),
810
+ role: None,
811
+ },
812
+ CommandEntry {
813
+ verb: "Banking::SafeDepositBox.Close".to_string(),
814
+ name: "Close".to_string(),
815
+ fn_name: "close".to_string(),
816
+ args_struct: "CloseArgs".to_string(),
817
+ creates: false,
818
+ identity_extra_params: Vec::new(),
819
+ reference_checks: Vec::new(),
820
+ tenant_boundary_checks: Vec::new(),
821
+ reference_specs: Vec::new(),
822
+ attributes: Vec::new(),
823
+ invariant_check_lines: Vec::new(),
824
+ role: None,
825
+ },
826
+ ],
827
+ entity_commands: vec![EntityCommandEntry {
828
+ verb: "Banking::SafeDepositBox.Visit.Annotate".to_string(),
829
+ name: "Annotate".to_string(),
830
+ entity_record: "Visit".to_string(),
831
+ fn_name: "visit_annotate".to_string(),
832
+ args_struct: "VisitAnnotateArgs".to_string(),
833
+ reference_checks: Vec::new(),
834
+ reference_specs: Vec::new(),
835
+ attributes: Vec::new(),
836
+ invariant_check_lines: Vec::new(),
837
+ role: None,
838
+ entity_name: "Visit".to_string(),
839
+ entity_identity_reading: "date, sequence".to_string(),
840
+ }],
841
+ nested_entity_commands: Vec::new(),
842
+ ports: vec![PortEntry {
843
+ verb: "Banking::SafeDepositBox.PaymentGateway.Receive".to_string(),
844
+ name: "Receive".to_string(),
845
+ fn_name: "paymentgateway_receive".to_string(),
846
+ args_struct: "PaymentGatewayReceiveArgs".to_string(),
847
+ reference_checks: Vec::new(),
848
+ legacy_receiver_field: None,
849
+ to_receiver_field: None,
850
+ }],
851
+ chapter_mod: "banking".to_string(),
852
+ domain_name: "Banking".to_string(),
853
+ reference_specs: Vec::new(),
854
+ identified_by: vec!["branch_code".to_string(), "box_number".to_string()],
855
+ entities: Vec::new(),
856
+ };
857
+
858
+ let generated = emit_registry(&Exemplar::load(), &[aggregate]);
859
+
860
+ assert!(generated.contains("CommandInvocation::from_json(args_json)?"));
861
+ assert!(generated.contains("let branch_code = facts_json.dig(\"branch_code\")"));
862
+ assert!(generated.contains("let box_number = facts_json.dig(\"box_number\")"));
863
+ // `from_json` is reached through the `normalize_args` hook; route depth leads the body and
864
+ // identity resolves after every gate.
865
+ assert!(generated.contains("crate::kernel::decode_aggregate_arguments(facts_json, &crate::kernel::ArgumentGates {"));
866
+ assert!(generated.contains("decode_arguments: &crate::generated::banking::safedepositbox::RentArgs::decode_arguments"));
867
+ assert!(generated.contains("refuse_unknown_arguments: &crate::generated::banking::safedepositbox::RentArgs::refuse_unknown_arguments"));
868
+ assert!(generated.contains("refuse_absent_arguments: &crate::generated::banking::safedepositbox::RentArgs::refuse_absent_arguments"));
869
+ assert!(generated.contains("RentArgs::from_json(v)?"));
870
+ assert!(generated.contains("if let Some(route) = route { route.require_depth(0)?; }"));
871
+ assert!(generated.contains("Some(route) => route.aggregate().to_string()"));
872
+ assert!(generated.contains("if let Some(route) = route { route.require_depth(1)?; }"));
873
+ assert!(generated.contains("crate::kernel::decode_entity_arguments(facts_json, &crate::kernel::ArgumentGates {"));
874
+ assert!(generated.contains("let element_id = route.entities()[0].clone()"));
875
+ assert!(generated.contains("VisitAnnotateArgs::from_json(v)?"));
876
+ assert!(!generated.contains("RentArgs::from_json(facts_json)?"));
877
+ assert!(generated.contains("Json::overlay(facts_json, &args.to_json())"));
878
+ assert!(!generated.contains("VisitAnnotateArgs::from_json(args_json)?"));
879
+ assert!(generated
880
+ .contains("let (id, port_facts) = invocation.split_aggregate_receiver(None, None)?;"));
881
+ assert!(generated.contains("store.safedepositbox.find(&id)"));
882
+ assert!(generated.contains("PaymentGatewayReceiveArgs::from_json(facts_json)?"));
883
+ assert!(generated.contains("dispatch_operation_paymentgateway_receive(&id, args)"));
884
+ }
885
+
886
+ // Pins that the generated code branches on `invocation.explicit_with()` at runtime: both the
887
+ // gates-first and route-depth-first orders are present, neither hard-coded.
888
+ #[test]
889
+ fn explicit_with_gates_precheck_ordering_for_an_acting_command() {
890
+ let aggregate = AggregateEntry {
891
+ name: "Hangar".to_string(),
892
+ module_name: "hangar".to_string(),
893
+ record: "Hangar".to_string(),
894
+ commands: vec![CommandEntry {
895
+ verb: "Fixture::Hangar.Prioritize".to_string(),
896
+ name: "Prioritize".to_string(),
897
+ fn_name: "prioritize".to_string(),
898
+ args_struct: "PrioritizeArgs".to_string(),
899
+ creates: false,
900
+ identity_extra_params: Vec::new(),
901
+ reference_checks: Vec::new(),
902
+ tenant_boundary_checks: Vec::new(),
903
+ reference_specs: Vec::new(),
904
+ attributes: vec!["priority".to_string()],
905
+ invariant_check_lines: Vec::new(),
906
+ role: None,
907
+ }],
908
+ entity_commands: Vec::new(),
909
+ nested_entity_commands: Vec::new(),
910
+ ports: Vec::new(),
911
+ chapter_mod: "fixture".to_string(),
912
+ domain_name: "Fixture".to_string(),
913
+ reference_specs: Vec::new(),
914
+ identified_by: vec!["code".to_string()],
915
+ entities: Vec::new(),
916
+ };
917
+
918
+ let generated = emit_registry(&Exemplar::load(), &[aggregate]);
919
+
920
+ assert!(generated.contains("let args = if invocation.explicit_with() { let args = crate::kernel::decode_aggregate_arguments(facts_json, &crate::kernel::ArgumentGates {"));
921
+ assert!(generated.contains("if let Some(route) = route { route.require_depth(0)?; } args } else { if let Some(route) = route { route.require_depth(0)?; } crate::kernel::decode_aggregate_arguments(facts_json, &crate::kernel::ArgumentGates {"));
922
+ assert!(generated.contains("Some(route) => route.aggregate().to_string()"));
923
+ }
924
+
925
+ // Pins that a failing `extract_id` in an acting command refuses NotFound
926
+ // (`acting_no_identity`), not TypeMismatch, with wording rendered at codegen time.
927
+ #[test]
928
+ fn acting_command_id_resolution_failure_refuses_not_found_not_type_mismatch() {
929
+ let aggregate = AggregateEntry {
930
+ name: "SafeDepositBox".to_string(),
931
+ module_name: "safedepositbox".to_string(),
932
+ record: "SafeDepositBox".to_string(),
933
+ commands: vec![CommandEntry {
934
+ verb: "Banking::SafeDepositBox.Close".to_string(),
935
+ name: "Close".to_string(),
936
+ fn_name: "close".to_string(),
937
+ args_struct: "CloseArgs".to_string(),
938
+ creates: false,
939
+ identity_extra_params: Vec::new(),
940
+ reference_checks: Vec::new(),
941
+ tenant_boundary_checks: Vec::new(),
942
+ reference_specs: Vec::new(),
943
+ attributes: Vec::new(),
944
+ invariant_check_lines: Vec::new(),
945
+ role: None,
946
+ }],
947
+ entity_commands: Vec::new(),
948
+ nested_entity_commands: Vec::new(),
949
+ ports: Vec::new(),
950
+ chapter_mod: "banking".to_string(),
951
+ domain_name: "Banking".to_string(),
952
+ reference_specs: Vec::new(),
953
+ identified_by: vec!["branch_code.value".to_string(), "box_number.value".to_string()],
954
+ entities: Vec::new(),
955
+ };
956
+
957
+ let generated = emit_registry(&Exemplar::load(), &[aggregate]);
958
+
959
+ // The raw `extract_id(facts_json)?` must not appear in an acting command's id_line ...
960
+ assert!(!generated.contains("SafeDepositBox::extract_id(facts_json)?,"));
961
+ // ... replaced by a wrapper converting its failure into NotFound/acting_no_identity.
962
+ assert!(generated.contains(
963
+ "SafeDepositBox::extract_id(facts_json).map_err(|_| crate::kernel::Refusal::NotFound(crate::kernel::refusal_wording::NotFoundActingNoIdentityArgs { command: \"Close\", aggregate: \"SafeDepositBox\", identity: \"branch_code.value, box_number.value\" }.render_args()))?,"
964
+ ));
965
+ }
966
+ }