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,641 @@
1
+ module RustProjection
2
+ module Projector
3
+ module_function
4
+
5
+ # Generates the WASM/CLI JSON boundary (to_json/from_json) alongside
6
+ # each Fielded impl; entities and aggregate records only get to_json.
7
+
8
+ def json_type_error(struct_name, key, expectation)
9
+ "crate::kernel::Refusal::TypeMismatch(#{"#{struct_name}.#{key}: expected #{expectation}".inspect}.to_string())"
10
+ end
11
+
12
+ # The refusal for a scalar offered where a list argument goes; worded as Ruby's
13
+ # `Value.refuse_scalar_list`, with the offered value known only at runtime.
14
+ def list_shape_error(struct_name, key, element_type, value_var)
15
+ template = "#{struct_name}.#{key} expects list_of(#{element_type}), got {}"
16
+ "crate::kernel::Refusal::TypeMismatch(format!(#{template.inspect}, #{value_var}.inspect()))"
17
+ end
18
+
19
+ # A String mismatch also reports TypeMismatch when the value is an
20
+ # Array/Hash/Null, matching Ruby's laxer scalar-shape check; other
21
+ # scalar mismatches fall through to the generic wording.
22
+ def scalar_type_error(struct_name, key, scalar_type, value_var)
23
+ return json_type_error(struct_name, key, scalar_type) unless %w[Integer Float String].include?(scalar_type)
24
+
25
+ template = "#{struct_name}.#{key} expects #{scalar_type}, got {}"
26
+ proper = "crate::kernel::Refusal::TypeMismatch(format!(#{template.inspect}, #{value_var}.inspect()))"
27
+ return proper if scalar_type != "String"
28
+
29
+ generic = json_type_error(struct_name, key, scalar_type)
30
+ "if matches!(#{value_var}, crate::kernel::Json::Array(_) | crate::kernel::Json::Object(_) | crate::kernel::Json::Null) " \
31
+ "{ #{proper} } else { #{generic} }"
32
+ end
33
+
34
+ # A missing required field refuses with the same wording as an
35
+ # explicit null value (Ruby's check_required_fields).
36
+ def required_field_expr(struct_name, key, expected)
37
+ message = "#{struct_name}.#{key} expects #{expected}, got nil"
38
+ "v.get(#{key.inspect}).ok_or_else(|| crate::kernel::Refusal::TypeMismatch(#{message.inspect}.to_string()))?"
39
+ end
40
+
41
+ # Boolean fields read via as_bool, mirroring naming.rb's SCALAR table.
42
+ SCALAR_JSON_ACCESSOR = { "String" => "as_str", "Integer" => "as_i64", "Float" => "as_f64",
43
+ "TrueClass" => "as_bool", "FalseClass" => "as_bool" }.freeze
44
+
45
+ # A single-attribute value object also accepts a bare JSON scalar in
46
+ # place of the wrapped {"field": ...} shape, matching Ruby's coercion.
47
+ def sole_field_of(type_name, value_objects_by_name)
48
+ vo = value_objects_by_name[type_name]
49
+ return nil unless vo && vo[:attributes]&.size == 1
50
+
51
+ vo[:attributes].first[:name].to_s
52
+ end
53
+
54
+ # Wraps a single-field value object's value with coerce_single_field
55
+ # before calling its from_json, so the bare-scalar shorthand applies.
56
+ def composite_from_json_expr(attr, value_objects_by_name, value_expr)
57
+ nested_type = rust_ident(attr[:type])
58
+ sole = sole_field_of(attr[:type], value_objects_by_name)
59
+ source =
60
+ if sole
61
+ "&#{value_expr}.coerce_single_field(#{sole.inspect})"
62
+ else
63
+ "#{value_expr}.expect_value_object_shape(#{attr[:name].to_s.inspect}, #{attr[:type].to_s.inspect})?"
64
+ end
65
+ "#{nested_type}::from_json(#{source})?"
66
+ end
67
+
68
+ # A scalar-represented list element (e.g. list_of(String), or a
69
+ # has_many reference) reads like any other scalar field rather than
70
+ # calling a from_json that was never generated for it.
71
+ def list_element_from_json_mapper(struct_name, key, attr, value_objects_by_name)
72
+ scalar = effective_scalar_type(attr[:type])
73
+ if scalar
74
+ body = scalar_from_json_value_expr(struct_name, key, scalar, "item")
75
+ return "|item| #{body.sub(/\?\z/, '')}"
76
+ end
77
+
78
+ nested_type = rust_ident(attr[:type])
79
+ sole = sole_field_of(attr[:type], value_objects_by_name)
80
+ return "|item| #{nested_type}::from_json(&item.coerce_single_field(#{sole.inspect}))" if sole
81
+
82
+ "#{nested_type}::from_json"
83
+ end
84
+
85
+ # The inverse of list_element_from_json_mapper: a scalar-represented
86
+ # element serializes like any other scalar field.
87
+ def list_element_to_json_expr(attr)
88
+ scalar = effective_scalar_type(attr[:type])
89
+ scalar ? scalar_to_json_expr(scalar, "x") : "x.to_json()"
90
+ end
91
+
92
+ # A required value-object argument given a bare JSON null builds from
93
+ # an empty object rather than one literal null field, matching Ruby's
94
+ # leniency for a command-argument door (not a nested field's own null).
95
+ def required_composite_argument_expr(struct_name, key, attr, value_objects_by_name)
96
+ fetch = required_field_expr(struct_name, key, attr[:type])
97
+ nested_type = rust_ident(attr[:type])
98
+ sole = sole_field_of(attr[:type], value_objects_by_name)
99
+ guarded = "match #{fetch} { crate::kernel::Json::Null => crate::kernel::Json::Object(Vec::new()), other => other.clone() }"
100
+ return "#{nested_type}::from_json(&(#{guarded}).coerce_single_field(#{sole.inspect}))?" if sole
101
+
102
+ "#{nested_type}::from_json((#{guarded}).expect_value_object_shape(#{attr[:name].to_s.inspect}, " \
103
+ "#{attr[:type].to_s.inspect})?)?"
104
+ end
105
+
106
+ # A scalar field missing from the caller's JSON falls back to the
107
+ # attribute's own default:, matching Value.build's rule.
108
+ def scalar_from_json_expr(struct_name, key, scalar_type, default: nil)
109
+ accessor = SCALAR_JSON_ACCESSOR.fetch(scalar_type)
110
+ wrap = scalar_type == "String" ? ".map(|s| s.to_string())" : ""
111
+ # Matches on presence first so a present-but-wrong-shape value still
112
+ # refuses instead of silently falling back to the default.
113
+ #
114
+ # default.nil?, not `if default` — a boolean attribute's own default
115
+ # can legitimately be false, which plain Ruby truthiness would treat
116
+ # as "no default."
117
+ unless default.nil?
118
+ return %(match v.get(#{key.inspect}) { Some(x) => x.#{accessor}()#{wrap}.ok_or_else(|| #{scalar_type_error(struct_name,
119
+ key, scalar_type, 'x')})?, None => #{literal_rhs(default)} })
120
+ end
121
+
122
+ %({ let x = #{required_field_expr(struct_name, key,
123
+ scalar_type)}; x.#{accessor}()#{wrap}.ok_or_else(|| #{scalar_type_error(struct_name, key,
124
+ scalar_type, 'x')})? })
125
+ end
126
+
127
+ # scalar_from_json_expr's scalar-extraction half, applied to a value
128
+ # already looked up (avoids a second key lookup).
129
+ def scalar_from_json_value_expr(struct_name, key, scalar_type, value_expr)
130
+ accessor = SCALAR_JSON_ACCESSOR.fetch(scalar_type)
131
+ wrap = scalar_type == "String" ? ".map(|s| s.to_string())" : ""
132
+ %(#{value_expr}.#{accessor}()#{wrap}.ok_or_else(|| #{scalar_type_error(struct_name, key, scalar_type, value_expr)})?)
133
+ end
134
+
135
+ def scalar_to_json_expr(scalar_type, rust_expr)
136
+ case scalar_type
137
+ when "String" then "crate::kernel::Json::Str(#{rust_expr}.clone())"
138
+ when "Integer" then "crate::kernel::Json::int(#{rust_expr})"
139
+ when "Float" then "crate::kernel::Json::Float(#{rust_expr})"
140
+ when "TrueClass", "FalseClass" then "crate::kernel::Json::Bool(#{rust_expr})"
141
+ end
142
+ end
143
+
144
+ # The allowlist of names a caller may address a command by: declared
145
+ # attributes plus id, the aggregate's identity heads, any reference
146
+ # key, and correlation keys a saga may smuggle through.
147
+ #
148
+ # extra_identity_heads: an entity dispatch also addresses the entity's
149
+ # own identity head, read directly out of args rather than declared.
150
+ def command_argument_allowlist(aggregate, command, process_managers, extra_identity_heads: [])
151
+ reference_key = command[:references].to_s.empty? ? nil : Hecks::Naming.reference_key(command[:references]).to_s
152
+ identity_heads = aggregate[:identified_by].map { |path| path.split(".").first }
153
+ correlation_keys = Array(process_managers).filter_map { |pm| pm[:correlates_by]&.split(".")&.first }
154
+ (["id", reference_key] + identity_heads + extra_identity_heads + correlation_keys).compact.uniq
155
+ end
156
+
157
+ # Emits the AbsentArgument check: every required, undeclared-missing
158
+ # name the caller's JSON omits, sorted, refused before any field is
159
+ # built (ADR 0037 finding 3). Only ever emitted for a command's own
160
+ # args struct — a value object's missing field is required_field_expr's
161
+ # job.
162
+ def emit_absent_argument_check(command_name, attributes)
163
+ required = attributes.reject { |a| a[:optional] }.map { |a| rust_field(a[:name]) }.sort
164
+ return "" if required.empty?
165
+
166
+ declared = attributes.map { |a| a[:name].to_s }
167
+ <<~RUST
168
+ let absent: Vec<&str> = [#{required.map(&:inspect).join(', ')}].into_iter().filter(|key| v.get(key).is_none()).collect();
169
+ if !absent.is_empty() {
170
+ return Err(crate::kernel::Refusal::AbsentArgument(crate::kernel::refusal_wording::AbsentArgumentAbsentArgsArgs {
171
+ command: #{command_name.inspect},
172
+ absent: &absent,
173
+ declared: &[#{declared.map(&:inspect).join(', ')}],
174
+ }.render_args()));
175
+ }
176
+ RUST
177
+ end
178
+
179
+ # Emits the UnknownArgument check: any key outside known_keys refuses
180
+ # before any field is built, using the shared refusal-wording template.
181
+ def emit_unknown_argument_check(command_name, known_keys, declared_names)
182
+ declared = declared_names.map(&:to_s)
183
+ <<~RUST
184
+ let unknown = v.unknown_keys(&[#{known_keys.map(&:inspect).join(', ')}]);
185
+ if !unknown.is_empty() {
186
+ let unknown: Vec<&str> = unknown.iter().map(|key| key.as_str()).collect();
187
+ return Err(crate::kernel::Refusal::UnknownArgument(crate::kernel::refusal_wording::UnknownArgumentUnknownArgsArgs {
188
+ command: #{command_name.inspect},
189
+ unknown: &unknown,
190
+ declared: &[#{declared.map(&:inspect).join(', ')}],
191
+ }.render_args()));
192
+ }
193
+ RUST
194
+ end
195
+
196
+ # Builds one field's RHS. interleave_checks: true (every command/
197
+ # entity-command Args struct) builds each field's shape then runs its
198
+ # own invariant check immediately, before the next field starts, so an
199
+ # earlier argument's invariant failure wins over a later argument's
200
+ # shape failure — matching Ruby's per-argument dispatch order exactly.
201
+ def flat_field_rhs(struct_name, attr, key, value_objects_by_name, absent_argument_check)
202
+ scalar = effective_scalar_type(attr[:type])
203
+ if attr[:list] && attr[:optional]
204
+ # Option<Vec<T>> — absent key means None, unlike a required list
205
+ # argument, which defaults to an empty Vec below.
206
+ mapper = list_element_from_json_mapper(struct_name, key, attr, value_objects_by_name)
207
+ array_error = json_type_error(struct_name, key, "an array")
208
+ "match v.get(#{key.inspect}) { " \
209
+ "Some(x) => Some(x.as_array().ok_or_else(|| #{array_error})?.iter().map(#{mapper}).collect::<Result<Vec<_>, crate::kernel::Refusal>>()?), " \
210
+ "None => None, }".sub("match v.get(#{key.inspect}) { ", "match v.get(#{key.inspect}) { Some(crate::kernel::Json::Null) | None => None, ").sub(", None => None, }", " }")
211
+ elsif attr[:list]
212
+ # A list argument is an array: a lone scalar is refused, as Ruby's
213
+ # `Value.refuse_scalar_list` does; null and an absent key are the empty list.
214
+ mapper = list_element_from_json_mapper(struct_name, key, attr, value_objects_by_name)
215
+ shape_error = list_shape_error(struct_name, key, attr[:type], "x")
216
+ "match v.get(#{key.inspect}) { Some(crate::kernel::Json::Null) | None => Vec::new(), " \
217
+ "Some(x) => x.as_array().ok_or_else(|| #{shape_error})?.iter().map(#{mapper}).collect::<Result<Vec<_>, crate::kernel::Refusal>>()?, }"
218
+ elsif attr[:optional] && scalar
219
+ # A null value is the same absence as an omitted key for an
220
+ # optional argument (Ruby's nil passthrough).
221
+ "match v.get(#{key.inspect}) { Some(crate::kernel::Json::Null) | None => None, Some(x) => Some(#{scalar_from_json_value_expr(
222
+ struct_name, key, scalar, 'x'
223
+ )}) }"
224
+ elsif attr[:optional]
225
+ "match v.get(#{key.inspect}) { Some(crate::kernel::Json::Null) | None => None, Some(x) => Some(#{composite_from_json_expr(
226
+ attr, value_objects_by_name, 'x'
227
+ )}) }"
228
+ elsif scalar
229
+ scalar_from_json_expr(struct_name, key, scalar, default: attr[:default])
230
+ elsif absent_argument_check
231
+ required_composite_argument_expr(struct_name, key, attr, value_objects_by_name)
232
+ else
233
+ composite_from_json_expr(attr, value_objects_by_name, required_field_expr(struct_name, key, attr[:type]))
234
+ end
235
+ end
236
+
237
+ # Builds the unknown-then-absent argument checks shared by
238
+ # emit_argument_gates' two gate functions, matching dispatch order.
239
+ def unknown_and_absent_argument_checks(command_name, attributes, unknown_argument_allowlist, absent_argument_check)
240
+ check =
241
+ if unknown_argument_allowlist
242
+ known_keys = (attributes.map { |a| rust_field(a[:name]) } + unknown_argument_allowlist).uniq
243
+ emit_unknown_argument_check(command_name, known_keys, attributes.map { |a| a[:name] })
244
+ else
245
+ ""
246
+ end
247
+ check += emit_absent_argument_check(command_name, attributes) if absent_argument_check
248
+ check
249
+ end
250
+
251
+ # Emits one gate function per declared dispatch step, called in order
252
+ # by kernel::ArgumentGates; reordering the steps in vocabulary.bluebook
253
+ # changes which refusal wins with no generator change.
254
+ def emit_argument_gates(struct_name, command_name, attributes, unknown_argument_allowlist)
255
+ unknown = if unknown_argument_allowlist
256
+ unknown_and_absent_argument_checks(command_name, attributes,
257
+ unknown_argument_allowlist, false)
258
+ else
259
+ ""
260
+ end
261
+ absent = emit_absent_argument_check(command_name, attributes)
262
+
263
+ [
264
+ "impl #{struct_name} {",
265
+ argument_gate_fn("decode_arguments", emit_object_shape_check(struct_name)),
266
+ "",
267
+ argument_gate_fn("refuse_unknown_arguments", unknown),
268
+ "",
269
+ argument_gate_fn("refuse_absent_arguments", absent),
270
+ "}\n"
271
+ ].join("\n")
272
+ end
273
+
274
+ # An empty body still gets its own function (the kernel calls every
275
+ # declared step for every command); its parameter is named _v so an
276
+ # empty body never warns.
277
+ def argument_gate_fn(name, body)
278
+ parameter = body.empty? ? "_v" : "v"
279
+ " pub fn #{name}(#{parameter}: &crate::kernel::Json) -> Result<(), crate::kernel::Refusal> {\n#{body} Ok(())\n }"
280
+ end
281
+
282
+ def emit_from_json_flat(struct_name, attributes, value_objects_by_name, unknown_argument_allowlist: nil, command_name: struct_name,
283
+ absent_argument_check: false, interleave_checks: false, aggregates_by_name: nil)
284
+ idents = attributes.map { |attr| rust_ident_field(attr[:name]) }
285
+ field_exprs = attributes.zip(idents).map do |attr, ident|
286
+ key = rust_field(attr[:name])
287
+ rhs = flat_field_rhs(struct_name, attr, key, value_objects_by_name, absent_argument_check)
288
+
289
+ if interleave_checks
290
+ checks = argument_check_lines(attr, ident, aggregates_by_name, value_objects_by_name)
291
+ ([" let #{ident} = #{rhs};"] + checks).join("\n")
292
+ else
293
+ Exemplar.render("field_assignment", "tmpl_ident" => ident, "tmpl_rhs_placeholder()" => rhs)
294
+ end
295
+ end
296
+
297
+ unknown_check = unknown_and_absent_argument_checks(command_name, attributes, unknown_argument_allowlist,
298
+ absent_argument_check)
299
+
300
+ emit_from_json_skeleton(struct_name, field_exprs, unknown_check, shorthand_fields: interleave_checks ? idents : nil)
301
+ end
302
+
303
+ # Checked before any field is read: a composite value offered as
304
+ # anything but an object (or the single-field auto-wrap) would
305
+ # otherwise default every field silently when each declares a
306
+ # default:, instead of refusing.
307
+ def emit_object_shape_check(struct_name)
308
+ message = "#{struct_name} expects an object"
309
+ <<~RUST
310
+ if !matches!(v, crate::kernel::Json::Object(_)) {
311
+ return Err(crate::kernel::Refusal::TypeMismatch(format!("#{message}, got {}", v.inspect())));
312
+ }
313
+ RUST
314
+ end
315
+
316
+ # shorthand_fields: nil keeps field_exprs as complete `ident: rhs,`
317
+ # struct-literal lines. An Array (interleaved callers) instead folds
318
+ # already-terminated let+check blocks into the preamble, closing the
319
+ # struct literal over plain field-init shorthand.
320
+ def emit_from_json_skeleton(struct_name, field_exprs, unknown_check, shorthand_fields: nil)
321
+ preamble = emit_object_shape_check(struct_name) + unknown_check
322
+ field_block =
323
+ if shorthand_fields
324
+ preamble += field_exprs.map { |f| "#{f}\n" }.join
325
+ shorthand_fields.map { |f| " #{f}," }.join("\n")
326
+ else
327
+ field_exprs.map { |f| " #{f}" }.join("\n")
328
+ end
329
+ # Trailing "\n" — see emit_to_json_flat's own comment on why.
330
+ "#{Exemplar.render(
331
+ 'from_json_flat',
332
+ 'TmplFlatType2' => struct_name,
333
+ "let _tmpl_unknown_check_placeholder = ();\n Ok(Self {" => "#{preamble} Ok(Self {",
334
+ 'tmpl_ident: tmpl_rhs_placeholder(),' => field_block
335
+ )}\n"
336
+ end
337
+
338
+ # optional: true Option-wraps every non-list field (aggregate records);
339
+ # extra_fields appends verbatim pairs (used for the lifecycle field,
340
+ # which is never Option-wrapped). sparse: true drops a
341
+ # (key, Json::Null) pair entirely, matching Ruby's payload: args for
342
+ # command-args serialization; sparse: false keeps every key, correct
343
+ # for a persisted record.
344
+ def emit_to_json_flat(struct_name, attributes, value_objects_by_name, optional: false, extra_fields: [], aggregate: nil,
345
+ sparse: false)
346
+ field_exprs = attributes.map do |attr|
347
+ ident = rust_ident_field(attr[:name])
348
+ key = rust_field(attr[:name])
349
+ scalar = effective_scalar_type(attr[:type])
350
+ # attr[:optional] is data-driven off the declaration, not a
351
+ # runtime emptiness check — an empty-but-required field (e.g. an
352
+ # unmatched ledger) must stay required.
353
+ #
354
+ # aggregate is passed only for a record's own to_json; the
355
+ # aggregate attribute's own optional: flag can differ from the
356
+ # command argument's, so list_attr_creation_optional? is the
357
+ # record-level equivalent check.
358
+ record_optional_list = attr[:list] && aggregate && list_attr_creation_optional?(aggregate, attr[:name],
359
+ value_objects_by_name)
360
+ field_optional = optional || attr[:optional]
361
+ # A record's own list field is Option-wrapped only per
362
+ # list_attr_creation_optional? — using attr[:optional] directly
363
+ # here would call as_ref on a plain Vec<T> field that was never
364
+ # Option-wrapped.
365
+ list_is_optional = aggregate ? record_optional_list : attr[:optional]
366
+ elem_to_json = list_element_to_json_expr(attr) if attr[:list]
367
+ value_expr =
368
+ if attr[:list] && list_is_optional
369
+ "self.#{ident}.as_ref().map(|v| crate::kernel::Json::Array(v.iter().map(|x| #{elem_to_json}).collect())).unwrap_or(crate::kernel::Json::Null)"
370
+ elsif attr[:list]
371
+ "crate::kernel::Json::Array(self.#{ident}.iter().map(|x| #{elem_to_json}).collect())"
372
+ elsif field_optional && scalar
373
+ "self.#{ident}.as_ref().map(|v| #{scalar_to_json_expr(scalar, 'v')}).unwrap_or(crate::kernel::Json::Null)"
374
+ elsif field_optional
375
+ "self.#{ident}.as_ref().map(|v| v.to_json()).unwrap_or(crate::kernel::Json::Null)"
376
+ elsif scalar
377
+ scalar_to_json_expr(scalar, "self.#{ident}")
378
+ else
379
+ "self.#{ident}.to_json()"
380
+ end
381
+ Exemplar.render("to_json_field", '"tmpl_field_name"' => key.inspect, "tmpl_json_value_placeholder()" => value_expr)
382
+ end
383
+ field_exprs += extra_fields.map do |key, expr|
384
+ Exemplar.render("to_json_field", '"tmpl_field_name"' => key.inspect, "tmpl_json_value_placeholder()" => expr)
385
+ end
386
+ field_block = field_exprs.map { |f| " #{f}" }.join("\n")
387
+
388
+ # Trailing "\n" is required because commands.rb's entity-command
389
+ # heredoc interpolates this return value directly.
390
+ rendered = if sparse
391
+ Exemplar.render("to_json_flat_sparse", "TmplFlatType3" => struct_name,
392
+ "tmpl_to_json_field_block_sparse()" => field_block)
393
+ else
394
+ Exemplar.render("to_json_flat", "TmplFlatType2" => struct_name,
395
+ "tmpl_to_json_field_block()" => field_block)
396
+ end
397
+ "#{rendered}\n"
398
+ end
399
+
400
+ # The inverse of emit_to_json_flat's record/entity to_json, not a
401
+ # third case of emit_from_json_flat: here an unset Option field's key
402
+ # is always present with value Json::Null (matching Ruby's JSON
403
+ # round-trip), so Some(&Json::Null) reads as absence everywhere
404
+ # emit_from_json_flat only ever checks for a missing key. Used by
405
+ # Store::from_seed (rust/host) to seed prior state directly instead
406
+ # of replaying command history.
407
+ def emit_from_json_state(struct_name, attributes, value_objects_by_name, optional: false, extra_fields: [], aggregate: nil)
408
+ field_exprs = attributes.map do |attr|
409
+ ident = rust_ident_field(attr[:name])
410
+ key = rust_field(attr[:name])
411
+ scalar = effective_scalar_type(attr[:type])
412
+ record_optional_list = attr[:list] && aggregate && list_attr_creation_optional?(aggregate, attr[:name],
413
+ value_objects_by_name)
414
+ list_is_optional = aggregate ? record_optional_list : attr[:optional]
415
+ field_optional = optional || attr[:optional]
416
+
417
+ rhs =
418
+ if attr[:list] && list_is_optional
419
+ mapper = list_element_from_json_mapper(struct_name, key, attr, value_objects_by_name)
420
+ array_error = json_type_error(struct_name, key, "an array")
421
+ "match v.get(#{key.inspect}) { " \
422
+ "Some(&crate::kernel::Json::Null) | None => None, " \
423
+ "Some(x) => Some(x.as_array().ok_or_else(|| #{array_error})?.iter().map(#{mapper}).collect::<Result<Vec<_>, crate::kernel::Refusal>>()?), }"
424
+ elsif attr[:list]
425
+ mapper = list_element_from_json_mapper(struct_name, key, attr, value_objects_by_name)
426
+ "match v.get(#{key.inspect}).and_then(crate::kernel::Json::as_array) { " \
427
+ "Some(items) => items.iter().map(#{mapper}).collect::<Result<Vec<_>, crate::kernel::Refusal>>()?, " \
428
+ "None => Vec::new(), }"
429
+ elsif field_optional && scalar
430
+ "match v.get(#{key.inspect}) { " \
431
+ "Some(&crate::kernel::Json::Null) | None => None, " \
432
+ "Some(x) => Some(#{scalar_from_json_value_expr(struct_name, key, scalar, 'x')}), }"
433
+ elsif field_optional
434
+ "match v.get(#{key.inspect}) { " \
435
+ "Some(&crate::kernel::Json::Null) | None => None, " \
436
+ "Some(x) => Some(#{composite_from_json_expr(attr, value_objects_by_name, 'x')}), }"
437
+ elsif scalar
438
+ scalar_from_json_expr(struct_name, key, scalar, default: attr[:default])
439
+ else
440
+ composite_from_json_expr(attr, value_objects_by_name, "v.require(#{key.inspect}, #{struct_name.inspect})?")
441
+ end
442
+ Exemplar.render("field_assignment", "tmpl_ident" => ident, "tmpl_rhs_placeholder()" => rhs)
443
+ end
444
+
445
+ # extra_fields carries (key, serialize_expr[, deserialize_rhs])
446
+ # tuples; deserialize_rhs is required for anything that isn't the
447
+ # lifecycle field's always-String shape (e.g. a bool flag). Omitted,
448
+ # the String reader below still applies.
449
+ extra_field_exprs = extra_fields.map do |key, _serialize_expr, deserialize_rhs|
450
+ ident = rust_ident_field(key)
451
+ rhs = deserialize_rhs || "v.require(#{key.inspect}, #{struct_name.inspect})?.as_str()" \
452
+ ".ok_or_else(|| #{json_type_error(struct_name, key, 'a string')})?.to_string()"
453
+ Exemplar.render("field_assignment", "tmpl_ident" => ident, "tmpl_rhs_placeholder()" => rhs)
454
+ end
455
+
456
+ emit_from_json_skeleton(struct_name, field_exprs + extra_field_exprs, "")
457
+ end
458
+
459
+ # A single-field closed set only; emit_value_object's multi-attribute
460
+ # branch generates neither Fielded nor invariant checking, so it gets
461
+ # no JSON codec either. The admitted-member wording is resolved here
462
+ # at codegen time since every member is already known statically.
463
+ def emit_closed_set_codec(vo)
464
+ name = rust_ident(vo[:name])
465
+ sole_attribute = vo[:attributes].first
466
+ field_name = rust_field(sole_attribute[:name])
467
+ rows = vo[:members].map { |row| [closed_set_variant(row), row.first.last.to_s] }
468
+
469
+ # Both arm shapes need `TmplMemberA`/`tmpl_member_a` per row, so one
470
+ # subs hash serves both `render_each` calls below.
471
+ row_subs = rows.map { |variant, raw| { "TmplKind" => name, "TmplMemberA" => variant, '"tmpl_member_a"' => raw.inspect } }
472
+
473
+ type_name = vo[:name].to_s
474
+ # The member list goes over raw; the wording itself is
475
+ # InvariantViolationClosedSetMemberArgs::render_args's own
476
+ # RefusalSiteArgument row.
477
+ admitted = "[#{rows.map { |_variant, raw| raw.inspect }.join(', ')}]"
478
+ # Uses the same missing-key wording required_field_expr gives every
479
+ # other composite field, resolved here since the sole field's name
480
+ # and type are already known statically.
481
+ null_message = "#{type_name}.#{sole_attribute[:name]} expects #{sole_attribute[:type]}, got nil"
482
+
483
+ Exemplar.assemble(
484
+ "closed_set_codec",
485
+ {
486
+ "TmplKind" => name,
487
+ '"tmpl_field_name"' => field_name.inspect,
488
+ '"tmpl_closed_set_type"' => type_name.inspect,
489
+ '["tmpl_closed_set_member_a"]' => admitted,
490
+ '"tmpl_null_field_message"' => null_message.inspect
491
+ },
492
+ slots: {
493
+ "closed_set_codec:TO_JSON_ARM" => Exemplar.render_each("closed_set_codec:TO_JSON_ARM", row_subs),
494
+ "closed_set_codec:FROM_JSON_ARM" => Exemplar.render_each("closed_set_codec:FROM_JSON_ARM", row_subs)
495
+ }
496
+ )
497
+ end
498
+
499
+ # A multi-field closed set is a data table, not a tag enum, with
500
+ # &'static str fields — from_json can only select and clone one of
501
+ # the table's fixed rows, since nothing can construct a fresh
502
+ # &'static str from parsed JSON.
503
+ def emit_closed_set_table_codec(vo)
504
+ name = rust_ident(vo[:name])
505
+ const_name = screaming_snake(vo[:name])
506
+
507
+ to_json_fields = vo[:attributes].map do |attr|
508
+ key = rust_field(attr[:name])
509
+ ident = rust_ident_field(attr[:name])
510
+ scalar = effective_scalar_type(attr[:type])
511
+ value_expr =
512
+ case scalar
513
+ when "String" then "crate::kernel::Json::Str(self.#{ident}.to_string())"
514
+ when "Integer" then "crate::kernel::Json::int(self.#{ident})"
515
+ when "Float" then "crate::kernel::Json::Float(self.#{ident})"
516
+ when "TrueClass", "FalseClass" then "crate::kernel::Json::Bool(self.#{ident})"
517
+ end
518
+ Exemplar.render("to_json_field", '"tmpl_field_name"' => key.inspect, "tmpl_json_value_placeholder()" => value_expr)
519
+ end
520
+ to_json_fields_block = to_json_fields.map { |f| " #{f}" }.join("\n")
521
+
522
+ match_conditions = vo[:attributes].map do |attr|
523
+ key = rust_field(attr[:name])
524
+ ident = rust_ident_field(attr[:name])
525
+ accessor = SCALAR_JSON_ACCESSOR.fetch(effective_scalar_type(attr[:type]))
526
+ Exemplar.render(
527
+ "closed_set_table_from_json_condition",
528
+ '"tmpl_field_name"' => key.inspect,
529
+ "tmpl_accessor_fn" => "crate::kernel::Json::#{accessor}",
530
+ "tmpl_field" => ident
531
+ )
532
+ end
533
+
534
+ Exemplar.render(
535
+ "closed_set_table_codec",
536
+ "TmplTableRow" => name,
537
+ "tmpl_to_json_fields_block()" => to_json_fields_block,
538
+ "TMPL_TABLE" => const_name,
539
+ "tmpl_from_json_conditions()" => match_conditions.join(" && ")
540
+ )
541
+ end
542
+
543
+ # Whether every identified_by component resolves to a dotted path or
544
+ # a bare declared attribute; a third addressing-key shape has no JSON
545
+ # source under this CLI's step shape and is skipped by the caller.
546
+ def extract_id_supported?(aggregate)
547
+ aggregate[:identified_by].all? do |path|
548
+ head, *rest = path.split(".")
549
+ rest.any? || head
550
+ end
551
+ end
552
+
553
+ # Mirrors CommandInterpreter#hydrate's three-tier identity chain:
554
+ # identity_of, then identity_from(:id), then identity_from(reference
555
+ # key) — needed because a process manager's with: forwarding can
556
+ # supply only the reference argument, not the identity field itself.
557
+ def emit_extract_id(aggregate)
558
+ emit_extract_id_shaped(aggregate, method_name: "extract_id", coercion: "to_id_component")
559
+ end
560
+
561
+ # The same extract_id shape using to_id_component_lenient instead of
562
+ # the strict coercion; wired only into entity/nested-entity addressing,
563
+ # never a root aggregate's own hydrate.
564
+ def emit_extract_id_lenient(entity)
565
+ emit_extract_id_shaped(entity, method_name: "extract_id_lenient", coercion: "to_id_component_lenient")
566
+ end
567
+
568
+ def emit_extract_id_shaped(aggregate, method_name:, coercion:)
569
+ name = rust_ident(aggregate[:name])
570
+ reference_key = Hecks::Naming.snake(aggregate[:name])
571
+
572
+ # tmpl_id_coercion also goes into each tier1_subs entry, since the
573
+ # nested TIER1_LINE slot renders independently of the outer subs.
574
+ tier1_subs = aggregate[:identified_by].each_with_index.map do |path, i|
575
+ { '"tmpl_path"' => path.inspect, "c0" => "c#{i}", "tmpl_id_coercion" => coercion }
576
+ end
577
+ tier1_join =
578
+ if aggregate[:identified_by].size == 1
579
+ "c0"
580
+ else
581
+ "vec![#{aggregate[:identified_by].size.times.map { |i| "c#{i}" }.join(', ')}].join(\":\")"
582
+ end
583
+
584
+ tried = (aggregate[:identified_by] + ["id", reference_key]).join(", ")
585
+
586
+ Exemplar.compose(
587
+ "extract_id",
588
+ {
589
+ "TmplExtractIdType" => name,
590
+ "tmpl_extract_id_name" => method_name,
591
+ "tmpl_id_coercion" => coercion,
592
+ '"tmpl_reference_key"' => reference_key.inspect,
593
+ "tmpl_tier1_join_placeholder()" => tier1_join,
594
+ '"tmpl_error_text"' => "#{name}: no identity found (tried #{tried})".inspect
595
+ },
596
+ field_id: "extract_id:TIER1_LINE",
597
+ field_subs_list: tier1_subs
598
+ )
599
+ end
600
+
601
+ # Ruby's own `wants` (element_of) is tier-1-only, unlike extract_id's
602
+ # three-tier chain, since every identity-path head is already known
603
+ # present by the time wants is computed. Joined with ", ", not ":".
604
+ def emit_extract_wants(entity)
605
+ name = rust_ident(entity[:name])
606
+
607
+ tier1_subs = entity[:identified_by].each_with_index.map { |path, i| { '"tmpl_path"' => path.inspect, "c0" => "c#{i}" } }
608
+ wants_join =
609
+ if entity[:identified_by].size == 1
610
+ "c0"
611
+ else
612
+ "vec![#{entity[:identified_by].size.times.map { |i| "c#{i}" }.join(', ')}].join(\", \")"
613
+ end
614
+
615
+ Exemplar.compose(
616
+ "extract_wants",
617
+ {
618
+ "TmplExtractWantsType" => name,
619
+ "tmpl_wants_join_placeholder()" => wants_join
620
+ },
621
+ field_id: "extract_wants:TIER1_LINE",
622
+ field_subs_list: tier1_subs
623
+ )
624
+ end
625
+
626
+ # An entity element's own identity read off an already-constructed
627
+ # Rust value rather than raw JSON, using the same dotted-path,
628
+ # join-with-":" shape extract_id produces, so dispatch_entity's
629
+ # matches closure can compare as plain strings.
630
+ def emit_self_identity(entity)
631
+ name = rust_ident(entity[:name])
632
+ components = entity[:identified_by].map do |path|
633
+ head, *rest = path.split(".")
634
+ (["self.#{rust_ident_field(head)}"] + rest.map { |seg| ".#{rust_ident_field(seg)}" }).join + ".to_string()"
635
+ end
636
+ body = components.size == 1 ? components.first : "vec![#{components.join(', ')}].join(\":\")"
637
+
638
+ Exemplar.render("self_identity", "TmplSelfIdentityType" => name, "tmpl_identity_body_placeholder()" => body)
639
+ end
640
+ end
641
+ end