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,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hecks"
4
+ require_relative "../tools"
5
+
6
+ module Hecks
7
+ module Tools
8
+ # Tail-merge: interleaves the old world's post-cut writes into the live head by their recorded
9
+ # global ordinals, in one transaction.
10
+ #
11
+ # A record touched by both worlds since the cut refuses until it has an explicit winner:
12
+ #
13
+ # hecks merge_tail <domain> [--winner <id>=old] [--winner <id>=new] ...
14
+ module TailMerge
15
+ USAGE = "usage: hecks merge_tail <domain> [--winner <id>=old|new] ..."
16
+
17
+ module_function
18
+
19
+ # Merges the tail and prints how many writes it interleaved.
20
+ #
21
+ # @param argv [Array<String>] the domain directory and any `--winner <id>=old|new`
22
+ # @param root [String] unused: the domain is read from the path given
23
+ # @return [Integer] 0
24
+ # @raise [SystemExit] when the arguments are malformed, the domain cannot be loaded, or the
25
+ # merge is refused
26
+ def main(argv, **)
27
+ domain_path, winners = parse(argv.dup)
28
+ registry, bluebook = load_domain(domain_path)
29
+ settings = lineage_settings(registry, bluebook)
30
+ report_divergence(bluebook, settings)
31
+
32
+ begin
33
+ Hecks::Adapters::PostgresEra::LineageManager.merge!(
34
+ registry: registry, bluebook: bluebook, settings: settings, winners: winners
35
+ )
36
+ rescue Hecks::Runtime::WiringError => e
37
+ abort "REFUSED: #{e.message}"
38
+ end
39
+
40
+ puts "merged — the head now interleaves both worlds by their recorded ordinals"
41
+ winners.each { |id, side| puts " winner #{id}=#{side} appended as the newest row" }
42
+ 0
43
+ end
44
+
45
+ # @return [Array] the domain directory and the winners by record id
46
+ def parse(argv)
47
+ domain_path = nil
48
+ winners = {}
49
+ until argv.empty?
50
+ argument = argv.shift
51
+ if argument == "--winner"
52
+ declaration = argv.shift or abort "--winner needs <id>=old|new"
53
+ id, side = declaration.split("=", 2)
54
+ abort "--winner #{declaration}: the side must be `old` or `new`" unless %w[old new].include?(side)
55
+ winners[id] = side
56
+ else
57
+ domain_path = argument
58
+ end
59
+ end
60
+ abort USAGE unless domain_path
61
+ [domain_path, winners]
62
+ end
63
+
64
+ # @param domain_path [String] the domain directory
65
+ # @return [Array] the registry and its bluebook
66
+ def load_domain(domain_path)
67
+ loading = Hecks::Ports::Loading.bootstrap
68
+ directory = loading.bluebook_directory(domain_path)
69
+ registry = Hecks::Runtime::Registry.new(root: File.dirname(directory))
70
+ begin
71
+ Hecks.with_registry(registry) do
72
+ loading.load_library
73
+ loading.load_project(loading.shared_root(nil, directory))
74
+ loading.load_domain(directory)
75
+ end
76
+ rescue Hecks::Bluebook::DSL::Malformed => e
77
+ abort "REFUSED at load: #{e.message}"
78
+ end
79
+ bluebook = registry.bluebooks.values.first or abort "no bluebook in #{directory}"
80
+ [registry, bluebook]
81
+ end
82
+
83
+ # @return [Hash] the persistence binding's settings
84
+ # @raise [SystemExit] when the bound adapter is not lineage-capable
85
+ def lineage_settings(registry, bluebook)
86
+ first = bluebook.aggregates.first or abort "#{bluebook.name} declares no aggregates"
87
+ adapter_name = Hecks::Ports::Persistence::BindingPolicy.resolve(registry, bluebook.name, first).adapter
88
+ capable =
89
+ begin
90
+ adapter_class = registry.adapter_class(adapter_name)
91
+ adapter_class.respond_to?(:lineage_capable?) && adapter_class.lineage_capable?
92
+ rescue StandardError
93
+ false
94
+ end
95
+ unless capable
96
+ abort "a tail-merge needs a lineage journal; #{bluebook.name} is bound to #{adapter_name}, " \
97
+ "not PostgresEra"
98
+ end
99
+
100
+ registry.binding_settings(bluebook.name, Hecks::Ports::Persistence::VERB, adapter_name)
101
+ end
102
+
103
+ # @return [void]
104
+ def report_divergence(bluebook, settings)
105
+ db = Hecks::Adapters::PostgresEra.connect_for(bluebook.name, settings)
106
+ lineage = Hecks::Adapters::PostgresEra::Lineage.new(db, bluebook.name)
107
+ lineage.ensure_base!
108
+ eras = lineage.eras
109
+ diverged = eras.size > 1 ? (1...eras.last[:ordinal]).sum { |era| lineage.diverged_count(era) } : 0
110
+ db.close
111
+
112
+ puts "#{bluebook.name}: #{diverged} post-cut write#{'s' unless diverged == 1} " \
113
+ "in ancestor eras before the merge"
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hecks"
4
+ # --adapter=PostgresEra needs the era persistence plugin (ADR 0033).
5
+ require "hecks/ports/persistence/plugins/era"
6
+ require "optparse"
7
+ require_relative "../tools"
8
+
9
+ module Hecks
10
+ module Tools
11
+ # Provisions a tenant: validates the declared shape, writes the `.world` overlay through the
12
+ # `TenantProvisioning` port, boots once, and checks `tenant_capable?` and the Tenancy record.
13
+ #
14
+ # Deploy and Tenancy boot into one shared registry so the `translates` reaction in
15
+ # tenancy.hecksagon can see Deploy's `TenantProvisioned` event. The overlay replaces (shallow
16
+ # merge) the base `persisted_by` settings, so `--database` is required. A policy's refusal is
17
+ # swallowed by `PolicyInterpreter#deliver`, so success is confirmed by query.
18
+ #
19
+ # hecks deploy provision <domain-directory> <slug> --domain=name --realm=name \
20
+ # --schema=name --database=name [--adapter=PostgresEra]
21
+ module TenantProvisioning
22
+ USAGE = "usage: hecks deploy provision <domain-directory> <slug> --domain=NAME --realm=NAME " \
23
+ "--schema=NAME --database=NAME [--adapter=PostgresEra]"
24
+
25
+ # The chapters that must boot together for a tenant to be declared and registered.
26
+ CHAPTER_FILES = %w[deploy/bluebook/deploy.bluebook deploy/bluebook/deploy.hecksagon
27
+ tenancy/bluebook/tenancy.bluebook tenancy/bluebook/tenancy.hecksagon].freeze
28
+
29
+ module_function
30
+
31
+ # Provisions the tenant and prints what it wrote and confirmed.
32
+ #
33
+ # @param argv [Array<String>] the domain directory, the tenant's slug, then the flags
34
+ # @param root [String] unused: the chapters are read from the library
35
+ # @return [Integer] 0
36
+ # @raise [SystemExit] with the reason on stderr when the shape is invalid, provisioning is
37
+ # refused, or the tenant never registers
38
+ def main(argv, **)
39
+ argv = argv.dup
40
+ directory = argv.shift
41
+ slug = argv.shift
42
+ (directory && slug) or abort USAGE
43
+
44
+ options = parse(argv)
45
+ domain_directory = File.expand_path(directory)
46
+ File.directory?(domain_directory) or abort "no such domain directory: #{domain_directory}"
47
+
48
+ dispatcher = provision(slug, options, domain_directory)
49
+ confirm(dispatcher, slug, options, domain_directory)
50
+ 0
51
+ end
52
+
53
+ # @param argv [Array<String>] the flags after the slug
54
+ # @return [Hash{Symbol => String}] `domain`, `realm`, `schema`, `database` and `adapter`
55
+ # @raise [SystemExit] when a required flag is missing
56
+ def parse(argv)
57
+ options = { adapter: "PostgresEra" }
58
+ OptionParser.new do |parser|
59
+ parser.on("--domain=NAME") { |v| options[:domain] = v }
60
+ parser.on("--realm=NAME") { |v| options[:realm] = v }
61
+ parser.on("--schema=NAME") { |v| options[:schema] = v }
62
+ parser.on("--database=NAME") { |v| options[:database] = v }
63
+ parser.on("--adapter=NAME") { |v| options[:adapter] = v }
64
+ end.parse!(argv)
65
+
66
+ %i[domain realm schema database].each do |flag|
67
+ options[flag] or abort "hecks deploy provision needs --#{flag}"
68
+ end
69
+ options
70
+ end
71
+
72
+ # Boots Deploy and Tenancy together, validates the declared shape with a dry run of
73
+ # `Tenant.Provision` (nothing is recorded), then dispatches it: the record is created once,
74
+ # its policy writes the overlay through the `TenantProvisioning` port, and `TenantProvisioned`
75
+ # reaches Tenancy. The port answers `TenantProvisioned` or `ProvisioningRefused` and never
76
+ # raises, so the record's status says which.
77
+ #
78
+ # @param slug [String] the tenant's slug
79
+ # @param options [Hash] the parsed flags
80
+ # @param domain_directory [String] the domain's directory
81
+ # @return [Hecks::Runtime::Dispatcher] the dispatcher both chapters share
82
+ # @raise [SystemExit] when the declared shape is refused, or provisioning is refused
83
+ def provision(slug, options, domain_directory)
84
+ lib_hecks = File.expand_path("..", __dir__)
85
+ dispatcher = Hecks.boot_files(CHAPTER_FILES.map { |file| File.join(lib_hecks, file) }, install_doors: false)
86
+ facts = {
87
+ slug: { value: slug },
88
+ domain: { value: options[:domain] },
89
+ realm: { value: options[:realm] },
90
+ schema: { value: options[:schema] },
91
+ database: { value: options[:database] },
92
+ adapter: { value: options[:adapter] },
93
+ directory: { value: domain_directory }
94
+ }
95
+
96
+ begin
97
+ dispatcher.dry_run?("Deploy::Tenant.Provision", **facts)
98
+ dispatcher.dispatch("Deploy::Tenant.Provision", to: slug, with: facts)
99
+ rescue *Hecks::Runtime::DOMAIN_REFUSALS => e
100
+ abort "tenant #{slug.inspect} is invalid: #{e.message}"
101
+ end
102
+
103
+ tenant = dispatcher.registry.bluebook("Deploy").aggregate("Tenant")
104
+ record = dispatcher.registry.repository("Deploy", tenant).find(slug)
105
+ refused = record&.state&.dig(:refusal)
106
+ refused = refused[:value] if refused.is_a?(Hash)
107
+ record&.state&.dig(:status) == "refused" and
108
+ abort "provisioning #{slug.inspect} was refused: #{refused}"
109
+
110
+ puts "wrote #{File.join(domain_directory, 'environments', "#{slug}.world")}"
111
+ dispatcher
112
+ end
113
+
114
+ # Boots the domain under the tenant's overlay (which also provisions the schema, since
115
+ # `PostgresEra#connect_for` runs an idempotent `CREATE SCHEMA`), checks every bluebook of
116
+ # the registry is tenant capable, and confirms the tenant by query.
117
+ #
118
+ # @param dispatcher [Hecks::Runtime::Dispatcher] the shared dispatcher
119
+ # @param slug [String] the tenant's slug
120
+ # @param options [Hash] the parsed flags
121
+ # @param domain_directory [String] the domain's directory
122
+ # @return [void]
123
+ # @raise [SystemExit] when the tenant was never registered in Tenancy
124
+ def confirm(dispatcher, slug, options, domain_directory)
125
+ target = Hecks.boot(domain_directory, environment: slug, install_doors: false)
126
+ puts "booted #{options[:domain]} for tenant #{slug.inspect} under realm #{options[:realm].inspect}"
127
+
128
+ target.registry.bluebooks.each_key do |name|
129
+ Hecks::Runtime::TenantCheck.refuse_unless_tenant_capable!(target.registry, name)
130
+ end
131
+
132
+ # A refused reaction never raises, so the record itself is the proof.
133
+ tenant = dispatcher.registry.repository("Tenancy", dispatcher.registry.bluebook("Tenancy").aggregate("Tenant"))
134
+ .find(slug)
135
+ tenant or abort "provisioning succeeded but #{slug.inspect} was never registered in Tenancy — " \
136
+ "check registry.reaction_log for a silently refused Register"
137
+
138
+ puts "#{options[:domain]} is tenant_capable? for #{slug.inspect} — registered under realm #{options[:realm].inspect}"
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hecks"
4
+ # The audit reads the era persistence plugin's lineage journal and its audit machinery (ADR 0033).
5
+ require "hecks/ports/persistence/plugins/era"
6
+ require "json"
7
+ require_relative "../tools"
8
+
9
+ module Hecks
10
+ module Tools
11
+ # Runs the translation audit's three layers standalone against a domain's latest or pending
12
+ # edge. Read-only except `--approve`, which records the human approval in the database.
13
+ #
14
+ # hecks audit_translation <domain> [--approve]
15
+ module TranslationAudit
16
+ module_function
17
+
18
+ # Audits the edge and prints each aggregate's verdict and samples.
19
+ #
20
+ # @param argv [Array<String>] the domain directory, and `--approve` to record approval
21
+ # @param root [String] unused: the domain is read from the path given
22
+ # @return [Integer] 0 when the audit passes or there is no edge, 1 when it is refused
23
+ # @raise [SystemExit] when the domain cannot be loaded or is not lineage-capable
24
+ def main(argv, **)
25
+ argv = argv.dup
26
+ approve = argv.delete("--approve")
27
+ domain_path = argv.shift or abort "usage: hecks audit_translation <domain> [--approve]"
28
+
29
+ registry, bluebook, directory = load_domain(domain_path)
30
+ db, lineage = open_lineage(registry, bluebook)
31
+ audit(registry, bluebook, directory, db, lineage, approve)
32
+ end
33
+
34
+ # @param domain_path [String] the domain directory
35
+ # @return [Array] the registry, its bluebook and the bluebook directory
36
+ def load_domain(domain_path)
37
+ loading = Hecks::Ports::Loading.bootstrap
38
+ directory = loading.bluebook_directory(domain_path)
39
+ registry = Hecks::Runtime::Registry.new(root: File.dirname(directory))
40
+ begin
41
+ Hecks.with_registry(registry) do
42
+ loading.load_library
43
+ loading.load_project(loading.shared_root(nil, directory))
44
+ # Passes an environment overlay to `load_domain`, as `scaffold_translation` does.
45
+ loading.load_domain(directory, environment: ENV.fetch("HECKS_PROJECT_ENVIRONMENT", nil))
46
+ end
47
+ rescue Hecks::Bluebook::DSL::Malformed => e
48
+ abort "REFUSED at load: #{e.message}"
49
+ end
50
+ bluebook = registry.bluebooks.values.first or abort "no bluebook in #{directory}"
51
+ [registry, bluebook, directory]
52
+ end
53
+
54
+ # @param registry [Hecks::Runtime::Registry] the loaded registry
55
+ # @param bluebook [Object] the domain's bluebook
56
+ # @return [Array] the open connection and its ensured lineage
57
+ # @raise [SystemExit] when the bound adapter is not lineage-capable
58
+ def open_lineage(registry, bluebook)
59
+ first = bluebook.aggregates.first or abort "#{bluebook.name} declares no aggregates"
60
+ adapter_name = Hecks::Ports::Persistence::BindingPolicy.resolve(registry, bluebook.name, first).adapter
61
+ capable =
62
+ begin
63
+ adapter_class = registry.adapter_class(adapter_name)
64
+ adapter_class.respond_to?(:lineage_capable?) && adapter_class.lineage_capable?
65
+ rescue StandardError
66
+ false
67
+ end
68
+ unless capable
69
+ abort "the audit reads a lineage journal; #{bluebook.name} is bound to #{adapter_name}, " \
70
+ "not PostgresEra"
71
+ end
72
+
73
+ settings = registry.world(bluebook.name)&.for_binding(Hecks::Ports::Persistence::VERB, adapter_name) || {}
74
+ # The same `HECKS_SCHEMA` support as `scaffold_translation`.
75
+ settings = settings.merge(schema: ENV["HECKS_SCHEMA"]) if ENV["HECKS_SCHEMA"]
76
+ db = Hecks::Adapters::PostgresEra.connect_for(bluebook.name, settings)
77
+ lineage = Hecks::Adapters::PostgresEra::Lineage.new(db, bluebook.name)
78
+ lineage.ensure_base!
79
+ [db, lineage]
80
+ end
81
+
82
+ # @return [Integer] the exit status
83
+ def audit(registry, bluebook, directory, db, lineage, approve)
84
+ if lineage.eras.empty?
85
+ lineage.hold_first!(Hecks::Runtime::EraCheck.source_text_for(bluebook, directory),
86
+ projection: Hecks::Runtime::StorageShape.project(bluebook))
87
+ puts "#{bluebook.name} held era 1 just now — no edge to audit."
88
+ db.close
89
+ return 0
90
+ end
91
+
92
+ era, chain = edge_chain(registry, bluebook, lineage)
93
+ return 0 unless era
94
+
95
+ edge = chain.last[:translation]
96
+ failed = report(bluebook, lineage, edge, era, chain)
97
+ conclude(db, lineage, edge, failed, approve)
98
+ end
99
+
100
+ # The era the edge leads to and the chain of translations ending in it, or nil when the
101
+ # journal stands at era 1 with nothing to audit.
102
+ #
103
+ # @return [Array, nil] the era ordinal and the chain
104
+ def edge_chain(registry, bluebook, lineage)
105
+ manager = Hecks::Adapters::PostgresEra::LineageManager
106
+ eras = lineage.eras
107
+ latest = eras.last
108
+ current_shape = Hecks::Runtime::StorageShape.project(bluebook)
109
+ held_shape = Hecks::Runtime::StorageShape.project(manager.shadow(latest[:held_text]))
110
+
111
+ if held_shape == current_shape
112
+ era = latest[:ordinal]
113
+ if era == 1
114
+ puts "#{bluebook.name} stands at era 1 — no edge to audit."
115
+ return nil
116
+ end
117
+ [era, manager.edge_chain(registry, bluebook, eras, latest[:label])]
118
+ else
119
+ # a pending edge: audit it before any mint
120
+ manager.ensure_named!(lineage, latest)
121
+ latest = lineage.eras.last
122
+ to_label = Hecks::Runtime::StorageShape.mint_hash(bluebook)[0, Hecks::Runtime::StorageShape::LABEL_LENGTH]
123
+ pending = registry.translations.find do |t|
124
+ t.domain == bluebook.name && t.from == latest[:label] && t.to == to_label
125
+ end
126
+ pending or abort "no translation edge leads #{latest[:label]} to #{to_label} — " \
127
+ "run hecks scaffold_translation first"
128
+ chain = begin
129
+ manager.edge_chain(registry, bluebook, eras, latest[:label])
130
+ rescue StandardError
131
+ []
132
+ end
133
+ [latest[:ordinal] + 1, (chain || []) + [{ translation: pending }]]
134
+ end
135
+ end
136
+
137
+ # Prints each aggregate's verdict.
138
+ #
139
+ # @return [Boolean] whether any aggregate's audit was refused
140
+ def report(bluebook, lineage, edge, era, chain)
141
+ failed = false
142
+ bluebook.aggregates.each do |aggregate|
143
+ declared = edge.for_aggregate(aggregate.name)
144
+ after = begin
145
+ lineage.translated_latest(aggregate, era, chain)
146
+ rescue PG::Error => e
147
+ abort "REFUSED: #{e.message.strip}"
148
+ end
149
+ before = if chain.size > 1
150
+ lineage.translated_latest(aggregate, era, chain[0..-2])
151
+ else
152
+ lineage.ancestor_latest(aggregate, era, chain)
153
+ end
154
+ verdict = Hecks::Translation::Audit.check(aggregate: aggregate, declared: declared,
155
+ before: before, after: after)
156
+ print_verdict(bluebook, aggregate, edge, after, verdict)
157
+ failed ||= !verdict.ok?
158
+ end
159
+ failed
160
+ end
161
+
162
+ # @return [void]
163
+ def print_verdict(bluebook, aggregate, edge, after, verdict)
164
+ puts "── #{bluebook.name}::#{aggregate.name} (edge #{edge.from} → #{edge.to}, " \
165
+ "#{after.size} record#{'s' unless after.size == 1})"
166
+ verdict.violations.each { |violation| puts " REFUSED: #{violation}" }
167
+ puts " dropped (declared data loss): #{verdict.dropped.join(', ')}" unless verdict.dropped.empty?
168
+ puts " unfed (no rule, no default — add default: if required): #{verdict.unfed.join(', ')}" unless verdict.unfed.empty?
169
+ verdict.samples.each do |sample|
170
+ puts " ##{sample[:id]}"
171
+ puts " before: #{JSON.generate(sample[:before])}" if sample[:before]
172
+ puts " after: #{JSON.generate(sample[:after])}" if sample[:after]
173
+ end
174
+ end
175
+
176
+ # @return [Integer] the exit status
177
+ def conclude(db, lineage, edge, failed, approve)
178
+ if failed
179
+ db.close
180
+ puts "AUDIT REFUSED."
181
+ return 1
182
+ end
183
+
184
+ needs_approval = edge.aggregates.any? { |declared| !declared.computes.empty? || !declared.rekeys.empty? }
185
+ if needs_approval && approve
186
+ # Binds to what was reviewed — this edge and the journal as it stands now — so it must
187
+ # live in the database, not the repo.
188
+ lineage.record_approval!(
189
+ from: edge.from, to: edge.to,
190
+ edge_digest: Hecks::Translation::Audit.edge_digest(edge)
191
+ )
192
+ db.close
193
+ puts "AUDIT PASSED — approval recorded in the database, bound to this edge and this journal."
194
+ puts "A journal that advances before the mint invalidates it; re-run with --approve if that happens."
195
+ elsif needs_approval
196
+ db.close
197
+ puts "AUDIT PASSED — this edge carries a compute or rekey rule, and the samples above are " \
198
+ "its ONLY verification."
199
+ puts "If they show what you intended, run again with --approve; the mint refuses until you do."
200
+ else
201
+ db.close
202
+ puts "AUDIT PASSED — review the samples above; intent is yours to approve."
203
+ end
204
+ 0
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hecks"
4
+ # The translation and scaffold machinery lives in the era persistence plugin (ADR 0033).
5
+ require "hecks/ports/persistence/plugins/era"
6
+ require_relative "../tools"
7
+
8
+ module Hecks
9
+ module Tools
10
+ # Diffs the held era against the current bluebook into an edge file: confident rules inline,
11
+ # ambiguities as `unresolved` lines to resolve by hand.
12
+ #
13
+ # hecks scaffold_translation <domain>
14
+ module TranslationScaffold
15
+ module_function
16
+
17
+ # Writes the edge file and prints what is left to decide.
18
+ #
19
+ # @param argv [Array<String>] the domain directory
20
+ # @param root [String] unused: the domain is read from the path given
21
+ # @return [Integer] 0
22
+ # @raise [SystemExit] when the domain cannot be loaded or its adapter holds no eras
23
+ def main(argv, **)
24
+ argv = argv.dup
25
+ domain_path = argv.shift or abort "usage: hecks scaffold_translation <domain>"
26
+
27
+ registry, bluebook, directory = load_domain(domain_path)
28
+ first = bluebook.aggregates.first or abort "#{bluebook.name} declares no aggregates"
29
+ adapter_name = Hecks::Ports::Persistence::BindingPolicy.resolve(registry, bluebook.name, first).adapter
30
+ capable =
31
+ begin
32
+ adapter_class = registry.adapter_class(adapter_name)
33
+ adapter_class.respond_to?(:lineage_capable?) && adapter_class.lineage_capable?
34
+ rescue StandardError
35
+ false
36
+ end
37
+ # Eras belong to the adapters that can carry data across them. An edge scaffolded for an
38
+ # adapter that cannot apply it would be a document nothing will ever run.
39
+ unless capable
40
+ abort "#{bluebook.name} is bound to #{adapter_name}, which holds no eras — " \
41
+ "a translation edge is only applied by a lineage-capable adapter. " \
42
+ "Bind PostgresEra, or change the shape and its stored data by hand."
43
+ end
44
+
45
+ scaffold(registry, bluebook, directory, adapter_name)
46
+ end
47
+
48
+ # @param domain_path [String] the domain directory
49
+ # @return [Array] the registry, its bluebook and the bluebook directory
50
+ def load_domain(domain_path)
51
+ loading = Hecks::Ports::Loading.bootstrap
52
+ directory = loading.bluebook_directory(domain_path)
53
+ registry = Hecks::Runtime::Registry.new(root: File.dirname(directory))
54
+ Hecks.with_registry(registry) do
55
+ loading.load_library
56
+ loading.load_project(loading.shared_root(nil, directory))
57
+ # deliberately not translations/*.bluebook: an unresolved edge file refuses to load, and
58
+ # regenerating it is this tool's whole job
59
+ loading.load_each(directory, %w[*.port *.adapter])
60
+ loading.load_bluebooks(directory)
61
+ loading.load_each(directory, %w[*.hecksagon *.world])
62
+
63
+ # A domain that splits `persisted_by` per environment declares none in its base
64
+ # `.hecksagon`, so resolution fails without this overlay — opt-in via
65
+ # HECKS_PROJECT_ENVIRONMENT, a no-op otherwise.
66
+ if (target_environment = ENV.fetch("HECKS_PROJECT_ENVIRONMENT", nil))
67
+ loading.load_each(directory, [File.join("environments", "#{target_environment}.hecksagon")])
68
+ loading.load_each(directory, [File.join("environments", "#{target_environment}.world")])
69
+ end
70
+ end
71
+ bluebook = registry.bluebooks.values.first or abort "no bluebook in #{directory}"
72
+ [registry, bluebook, directory]
73
+ end
74
+
75
+ # @return [Integer] 0
76
+ def scaffold(registry, bluebook, directory, adapter_name)
77
+ current_shape = Hecks::Runtime::StorageShape.project(bluebook)
78
+ manager = Hecks::Adapters::PostgresEra::LineageManager
79
+
80
+ # `World#for_binding` keys settings by the world-declared adapter name (e.g.
81
+ # "PostgresEra"); a bypassed name like "Postgres" finds nothing.
82
+ settings = registry.world(bluebook.name)&.for_binding(Hecks::Ports::Persistence::VERB, adapter_name) || {}
83
+ # A Shared-mode domain's `.world` file declares no schema of its own — production is scoped
84
+ # by the Lambda's own HECKS_SCHEMA. Run by hand outside Lambda, this tool needs that same
85
+ # schema supplied the same way.
86
+ settings = settings.merge(schema: ENV["HECKS_SCHEMA"]) if ENV["HECKS_SCHEMA"]
87
+ db = Hecks::Adapters::PostgresEra.connect_for(bluebook.name, settings)
88
+ lineage = Hecks::Adapters::PostgresEra::Lineage.new(db, bluebook.name)
89
+ lineage.ensure_base!
90
+ if lineage.eras.empty?
91
+ lineage.hold_first!(Hecks::Runtime::EraCheck.source_text_for(bluebook, directory),
92
+ projection: current_shape)
93
+ puts "#{bluebook.name} held era 1 just now — nothing to scaffold."
94
+ return 0
95
+ end
96
+ latest = lineage.eras.last
97
+ held_bluebook = manager.shadow(latest[:held_text])
98
+ if Hecks::Runtime::StorageShape.project(held_bluebook) == current_shape
99
+ puts "#{bluebook.name} matches era #{latest[:ordinal]} — nothing to scaffold."
100
+ return 0
101
+ end
102
+ path = manager.scaffold!(registry, bluebook, lineage, latest, directory)
103
+ diffed = Hecks::Translation::Scaffold.diff(held_bluebook, bluebook)
104
+ db.close
105
+
106
+ report(path, diffed)
107
+ 0
108
+ end
109
+
110
+ # @return [void]
111
+ def report(path, diffed)
112
+ text = File.read(path)
113
+ unresolved = text.scan(/^\s*unresolved /).size
114
+ unclaimed = diffed ? diffed[:unclaimed] : []
115
+ puts "wrote #{path}"
116
+ unclaimed.each do |name|
117
+ puts "UNCLAIMED: #{name} existed and now doesn't, and its successor is ambiguous — " \
118
+ "add `aggregate \"NewName\", was: #{name.inspect}` (with its rules) or " \
119
+ "`retired #{name.inspect}` by hand."
120
+ end
121
+ if unresolved.zero? && unclaimed.empty?
122
+ puts "0 unresolved — this shape change costs one extra boot and no typing. " \
123
+ "Check it with hecks audit_translation, then boot."
124
+ elsif unresolved.positive?
125
+ puts "#{unresolved} unresolved — decide what each became (rename/move/convert/drop, or " \
126
+ "compute on PostgresEra), then boot."
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end