hecks 2.9.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +4 -2
  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 +7 -4
  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 +6 -3
  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,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "hecks"
5
+ require "fileutils"
6
+
7
+ module Hecks
8
+ # The seed records of `spec/fixtures/persistence_legacy/`, over `examples/banking`, and the
9
+ # regeneration of those fixtures through the real adapters.
10
+ #
11
+ # Banking's IR only: no hecksagon, no boot; adapters are built straight from `aggregate:`.
12
+ module PersistenceLegacyFixture
13
+ # The gem's root directory.
14
+ ROOT = File.expand_path("../..", __dir__)
15
+ # Where the fixtures stand in a checkout.
16
+ DIR = File.join(ROOT, "spec/fixtures/persistence_legacy").freeze
17
+
18
+ # Each seed: aggregate name, id and field values.
19
+ SEEDS = [
20
+ [
21
+ "Account", "ACC-1",
22
+ {
23
+ number: { value: "ACC-1" },
24
+ customer: "CUST-1",
25
+ balance: { cents: 1250, currency: "USD" },
26
+ kind: { name: "current" },
27
+ daily_limit: { cents: 500 },
28
+ ledger: [
29
+ { sequence: { value: 1 }, amount: { cents: 1000, currency: "USD" }, narrative: { text: "opening" },
30
+ direction: { value: "credit" }, state: "posted" },
31
+ { sequence: { value: 2 }, amount: { cents: 250, currency: "USD" }, narrative: { text: "top up" },
32
+ direction: { value: "credit" }, state: "reversed" }
33
+ ],
34
+ status: "open",
35
+ customer_status: "active"
36
+ }
37
+ ],
38
+ [
39
+ "CardPayment", "AUTH-1",
40
+ {
41
+ authorisation: { value: "AUTH-1" },
42
+ account: "ACC-1",
43
+ amount: { cents: 300 },
44
+ merchant: { value: "Cafe" },
45
+ tags: [{ value: "food" }, { value: "travel" }],
46
+ status: "authorized",
47
+ account_status: "open"
48
+ }
49
+ ]
50
+ ].freeze
51
+
52
+ module_function
53
+
54
+ # @return [Hecks::Bluebook::Structure::Domain] banking's bluebook, loaded once
55
+ def bluebook
56
+ @bluebook ||= begin
57
+ registry = Hecks::Runtime::Registry.new
58
+ Hecks.with_registry(registry) do
59
+ Kernel.load(File.join(ROOT, "lib/hecks/ports/persistence.port"))
60
+ Kernel.load(File.join(ROOT, "lib/hecks/ports/extraction.port"))
61
+ Kernel.load(File.join(ROOT, "lib/hecks/adapters/driven/prism.adapter"))
62
+ folder = Hecks::Adapters::Folder.new
63
+ folder.load_bluebooks(folder.bluebook_directory(File.join(ROOT, "examples/banking/bluebook")))
64
+ end
65
+ registry.bluebook("Banking")
66
+ end
67
+ end
68
+
69
+ # @param name [String] an aggregate of banking
70
+ # @return [Object] its definition
71
+ def aggregate(name) = bluebook.aggregate(name)
72
+
73
+ # @return [Array<Hecks::Runtime::Instance>] one instance for each seed
74
+ def instances
75
+ SEEDS.map do |name, id, fields|
76
+ aggregate = aggregate(name)
77
+ instance = Hecks::Runtime::Instance.new(aggregate: aggregate, id: id)
78
+ fields.each { |field, value| instance[field] = Hecks::Runtime::Value.for(aggregate, field, value) }
79
+ instance
80
+ end
81
+ end
82
+
83
+ # In-memory SQLite answering `Connection`'s four methods, as `d1_spec.rb` does.
84
+ #
85
+ # @return [Object] the connection
86
+ def fake_d1_connection
87
+ require "sqlite3"
88
+ db = SQLite3::Database.new(":memory:")
89
+ db.results_as_hash = true
90
+ Class.new do
91
+ def initialize(db) = @db = db
92
+ def execute(sql, binds = []) = @db.execute(sql, binds)
93
+ def get_first_row(sql, binds = []) = execute(sql, binds).first
94
+ def get_first_value(sql, binds = []) = get_first_row(sql, binds)&.values&.first
95
+ end.new(db)
96
+ end
97
+
98
+ # Makes `D1::Connection.new` answer `connection` for the length of the block.
99
+ #
100
+ # @param connection [Object] the connection to answer
101
+ # @yield the work that builds a D1 adapter
102
+ # @return [Object] the block's value
103
+ def with_d1_connection(connection)
104
+ klass = Hecks::Adapters::D1::Connection
105
+ klass.define_singleton_method(:new) { |**| connection }
106
+ yield
107
+ ensure
108
+ klass.singleton_class.send(:remove_method, :new) if klass.singleton_class.method_defined?(:new, false)
109
+ end
110
+ end
111
+ end
112
+
113
+ require_relative "persistence_legacy_fixture/regenerate"
@@ -3,6 +3,7 @@ require_relative "../../../../ports/persistence/binding_policy"
3
3
  require_relative "lineage"
4
4
  require_relative "../../../../naming"
5
5
  require_relative "../../../../framework"
6
+ require_relative "../../../../chapters"
6
7
  require_relative "../../../../runtime/registry"
7
8
  require_relative "expected_era"
8
9
 
@@ -115,21 +116,23 @@ module Hecks
115
116
 
116
117
  # Picks the files to read for a bluebook no file in the domain directory declares.
117
118
  #
118
- # Tries, in order: the domain's own single remaining file, a framework member, then a
119
- # vendored embryonaut bluebook.
119
+ # Tries, in order: a chapter the gem carries (the QA ledger's QualityControl), the domain's
120
+ # own single remaining file, a framework member, then a vendored embryonaut bluebook.
120
121
  #
121
122
  # @param bluebook [Bluebook::Chapter] the bluebook whose source is wanted
122
123
  # @param directory [String] path of the domain's own bluebook directory
123
124
  # @param domain_files [Array<String>] paths of every `.bluebook` file in `directory`
124
125
  # @param registry [Runtime::Registry, nil] the registry asked for a vendored name; nil
125
126
  # skips the vendored lookup
126
- # @return [Array<String>] file paths to read, in load order; `[]` when the bluebook
127
- # comes from none of the three sources
127
+ # @return [Array<String>] file paths to read, in load order; `[]` when none applies
128
128
  def fallback_source_files(bluebook, directory, domain_files, registry)
129
129
  vendored_name = registry && vendored_bluebook_name_for(registry, bluebook.name)
130
130
  framework_path = Framework.members[bluebook.name]
131
+ attached = Chapters.index.fetch(bluebook.name, [])
131
132
 
132
- if domain_files.size == 1 && !framework_path && !vendored_name
133
+ if attached.any? && !framework_path && !vendored_name
134
+ attached
135
+ elsif domain_files.size == 1 && !framework_path && !vendored_name
133
136
  domain_files
134
137
  elsif framework_path
135
138
  [framework_path]
@@ -139,7 +139,10 @@ module Hecks
139
139
  run = lambda do
140
140
  Hecks.with_registry(scratch) do
141
141
  loading.load_library
142
- Kernel.eval(source, TOPLEVEL_BINDING, path, 1)
142
+ # Held text of a chapter spread over files is several blocks in one string, each
143
+ # opening the same chapter: judge once after all of them, as a directory load does.
144
+ Hecks::Bluebook::MetaValidator.defer { Kernel.eval(source, TOPLEVEL_BINDING, path, 1) }
145
+ Hecks::Bluebook::MetaValidator.judge_deferred!(scratch)
143
146
  end
144
147
  end
145
148
  shadow ? Hecks::Bluebook::MetaValidator.while_shadow_parsing(&run) : run.call
@@ -59,13 +59,26 @@ module Hecks
59
59
  # @raise [Unreachable] if the request fails or the status is not `200`
60
60
  # @raise [BadResponse] if the body is not JSON or its `era` is not a non-empty string
61
61
  def fetch_era(url, timeout: 10)
62
+ fetch_version(url, timeout: timeout).fetch("era")
63
+ end
64
+
65
+ # Asks a host for its `/version` document.
66
+ #
67
+ # @param url [String] the host's base URL, or its `/version` URL
68
+ # @param timeout [Numeric] seconds allowed for connecting and for reading
69
+ # @return [Hash{String => String}] `"era"` (never empty) and `"version"` (empty when the
70
+ # host reports none)
71
+ # @raise [Unreachable] if the request fails or the status is not `200`
72
+ # @raise [BadResponse] if the body is not JSON or its `era` is not a non-empty string
73
+ def fetch_version(url, timeout: 10)
62
74
  response = get(version_url(url), timeout)
63
75
  raise Unreachable, "#{version_url(url)} answered #{response.code}" unless response.code == "200"
64
76
 
65
- era = JSON.parse(response.body)["era"]
77
+ document = JSON.parse(response.body)
78
+ era = document["era"]
66
79
  raise BadResponse, "#{version_url(url)} carries no era" unless era.is_a?(String) && !era.empty?
67
80
 
68
- era
81
+ { "era" => era, "version" => document["version"].to_s }
69
82
  rescue JSON::ParserError, TypeError
70
83
  raise BadResponse, "#{version_url(url)} did not answer a JSON version document"
71
84
  end
@@ -22,7 +22,7 @@ module Hecks
22
22
  end
23
23
  end
24
24
 
25
- # Skips the integrity check `eras` runs, so bin/reattest_era can show the raw row first.
25
+ # Skips the integrity check `eras` runs, so hecks reattest can show the raw row first.
26
26
  def raw_era(ordinal)
27
27
  rows = @db.exec_params(
28
28
  "SELECT held_text, held_digest, hash, held_projection::text FROM hecks_eras WHERE domain = $1 AND ordinal = $2",
@@ -53,7 +53,7 @@ module Hecks
53
53
  raise Runtime::WiringError,
54
54
  "cannot merge the tail of #{@domain}: touched by both worlds since the cut — " \
55
55
  "#{unresolved.map { |storage, id| "#{storage}##{id}" }.sort.join(', ')}. " \
56
- "Name each winner (--winner <id>=old or --winner <id>=new), then run bin/merge_tail again. " \
56
+ "Name each winner (--winner <id>=old or --winner <id>=new), then run hecks merge_tail again. " \
57
57
  "A winner takes the WHOLE record — the aggregate is the consistency boundary, so the " \
58
58
  "loser's edits are discarded even where they touched different attributes"
59
59
  end
@@ -6,7 +6,7 @@ module Hecks
6
6
  module Adapters
7
7
  class PostgresEra
8
8
  module LineageManager
9
- # Tail-merge (bin/merge_tail): interleaves the stale world's post-cut writes into the
9
+ # Tail-merge (hecks merge_tail): interleaves the stale world's post-cut writes into the
10
10
  # head by global ordinal, audited, in one transaction.
11
11
  module MergeCoordinator
12
12
  # Merges writes old checkouts made after the last mint into the current era's head.
@@ -2,6 +2,7 @@ require_relative "../../../../../../runtime/registry"
2
2
  require_relative "../../storage_shape"
3
3
  require_relative "../../translation/audit"
4
4
  require_relative "../../translation/scaffold"
5
+ require_relative "../../translation/approval_file"
5
6
 
6
7
  module Hecks
7
8
  module Adapters
@@ -20,7 +21,7 @@ module Hecks
20
21
  ordinal = latest[:ordinal] + 1
21
22
 
22
23
  edge = resolve_edge!(registry, bluebook, lineage, latest, label, ordinal, directory)
23
- ensure_compute_rekey_approved!(bluebook, lineage, edge, ordinal)
24
+ ensure_compute_rekey_approved!(bluebook, lineage, edge, ordinal, directory: directory)
24
25
  check_coverage!(registry, bluebook, shadow(latest[:held_text]), edge)
25
26
 
26
27
  chain = edge_chain(registry, bluebook, lineage.eras, label)
@@ -49,32 +50,48 @@ module Hecks
49
50
  unless edge.to == label
50
51
  raise Runtime::WiringError,
51
52
  "cannot boot #{bluebook.name}: the translation edge from #{latest[:label]} targets " \
52
- "#{edge.to}, but the current shape is #{label} — the edge is stale; re-run bin/scaffold_translation"
53
+ "#{edge.to}, but the current shape is #{label} — the edge is stale; re-run hecks scaffold_translation"
53
54
  end
54
55
  edge
55
56
  end
56
57
 
57
- # Refuses a mint whose edge carries a compute or rekey rule without a recorded,
58
- # edge-matching, journal-current approval.
58
+ # Refuses a mint whose edge carries a compute or rekey rule without an approval.
59
59
  #
60
- # A compute or rekey's only verification is a human-approved audit sample.
61
- def ensure_compute_rekey_approved!(bluebook, lineage, edge, ordinal)
62
- return unless edge.aggregates.any? { |declared| !declared.computes.empty? || !declared.rekeys.empty? }
60
+ # A compute or rekey's only verification is a human-approved audit sample. Two approvals
61
+ # satisfy it: one recorded in the journal that matches the edge and the journal's current
62
+ # tip, or a committed `translations/<edge>.approval` that matches the edge's digest and
63
+ # records a passed rehearsal on a compatible host release. A committed approval that
64
+ # applies is written into the journal, so the journal stays the single history.
65
+ def ensure_compute_rekey_approved!(bluebook, lineage, edge, ordinal, directory: nil)
66
+ return unless Translation::ApprovalFile.needs_rehearsal?(edge)
63
67
 
64
68
  approval = lineage.approval_for(from: edge.from, to: edge.to)
65
- unless approval && approval[:edge_digest] == Translation::Audit.edge_digest(edge)
69
+ digest = Translation::Audit.edge_digest(edge)
70
+ tip = lineage.last_ordinal
71
+ return if approval && approval[:edge_digest] == digest && approval[:reviewed_ordinal] == tip
72
+
73
+ if Translation::ApprovalFile.applicable(directory, edge)
74
+ lineage.record_approval!(from: edge.from, to: edge.to, edge_digest: digest)
75
+ return
76
+ end
77
+
78
+ unless approval && approval[:edge_digest] == digest
79
+ if (mismatch = Translation::ApprovalFile.host_mismatch(directory, edge))
80
+ raise Runtime::WiringError,
81
+ "cannot mint era #{ordinal} of #{bluebook.name}: the committed approval does not " \
82
+ "apply — #{mismatch}"
83
+ end
84
+
66
85
  raise Runtime::WiringError,
67
86
  "cannot mint era #{ordinal} of #{bluebook.name}: this edge carries a compute or rekey " \
68
87
  "rule, and the audit's human-approved sample is its only verification — run " \
69
- "bin/translation_audit with --approve, then boot again"
88
+ "hecks audit_translation with --approve, then boot again"
70
89
  end
71
- tip = lineage.last_ordinal
72
- return unless approval[:reviewed_ordinal] != tip
73
90
 
74
91
  raise Runtime::WiringError,
75
92
  "cannot mint era #{ordinal} of #{bluebook.name}: the journal advanced past the approved " \
76
93
  "review (ordinal #{approval[:reviewed_ordinal]} reviewed, #{tip} now) — the samples a " \
77
- "human approved no longer cover the data; re-run bin/translation_audit with --approve"
94
+ "human approved no longer cover the data; re-run hecks audit_translation with --approve"
78
95
  end
79
96
 
80
97
  # Refuses toward the authoring loop, or, under HECKS_SCAFFOLD=1, scaffolds the
@@ -84,13 +101,13 @@ module Hecks
84
101
  path = scaffold!(registry, bluebook, lineage, latest, directory)
85
102
  raise Runtime::WiringError,
86
103
  "cannot boot #{bluebook.name}: the shape changed (era #{ordinal}) — wrote #{path}; " \
87
- "review it (resolve every unresolved), check it with bin/translation_audit, then boot again"
104
+ "review it (resolve every unresolved), check it with hecks audit_translation, then boot again"
88
105
  end
89
106
 
90
107
  raise Runtime::WiringError,
91
108
  "cannot boot #{bluebook.name}: the shape changed (era #{ordinal}) and no translation edge " \
92
- "covers it — run bin/scaffold_translation to write the edge, " \
93
- "check it with bin/translation_audit, then boot again"
109
+ "covers it — run hecks scaffold_translation to write the edge, " \
110
+ "check it with hecks audit_translation, then boot again"
94
111
  end
95
112
 
96
113
  # Diffs the held era against the current shape and writes the edge file —
@@ -3,6 +3,7 @@ require "json"
3
3
  require_relative "../../../../adapters/driven/sql_query_builder"
4
4
  require_relative "../../../../adapters/driven/postgres/outbox"
5
5
  require_relative "../../../../adapters/driven/postgres/reconnect"
6
+ require_relative "../../../../adapters/driven/postgres/shared_connection"
6
7
  require_relative "postgres_era/lineage"
7
8
  require_relative "postgres_era/lineage_manager"
8
9
  require_relative "postgres_era/events"
@@ -118,8 +119,10 @@ module Hecks
118
119
  "cannot bind PostgresEra at #{declared} for #{name}: #{e.message.strip}"
119
120
  end
120
121
 
121
- # Connects and provisions the journal, this aggregate's head and field caches, and the
122
- # event, saga and outbox tables. Every step is idempotent.
122
+ # Joins the process's shared connection for the declared database and schema, so every
123
+ # aggregate of a domain runs on one connection, then provisions the journal, this
124
+ # aggregate's head and field caches, and the event, saga and outbox tables. Every step is
125
+ # idempotent.
123
126
  #
124
127
  # @param settings [Hash{Symbol, String => Object}] world settings plus what
125
128
  # `RepositoryFactory.build` merges in: `domain`, `era` and `superseded_by`
@@ -127,7 +130,7 @@ module Hecks
127
130
  def initialize(aggregate:, settings: {}, root: nil)
128
131
  @aggregate = aggregate
129
132
  @settings = settings
130
- @db = self.class.connect_for(aggregate.name, settings)
133
+ @db = PostgresSharedConnection.for(aggregate.name, settings, connector: self.class)
131
134
  # Journal name: the owning bluebook's declared name, matching the key rust/host derives its
132
135
  # advisory lock from (ADR 0036). A bare aggregate with no owner falls back to its own name.
133
136
  @domain = self.class.setting(
@@ -0,0 +1,227 @@
1
+ require "json"
2
+ require "date"
3
+ require "fileutils"
4
+ require_relative "audit/approval_digest"
5
+ require_relative "../../../../../version"
6
+
7
+ module Hecks
8
+ module Translation
9
+ # The committed form of an edge's approval: `translations/<edge>.approval`, a JSON file beside
10
+ # the edge that a reviewer sees in the same diff.
11
+ #
12
+ # It binds to the edge's content digest and not to the journal's tip, so a production journal
13
+ # that keeps writing between the commit and the deploy does not void it. An edge with a compute
14
+ # or rekey rule also carries the rehearsal a person ran against real data, since the audit's
15
+ # samples are that edge's only other verification.
16
+ module ApprovalFile
17
+ EXTENSION = ".approval".freeze
18
+
19
+ # The keys of a rehearsal block; every one is required when the block is required.
20
+ #
21
+ # `host_version` is the Hecks release the rehearsal ran on, and the gate checks it: a
22
+ # rehearsal counts only on a host whose release shares its `major.minor` (see
23
+ # `host_compatible?`).
24
+ REHEARSAL_KEYS = %w[snapshot host_version result at].freeze
25
+
26
+ # How a `host_version` reads: `major.minor`, an optional patch, an optional pre-release or
27
+ # build suffix. Only the first two numbers take part in the comparison.
28
+ HOST_LINE = /\A(\d+)\.(\d+)(?:\.\d+)?(?:[-+][0-9A-Za-z.+-]+)?\z/
29
+
30
+ # The release the running host reports: the Hecks release this code was built for. The Rust
31
+ # host embeds the same string from `rust/host/HECKS_RELEASE`, and a spec fails when the two
32
+ # drift.
33
+ HOST_RELEASE = Hecks::VERSION
34
+
35
+ # An ISO 8601 time: `2026-09-28T12:30:00Z`, with optional fraction and `+HH:MM` offset. The
36
+ # host's approval.rs accepts the same shape.
37
+ TIMESTAMP = /\A(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})\z/
38
+
39
+ extend Audit::ApprovalDigest
40
+
41
+ module_function
42
+
43
+ # Names an edge by the labels it joins; labels are hex, so the dash cannot be part of one.
44
+ #
45
+ # @param edge [Bluebook::Translation] the parsed translation edge
46
+ # @return [String] `<from>-<to>`
47
+ def edge_name(edge) = "#{edge.from}-#{edge.to}"
48
+
49
+ # Where an edge's approval lives.
50
+ #
51
+ # @param directory [String] the domain's bluebook directory
52
+ # @param edge [Bluebook::Translation] the parsed translation edge
53
+ # @return [String] `<directory>/translations/<edge>.approval`
54
+ def path_for(directory, edge) = File.join(directory, "translations", "#{edge_name(edge)}#{EXTENSION}")
55
+
56
+ # Whether the edge carries a rule whose only verification is a human's review of samples.
57
+ #
58
+ # @param edge [Bluebook::Translation] the parsed translation edge
59
+ # @return [Boolean] true when any aggregate declares a compute or a rekey
60
+ def needs_rehearsal?(edge)
61
+ edge.aggregates.any? { |declared| !declared.computes.empty? || !declared.rekeys.empty? }
62
+ end
63
+
64
+ # Whether a rehearsal block records a run that passed, with every field named.
65
+ #
66
+ # @param rehearsal [Hash, nil] the block, string-keyed as it reads from JSON
67
+ # @return [Boolean] true when all of `REHEARSAL_KEYS` are non-empty Strings and `result` is
68
+ # `pass`
69
+ def rehearsed?(rehearsal)
70
+ return false unless rehearsal.is_a?(Hash)
71
+
72
+ REHEARSAL_KEYS.all? { |key| named?(rehearsal[key]) } && rehearsal["result"] == "pass"
73
+ end
74
+
75
+ # The `major.minor` a version names.
76
+ #
77
+ # @param version [Object] a `host_version` as it reads from JSON
78
+ # @return [Array<Integer>, nil] `[major, minor]`, or nil when it is not a version
79
+ def release_line(version)
80
+ match = HOST_LINE.match(version) if version.is_a?(String)
81
+ match && [match[1].to_i, match[2].to_i]
82
+ end
83
+
84
+ # Whether a rehearsal ran on a host compatible with the running one.
85
+ #
86
+ # The rule is the smallest sound one: equal `major.minor`. A patch release does not change
87
+ # what a mint does, so a rehearsal survives it; a minor or major release may, so a rehearsal
88
+ # from an older host (or a newer one, which proves nothing about this one) does not.
89
+ #
90
+ # @param rehearsal [Hash] a rehearsal block, string-keyed
91
+ # @param host_version [String] the running host's release
92
+ # @return [Boolean] true when both versions parse and share `major.minor`
93
+ def host_compatible?(rehearsal, host_version = HOST_RELEASE)
94
+ line = release_line(rehearsal["host_version"])
95
+ !line.nil? && line == release_line(host_version)
96
+ end
97
+
98
+ # The refusal for a rehearsal that ran on another host line; rust/host words it the same.
99
+ #
100
+ # @param rehearsal [Hash] a rehearsal block, string-keyed
101
+ # @param host_version [String] the running host's release
102
+ # @return [String] names both versions
103
+ def host_refusal(rehearsal, host_version = HOST_RELEASE)
104
+ line = release_line(host_version)&.join(".")
105
+ "the rehearsal ran on Hecks #{rehearsal['host_version']}, but this host is Hecks #{host_version}; " \
106
+ "a rehearsal counts only on a host of the same major.minor (#{line}.x) — " \
107
+ "re-run the rehearsal on this host and approve again"
108
+ end
109
+
110
+ # Whether a value is a String with something in it; a number or nil names nothing.
111
+ #
112
+ # @param value [Object] a field as it reads from JSON
113
+ # @return [Boolean] true for a String that is not blank
114
+ def named?(value) = value.is_a?(String) && !value.strip.empty?
115
+
116
+ # Whether a value is an ISO 8601 time that exists on the calendar.
117
+ #
118
+ # @param value [Object] a field as it reads from JSON
119
+ # @return [Boolean] true for a String of the `TIMESTAMP` shape with a real date and clock time
120
+ def timestamp?(value)
121
+ return false unless value.is_a?(String) && (match = TIMESTAMP.match(value))
122
+
123
+ year, month, day, hour, minute, second = match.captures.map(&:to_i)
124
+ Date.valid_date?(year, month, day) && hour < 24 && minute < 60 && second < 60
125
+ end
126
+
127
+ # Whether an approval names who approved it and when, the two things a reviewer signs.
128
+ #
129
+ # @param approval [Hash] the file, string-keyed as it reads from JSON
130
+ # @return [Boolean] true when `approved_by` is a non-empty String and `approved_at` a time
131
+ def attributed?(approval)
132
+ named?(approval["approved_by"]) && timestamp?(approval["approved_at"])
133
+ end
134
+
135
+ # Builds the JSON document of one approval.
136
+ #
137
+ # @param edge [Bluebook::Translation] the parsed translation edge
138
+ # @param approved_by [String] who approved it, from their git identity
139
+ # @param approved_at [String] when, as an ISO 8601 UTC time
140
+ # @param rehearsal [Hash{String => String}, nil] the block; required for a compute or rekey
141
+ # @return [Hash{String => Object}] `edge`, `edge_digest`, `approved_by`, `approved_at`, and
142
+ # `rehearsal` when one was given
143
+ # @raise [ArgumentError] if `approved_by` is blank or `approved_at` is not a time, or if the
144
+ # edge needs a rehearsal and none that passed was given
145
+ def build(edge:, approved_by:, approved_at:, rehearsal: nil)
146
+ unless attributed?("approved_by" => approved_by, "approved_at" => approved_at)
147
+ raise ArgumentError, "an approval names who approved it (approved_by, your git identity) " \
148
+ "and when (approved_at, an ISO 8601 time)"
149
+ end
150
+
151
+ if needs_rehearsal?(edge) && !rehearsed?(rehearsal)
152
+ raise ArgumentError, "an edge with a compute or rekey rule is approved on a rehearsal that passed: " \
153
+ "name the snapshot, the host version, the result (pass) and when it ran"
154
+ end
155
+
156
+ document = { "edge" => edge_name(edge), "edge_digest" => edge_digest(edge),
157
+ "approved_by" => approved_by, "approved_at" => approved_at }
158
+ document["rehearsal"] = rehearsal.slice(*REHEARSAL_KEYS) if rehearsal
159
+ document
160
+ end
161
+
162
+ # Writes an edge's approval file, replacing an earlier approval of the same edge.
163
+ #
164
+ # @param directory [String] the domain's bluebook directory
165
+ # @param edge [Bluebook::Translation] the parsed translation edge
166
+ # @param document [Hash{String => Object}] what `build` answered
167
+ # @return [String] the path written
168
+ def write!(directory, edge, document)
169
+ path = path_for(directory, edge)
170
+ FileUtils.mkdir_p(File.dirname(path))
171
+ File.write(path, "#{JSON.pretty_generate(document)}\n")
172
+ path
173
+ end
174
+
175
+ # Reads every committed approval of a domain.
176
+ #
177
+ # @param directory [String] the domain's bluebook directory
178
+ # @return [Array<Hash{String => Object}>] each parsed file; one that is not JSON is skipped,
179
+ # since a file that cannot be read approves nothing
180
+ def read_all(directory)
181
+ Dir[File.join(directory, "translations", "*#{EXTENSION}")].filter_map do |path|
182
+ parsed = JSON.parse(File.read(path))
183
+ parsed if parsed.is_a?(Hash)
184
+ rescue JSON::ParserError
185
+ nil
186
+ end
187
+ end
188
+
189
+ # The committed approval that lets an edge mint, if there is one.
190
+ #
191
+ # @param directory [String, nil] the domain's bluebook directory
192
+ # @param edge [Bluebook::Translation] the parsed translation edge
193
+ # @param host_version [String] the running host's release
194
+ # @return [Hash{String => Object}, nil] an approval whose digest is the edge's, that names
195
+ # who approved it and when, and whose rehearsal passed on a compatible host when the edge
196
+ # needs one; nil when none applies
197
+ def applicable(directory, edge, host_version: HOST_RELEASE)
198
+ return unless directory
199
+
200
+ digest = edge_digest(edge)
201
+ read_all(directory).find do |approval|
202
+ approval["edge_digest"] == digest && attributed?(approval) &&
203
+ (!needs_rehearsal?(edge) ||
204
+ (rehearsed?(approval["rehearsal"]) && host_compatible?(approval["rehearsal"], host_version)))
205
+ end
206
+ end
207
+
208
+ # Why a committed approval that covers the edge was still not applied, when the only thing
209
+ # wrong is the host it was rehearsed on.
210
+ #
211
+ # @param directory [String, nil] the domain's bluebook directory
212
+ # @param edge [Bluebook::Translation] the parsed translation edge
213
+ # @param host_version [String] the running host's release
214
+ # @return [String, nil] `host_refusal`'s text, or nil when no such approval exists
215
+ def host_mismatch(directory, edge, host_version: HOST_RELEASE)
216
+ return unless directory && needs_rehearsal?(edge)
217
+
218
+ digest = edge_digest(edge)
219
+ stale = read_all(directory).find do |approval|
220
+ approval["edge_digest"] == digest && attributed?(approval) && rehearsed?(approval["rehearsal"]) &&
221
+ !host_compatible?(approval["rehearsal"], host_version)
222
+ end
223
+ host_refusal(stale["rehearsal"], host_version) if stale
224
+ end
225
+ end
226
+ end
227
+ end
@@ -4,7 +4,7 @@ require_relative "../../../../../../projector/exporter"
4
4
  module Hecks
5
5
  module Translation
6
6
  module Audit
7
- # The human gate: a compute edge cannot mint until `bin/translation_audit --approve`
7
+ # The human gate: a compute edge cannot mint until `hecks audit_translation --approve`
8
8
  # binds an approval to the edge's content digest and the journal's ordinal at review.
9
9
  module ApprovalDigest
10
10
  # Fingerprints a parsed translation edge, so an approval lapses when the edge's