hecks 2.9.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (600) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +51 -6
  3. data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
  4. data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
  5. data/lib/hecks/adapters/driven/lambda.rb +1 -1
  6. data/lib/hecks/adapters/driven/memory.rb +16 -3
  7. data/lib/hecks/adapters/driven/postgres/reconnect.rb +6 -2
  8. data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
  9. data/lib/hecks/adapters/driven/postgres.rb +13 -4
  10. data/lib/hecks/adapters/driven/prism.rb +41 -8
  11. data/lib/hecks/adapters/driven/tenant_provisioner.rb +73 -16
  12. data/lib/hecks/behaviors/expectations.rb +1 -1
  13. data/lib/hecks/behaviors.rb +1 -1
  14. data/lib/hecks/bench/cli.rb +62 -0
  15. data/lib/hecks/bench/environment.rb +71 -0
  16. data/lib/hecks/bench/postgres_probe.rb +30 -0
  17. data/lib/hecks/bench/report.rb +76 -0
  18. data/lib/hecks/bench/ruby_runner.rb +93 -0
  19. data/lib/hecks/bench/run.rb +55 -0
  20. data/lib/hecks/bench/rust_runner.rb +101 -0
  21. data/lib/hecks/bench/stats.rb +46 -0
  22. data/lib/hecks/bench/suite.rb +98 -0
  23. data/lib/hecks/bench/workload.rb +89 -0
  24. data/lib/hecks/bench.rb +17 -0
  25. data/lib/hecks/bluebook/assembly/contracts.rb +2 -0
  26. data/lib/hecks/bluebook/attribute.rb +1 -1
  27. data/lib/hecks/bluebook/behaviour/aggregate.rb +30 -1
  28. data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -0
  29. data/lib/hecks/bluebook/chapter.rb +6 -2
  30. data/lib/hecks/bluebook/domain_port.rb +36 -5
  31. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +10 -0
  32. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +8 -0
  33. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -1
  34. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +31 -3
  35. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +122 -3
  36. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +1 -1
  37. data/lib/hecks/bluebook/dsl/query_builder.rb +27 -1
  38. data/lib/hecks/bluebook/dsl/rule_reference.rb +1 -1
  39. data/lib/hecks/bluebook/dsl/world_builder.rb +29 -2
  40. data/lib/hecks/bluebook/expression/canonical_form.rb +1 -1
  41. data/lib/hecks/bluebook/expression/resolver.rb +3 -2
  42. data/lib/hecks/bluebook/hexagon.rb +13 -5
  43. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -0
  44. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +4 -1
  45. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
  46. data/lib/hecks/bluebook/meta_validator.rb +9 -4
  47. data/lib/hecks/bluebook/model_check.rb +23 -0
  48. data/lib/hecks/bluebook/pattern_subset.rb +38 -0
  49. data/lib/hecks/bluebook/query.rb +20 -5
  50. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  51. data/lib/hecks/canonical_json.rb +30 -0
  52. data/lib/hecks/chapters.rb +102 -0
  53. data/lib/hecks/cli/behaviors.rb +61 -0
  54. data/lib/hecks/cli/canonicalise.rb +25 -0
  55. data/lib/hecks/cli/check_engine_agreement.rb +32 -0
  56. data/lib/hecks/cli/check_era.rb +88 -0
  57. data/lib/hecks/cli/console.rb +66 -0
  58. data/lib/hecks/cli/deploy_template_diff.rb +40 -0
  59. data/lib/hecks/cli/doc_coverage.rb +32 -0
  60. data/lib/hecks/cli/document.rb +2 -2
  61. data/lib/hecks/cli/expression_projection.rb +41 -0
  62. data/lib/hecks/cli/follow.rb +88 -0
  63. data/lib/hecks/cli/history.rb +44 -0
  64. data/lib/hecks/cli/ir.rb +1 -1
  65. data/lib/hecks/cli/mcp.rb +3 -3
  66. data/lib/hecks/cli/model_check.rb +15 -8
  67. data/lib/hecks/cli/pattern_cases.rb +47 -0
  68. data/lib/hecks/cli/present.rb +39 -0
  69. data/lib/hecks/cli/project_cli.rb +224 -32
  70. data/lib/hecks/cli/project_diagrams.rb +4 -3
  71. data/lib/hecks/cli/project_glossary.rb +44 -0
  72. data/lib/hecks/cli/reference.rb +46 -0
  73. data/lib/hecks/cli/refresh_projections.rb +44 -0
  74. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
  75. data/lib/hecks/cli/release.rb +70 -0
  76. data/lib/hecks/cli/release_gem.rb +59 -0
  77. data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
  78. data/lib/hecks/cli/rspec_shard_files.rb +66 -0
  79. data/lib/hecks/cli/run.rb +3 -3
  80. data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
  81. data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
  82. data/lib/hecks/cli/shape.rb +70 -0
  83. data/lib/hecks/cli/smoke_http.rb +230 -0
  84. data/lib/hecks/cli/smoke_test.rb +5 -3
  85. data/lib/hecks/cli/spec_example.rb +27 -0
  86. data/lib/hecks/cli/statements.rb +32 -0
  87. data/lib/hecks/cli/stores.rb +4 -5
  88. data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
  89. data/lib/hecks/cli.rb +201 -52
  90. data/lib/hecks/codemod.rb +324 -0
  91. data/lib/hecks/corpus.rb +423 -0
  92. data/lib/hecks/deploy/bluebook/deploy.bluebook +591 -23
  93. data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -4
  94. data/lib/hecks/deploy/oidc.json +82 -2
  95. data/lib/hecks/doc/reference.rb +351 -0
  96. data/lib/hecks/doors/cli_door.rb +196 -0
  97. data/lib/hecks/doors/cli_runner.rb +297 -0
  98. data/lib/hecks/{facade → doors}/command_request.rb +1 -1
  99. data/lib/hecks/{facade → doors}/handle.rb +2 -2
  100. data/lib/hecks/{facade → doors}/json_door.rb +4 -4
  101. data/lib/hecks/doors/launcher_options.rb +129 -0
  102. data/lib/hecks/{cli → doors}/mcp_door.rb +7 -7
  103. data/lib/hecks/doors/mcp_door_scope.rb +97 -0
  104. data/lib/hecks/{facade/surface → doors/ruby_door}/aggregate_door.rb +8 -3
  105. data/lib/hecks/{facade/surface → doors/ruby_door}/chapter.rb +3 -3
  106. data/lib/hecks/{facade/surface.rb → doors/ruby_door.rb} +36 -5
  107. data/lib/hecks/doors.rb +61 -0
  108. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +1 -1
  109. data/lib/hecks/embryonaut_bluebook.rb +1 -1
  110. data/lib/hecks/engine_agreement.rb +156 -0
  111. data/lib/hecks/facade.rb +24 -37
  112. data/lib/hecks/forms/app.rb +6 -6
  113. data/lib/hecks/forms/banking_presentation.rb +50 -0
  114. data/lib/hecks/forms/field_shape.rb +2 -1
  115. data/lib/hecks/forms/port_argument.rb +1 -1
  116. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
  117. data/lib/hecks/fuzzing/combination_miner.rb +86 -0
  118. data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
  119. data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
  120. data/lib/hecks/fuzzing/differential.rb +156 -0
  121. data/lib/hecks/fuzzing/domain_generator.rb +615 -0
  122. data/lib/hecks/fuzzing/era_boundary.rb +71 -0
  123. data/lib/hecks/fuzzing/form_census.rb +136 -0
  124. data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
  125. data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
  126. data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
  127. data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
  128. data/lib/hecks/fuzzing/outside_world.rb +45 -0
  129. data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
  130. data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
  131. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
  132. data/lib/hecks/fuzzing/properties/guards.rb +190 -0
  133. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
  134. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
  135. data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
  136. data/lib/hecks/fuzzing/properties/querying.rb +175 -0
  137. data/lib/hecks/fuzzing/properties.rb +162 -0
  138. data/lib/hecks/fuzzing/qa_settings.rb +123 -0
  139. data/lib/hecks/fuzzing/replay.rb +505 -0
  140. data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
  141. data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
  142. data/lib/hecks/fuzzing/self_consistency.rb +339 -0
  143. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
  144. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
  145. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
  146. data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
  147. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
  148. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
  149. data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
  150. data/lib/hecks/fuzzing/shrinker.rb +166 -0
  151. data/lib/hecks/fuzzing/structural_skips.rb +33 -0
  152. data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
  153. data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
  154. data/lib/hecks/fuzzing/value_generator.rb +216 -0
  155. data/lib/hecks/fuzzing.rb +23 -0
  156. data/lib/hecks/grammar/evolve.rb +336 -0
  157. data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
  158. data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
  159. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
  160. data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
  161. data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
  162. data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
  163. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
  164. data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
  165. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
  166. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
  167. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
  168. data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
  169. data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
  170. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
  171. data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
  172. data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
  173. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
  174. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
  175. data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
  176. data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
  177. data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
  178. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
  179. data/lib/hecks/hecks/adapters/git.adapter +7 -0
  180. data/lib/hecks/hecks/adapters/git.rb +102 -0
  181. data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
  182. data/lib/hecks/hecks/adapters/host_http.rb +65 -0
  183. data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
  184. data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
  185. data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
  186. data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
  187. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
  188. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
  189. data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
  190. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
  191. data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
  192. data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
  193. data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
  194. data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
  195. data/lib/hecks/hecks/adapters/local_files.rb +46 -0
  196. data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
  197. data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
  198. data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
  199. data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
  200. data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
  201. data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
  202. data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
  203. data/lib/hecks/hecks/adapters/shell.adapter +7 -0
  204. data/lib/hecks/hecks/adapters/shell.rb +49 -0
  205. data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
  206. data/lib/hecks/hecks/adapters/terminal.rb +74 -0
  207. data/lib/hecks/hecks/codebase.behaviors +172 -0
  208. data/lib/hecks/hecks/codebase.bluebook +2453 -0
  209. data/lib/hecks/hecks/context_map.hecksagon +13 -0
  210. data/lib/hecks/hecks/custodian.behaviors +179 -0
  211. data/lib/hecks/hecks/custodian.bluebook +1847 -0
  212. data/lib/hecks/hecks/deploy.behaviors +74 -0
  213. data/lib/hecks/hecks/deploy.world +12 -0
  214. data/lib/hecks/hecks/environments/memory.world +10 -0
  215. data/lib/hecks/hecks/governance.world +7 -0
  216. data/lib/hecks/hecks/hecks.behaviors +62 -0
  217. data/lib/hecks/hecks/hecks.bluebook +124 -0
  218. data/lib/hecks/hecks/hecks.hecksagon +390 -0
  219. data/lib/hecks/hecks/hecks.world +42 -0
  220. data/lib/hecks/kernel_capabilities.rb +194 -0
  221. data/lib/hecks/language/bluebook/bluebook.bluebook +11 -0
  222. data/lib/hecks/language/bluebook/query.bluebook +7 -0
  223. data/lib/hecks/language/bluebook/vocabulary.bluebook +4 -4
  224. data/lib/hecks/language/hecksagon/domain_port.bluebook +10 -0
  225. data/lib/hecks/language/hecksagon/hecksagon.bluebook +20 -0
  226. data/lib/hecks/mcp_stdio_guard.rb +2 -2
  227. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
  228. data/lib/hecks/persistence_legacy_fixture.rb +113 -0
  229. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +8 -5
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +4 -1
  231. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +15 -2
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +1 -1
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +1 -1
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +32 -15
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +15 -5
  237. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
  238. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +1 -1
  239. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +43 -24
  240. data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
  241. data/lib/hecks/ports/persistence/state_codec.rb +24 -5
  242. data/lib/hecks/projection_files.rb +285 -0
  243. data/lib/hecks/projections/bootstrap_table.rb +1 -1
  244. data/lib/hecks/projections/deploy/fargate.rb +5 -5
  245. data/lib/hecks/projections/deploy/lambda.rb +43 -32
  246. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  247. data/lib/hecks/projections/deploy/preview/template.rb +1 -1
  248. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
  249. data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
  250. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
  251. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
  252. data/lib/hecks/projections/deploy/shared.rb +1 -1
  253. data/lib/hecks/projections/diagrams.rb +3 -3
  254. data/lib/hecks/projections/model/deviations.rb +1 -1
  255. data/lib/hecks/projections/parser_table.rb +4 -4
  256. data/lib/hecks/projections/reference.rb +2 -3
  257. data/lib/hecks/projections/rust_vocabulary.rb +1 -1
  258. data/lib/hecks/projector/cli_projector.rb +106 -37
  259. data/lib/hecks/projector.rb +1 -1
  260. data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
  261. data/lib/hecks/quality_control/adapters/agent.rb +58 -0
  262. data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
  263. data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
  264. data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
  265. data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
  266. data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
  267. data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
  268. data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
  269. data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
  270. data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
  271. data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
  272. data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
  273. data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
  274. data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
  275. data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
  276. data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
  277. data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
  278. data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
  279. data/lib/hecks/quality_control/cli/child.rb +45 -0
  280. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
  281. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
  282. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
  283. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
  284. data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
  285. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
  286. data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
  287. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
  288. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
  289. data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
  290. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
  291. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
  292. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
  293. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
  294. data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
  295. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
  296. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
  297. data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
  298. data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
  299. data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
  300. data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
  301. data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
  302. data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
  303. data/lib/hecks/query_ir.rb +346 -0
  304. data/lib/hecks/query_ir_mcp.rb +198 -0
  305. data/lib/hecks/release/runner/ci_publisher.rb +2 -2
  306. data/lib/hecks/release/runner/clean_tree.rb +42 -0
  307. data/lib/hecks/release/runner/console.rb +6 -0
  308. data/lib/hecks/release/runner/gem_publisher.rb +14 -7
  309. data/lib/hecks/release/runner/npm_publisher.rb +6 -36
  310. data/lib/hecks/release/runner/preflight.rb +26 -2
  311. data/lib/hecks/release/runner/published.rb +8 -26
  312. data/lib/hecks/release/runner/tagger.rb +30 -15
  313. data/lib/hecks/release/runner.rb +57 -6
  314. data/lib/hecks/router/namespace_installer.rb +3 -3
  315. data/lib/hecks/runtime/adapter_lookup.rb +124 -0
  316. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +4 -0
  317. data/lib/hecks/runtime/dependency_planning.rb +20 -2
  318. data/lib/hecks/runtime/dispatcher.rb +53 -3
  319. data/lib/hecks/runtime/interpreting.rb +3 -0
  320. data/lib/hecks/runtime/loader.rb +65 -12
  321. data/lib/hecks/runtime/outbox.rb +1 -1
  322. data/lib/hecks/runtime/policy_interpreter.rb +5 -2
  323. data/lib/hecks/runtime/port_operation_interpreter.rb +2 -10
  324. data/lib/hecks/runtime/query_interpreter.rb +77 -0
  325. data/lib/hecks/runtime/reaction_outcome.rb +89 -0
  326. data/lib/hecks/runtime/refusal_wording.rb +23 -1
  327. data/lib/hecks/runtime/registry/verification.rb +163 -4
  328. data/lib/hecks/runtime/registry.rb +57 -3
  329. data/lib/hecks/runtime/remote_dispatcher.rb +31 -2
  330. data/lib/hecks/runtime/value/coercion.rb +23 -135
  331. data/lib/hecks/runtime/value/field_checks.rb +172 -0
  332. data/lib/hecks/runtime/value.rb +4 -1
  333. data/lib/hecks/runtime.rb +20 -6
  334. data/lib/hecks/rust_build/conformance.rb +158 -0
  335. data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
  336. data/lib/hecks/rust_build/coverage.rb +218 -0
  337. data/lib/hecks/rust_build/native_build.rb +124 -0
  338. data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
  339. data/lib/hecks/rust_build/project_rust.rb +332 -0
  340. data/lib/hecks/rust_build/wasm.rb +105 -0
  341. data/lib/hecks/rust_build/wasm_browser.rb +113 -0
  342. data/lib/hecks/rust_build.rb +144 -0
  343. data/lib/hecks/storehouse.rb +16 -16
  344. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +2 -2
  345. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +1 -1
  346. data/lib/hecks/three_zero/forms.yml +127 -0
  347. data/lib/hecks/three_zero.rb +12 -0
  348. data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
  349. data/lib/hecks/tools/comment_style.rb +1070 -0
  350. data/lib/hecks/tools/corpus_report.rb +87 -0
  351. data/lib/hecks/tools/deploy_recipe.rb +255 -0
  352. data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
  353. data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
  354. data/lib/hecks/tools/era_projection_backfill.rb +94 -0
  355. data/lib/hecks/tools/era_reattest.rb +146 -0
  356. data/lib/hecks/tools/evolve_run.rb +189 -0
  357. data/lib/hecks/tools/fuzz_sweep.rb +373 -0
  358. data/lib/hecks/tools/heki_compaction.rb +100 -0
  359. data/lib/hecks/tools/hoist_local_givens.rb +246 -0
  360. data/lib/hecks/tools/journal_compaction.rb +89 -0
  361. data/lib/hecks/tools/oidc_manifests.rb +70 -0
  362. data/lib/hecks/tools/query_ir_run.rb +81 -0
  363. data/lib/hecks/tools/regeneration_run.rb +166 -0
  364. data/lib/hecks/tools/rust_comment_style.rb +642 -0
  365. data/lib/hecks/tools/sequence_script.rb +62 -0
  366. data/lib/hecks/tools/tail_merge.rb +117 -0
  367. data/lib/hecks/tools/tenant_provisioning.rb +142 -0
  368. data/lib/hecks/tools/translation_audit.rb +208 -0
  369. data/lib/hecks/tools/translation_scaffold.rb +131 -0
  370. data/lib/hecks/tools.rb +86 -0
  371. data/lib/hecks/version.rb +1 -1
  372. data/lib/hecks.rb +32 -15
  373. data/qa/settings.yml +66 -0
  374. data/rust/.cargo/config.toml +6 -0
  375. data/rust/.gitignore +2 -0
  376. data/rust/Cargo.lock +54 -0
  377. data/rust/Cargo.toml +61 -0
  378. data/rust/build/.cargo/config.toml +3 -0
  379. data/rust/build/.gitignore +1 -0
  380. data/rust/build/Cargo.lock +7 -0
  381. data/rust/build/Cargo.toml +19 -0
  382. data/rust/build/src/build_artifact.rs +47 -0
  383. data/rust/build/src/cargo_sync.rs +248 -0
  384. data/rust/build/src/json.rs +421 -0
  385. data/rust/build/src/lineage_pass.rs +285 -0
  386. data/rust/build/src/main.rs +51 -0
  387. data/rust/build/src/optional_pass.rs +150 -0
  388. data/rust/build/src/pipeline.rs +264 -0
  389. data/rust/build/src/reserved_names.rs +76 -0
  390. data/rust/build/src/resolve.rs +124 -0
  391. data/rust/build/src/root.rs +25 -0
  392. data/rust/build/src/sidecars.rs +73 -0
  393. data/rust/build/src/subprocess.rs +51 -0
  394. data/rust/build/src/tmp.rs +31 -0
  395. data/rust/codegen/.cargo/config.toml +3 -0
  396. data/rust/codegen/.gitignore +1 -0
  397. data/rust/codegen/Cargo.lock +7 -0
  398. data/rust/codegen/Cargo.toml +20 -0
  399. data/rust/codegen/src/attr.rs +32 -0
  400. data/rust/codegen/src/bridging.rs +481 -0
  401. data/rust/codegen/src/commands.rs +1226 -0
  402. data/rust/codegen/src/constraints.rs +109 -0
  403. data/rust/codegen/src/dependency_planning.rs +209 -0
  404. data/rust/codegen/src/domain_generator.rs +1288 -0
  405. data/rust/codegen/src/exemplar.rs +230 -0
  406. data/rust/codegen/src/expr_emitter.rs +87 -0
  407. data/rust/codegen/src/fielded.rs +192 -0
  408. data/rust/codegen/src/hecks_naming.rs +33 -0
  409. data/rust/codegen/src/json.rs +335 -0
  410. data/rust/codegen/src/json_codec.rs +697 -0
  411. data/rust/codegen/src/literal.rs +207 -0
  412. data/rust/codegen/src/main.rs +290 -0
  413. data/rust/codegen/src/manifest.rs +227 -0
  414. data/rust/codegen/src/mutations.rs +784 -0
  415. data/rust/codegen/src/naming.rs +286 -0
  416. data/rust/codegen/src/ports.rs +208 -0
  417. data/rust/codegen/src/prelude.rs +116 -0
  418. data/rust/codegen/src/queries.rs +786 -0
  419. data/rust/codegen/src/reactions.rs +532 -0
  420. data/rust/codegen/src/read_models.rs +697 -0
  421. data/rust/codegen/src/reference_specs.rs +40 -0
  422. data/rust/codegen/src/registry.rs +966 -0
  423. data/rust/codegen/src/reserved_names.rs +76 -0
  424. data/rust/codegen/src/shared.rs +93 -0
  425. data/rust/codegen/src/skip_reason.rs +25 -0
  426. data/rust/codegen/src/types.rs +362 -0
  427. data/rust/host/.cargo/config.toml +3 -0
  428. data/rust/host/.gitignore +1 -0
  429. data/rust/host/Cargo.lock +4297 -0
  430. data/rust/host/Cargo.toml +61 -0
  431. data/rust/host/HECKS_RELEASE +1 -0
  432. data/rust/host/rds-ca-bundle.pem +2736 -0
  433. data/rust/host/src/api.rs +1596 -0
  434. data/rust/host/src/approval.rs +652 -0
  435. data/rust/host/src/auth.rs +1287 -0
  436. data/rust/host/src/bin/expr_harness.rs +46 -0
  437. data/rust/host/src/bin/lineage_harness.rs +119 -0
  438. data/rust/host/src/bin/mint_harness.rs +135 -0
  439. data/rust/host/src/checkout.rs +265 -0
  440. data/rust/host/src/dispatch.rs +1443 -0
  441. data/rust/host/src/expr_json.rs +1117 -0
  442. data/rust/host/src/field_hints.rs +52 -0
  443. data/rust/host/src/ir.rs +733 -0
  444. data/rust/host/src/journal.rs +874 -0
  445. data/rust/host/src/lambda_client.rs +378 -0
  446. data/rust/host/src/log.rs +147 -0
  447. data/rust/host/src/main.rs +474 -0
  448. data/rust/host/src/mint.rs +1865 -0
  449. data/rust/host/src/payments/keystore.rs +330 -0
  450. data/rust/host/src/payments/tests.rs +1400 -0
  451. data/rust/host/src/payments.rs +767 -0
  452. data/rust/host/src/presentation.rs +698 -0
  453. data/rust/host/src/presentation_write.rs +1431 -0
  454. data/rust/host/src/query_step.rs +149 -0
  455. data/rust/host/src/rate_limit.rs +904 -0
  456. data/rust/host/src/reference_transform.rs +241 -0
  457. data/rust/host/src/reference_validate.rs +338 -0
  458. data/rust/host/src/resend.rs +296 -0
  459. data/rust/host/src/secrets.rs +106 -0
  460. data/rust/host/src/server.rs +663 -0
  461. data/rust/host/src/storage_shape.rs +175 -0
  462. data/rust/host/src/test_pg.rs +40 -0
  463. data/rust/host/src/ui_schema.rs +1351 -0
  464. data/rust/host/src/wasm_runner.rs +191 -0
  465. data/rust/host/src/web/newsletter/cooldown.rs +126 -0
  466. data/rust/host/src/web/newsletter/tests.rs +104 -0
  467. data/rust/host/src/web/newsletter.rs +310 -0
  468. data/rust/host/src/web/newsletter_send.rs +357 -0
  469. data/rust/host/src/web/registrations.rs +1505 -0
  470. data/rust/host/src/web.rs +2937 -0
  471. data/rust/lsp/.gitignore +1 -0
  472. data/rust/lsp/Cargo.lock +7 -0
  473. data/rust/lsp/Cargo.toml +16 -0
  474. data/rust/lsp/README.md +133 -0
  475. data/rust/lsp/src/diagnostics.rs +165 -0
  476. data/rust/lsp/src/json.rs +385 -0
  477. data/rust/lsp/src/main.rs +399 -0
  478. data/rust/lsp/src/outline.rs +177 -0
  479. data/rust/lsp/src/rpc.rs +67 -0
  480. data/rust/parser/.cargo/config.toml +5 -0
  481. data/rust/parser/.gitignore +1 -0
  482. data/rust/parser/Cargo.lock +7 -0
  483. data/rust/parser/Cargo.toml +21 -0
  484. data/rust/parser/src/build/closed_sets.rs +27 -0
  485. data/rust/parser/src/build/identity.rs +276 -0
  486. data/rust/parser/src/build/mod.rs +12 -0
  487. data/rust/parser/src/build/naming.rs +141 -0
  488. data/rust/parser/src/build/pattern_subset.rs +161 -0
  489. data/rust/parser/src/build/query_derive.rs +72 -0
  490. data/rust/parser/src/build/query_inference.rs +269 -0
  491. data/rust/parser/src/build/query_options.rs +100 -0
  492. data/rust/parser/src/build/read_model.rs +94 -0
  493. data/rust/parser/src/build/references.rs +32 -0
  494. data/rust/parser/src/canonical.rs +114 -0
  495. data/rust/parser/src/diag.rs +55 -0
  496. data/rust/parser/src/emit.rs +953 -0
  497. data/rust/parser/src/expr/ast_json.rs +147 -0
  498. data/rust/parser/src/expr/evaluator.rs +81 -0
  499. data/rust/parser/src/expr/mod.rs +84 -0
  500. data/rust/parser/src/expr/resolver.rs +497 -0
  501. data/rust/parser/src/ir.rs +387 -0
  502. data/rust/parser/src/keywords.rs +585 -0
  503. data/rust/parser/src/lex.rs +662 -0
  504. data/rust/parser/src/main.rs +252 -0
  505. data/rust/parser/src/parse/aggregate.rs +612 -0
  506. data/rust/parser/src/parse/chapter.rs +517 -0
  507. data/rust/parser/src/parse/command.rs +637 -0
  508. data/rust/parser/src/parse/domain_port.rs +127 -0
  509. data/rust/parser/src/parse/entity.rs +527 -0
  510. data/rust/parser/src/parse/file.rs +67 -0
  511. data/rust/parser/src/parse/hecksagon.rs +231 -0
  512. data/rust/parser/src/parse/lifecycle.rs +162 -0
  513. data/rust/parser/src/parse/mod.rs +1262 -0
  514. data/rust/parser/src/parse/policy.rs +87 -0
  515. data/rust/parser/src/parse/process_manager.rs +305 -0
  516. data/rust/parser/src/parse/query.rs +100 -0
  517. data/rust/parser/src/parse/read_model.rs +214 -0
  518. data/rust/parser/src/parse/value_object.rs +250 -0
  519. data/rust/parser/src/ruby_value.rs +422 -0
  520. data/rust/project/bridging.rb +257 -0
  521. data/rust/project/commands.rb +877 -0
  522. data/rust/project/constraints.rb +86 -0
  523. data/rust/project/dependency_planning.rb +178 -0
  524. data/rust/project/domain_generator.rb +980 -0
  525. data/rust/project/exemplar.rb +148 -0
  526. data/rust/project/expr_emitter.rb +72 -0
  527. data/rust/project/fielded.rb +141 -0
  528. data/rust/project/json_codec.rb +641 -0
  529. data/rust/project/mutations.rb +721 -0
  530. data/rust/project/naming.rb +205 -0
  531. data/rust/project/ports.rb +65 -0
  532. data/rust/project/queries.rb +493 -0
  533. data/rust/project/reactions.rb +317 -0
  534. data/rust/project/read_models.rb +690 -0
  535. data/rust/project/reference_specs.rb +27 -0
  536. data/rust/project/registry.rb +396 -0
  537. data/rust/project/skip_reason.rb +29 -0
  538. data/rust/project/types.rb +225 -0
  539. data/rust/project/write_if_changed.rb +83 -0
  540. data/rust/project.rb +30 -0
  541. data/rust/project_rust_pipeline.rb +383 -0
  542. data/rust/src/exemplar/commands.rs +279 -0
  543. data/rust/src/exemplar/constraints.rs +18 -0
  544. data/rust/src/exemplar/fielded.rs +280 -0
  545. data/rust/src/exemplar/json.rs +277 -0
  546. data/rust/src/exemplar/mod.rs +17 -0
  547. data/rust/src/exemplar/mutations.rs +80 -0
  548. data/rust/src/exemplar/queries.rs +33 -0
  549. data/rust/src/exemplar/reactions.rs +88 -0
  550. data/rust/src/exemplar/read_models.rs +48 -0
  551. data/rust/src/exemplar/registry.rs +166 -0
  552. data/rust/src/exemplar/types.rs +44 -0
  553. data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
  554. data/rust/src/kernel/attribute_shapes/list.rs +18 -0
  555. data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
  556. data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
  557. data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
  558. data/rust/src/kernel/cli.rs +585 -0
  559. data/rust/src/kernel/dispatch.rs +1090 -0
  560. data/rust/src/kernel/expr.rs +458 -0
  561. data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
  562. data/rust/src/kernel/expression_operators/comparison.rs +48 -0
  563. data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
  564. data/rust/src/kernel/expression_operators/logical.rs +13 -0
  565. data/rust/src/kernel/expression_operators/membership.rs +18 -0
  566. data/rust/src/kernel/expression_operators/mod.rs +68 -0
  567. data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
  568. data/rust/src/kernel/expression_operators/positional.rs +126 -0
  569. data/rust/src/kernel/expression_operators/presence.rs +101 -0
  570. data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
  571. data/rust/src/kernel/expression_operators/sized.rs +35 -0
  572. data/rust/src/kernel/expression_operators/text.rs +79 -0
  573. data/rust/src/kernel/expression_operators/to_string.rs +18 -0
  574. data/rust/src/kernel/json.rs +779 -0
  575. data/rust/src/kernel/mod.rs +133 -0
  576. data/rust/src/kernel/named_query.rs +219 -0
  577. data/rust/src/kernel/orchestrate.rs +1810 -0
  578. data/rust/src/kernel/pattern.rs +307 -0
  579. data/rust/src/kernel/query_comparators.rs +206 -0
  580. data/rust/src/kernel/query_ordering.rs +103 -0
  581. data/rust/src/kernel/read_model.rs +914 -0
  582. data/rust/src/kernel/reference_lookup.rs +149 -0
  583. data/rust/src/kernel/repository.rs +468 -0
  584. data/rust/src/kernel/routing.rs +612 -0
  585. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
  586. data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
  587. data/rust/src/kernel/vocab/field_hint.rs +70 -0
  588. data/rust/src/kernel/vocab/mod.rs +20 -0
  589. data/rust/src/kernel/vocab/query_comparator.rs +65 -0
  590. data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
  591. data/rust/src/lib.rs +8 -0
  592. data/rust/src/main.rs +16 -0
  593. data/rust/web/.gitignore +1 -0
  594. data/rust/web/Cargo.lock +119 -0
  595. data/rust/web/Cargo.toml +18 -0
  596. data/rust/web/src/lib.rs +8 -0
  597. metadata +477 -12
  598. data/lib/hecks/facade/cli_door.rb +0 -96
  599. data/lib/hecks/facade/cli_runner.rb +0 -127
  600. data/lib/hecks/mcp_door_scope.rb +0 -95
@@ -0,0 +1,585 @@
1
+ //! The stdin/stdout JSON CLI shared by the native binary and the wasm32-wasip1 module.
2
+ //! `run` reads `{"steps": [...]}` and hands each step to `kernel::orchestrate` (ADR 0012).
3
+
4
+ use super::{named_query, orchestrate, query_comparators, read_model, repository, AggregateScan, CompletedCompensation, Event, Json, MutationRecord, PendingCrossDomainReaction, Refusal, SagaInstance, Tables};
5
+ use crate::generated::active::{command_attributes_for_verb, command_creates, dispatch_by_name, entity_identity_head_for_path, identity_head_for_aggregate, reference_key_for_aggregate, Store, CROSS_DOMAIN_POLICIES, POLICIES, PROCESS_MANAGERS, QUERIES, READ_MODELS};
6
+ use std::collections::HashMap;
7
+
8
+ // Builds and invariant-checks a named query's value-object arguments (ADR 0037).
9
+ use crate::generated::active::check_query_args;
10
+
11
+ use crate::generated::active::ENTITY_QUERIES;
12
+
13
+ pub fn run(input: &str) -> String {
14
+ let parsed = match Json::parse(input) {
15
+ Ok(v) => v,
16
+ Err(e) => return error_output(&format!("invalid JSON on stdin: {e}")),
17
+ };
18
+
19
+ let steps = match parsed.get("steps").and_then(Json::as_array) {
20
+ Some(s) => s,
21
+ None => return error_output("expected a top-level {\"steps\": [...]} object"),
22
+ };
23
+
24
+ // Optional `"seed"`: prior state as `"instances"` emits it, so a host need not replay history.
25
+ let mut store = match parsed.get("seed") {
26
+ Some(seed) => match Store::from_seed(seed) {
27
+ Ok(s) => s,
28
+ Err(refusal) => return error_output(&format!("invalid seed: {refusal}")),
29
+ },
30
+ None => Store::new(),
31
+ };
32
+ let mut events: Vec<Event> = Vec::new();
33
+ let mut refusals: Vec<(String, Refusal)> = Vec::new();
34
+ // Process-manager instances are domain-level, so not a `Store` field.
35
+ // Optional `"sagas"` seeds in-flight saga state the way `"seed"` seeds aggregates.
36
+ let mut sagas: HashMap<(String, String), SagaInstance> = match parsed.get("sagas").and_then(Json::as_array) {
37
+ Some(entries) => entries
38
+ .iter()
39
+ .filter_map(|entry| {
40
+ let process_manager = entry.get("process_manager")?.as_str()?.to_string();
41
+ let correlation = entry.get("correlation")?.as_str()?.to_string();
42
+ let state = entry.get("state")?.as_str()?.to_string();
43
+ let memory = entry.get("memory").cloned().unwrap_or_else(|| Json::Object(vec![]));
44
+ // Optional: an instance persisted without a ledger rehydrates with an empty one.
45
+ let completed_compensations = entry
46
+ .get("completed_compensations")
47
+ .and_then(Json::as_array)
48
+ .map(|entries| {
49
+ entries
50
+ .iter()
51
+ .filter_map(|e| {
52
+ let command_name = e.get("command_name")?.as_str()?.to_string();
53
+ let args = e.get("args").cloned().unwrap_or(Json::Null);
54
+ Some(CompletedCompensation { command_name, args })
55
+ })
56
+ .collect()
57
+ })
58
+ .unwrap_or_default();
59
+ Some(((process_manager, correlation), SagaInstance { state, memory, completed_compensations }))
60
+ })
61
+ .collect(),
62
+ None => HashMap::new(),
63
+ };
64
+ // One entry per step, parallel to `steps`, so a host can read `.last()` for the newest step.
65
+ let mut mutations_per_step: Vec<Vec<MutationRecord>> = Vec::new();
66
+ // Parallel to `mutations_per_step`: policies that named a domain not compiled into this
67
+ // `Store`; rust/host delivers them.
68
+ let mut cross_domain_per_step: Vec<Vec<PendingCrossDomainReaction>> = Vec::new();
69
+ // Parallel to `mutations_per_step`: the slice of `reaction_log` each step appended, so a
70
+ // host reads `.last()` for only the newest step's reactions, not the replayed history's.
71
+ let mut reactions_per_step: Vec<Json> = Vec::new();
72
+ // Whole-run accumulations matching `Registry#reaction_log`/`#saga_log`; never reset per step.
73
+ let mut reaction_log: Vec<Json> = Vec::new();
74
+ let mut saga_log: Vec<Json> = Vec::new();
75
+ // One entry per answered `query` step; a refused query goes to `refusals` instead.
76
+ let mut query_results: Vec<Json> = Vec::new();
77
+ // One entry per `dry_run` step: the command run against a throwaway clone of the store.
78
+ let mut dry_runs: Vec<Json> = Vec::new();
79
+
80
+ for step in steps {
81
+ // A query step's `args` sit at the sibling `"args"` key, as for a command step.
82
+ let empty_args = Json::Object(vec![]);
83
+ let args = step.get("args").unwrap_or(&empty_args);
84
+
85
+ // A `"query"` step carries no verb and takes one of three shapes:
86
+ // - `"Domain::Aggregate.Query"`: a declared aggregate query, looked up in `QUERIES`.
87
+ // - `"Domain.ReadModel"` (no `::`): a read model from `READ_MODELS`.
88
+ // - `{aggregate, field, op, value}`: an ad hoc filter over one aggregate.
89
+ // A question with no generated table row refuses with `TypeMismatch`.
90
+ if let Some(query) = step.get("query") {
91
+ // Read for a future `authorize policy` pass; nothing checks it yet.
92
+ let caller_role = step.get("role").and_then(Json::as_str);
93
+ match query {
94
+ // Entity-query rows are already `{ parent_key => id }.merge(element)`; the
95
+ // reference twin is the same engine.
96
+ // `reference_interpret` delegates to the same `entity_rows`).
97
+ Json::Str(question) if question.contains("::") && named_query::find_entity(ENTITY_QUERIES, question).is_some() => {
98
+ let def = named_query::find_entity(ENTITY_QUERIES, question).expect("matched by the guard above");
99
+ match named_query::run_entity(&store, def, args) {
100
+ Ok(rows) => {
101
+ let rows = Json::Array(rows);
102
+ query_results.push(Json::obj(vec![
103
+ ("query", Json::Str(question.clone())),
104
+ ("args", args.clone()),
105
+ ("rows", rows.clone()),
106
+ ("reference_rows", rows),
107
+ ]));
108
+ }
109
+ Err(refusal) => {
110
+ let message = refusal.to_string();
111
+ query_results.push(Json::obj(vec![
112
+ ("query", Json::Str(question.clone())),
113
+ ("args", args.clone()),
114
+ ("rows", Json::Null),
115
+ ("error", Json::Str(message.clone())),
116
+ ("reference_rows", Json::Null),
117
+ ("reference_error", Json::Str(message)),
118
+ ]));
119
+ refusals.push((question.clone(), refusal));
120
+ }
121
+ }
122
+ }
123
+ Json::Str(question) if question.contains("::") => match named_query::find(QUERIES, question) {
124
+ Some(def) => match check_query_args(question, args).and_then(|()| named_query::run(&store, def, args, caller_role)) {
125
+ Ok(entries) => {
126
+ let rows = Json::Array(entries.into_iter().map(|(id, record)| repository::row_json(id, record)).collect());
127
+ query_results.push(Json::obj(vec![
128
+ ("query", Json::Str(question.clone())),
129
+ ("args", args.clone()),
130
+ ("rows", rows.clone()),
131
+ // Same as `rows`: one compiled path (named_query.rs).
132
+ ("reference_rows", rows),
133
+ ]));
134
+ }
135
+ // A refused query still gets an entry with `rows: null`, as in Ruby; one
136
+ // compiled path means `error` and `reference_error` are the same string.
137
+ Err(refusal) => {
138
+ let message = refusal.to_string();
139
+ query_results.push(Json::obj(vec![
140
+ ("query", Json::Str(question.clone())),
141
+ ("args", args.clone()),
142
+ ("rows", Json::Null),
143
+ ("error", Json::Str(message.clone())),
144
+ ("reference_rows", Json::Null),
145
+ ("reference_error", Json::Str(message)),
146
+ ]));
147
+ refusals.push((question.clone(), refusal));
148
+ }
149
+ },
150
+ None => refusals.push((
151
+ question.clone(),
152
+ Refusal::TypeMismatch(format!(
153
+ "named/declared query {question:?} is not generated for this domain — either unknown, or a \
154
+ real declared query whose shape this generator's codegen doesn't cover yet (order_by/limit/\
155
+ cursor/consistency/freshness/authorization/null_semantics/inspection/index_hints, a where \
156
+ clause hopping through a reference, or a literal comparator value whose true JSON type \
157
+ can't be recovered from the exported IR — rust/project/queries.rb's own header has the \
158
+ full argument); the wheres-only, single-aggregate field-comparator subset and the ad hoc \
159
+ filter shape ({{\"aggregate\",\"field\",\"op\",\"value\"}}) both execute for real"
160
+ )),
161
+ )),
162
+ },
163
+ Json::Str(question) => match read_model::find(READ_MODELS, question) {
164
+ Some(def) => match read_model::run(&store, def, args) {
165
+ Ok(row) => {
166
+ query_results.push(Json::obj(vec![
167
+ ("query", Json::Str(question.clone())),
168
+ ("args", args.clone()),
169
+ ("rows", Json::Array(vec![row])),
170
+ // No `reference_rows` key: Ruby sets it only for questions with a
171
+ // reference twin, and a read model has none.
172
+ ]));
173
+ }
174
+ // As above, minus the reference keys.
175
+ Err(refusal) => {
176
+ let message = refusal.to_string();
177
+ query_results.push(Json::obj(vec![
178
+ ("query", Json::Str(question.clone())),
179
+ ("args", args.clone()),
180
+ ("rows", Json::Null),
181
+ ("error", Json::Str(message)),
182
+ ]));
183
+ refusals.push((question.clone(), refusal));
184
+ }
185
+ },
186
+ None => refusals.push((
187
+ question.clone(),
188
+ Refusal::TypeMismatch(format!(
189
+ "named/declared read model {question:?} is not generated for this domain — either unknown, \
190
+ or a real declared read model whose shape this generator's codegen doesn't cover yet \
191
+ (anything beyond a root aggregate fetched by reference id plus reference-matched sibling \
192
+ heads — where/order_by/limit/offset/cursor/consistency/freshness/authorize(TenantScope)/\
193
+ nulls/inspect_query/use_index — rust/project/read_models.rb's own header has the full \
194
+ argument, including why where/order_by/limit specifically can never be recovered from the \
195
+ canonical IR at all); the named/declared AGGREGATE query form (\"Domain::Aggregate.Query\") \
196
+ and the ad hoc filter shape ({{\"aggregate\",\"field\",\"op\",\"value\"}}) both execute for \
197
+ real too"
198
+ )),
199
+ )),
200
+ },
201
+ Json::Object(_) => match run_filter(&store, query) {
202
+ Ok(rows) => query_results.push(Json::obj(vec![("query", query.clone()), ("rows", Json::Array(rows))])),
203
+ Err(refusal) => refusals.push((filter_label(query), refusal)),
204
+ },
205
+ _ => refusals.push((
206
+ "query".to_string(),
207
+ Refusal::TypeMismatch("a \"query\" step must be a string (named ask) or an object (ad hoc filter)".to_string()),
208
+ )),
209
+ }
210
+ mutations_per_step.push(Vec::new());
211
+ cross_domain_per_step.push(Vec::new());
212
+ reactions_per_step.push(Json::Array(Vec::new()));
213
+ continue;
214
+ }
215
+
216
+ if let Some(verb) = step.get("dry_run").and_then(Json::as_str) {
217
+ let caller_role = step.get("role").and_then(Json::as_str);
218
+ // See `actor_id` on the command step below.
219
+ let caller_actor_id = step.get("actor_id").and_then(Json::as_str);
220
+ let command_input = dry_run_command_input(args);
221
+ dry_runs.push(dry_run(&store, verb, &command_input, caller_role, caller_actor_id));
222
+ mutations_per_step.push(Vec::new());
223
+ cross_domain_per_step.push(Vec::new());
224
+ reactions_per_step.push(Json::Array(Vec::new()));
225
+ continue;
226
+ }
227
+
228
+ let verb = match step.get("verb").and_then(Json::as_str) {
229
+ Some(v) => v,
230
+ None => return error_output("step missing \"verb\", \"dry_run\", or \"query\""),
231
+ };
232
+
233
+ // Per-step `role:` stands in for Ruby's ambient `Hecks.as_caller`; passed to the top-level
234
+ // `orchestrate` call only, never to a reaction's re-entry (as `Dispatcher#reenter`).
235
+ let caller_role = step.get("role").and_then(Json::as_str);
236
+
237
+ // Without `actor_id:` the role is checked by string equality against the command's `role`;
238
+ // with it, by a Governance lookup (`check_role_via`). Top-level call only, like `role:`.
239
+ let caller_actor_id = step.get("actor_id").and_then(Json::as_str);
240
+
241
+ // Stamped once per step by rust/host, since the kernel has no clock; absent means `None`.
242
+ let occurred_at = step.get("occurred_at").and_then(Json::as_str);
243
+
244
+ // Direct callers use top-level `to`/`with`; the durable host wraps them under `args`.
245
+ let command_input = command_input(step, args);
246
+
247
+ // `orchestrate` appends every event itself; only the top-level refusal is recorded here.
248
+ // Mutations from a refused command stay recorded: earlier saga legs may have saved.
249
+ let mut step_mutations: Vec<MutationRecord> = Vec::new();
250
+ let mut step_cross_domain: Vec<PendingCrossDomainReaction> = Vec::new();
251
+ let reactions_before = reaction_log.len();
252
+ let tables = Tables {
253
+ policies: POLICIES,
254
+ cross_domain_policies: CROSS_DOMAIN_POLICIES,
255
+ process_managers: PROCESS_MANAGERS,
256
+ reference_key_fn: reference_key_for_aggregate,
257
+ queries: QUERIES,
258
+ command_creates_fn: command_creates,
259
+ identity_head_fn: identity_head_for_aggregate,
260
+ command_attributes_fn: command_attributes_for_verb,
261
+ entity_identity_head_fn: entity_identity_head_for_path,
262
+ };
263
+ if let Err(refusal) = orchestrate(
264
+ &mut store,
265
+ dispatch_by_name,
266
+ tables,
267
+ &mut sagas,
268
+ verb,
269
+ command_input,
270
+ caller_role,
271
+ caller_actor_id,
272
+ None,
273
+ occurred_at,
274
+ 0,
275
+ &mut events,
276
+ &mut step_mutations,
277
+ &mut step_cross_domain,
278
+ &mut reaction_log,
279
+ &mut saga_log,
280
+ ) {
281
+ refusals.push((verb.to_string(), refusal));
282
+ }
283
+ mutations_per_step.push(step_mutations);
284
+ cross_domain_per_step.push(step_cross_domain);
285
+ reactions_per_step.push(Json::Array(reaction_log[reactions_before..].to_vec()));
286
+ }
287
+
288
+ let events_json = Json::Array(events.iter().map(event_to_json).collect());
289
+ let refusals_json = Json::Array(
290
+ refusals
291
+ .iter()
292
+ .map(|(verb, r)| Json::obj(vec![("verb", Json::str(verb.clone())), ("error", Json::str(r.to_string())), ("kind", Json::str(r.kind().to_string()))]))
293
+ .collect(),
294
+ );
295
+ let mutations_json = Json::Array(
296
+ mutations_per_step
297
+ .iter()
298
+ .map(|step_mutations| Json::Array(step_mutations.iter().map(mutation_to_json).collect()))
299
+ .collect(),
300
+ );
301
+ let cross_domain_json = Json::Array(
302
+ cross_domain_per_step
303
+ .iter()
304
+ .map(|step_reactions| Json::Array(step_reactions.iter().map(cross_domain_reaction_to_json).collect()))
305
+ .collect(),
306
+ );
307
+
308
+ Json::Object(vec![
309
+ ("instances".to_string(), Json::Object(store.instances())),
310
+ ("events".to_string(), events_json),
311
+ ("refusals".to_string(), refusals_json),
312
+ ("dry_runs".to_string(), Json::Array(dry_runs)),
313
+ ("mutations".to_string(), mutations_json),
314
+ ("queries".to_string(), Json::Array(query_results)),
315
+ ("cross_domain_reactions".to_string(), cross_domain_json),
316
+ // Whole-run logs, not per-step.
317
+ ("reactions".to_string(), Json::Array(reaction_log)),
318
+ ("reactions_per_step".to_string(), Json::Array(reactions_per_step)),
319
+ ("sagas".to_string(), Json::Array(saga_log)),
320
+ // State at the end of the run, fed back as `"sagas"` next time; the `"sagas"` key above
321
+ // is the transition log, including refused transitions.
322
+ ("saga_snapshot".to_string(), saga_snapshot_json(&sagas)),
323
+ ])
324
+ .to_json_string()
325
+ }
326
+
327
+ /// Evaluates a command against a throwaway copy of the store; `"error"` carries the refusal
328
+ /// a real dispatch would have raised.
329
+ fn dry_run(store: &Store, verb: &str, command_input: &Json, caller_role: Option<&str>, caller_actor_id: Option<&str>) -> Json {
330
+ let mut scratch = store.clone();
331
+ match dispatch_by_name(&mut scratch, verb, command_input, caller_role, caller_actor_id, &mut Vec::new()) {
332
+ Ok(_) => Json::obj(vec![("verb", Json::str(verb.to_string())), ("ok", Json::Bool(true))]),
333
+ Err(refusal) => Json::obj(vec![("verb", Json::str(verb.to_string())), ("ok", Json::Bool(false)), ("error", Json::str(refusal.to_string()))]),
334
+ }
335
+ }
336
+
337
+ fn tables() -> Tables<'static> {
338
+ Tables {
339
+ policies: POLICIES,
340
+ cross_domain_policies: CROSS_DOMAIN_POLICIES,
341
+ process_managers: PROCESS_MANAGERS,
342
+ reference_key_fn: reference_key_for_aggregate,
343
+ queries: QUERIES,
344
+ command_creates_fn: command_creates,
345
+ identity_head_fn: identity_head_for_aggregate,
346
+ command_attributes_fn: command_attributes_for_verb,
347
+ entity_identity_head_fn: entity_identity_head_for_path,
348
+ }
349
+ }
350
+
351
+ /// `--serve` mode: one JSON step per stdin line, one answer per stdout line, store kept across
352
+ /// lines. Beyond `verb` it takes `dry_run`, `snapshot`, `restore` and `instances` steps.
353
+ pub fn serve(input: impl std::io::BufRead, mut output: impl std::io::Write) {
354
+ let mut store = Store::new();
355
+ let mut sagas: HashMap<(String, String), SagaInstance> = HashMap::new();
356
+ let mut reaction_log: Vec<Json> = Vec::new();
357
+ let mut saga_log: Vec<Json> = Vec::new();
358
+ let mut snapshot: Option<(Store, HashMap<(String, String), SagaInstance>)> = None;
359
+ let ok = || Json::obj(vec![("ok", Json::Bool(true))]);
360
+
361
+ for line in input.lines() {
362
+ let Ok(line) = line else { break };
363
+ if line.trim().is_empty() {
364
+ continue;
365
+ }
366
+ let answer = match Json::parse(&line) {
367
+ Err(e) => Json::obj(vec![("ok", Json::Bool(false)), ("error", Json::str(format!("invalid JSON: {e}")))]),
368
+ Ok(step) => {
369
+ let empty_args = Json::Object(vec![]);
370
+ let args = step.get("args").unwrap_or(&empty_args);
371
+ let caller_role = step.get("role").and_then(Json::as_str);
372
+ let caller_actor_id = step.get("actor_id").and_then(Json::as_str);
373
+ let occurred_at = step.get("occurred_at").and_then(Json::as_str);
374
+ if step.get("snapshot").is_some() {
375
+ snapshot = Some((store.clone(), sagas.clone()));
376
+ ok()
377
+ } else if step.get("restore").is_some() {
378
+ match &snapshot {
379
+ Some((s, g)) => {
380
+ store = s.clone();
381
+ sagas = g.clone();
382
+ ok()
383
+ }
384
+ None => Json::obj(vec![("ok", Json::Bool(false)), ("error", Json::str("no snapshot to restore"))]),
385
+ }
386
+ } else if step.get("instances").is_some() {
387
+ Json::obj(vec![("ok", Json::Bool(true)), ("instances", Json::Object(store.instances()))])
388
+ } else if let Some(verb) = step.get("dry_run").and_then(Json::as_str) {
389
+ dry_run(&store, verb, &dry_run_command_input(args), caller_role, caller_actor_id)
390
+ } else if let Some(verb) = step.get("verb").and_then(Json::as_str) {
391
+ let mut events: Vec<Event> = Vec::new();
392
+ let outcome = orchestrate(
393
+ &mut store,
394
+ dispatch_by_name,
395
+ tables(),
396
+ &mut sagas,
397
+ verb,
398
+ command_input(&step, args),
399
+ caller_role,
400
+ caller_actor_id,
401
+ None,
402
+ occurred_at,
403
+ 0,
404
+ &mut events,
405
+ &mut Vec::new(),
406
+ &mut Vec::new(),
407
+ &mut reaction_log,
408
+ &mut saga_log,
409
+ );
410
+ match outcome {
411
+ Ok(()) => Json::obj(vec![("ok", Json::Bool(true)), ("events", Json::Array(events.iter().map(event_to_json).collect()))]),
412
+ Err(refusal) => Json::obj(vec![("ok", Json::Bool(false)), ("error", Json::str(refusal.to_string()))]),
413
+ }
414
+ } else {
415
+ Json::obj(vec![("ok", Json::Bool(false)), ("error", Json::str("step needs verb, dry_run, snapshot, restore, or instances"))])
416
+ }
417
+ }
418
+ };
419
+ if writeln!(output, "{}", answer.to_json_string()).is_err() || output.flush().is_err() {
420
+ break;
421
+ }
422
+ }
423
+ }
424
+
425
+ fn run_filter(store: &Store, filter: &Json) -> Result<Vec<Json>, Refusal> {
426
+ let aggregate = required_str(filter, "aggregate")?;
427
+ let field = required_str(filter, "field")?;
428
+ let op = required_str(filter, "op")?;
429
+ let value = filter.require("value", "query filter")?;
430
+
431
+ let comparator = query_comparators::QueryComparator::parse(op)
432
+ .ok_or_else(|| Refusal::Fault(format!("unknown query comparator {op:?}")))?;
433
+ let entries = store
434
+ .scan(aggregate)
435
+ .ok_or_else(|| Refusal::Fault(format!("unknown aggregate {aggregate:?}")))?;
436
+
437
+ let matched = repository::filter_entries(entries, field, comparator, value);
438
+ Ok(matched.into_iter().map(|(id, record)| repository::row_json(id, record)).collect())
439
+ }
440
+
441
+ fn required_str<'a>(filter: &'a Json, key: &str) -> Result<&'a str, Refusal> {
442
+ filter
443
+ .require(key, "query filter")?
444
+ .as_str()
445
+ .ok_or_else(|| Refusal::TypeMismatch(format!("query filter {key:?} must be a string")))
446
+ }
447
+
448
+ /// The `refusals` verb column for a refused ad hoc filter, which has no verb; tolerates
449
+ /// missing or mistyped fields.
450
+ fn filter_label(filter: &Json) -> String {
451
+ let field = |key: &str| filter.get(key).and_then(Json::as_str).unwrap_or("");
452
+ format!("filter {}.{} {}", field("aggregate"), field("field"), field("op"))
453
+ }
454
+
455
+ fn command_input<'a>(step: &'a Json, legacy_args: &'a Json) -> &'a Json {
456
+ if step.get("to").is_some() || step.get("with").is_some() {
457
+ step
458
+ } else {
459
+ legacy_args
460
+ }
461
+ }
462
+
463
+ // Not `command_input()`: Ruby's `dry_run?` forwards every arg key, `to`/`with` included, as flat
464
+ // facts. Wrapping under `with` keeps a fact named `to` from being read as a routing envelope.
465
+ fn dry_run_command_input(args: &Json) -> Json {
466
+ Json::obj(vec![("with", args.clone())])
467
+ }
468
+
469
+ fn mutation_to_json(mutation: &MutationRecord) -> Json {
470
+ Json::obj(vec![
471
+ ("aggregate", Json::str(mutation.aggregate.clone())),
472
+ ("id", Json::str(mutation.id.clone())),
473
+ ("operation", Json::str(mutation.operation.to_string())),
474
+ ("state", mutation.state.clone()),
475
+ ])
476
+ }
477
+
478
+ fn cross_domain_reaction_to_json(reaction: &PendingCrossDomainReaction) -> Json {
479
+ Json::obj(vec![
480
+ ("policy", Json::str(reaction.policy_name.clone())),
481
+ // Additive: `lambda_client::deliver` never reads `on`; rust/host uses it to build a
482
+ // reaction_log record.
483
+ ("on", Json::str(reaction.event_name.clone())),
484
+ ("target_domain", Json::str(reaction.target_domain.clone())),
485
+ ("target_verb", Json::str(reaction.target_verb.clone())),
486
+ ("payload", reaction.payload.clone()),
487
+ ])
488
+ }
489
+
490
+ /// The live `sagas` map as a JSON array, in the shape the `"sagas"` input key parses.
491
+ fn saga_snapshot_json(sagas: &HashMap<(String, String), SagaInstance>) -> Json {
492
+ Json::Array(
493
+ sagas
494
+ .iter()
495
+ .map(|((process_manager, correlation), instance)| {
496
+ Json::obj(vec![
497
+ ("process_manager", Json::str(process_manager.clone())),
498
+ ("correlation", Json::str(correlation.clone())),
499
+ ("state", Json::str(instance.state.clone())),
500
+ ("memory", instance.memory.clone()),
501
+ // Round-trips the compensation ledger, as `state`/`memory`.
502
+ (
503
+ "completed_compensations",
504
+ Json::Array(
505
+ instance
506
+ .completed_compensations
507
+ .iter()
508
+ .map(|entry| {
509
+ Json::obj(vec![
510
+ ("command_name", Json::str(entry.command_name.clone())),
511
+ ("args", entry.args.clone()),
512
+ ])
513
+ })
514
+ .collect(),
515
+ ),
516
+ ),
517
+ ])
518
+ })
519
+ .collect(),
520
+ )
521
+ }
522
+
523
+ fn event_to_json(event: &Event) -> Json {
524
+ Json::obj(vec![
525
+ ("name", Json::str(event.name.clone())),
526
+ ("aggregate", Json::str(event.aggregate.clone())),
527
+ ("id", Json::str(event.id.clone())),
528
+ ("payload", event.payload.clone()),
529
+ // Key order matches Ruby's `Event#to_h`; `None` serialises as null.
530
+ ("occurred_at", event.occurred_at.clone().map(Json::Str).unwrap_or(Json::Null)),
531
+ ])
532
+ }
533
+
534
+ fn error_output(message: &str) -> String {
535
+ Json::obj(vec![("error", Json::str(message.to_string()))]).to_json_string()
536
+ }
537
+
538
+ #[cfg(test)]
539
+ mod routing_tests {
540
+ use super::*;
541
+
542
+ #[test]
543
+ fn top_level_with_selects_an_unrouted_compound_create_invocation() {
544
+ let step = Json::obj(vec![
545
+ ("verb", Json::str("Banking::SafeDepositBox.Rent")),
546
+ (
547
+ "with",
548
+ Json::obj(vec![
549
+ ("branch_code", Json::str("DOWNTOWN")),
550
+ ("box_number", Json::int(12)),
551
+ ]),
552
+ ),
553
+ ]);
554
+ let legacy_args = Json::obj(vec![]);
555
+
556
+ let selected = command_input(&step, &legacy_args);
557
+ let invocation = crate::kernel::CommandInvocation::from_json(selected).unwrap();
558
+ assert_eq!(invocation.route(), None);
559
+ assert_eq!(invocation.facts().get("branch_code").and_then(Json::as_str), Some("DOWNTOWN"));
560
+ assert_eq!(invocation.facts().get("box_number").and_then(Json::as_i64), Some(12));
561
+ }
562
+
563
+ // A domain fact named `to` must dry-run as a plain fact, never a routing attempt
564
+ // (pinned end to end by `roster_mark_dry_run_to_collision.json`).
565
+ #[test]
566
+ fn dry_run_command_input_never_sniffs_a_flat_facts_to_key_as_routing() {
567
+ let args = Json::obj(vec![
568
+ ("to", Json::obj(vec![("value", Json::int(570))])),
569
+ ("name", Json::obj(vec![("value", Json::str("crew-1"))])),
570
+ ]);
571
+
572
+ let wrapped = dry_run_command_input(&args);
573
+ let invocation = crate::kernel::CommandInvocation::from_json(&wrapped).unwrap();
574
+
575
+ assert_eq!(invocation.route(), None);
576
+ assert_eq!(
577
+ invocation.facts().get("to").and_then(|to| to.get("value")).and_then(Json::as_i64),
578
+ Some(570)
579
+ );
580
+ assert_eq!(
581
+ invocation.facts().get("name").and_then(|name| name.get("value")).and_then(Json::as_str),
582
+ Some("crew-1")
583
+ );
584
+ }
585
+ }