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,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "json"
5
+ require_relative "held_domain"
6
+ require_relative "edge_audit"
7
+ require_relative "compaction"
8
+
9
+ module Hecks
10
+ module Adapters
11
+ class JournalStore
12
+ # The facts the journal store reports about a change before it is made, for the rules of
13
+ # Custodian's `Era.Permit` to hold against. Nothing here writes: an examination reads the
14
+ # domain and its journal and answers numbers and yes-or-no.
15
+ module Examination
16
+ # What every fact is until an examination finds otherwise.
17
+ NEUTRAL = {
18
+ capable: false, held: 0, forks: 0, contested: 0, edges: 0, bound: 0, candidates: 0,
19
+ audited: false, rehearsal_needed: false, rehearsal_recorded: false,
20
+ drifted: false, loadable: false, shape_kept: false
21
+ }.freeze
22
+
23
+ # Examines the domain a change was asked for.
24
+ #
25
+ # @param held [Hash] the `Era` record: `operation`, `domain`, and whichever of `winners`,
26
+ # `era`, `aggregates` and the rehearsal fields the request carried
27
+ # @return [Hash{Symbol => Hash}] every fact, as `{ value: x }`, and the request's own
28
+ # `operation`, which the reactions to the examination read
29
+ # @raise [Runtime::NotFound] if the domain cannot be loaded or has no such era
30
+ def examine(**held)
31
+ found = send(:"examine_#{plain(held[:operation])}", held)
32
+ NEUTRAL.merge(found).transform_values { |fact| { value: fact } }
33
+ .merge(operation: { value: plain(held[:operation]) })
34
+ end
35
+
36
+ private
37
+
38
+ def examine_hold_first(held)
39
+ domain = HeldDomain.open(plain(held[:domain]), mode: :bare)
40
+ return {} unless domain.capable?
41
+
42
+ { capable: true, held: domain.reading { |lineage| domain.held_eras(lineage).size } }
43
+ end
44
+
45
+ def examine_merge_tail(held)
46
+ domain = HeldDomain.open(plain(held[:domain]))
47
+ return {} unless domain.capable?
48
+
49
+ domain.reading do |lineage|
50
+ eras = domain.held_eras(lineage)
51
+ forks = forks_in(domain, eras)
52
+ unresolved = eras.size > 1 && forks.zero? ? unresolved_conflicts(domain, lineage, eras, winners(held)) : 0
53
+ { capable: true, held: eras.size, forks: forks, contested: unresolved }
54
+ end
55
+ end
56
+
57
+ def examine_reattest(held)
58
+ domain = HeldDomain.open(plain(held[:domain]), mode: :bare)
59
+ return {} unless domain.capable?
60
+
61
+ ordinal = plain(held[:era])
62
+ era = domain.reading { |lineage| raw_era(domain, lineage, ordinal) }
63
+ { capable: true }.merge(text_facts(era))
64
+ end
65
+
66
+ # Whether the held text drifted from its digest and, if so, whether it still loads and
67
+ # still projects to the shape the era froze with. A text that matches its digest has
68
+ # nothing to attest, so its shape is not examined.
69
+ def text_facts(era)
70
+ return {} if era[:held_digest] == Digest::SHA256.hexdigest(era[:held_text])
71
+
72
+ found = Translation::Reattest.verdict(
73
+ text: era[:held_text], stored_hash: era[:hash],
74
+ stored_projection: era[:held_projection] && JSON.parse(era[:held_projection])
75
+ )
76
+ { drifted: true, loadable: found != :unloadable, shape_kept: %i[cosmetic unnamed].include?(found) }
77
+ end
78
+
79
+ def examine_backfill_projections(held)
80
+ domain = HeldDomain.open(plain(held[:domain]), mode: :bare)
81
+ { capable: domain.capable? }
82
+ end
83
+
84
+ def examine_compact(held) = examine_compaction(held, :entries)
85
+
86
+ def examine_compact_heki(held) = examine_compaction(held, :heki)
87
+
88
+ def examine_compaction(held, kind)
89
+ compaction = Compaction.new(plain(held[:domain]), aggregates: names(held[:aggregates]), kind: kind)
90
+ { candidates: compaction.candidates.size, bound: compaction.bound.size }
91
+ end
92
+
93
+ def examine_approve_translation(held)
94
+ domain = HeldDomain.open(plain(held[:domain]))
95
+ return {} unless domain.capable?
96
+
97
+ domain.reading do |lineage|
98
+ eras = domain.held_eras(lineage)
99
+ finding = eras.empty? ? nil : EdgeAudit.call(domain, lineage, eras)
100
+ next { capable: true, held: eras.size } unless finding
101
+
102
+ { capable: true, held: eras.size, edges: finding.leaving, audited: finding.ok }.merge(rehearsal_facts(finding, held))
103
+ end
104
+ end
105
+
106
+ def rehearsal_facts(finding, held)
107
+ {
108
+ rehearsal_needed: Translation::ApprovalFile.needs_rehearsal?(finding.edge),
109
+ rehearsal_recorded: Translation::ApprovalFile.rehearsed?(rehearsal_block(held))
110
+ }
111
+ end
112
+
113
+ # How many eras, before the latest, do not have exactly one edge leading to the next.
114
+ def forks_in(domain, eras)
115
+ eras.each_cons(2).count do |from, to|
116
+ leaving = domain.registry.translations.select do |edge|
117
+ edge.domain == domain.bluebook.name && edge.from == from[:label]
118
+ end
119
+ leaving.size != 1 || leaving.first.to != to[:label]
120
+ end
121
+ end
122
+
123
+ # Records both worlds wrote since the cut that no winner was named for.
124
+ def unresolved_conflicts(domain, lineage, eras, named)
125
+ latest = eras.last
126
+ chain = PostgresEra::LineageManager.edge_chain(domain.registry, domain.bluebook, eras[0..-2], latest[:label])
127
+ conflicts = domain.bluebook.aggregates.flat_map do |aggregate|
128
+ lineage.conflict_ids(aggregate, chain, latest[:ordinal], latest[:watermark].to_i)
129
+ end
130
+ conflicts.count { |_, id| !named.key?(id) }
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hecks"
4
+ require "hecks/ports/persistence/plugins/era"
5
+
6
+ module Hecks
7
+ module Adapters
8
+ class JournalStore
9
+ # A domain directory loaded the way the era tools load it, with the journal store its first
10
+ # aggregate is bound to.
11
+ #
12
+ # `translated` loads the whole domain, translation edges included; `bare` loads the
13
+ # bluebooks, hecksagons and worlds only, so an edge that does not resolve cannot stop a
14
+ # text from being attested; `scaffold` loads everything but the edges, since an unresolved
15
+ # edge file refuses to load and writing it is the point.
16
+ class HeldDomain
17
+ MODES = %i[translated bare scaffold].freeze
18
+
19
+ attr_reader :registry, :bluebook, :directory, :adapter_name
20
+
21
+ # Loads a domain and finds where its eras would be held.
22
+ #
23
+ # @param path [String] the domain directory, or the project holding its `bluebook/`
24
+ # @param mode [Symbol] one of `MODES`
25
+ # @return [HeldDomain] the loaded domain
26
+ # @raise [Runtime::NotFound] if the directory does not exist, declares no bluebook, or
27
+ # does not load
28
+ def self.open(path, mode: :translated)
29
+ raise ArgumentError, "unknown mode #{mode.inspect}" unless MODES.include?(mode)
30
+
31
+ loading = Ports::Loading.bootstrap
32
+ directory = loading.bluebook_directory(path)
33
+ registry = Runtime::Registry.new(root: File.dirname(directory))
34
+ load_into(registry, loading, directory, mode)
35
+ new(registry, directory)
36
+ rescue Errno::ENOENT
37
+ raise Runtime::NotFound, "no such domain #{path.inspect}"
38
+ rescue Bluebook::DSL::Malformed => e
39
+ raise Runtime::NotFound, "REFUSED at load: #{e.message}"
40
+ end
41
+
42
+ # @api private
43
+ def self.load_into(registry, loading, directory, mode)
44
+ environment = ENV.fetch("HECKS_PROJECT_ENVIRONMENT", nil)
45
+ Hecks.with_registry(registry) do
46
+ loading.load_library
47
+ loading.load_project(loading.shared_root(nil, directory))
48
+ case mode
49
+ when :translated then loading.load_domain(directory, environment: environment)
50
+ when :bare then loading.load_each(directory, %w[*.port *.adapter *.bluebook *.hecksagon *.world])
51
+ else load_scaffold(loading, directory, environment)
52
+ end
53
+ end
54
+ end
55
+
56
+ # @api private
57
+ def self.load_scaffold(loading, directory, environment)
58
+ loading.load_each(directory, %w[*.port *.adapter])
59
+ loading.load_bluebooks(directory)
60
+ loading.load_each(directory, %w[*.hecksagon *.world])
61
+ return unless environment
62
+
63
+ loading.load_each(directory, [File.join("environments", "#{environment}.hecksagon")])
64
+ loading.load_each(directory, [File.join("environments", "#{environment}.world")])
65
+ end
66
+
67
+ # @param registry [Runtime::Registry] the loaded registry
68
+ # @param directory [String] the domain's bluebook directory
69
+ # @raise [Runtime::NotFound] if the registry holds no bluebook, or a bluebook no aggregate
70
+ def initialize(registry, directory)
71
+ @registry = registry
72
+ @directory = directory
73
+ @bluebook = registry.bluebooks.values.first or raise Runtime::NotFound, "no bluebook in #{directory}"
74
+ first = @bluebook.aggregates.first or raise Runtime::NotFound, "#{@bluebook.name} declares no aggregates"
75
+ @adapter_name = Ports::Persistence::BindingPolicy.resolve(registry, @bluebook.name, first).adapter
76
+ end
77
+
78
+ # Whether the store the domain is bound to keeps eras (`EraCheck`'s own question).
79
+ #
80
+ # @return [Boolean] true when the adapter is lineage capable
81
+ def capable? = Runtime::EraCheck.lineage_capable?(registry, adapter_name)
82
+
83
+ # Explains, as a refusal, why a domain has no eras.
84
+ #
85
+ # @return [String] the sentence
86
+ def incapable_reason = "#{bluebook.name} is bound to #{adapter_name}, which holds no eras"
87
+
88
+ # The settings its journal store is connected with: the world's, and `HECKS_SCHEMA` for a
89
+ # domain on a shared database, whose schema production takes from the environment.
90
+ #
91
+ # @return [Hash] the settings
92
+ def settings
93
+ declared = registry.binding_settings(bluebook.name, Ports::Persistence::VERB, adapter_name)
94
+ ENV["HECKS_SCHEMA"] ? declared.merge(schema: ENV["HECKS_SCHEMA"]) : declared
95
+ end
96
+
97
+ # The text the era is frozen from: the bluebook source as it stands on disk.
98
+ #
99
+ # @return [String] the text
100
+ # @raise [Runtime::NotFound] if no file declares the bluebook
101
+ def source_text
102
+ Runtime::EraCheck.source_text_for(bluebook, directory) or
103
+ raise Runtime::NotFound, "no file under #{directory} declares #{bluebook.name}"
104
+ end
105
+
106
+ # An era with the name it would be minted under, worked out in memory when none was stored.
107
+ # Naming an era is a write, so a read never does it.
108
+ #
109
+ # @param era [Hash] an era as `Lineage#eras` answers it
110
+ # @return [Hash] the era, with `hash` and `label` filled in
111
+ def named(era)
112
+ return era if era[:hash]
113
+
114
+ hash = Runtime::StorageShape.mint_hash(PostgresEra::LineageManager.shadow(era[:held_text]))
115
+ era.merge(hash: hash, label: hash[0, Runtime::StorageShape::LABEL_LENGTH])
116
+ end
117
+
118
+ # The bluebook's storage shape as it stands.
119
+ #
120
+ # @return [Hash] the projection `StorageShape.project` answers
121
+ def shape = Runtime::StorageShape.project(bluebook)
122
+
123
+ # Connects to the journal store and yields a lineage over it. Nothing is provisioned, so a
124
+ # database that holds no eras is left as it is.
125
+ #
126
+ # @yield [Lineage] the domain's lineage
127
+ # @return [Object] the block's value
128
+ def reading
129
+ db = PostgresEra.connect_for(bluebook.name, settings)
130
+ yield PostgresEra::Lineage.new(db, bluebook.name)
131
+ ensure
132
+ db&.close
133
+ end
134
+
135
+ # Connects to the journal store, refuses a role that bypasses the write fence unless the
136
+ # world allows it, provisions the lineage tables, and yields a lineage over it.
137
+ #
138
+ # @yield [Lineage] the domain's lineage
139
+ # @return [Object] the block's value
140
+ # @raise [Runtime::WiringError] on a superuser connection the world has not allowed
141
+ def writing
142
+ db = PostgresEra.connect_for(bluebook.name, settings)
143
+ lineage = PostgresEra::Lineage.new(db, bluebook.name, formerly_known_as: bluebook.formerly_known_as)
144
+ lineage.check_fence_applies!(allow_superuser: PostgresEra.setting(settings, :allow_superuser, default: false))
145
+ lineage.ensure_base!
146
+ yield lineage
147
+ ensure
148
+ db&.close
149
+ end
150
+
151
+ # The eras a lineage holds, without provisioning anything: none when the lineage tables do
152
+ # not exist yet.
153
+ #
154
+ # @param lineage [Lineage] the domain's lineage
155
+ # @return [Array<Hash>] `Lineage#eras`, verified against their digests; empty when none held
156
+ def held_eras(lineage)
157
+ present = lineage.db.exec("SELECT to_regclass('hecks_eras') AS present")[0]["present"]
158
+ present ? lineage.eras : []
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "digest"
5
+ require_relative "held_domain"
6
+ require_relative "edge_audit"
7
+ require_relative "compaction"
8
+
9
+ module Hecks
10
+ module Adapters
11
+ class JournalStore
12
+ # The queries the journal store answers: what would be scaffolded, how the audit reads, an
13
+ # era's held text, and what compacting would delete.
14
+ #
15
+ # A reading never writes. It provisions nothing, holds no era, names none and writes no file;
16
+ # a domain that holds no era answers that it holds none, and only `hold_first` holds one.
17
+ # What a reading refuses is a `Runtime::NotFound`, the refusal a launcher words as an answer.
18
+ module Readings
19
+ # Words the answer for a domain that holds no era.
20
+ #
21
+ # @param name [String] the domain's name
22
+ # @param path [String] the domain path, as the operator typed it
23
+ # @return [String] the sentence
24
+ def self.no_era(name, path)
25
+ "#{name} holds no era yet. `hecks hold_first #{path} --confirm` holds the first."
26
+ end
27
+
28
+ # The edge file that would lead the latest held era to the current shape, as its text.
29
+ #
30
+ # @param domain [Hash, String] the domain directory
31
+ # @return [Hash] `text:` the `.bluebook` text, under comments saying where to save it and
32
+ # what is left to decide
33
+ # @raise [Runtime::NotFound] if the domain holds no eras, or cannot be loaded
34
+ def scaffold_translation(domain:)
35
+ answering do
36
+ held = HeldDomain.open(plain(domain), mode: :scaffold)
37
+ refuse_incapable!(held)
38
+ held.reading { |lineage| scaffold(held, lineage, plain(domain)) }
39
+ end
40
+ end
41
+
42
+ # The audit of the edge that leads the latest held era to the current shape.
43
+ #
44
+ # @param domain [Hash, String] the domain directory
45
+ # @return [Hash] `text:` the report, ending in what approving needs
46
+ # @raise [Runtime::NotFound] if the audit refuses, or no edge leads to the current shape
47
+ def audit_translation(domain:)
48
+ answering do
49
+ held = HeldDomain.open(plain(domain))
50
+ refuse_incapable!(held)
51
+ held.reading { |lineage| audit(held, lineage, plain(domain)) }
52
+ end
53
+ end
54
+
55
+ # An era's held text as it now stands, and whether it still matches its digest.
56
+ #
57
+ # @param domain [Hash, String] the domain directory
58
+ # @param era [Hash, Integer] the era's ordinal
59
+ # @return [Hash] `text:` the digests and the text a person reads before attesting to it
60
+ # @raise [Runtime::NotFound] if the domain holds no such era
61
+ def attestation(domain:, era:)
62
+ answering do
63
+ held = HeldDomain.open(plain(domain), mode: :bare)
64
+ refuse_incapable!(held)
65
+ held.reading { |lineage| attest(held, lineage, plain(era)) }
66
+ end
67
+ end
68
+
69
+ # What compacting the domain's journals would delete, without deleting anything.
70
+ #
71
+ # @param domain [Hash, String] the domain directory
72
+ # @param aggregates [Hash, String, nil] aggregate names, comma separated
73
+ # @return [Hash] `text:` one line per journal
74
+ # @raise [Runtime::NotFound] if the domain cannot be loaded
75
+ def compaction(domain:, aggregates: nil)
76
+ answering do
77
+ lines = %i[entries heki].flat_map do |kind|
78
+ Compaction.new(plain(domain), aggregates: names(aggregates), kind: kind).preview
79
+ end
80
+ lines.empty? ? "no journal of #{plain(domain)} can be compacted" : lines.join("\n")
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ # Words a lower layer's refusal as the one a query is refused with. `pg` is loaded lazily,
87
+ # so its error is only named once something has connected.
88
+ def answering
89
+ { text: yield }
90
+ rescue *lower_refusals => e
91
+ raise Runtime::NotFound, e.message.strip
92
+ end
93
+
94
+ def lower_refusals
95
+ [Runtime::WiringError, Bluebook::DSL::Malformed, ArgumentError, *(PG::Error if defined?(PG))]
96
+ end
97
+
98
+ def refuse_incapable!(held)
99
+ raise Runtime::NotFound, held.incapable_reason unless held.capable?
100
+ end
101
+
102
+ def scaffold(held, lineage, path)
103
+ eras = held.held_eras(lineage)
104
+ return Readings.no_era(held.bluebook.name, path) if eras.empty?
105
+
106
+ latest = held.named(eras.last)
107
+ shadow = PostgresEra::LineageManager.shadow(latest[:held_text])
108
+ if Runtime::StorageShape.project(shadow) == held.shape
109
+ return "#{held.bluebook.name} matches era #{latest[:ordinal]} — nothing to scaffold."
110
+ end
111
+
112
+ edge_text(held, latest, Translation::Scaffold.diff(shadow, held.bluebook))
113
+ end
114
+
115
+ def edge_text(held, latest, diffed)
116
+ label = Runtime::StorageShape.mint_hash(held.bluebook)[0, Runtime::StorageShape::LABEL_LENGTH]
117
+ edge = Translation::Scaffold::Edge.new(
118
+ domain: held.bluebook.name, from: latest[:label], to: label, ordinal: latest[:ordinal] + 1,
119
+ label: label, aggregates: diffed[:aggregates], retired: diffed[:retired]
120
+ )
121
+ text = Translation::Scaffold.render(edge)
122
+ [*scaffold_notes(edge, text, diffed[:unclaimed]), text].join("\n")
123
+ end
124
+
125
+ def scaffold_notes(edge, text, unclaimed)
126
+ unresolved = text.scan(/^\s*unresolved /).size
127
+ notes = ["# Save as translations/#{edge.ordinal}-#{edge.label}.bluebook."]
128
+ notes << "# #{unresolved} unresolved: decide what each became, then boot." if unresolved.positive?
129
+ unclaimed.each do |name|
130
+ notes << "# UNCLAIMED: #{name} existed and now doesn't, and its successor is ambiguous — add " \
131
+ "`aggregate \"NewName\", was: #{name.inspect}` (with its rules) or `retired #{name.inspect}`."
132
+ end
133
+ notes << "# 0 unresolved: this shape change costs one extra boot and no typing." if notes.size == 1
134
+ notes
135
+ end
136
+
137
+ def audit(held, lineage, path)
138
+ eras = held.held_eras(lineage)
139
+ return Readings.no_era(held.bluebook.name, path) if eras.empty?
140
+
141
+ finding = EdgeAudit.call(held, lineage, eras)
142
+ return "#{held.bluebook.name} stands at era 1 — no edge to audit." unless finding
143
+ raise Runtime::NotFound, "#{finding.report}\nAUDIT REFUSED." unless finding.ok
144
+
145
+ [finding.report, audit_verdict(finding.edge, path)].join("\n")
146
+ end
147
+
148
+ def audit_verdict(edge, path)
149
+ if Translation::ApprovalFile.needs_rehearsal?(edge)
150
+ "AUDIT PASSED — this edge carries a compute or rekey rule, and the samples above are its ONLY " \
151
+ "verification. If they show what you intended, rehearse it against a snapshot and run " \
152
+ "`hecks approve_translation #{path} --confirm` with the rehearsal; the mint refuses until then."
153
+ else
154
+ "AUDIT PASSED — review the samples above; intent is yours to approve."
155
+ end
156
+ end
157
+
158
+ def attest(held, lineage, ordinal)
159
+ era = raw_era(held, lineage, ordinal)
160
+ computed = Digest::SHA256.hexdigest(era[:held_text])
161
+ name = "#{held.bluebook.name} era #{ordinal}"
162
+ return "#{name}: the held text matches its digest — nothing to re-attest." if era[:held_digest] == computed
163
+
164
+ [
165
+ "#{name}: the held text does NOT match its recorded digest.",
166
+ " recorded: #{era[:held_digest] || '(none)'}", " computed: #{computed}", shape_line(held, era, ordinal),
167
+ "The held text AS IT NOW STANDS — the original is gone; this is what you would be attesting to:",
168
+ "─" * 72, era[:held_text], "─" * 72,
169
+ "Read it, then `hecks reattest #{held.bluebook.name} era=#{ordinal} --confirm` accepts it."
170
+ ].join("\n")
171
+ end
172
+
173
+ def shape_line(held, era, ordinal)
174
+ verdict = Translation::Reattest.shape_guard!(
175
+ domain: held.bluebook.name, ordinal: ordinal, text: era[:held_text], stored_hash: era[:hash],
176
+ stored_projection: era[:held_projection] && JSON.parse(era[:held_projection])
177
+ )
178
+ if verdict == :cosmetic
179
+ " shape: unchanged — the edit is cosmetic (the text still projects to era #{ordinal}'s minted name)"
180
+ else
181
+ " shape: era #{ordinal} was never named, so no shape comparison is possible — read carefully"
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,7 @@
1
+ # Required by name, not relative to this file: a fuzz or model check boots a copy of this directory,
2
+ # and the implementation must load once, from `lib/`, not from the copy.
3
+ require "hecks/hecks/adapters/journal_store"
4
+
5
+ Hecks.adapter "JournalStore" do
6
+ port "JournalStore"
7
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "journal_store/examination"
4
+ require_relative "journal_store/changes"
5
+ require_relative "journal_store/readings"
6
+
7
+ module Hecks
8
+ module Adapters
9
+ # The `JournalStore` port's adapter: reaches the era plugin and the persistence adapters on an
10
+ # operator's behalf, to examine a domain's journal, change it, or read how it stands.
11
+ #
12
+ # `examine` reports the facts Custodian's `Era.Permit` holds against the request, `apply` makes
13
+ # an admitted change, and the queries (`scaffold_translation`, `audit_translation`,
14
+ # `attestation`, `compaction`) answer without writing. Every method reuses the era plugin's
15
+ # own logic (`LineageManager`, `Lineage`, `Translation::*`); only the entry point moved.
16
+ #
17
+ # Arguments arrive materialized, so a value object is `{ value: x }`.
18
+ class JournalStore
19
+ include Examination
20
+ include Changes
21
+ include Readings
22
+
23
+ # Accepts the arguments every driven adapter is built with and keeps none of them.
24
+ #
25
+ # @param aggregate [Object, nil] unused
26
+ # @param settings [Hash] unused
27
+ # @param root [String, nil] unused
28
+ def initialize(aggregate: nil, settings: {}, root: nil); end
29
+
30
+ private
31
+
32
+ def plain(argument) = argument.is_a?(Hash) ? argument[:value] : argument
33
+
34
+ # Aggregate names, from the comma-separated list a request carries.
35
+ def names(list) = plain(list).to_s.split(",").map(&:strip).reject(&:empty?)
36
+
37
+ # `id:old,id:new` as the map the tail merge takes; the side is what follows the last colon.
38
+ def winners(held)
39
+ plain(held[:winners]).to_s.split(",").to_h do |pair|
40
+ id, _, side = pair.rpartition(":")
41
+ [id, side]
42
+ end
43
+ end
44
+
45
+ # The rehearsal a compute or rekey edge is approved on, as the block the file carries. A
46
+ # rehearsal that names no host version ran on this one, so it records this release.
47
+ def rehearsal_block(held)
48
+ block = { "snapshot" => plain(held[:snapshot]), "host_version" => plain(held[:host_version]),
49
+ "result" => plain(held[:rehearsal]), "at" => plain(held[:rehearsed_at]) }.compact
50
+ return nil if block.empty?
51
+
52
+ block["host_version"] ||= Hecks::VERSION
53
+ block
54
+ end
55
+
56
+ # The era as stored, digest unchecked: a text that drifted is what re-attesting is for.
57
+ def raw_era(domain, lineage, ordinal)
58
+ present = lineage.db.exec("SELECT to_regclass('hecks_eras') AS present")[0]["present"]
59
+ (present && lineage.raw_era(ordinal)) or
60
+ raise Runtime::NotFound, "#{domain.bluebook.name} holds no era #{ordinal}"
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,7 @@
1
+ # Required by name, not relative to this file: a fuzz or model check boots a copy of this directory,
2
+ # and the implementation must load once, from `lib/`, not from the copy.
3
+ require "hecks/hecks/adapters/local_files"
4
+
5
+ Hecks.adapter "LocalFiles" do
6
+ port "Workspace"
7
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "console_capture"
4
+ require "hecks/cli/project_cli"
5
+
6
+ module Hecks
7
+ module Adapters
8
+ # The `Workspace` port's adapter: writes files into the project the operator is standing in.
9
+ #
10
+ # Custodian commands whose whole effect is files on disk ask it, so the journal records that a
11
+ # write was requested and what came of it, and the writing happens here and nowhere else.
12
+ class LocalFiles
13
+ # Accepts the arguments every driven adapter is built with and keeps none of them.
14
+ #
15
+ # @param aggregate [Object, nil] unused
16
+ # @param settings [Hash] unused
17
+ # @param root [String, nil] unused
18
+ def initialize(aggregate: nil, settings: {}, root: nil); end
19
+
20
+ # Writes a command-line launcher beside each domain of the current directory, or beside the
21
+ # named ones, through `Hecks::CLI::ProjectCli`. A launcher is a generated file that only
22
+ # points at its domain, so an existing one is replaced and nothing else is touched.
23
+ #
24
+ # @param held [Hash] the `Door` record: `domains` (comma separated paths under the current
25
+ # directory; every domain found when absent)
26
+ # @return [Hash{Symbol => Hash}] `output:` one line per launcher written
27
+ # @raise [ConsoleCapture::Failure] when no launcher was written
28
+ def write(**held)
29
+ domains = plain(held[:domains]).to_s.split(",").map(&:strip).reject(&:empty?)
30
+ text = ConsoleCapture.answer do
31
+ CLI::ProjectCli.call(domains, program: "hecks project_cli", root: Dir.pwd, remove_stale_bin: false)
32
+ end
33
+ unless text.include?("->")
34
+ reason = text.strip.empty? ? "no domain found" : text.strip
35
+ raise ConsoleCapture::Failure, "no launcher written: #{reason}"
36
+ end
37
+
38
+ { output: { value: text } }
39
+ end
40
+
41
+ private
42
+
43
+ def plain(argument) = argument.is_a?(Hash) ? argument[:value] : argument
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,7 @@
1
+ # Required by name, not relative to this file: a fuzz or model check boots a copy of this directory,
2
+ # and the implementation must load once, from `lib/`, not from the copy.
3
+ require "hecks/hecks/adapters/pg_admin"
4
+
5
+ Hecks.adapter "PgAdmin" do
6
+ port "PgAdmin"
7
+ end