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
@@ -1,10 +1,13 @@
1
- require "fileutils"
2
1
  require_relative "commands"
2
+ require_relative "clean_tree"
3
+ require_relative "git"
4
+ require_relative "../../cli/release_gem"
5
+ require "hecks/hecks/adapters/codebase/gem_registry"
3
6
 
4
7
  module Hecks
5
8
  module Release
6
9
  class Runner
7
- # The gem step: hands the push to `bin/release_gem`, which builds and pushes
10
+ # The gem step: hands the push to `Hecks::CLI::ReleaseGem`, which builds and pushes
8
11
  # it with the key 1Password holds; a dry run only builds it, then deletes it.
9
12
  class GemPublisher
10
13
  # @param root [String] the repository root
@@ -14,6 +17,7 @@ module Hecks
14
17
  @root = root
15
18
  @commands = commands
16
19
  @console = console
20
+ @registry = Hecks::Adapters::Codebase::GemRegistry.new(root: root, commands: commands)
17
21
  end
18
22
 
19
23
  # Pushes the gem, or with `dry_run` only builds it.
@@ -22,20 +26,23 @@ module Hecks
22
26
  # @param dry_run [Boolean] build and delete the gem, pushing nothing
23
27
  # @return [void]
24
28
  # @raise [CommandFailed] if the build or the push fails
29
+ # @raise [Refusal] if a packaged path holds a modified, untracked or ignored file
25
30
  def publish!(version, dry_run:)
26
31
  return build_only(version) if dry_run
27
32
 
28
- @console.say("Publishing hecks #{version} to rubygems.org via bin/release_gem...")
29
- @commands.run!(File.join(@root, "bin/release_gem"), chdir: @root)
33
+ CleanTree.new(git: Git.new(root: @root, commands: @commands)).check!
34
+
35
+ @console.say("Publishing hecks #{version} to rubygems.org...")
36
+ status = Hecks::CLI::ReleaseGem.call(root: @root, commands: @commands, out: @console.out,
37
+ err: @console.err, version: version)
38
+ raise CommandFailed, "the gem push was refused" unless status.zero?
30
39
  end
31
40
 
32
41
  private
33
42
 
34
43
  def build_only(version)
35
44
  @console.say("Dry run: building hecks-#{version}.gem (nothing is pushed)...")
36
- @commands.run!("gem", "build", "hecks.gemspec", chdir: @root)
37
- ensure
38
- FileUtils.rm_f(File.join(@root, "hecks-#{version}.gem"))
45
+ @registry.build_only!(version)
39
46
  end
40
47
  end
41
48
  end
@@ -1,5 +1,5 @@
1
- require "tempfile"
2
1
  require_relative "commands"
2
+ require "hecks/hecks/adapters/codebase/npm_registry"
3
3
 
4
4
  module Hecks
5
5
  module Release
@@ -7,16 +7,13 @@ module Hecks
7
7
  # The npm step: publishes `@hecks/client` from `packages/hecks-client`
8
8
  # with the token 1Password holds.
9
9
  class NpmPublisher
10
- PACKAGE_DIR = "packages/hecks-client".freeze
11
- ENV_FILE = "release/npm_publish.env".freeze
12
10
  APPROVAL_NOTICE = "npm will print an approval link; open it and approve with your security key or passkey. " \
13
11
  "This step waits for you.".freeze
14
- USERCONFIG_LINE = "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}".freeze
15
12
 
16
13
  def initialize(root:, commands:, console:)
17
14
  @root = root
18
- @commands = commands
19
15
  @console = console
16
+ @registry = Hecks::Adapters::Codebase::NpmRegistry.new(root: root, commands: commands)
20
17
  end
21
18
 
22
19
  # Publishes the package, or with `dry_run` only packs it, which needs no
@@ -25,49 +22,22 @@ module Hecks
25
22
  install_dependencies
26
23
  if dry_run
27
24
  @console.say("Dry run: npm publish --dry-run for @hecks/client #{version} (nothing is published)...")
28
- @commands.run!("npm", "publish", "--dry-run", "--access", "public", chdir: package_dir)
25
+ @registry.dry_publish!
29
26
  else
30
27
  @console.say("Publishing @hecks/client #{version} to npm (1Password will prompt for Touch ID)...")
31
28
  @console.say(APPROVAL_NOTICE)
32
- publish_with_token
29
+ @registry.publish!
33
30
  end
34
31
  end
35
32
 
36
33
  private
37
34
 
38
- def package_dir
39
- File.join(@root, PACKAGE_DIR)
40
- end
41
-
42
35
  # npm's `prepack` hook builds `dist/`; there is no separate build step here.
43
36
  def install_dependencies
44
- return if Dir.exist?(File.join(package_dir, "node_modules"))
37
+ return if @registry.installed?
45
38
 
46
39
  @console.say("Installing the client's dependencies (npm ci)...")
47
- @commands.run!("npm", "ci", chdir: package_dir)
48
- end
49
-
50
- # The token bypasses 2FA (the account's second factor is a passkey); this
51
- # fallback publish runs with `--auth-type=web` and doesn't capture output,
52
- # so an approval prompt reaches the terminal.
53
- def publish_with_token
54
- with_userconfig do |userconfig|
55
- @commands.run!(
56
- "op", "run", "--env-file=#{File.join(@root, ENV_FILE)}", "--",
57
- "npm", "publish", "--access", "public", "--auth-type=web", "--userconfig", userconfig,
58
- chdir: package_dir
59
- )
60
- end
61
- end
62
-
63
- def with_userconfig
64
- file = Tempfile.new(["hecks-npmrc", ".npmrc"])
65
- file.chmod(0o600)
66
- file.write("#{USERCONFIG_LINE}\n")
67
- file.flush
68
- yield file.path
69
- ensure
70
- file&.close!
40
+ @registry.install!
71
41
  end
72
42
  end
73
43
  end
@@ -1,6 +1,7 @@
1
1
  require "json"
2
2
  require_relative "commands"
3
3
  require_relative "git"
4
+ require_relative "clean_tree"
4
5
 
5
6
  module Hecks
6
7
  module Release
@@ -20,6 +21,7 @@ module Hecks
20
21
  }.freeze
21
22
  VERSION_LINE = /^\s*VERSION\s*=\s*"(?<version>[^"]+)"/
22
23
  CLIENT_PACKAGE = "packages/hecks-client/package.json".freeze
24
+ HOST_RELEASE_FILE = "rust/host/HECKS_RELEASE".freeze
23
25
 
24
26
  def initialize(root:, commands:, git:, tools:)
25
27
  @root = root
@@ -28,6 +30,14 @@ module Hecks
28
30
  @tools = tools
29
31
  end
30
32
 
33
+ # Refuses when a tool the release runs is not installed.
34
+ #
35
+ # @return [void]
36
+ # @raise [Refusal] naming the tool and how to install it
37
+ def check_tools!
38
+ check_tools
39
+ end
40
+
31
41
  # Runs every check; the first refusal stops it.
32
42
  def check!
33
43
  check_tools
@@ -36,6 +46,7 @@ module Hecks
36
46
  check_clean
37
47
  version = declared_version
38
48
  check_client_version(version)
49
+ check_host_release(version)
39
50
  check_changelog(version)
40
51
  Facts.new(version: version, sha: git("rev-parse", "HEAD").strip)
41
52
  end
@@ -70,9 +81,11 @@ module Hecks
70
81
  end
71
82
 
72
83
  def check_clean
73
- return if git("status", "--porcelain").strip.empty?
84
+ unless git("status", "--porcelain").strip.empty?
85
+ raise Refusal, "the working tree has uncommitted changes; commit or discard them before releasing"
86
+ end
74
87
 
75
- raise Refusal, "the working tree has uncommitted changes; commit or discard them before releasing"
88
+ CleanTree.new(git: @git).check!
76
89
  end
77
90
 
78
91
  def declared_version
@@ -89,6 +102,17 @@ module Hecks
89
102
  raise Refusal, "packages/hecks-client is at #{client} but Hecks::VERSION is #{version}; bump the package first."
90
103
  end
91
104
 
105
+ # The Rust host reports the Hecks release it was built for from this file, and the
106
+ # committed-approval gate compares rehearsals against it.
107
+ def check_host_release(version)
108
+ path = File.join(@root, HOST_RELEASE_FILE)
109
+ host = File.exist?(path) ? File.read(path).strip : nil
110
+ return if host == version
111
+
112
+ raise Refusal, "#{HOST_RELEASE_FILE} says #{host.inspect} but Hecks::VERSION is #{version}; " \
113
+ "write #{version} into it in the release PR."
114
+ end
115
+
92
116
  def check_changelog(version)
93
117
  return if File.read(File.join(@root, "CHANGELOG.md")).match?(/^## \[#{Regexp.escape(version)}\]/)
94
118
 
@@ -1,5 +1,6 @@
1
- require "json"
2
1
  require_relative "commands"
2
+ require "hecks/hecks/adapters/codebase/gem_registry"
3
+ require "hecks/hecks/adapters/codebase/npm_registry"
3
4
 
4
5
  module Hecks
5
6
  module Release
@@ -7,16 +8,14 @@ module Hecks
7
8
  # Asks the registries whether a version is already out, so a re-run of the
8
9
  # release skips what an earlier run finished.
9
10
  #
10
- # Neither question needs credentials.
11
+ # Neither question needs credentials. The registries themselves are the GemRegistry and
12
+ # NpmRegistry adapters.
11
13
  class Published
12
- GEM_VERSIONS_URL = "https://rubygems.org/api/v1/versions/hecks.json".freeze
13
- NPM_PACKAGE = "@hecks/client".freeze
14
-
15
14
  # @param root [String] the repository root
16
15
  # @param commands [#capture] runs curl and npm
17
16
  def initialize(root:, commands:)
18
- @root = root
19
- @commands = commands
17
+ @gems = Hecks::Adapters::Codebase::GemRegistry.new(root: root, commands: commands)
18
+ @npm = Hecks::Adapters::Codebase::NpmRegistry.new(root: root, commands: commands)
20
19
  end
21
20
 
22
21
  # Says whether RubyGems lists the hecks gem at a version.
@@ -24,31 +23,14 @@ module Hecks
24
23
  # @param version [String] the version to look for
25
24
  # @return [Boolean] true when it is published
26
25
  # @raise [Refusal] if RubyGems cannot be reached or answers with something unreadable
27
- def gem?(version)
28
- result = @commands.capture("curl", "-fsS", GEM_VERSIONS_URL, chdir: @root)
29
- unless result.success?
30
- raise Refusal, "could not list published hecks versions (#{result.stderr.strip}); " \
31
- "check the network and re-run"
32
- end
33
-
34
- JSON.parse(result.stdout).any? { |entry| entry["number"] == version }
35
- rescue JSON::ParserError
36
- raise Refusal, "RubyGems answered with something other than a version list; re-run in a minute"
37
- end
26
+ def gem?(version) = @gems.published?(version)
38
27
 
39
28
  # Says whether npm lists @hecks/client at a version.
40
29
  #
41
30
  # @param version [String] the version to look for
42
31
  # @return [Boolean] true when it is published
43
32
  # @raise [Refusal] if npm fails for any reason other than the version not existing
44
- def npm?(version)
45
- result = @commands.capture("npm", "view", "#{NPM_PACKAGE}@#{version}", "version", chdir: @root)
46
- return result.stdout.strip == version if result.success?
47
- return false if result.stderr.include?("E404")
48
-
49
- raise Refusal, "could not check npm for #{NPM_PACKAGE}@#{version} (#{result.stderr.strip.lines.first}); " \
50
- "check the network and re-run"
51
- end
33
+ def npm?(version) = @npm.published?(version)
52
34
  end
53
35
  end
54
36
  end
@@ -11,24 +11,50 @@ module Hecks
11
11
  @git = git
12
12
  @console = console
13
13
  @dry_run = dry_run
14
+ @changed = false
15
+ end
16
+
17
+ # @return [Boolean] whether this run created or pushed the tag, for real
18
+ def changed?
19
+ @changed
14
20
  end
15
21
 
16
22
  # Brings the tag to the state described above, saying what it did; returns
17
23
  # false only when the person declined the confirmation.
18
- def ensure!(facts)
24
+ #
25
+ # @param facts [#version, #sha] the release
26
+ # @param note [String, nil] what pushing the tag sets off, worded into the question
27
+ def ensure!(facts, note: nil)
19
28
  tag = "v#{facts.version}"
20
29
  local = local_commit(tag)
21
30
  remote = remote_commit(tag)
22
31
  refuse_elsewhere(tag, facts.sha, local: local, remote: remote)
23
32
  return true.tap { already_there(tag) } if remote
24
33
  return true.tap { preview(tag, facts.sha, local) } if @dry_run
25
- return false unless @console.confirm?(question(tag, facts.sha, local))
34
+ return false unless @console.confirm?(question(tag, facts.sha, local, note))
26
35
 
27
36
  create(tag, facts.sha) unless local
28
37
  push(tag)
38
+ @changed = true
29
39
  true
30
40
  end
31
41
 
42
+ # @param tag [String] the tag, such as `v3.0.0`
43
+ # @return [String, nil] the commit the tag points at in this checkout, or nil when absent
44
+ def local_commit(tag)
45
+ result = @git.capture("rev-parse", "-q", "--verify", "refs/tags/#{tag}^{commit}")
46
+ result.success? ? result.stdout.strip : nil
47
+ end
48
+
49
+ # @param tag [String] the tag, such as `v3.0.0`
50
+ # @return [String, nil] the commit the tag points at on origin, or nil when it is not there
51
+ # @raise [Refusal] when the remote's tags cannot be listed
52
+ def remote_commit(tag)
53
+ listing = @git.read("ls-remote", "--tags", "origin", "refs/tags/#{tag}", "refs/tags/#{tag}^{}")
54
+ shas = listing.lines.to_h { |line| line.split.then { |sha, ref| [ref, sha] } }
55
+ shas.fetch("refs/tags/#{tag}^{}") { shas["refs/tags/#{tag}"] }
56
+ end
57
+
32
58
  private
33
59
 
34
60
  def already_there(tag)
@@ -40,9 +66,9 @@ module Hecks
40
66
  @console.say("Would push #{tag} to origin.")
41
67
  end
42
68
 
43
- def question(tag, sha, local)
69
+ def question(tag, sha, local, note)
44
70
  verb = local ? "Push" : "Create and push"
45
- "#{verb} annotated tag #{tag} at #{sha[0, 7]} to origin?"
71
+ "#{verb} annotated tag #{tag} at #{sha[0, 7]} to origin#{" (#{note})" if note}?"
46
72
  end
47
73
 
48
74
  def create(tag, sha)
@@ -63,17 +89,6 @@ module Hecks
63
89
  "and the missing package must be published from a checkout of #{tag}"
64
90
  end
65
91
  end
66
-
67
- def local_commit(tag)
68
- result = @git.capture("rev-parse", "-q", "--verify", "refs/tags/#{tag}^{commit}")
69
- result.success? ? result.stdout.strip : nil
70
- end
71
-
72
- def remote_commit(tag)
73
- listing = @git.read("ls-remote", "--tags", "origin", "refs/tags/#{tag}", "refs/tags/#{tag}^{}")
74
- shas = listing.lines.to_h { |line| line.split.then { |sha, ref| [ref, sha] } }
75
- shas.fetch("refs/tags/#{tag}^{}") { shas["refs/tags/#{tag}"] }
76
- end
77
92
  end
78
93
  end
79
94
  end
@@ -94,9 +94,16 @@ module Hecks
94
94
  # @param err [IO] where refusals and failures are written
95
95
  # @param pause [#call] sleeps for a number of seconds while waiting for CI
96
96
  # @param now [#call] answers the current time in seconds, monotonically, while waiting for CI
97
+ # @param facts [Preflight::Facts, nil] what a caller already checked and cleared (a branch, a
98
+ # clean tree, matching versions, a changelog entry); the release then checks only that its
99
+ # tools are installed, and every rule stays with the caller
97
100
  def initialize(root:, options: Options.new, commands: Commands.new, input: $stdin, out: $stdout, err: $stderr,
98
- pause: ->(seconds) { sleep(seconds) }, now: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) })
101
+ pause: ->(seconds) { sleep(seconds) }, now: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) },
102
+ facts: nil)
99
103
  @root = root
104
+ @facts = facts
105
+ @steps = []
106
+ @verified = false
100
107
  @options = options
101
108
  @commands = commands
102
109
  @console = Console.new(input: input, out: out, err: err, assume_yes: options.yes?)
@@ -105,16 +112,28 @@ module Hecks
105
112
  @ci = CiPublisher.new(root: root, published: @published, console: @console, pause: pause, now: now)
106
113
  end
107
114
 
115
+ # @return [Array<Symbol>] the steps this run carried out for real: `:tagged`, `:gem`, `:npm`
116
+ attr_reader :steps
117
+
118
+ # @return [Boolean] whether, after a real run, every registry in scope lists the version
119
+ attr_reader :verified
120
+
108
121
  # Runs the release.
109
122
  #
110
123
  # @return [Integer] 0 when the release finished or had nothing to do, 1 when a check refused,
111
124
  # a step failed, CI's publish did not arrive in time or a confirmation was declined
112
125
  def call
113
- facts = Preflight.new(root: @root, commands: @commands, git: @git, tools: tools).check!
126
+ facts = preflight
114
127
  pending = pending_steps(facts.version)
115
128
  require_workflow(pending)
116
- return declined unless Tagger.new(git: @git, console: @console, dry_run: @options.dry_run).ensure!(facts)
129
+ # Nothing is tagged or pushed until the person has agreed to the publish: the tag push
130
+ # is what starts CI's npm publish.
131
+ return declined unless @options.dry_run || pending.empty? || publish_confirmed?(facts.version, pending)
117
132
 
133
+ tagger = Tagger.new(git: @git, console: @console, dry_run: @options.dry_run)
134
+ return declined unless tagger.ensure!(facts, note: tag_note(facts.version, pending))
135
+
136
+ @steps << :tagged if tagger.changed?
118
137
  publish(facts, pending)
119
138
  rescue Refusal, CommandFailed => e
120
139
  @console.warn(e.message)
@@ -123,6 +142,14 @@ module Hecks
123
142
 
124
143
  private
125
144
 
145
+ def preflight
146
+ check = Preflight.new(root: @root, commands: @commands, git: @git, tools: tools)
147
+ return check.check! unless @facts
148
+
149
+ check.check_tools!
150
+ @facts
151
+ end
152
+
126
153
  def tools
127
154
  list = %w[git curl]
128
155
  list << "gem" if @options.gem?
@@ -143,6 +170,11 @@ module Hecks
143
170
  published
144
171
  end
145
172
 
173
+ # The steps this run's flags put in scope, whether or not a registry already lists them.
174
+ def in_scope
175
+ [(:gem if @options.gem?), (:npm if @options.npm?)].compact
176
+ end
177
+
146
178
  def ci_publishes?(pending)
147
179
  pending.include?(:npm) && !@options.npm_local?
148
180
  end
@@ -157,13 +189,19 @@ module Hecks
157
189
  def publish(facts, pending)
158
190
  if pending.empty?
159
191
  @console.say("Nothing to publish for #{facts.version}.")
192
+ @verified = registries_list?(facts.version, in_scope) unless @options.dry_run
160
193
  return 0
161
194
  end
162
- return declined unless @options.dry_run || publish_confirmed?(facts.version, pending)
163
-
164
195
  run_steps(facts.version, pending)
165
196
  end
166
197
 
198
+ # What pushing the tag sets off, for the tag question; nothing when CI publishes nothing.
199
+ def tag_note(version, pending)
200
+ return unless ci_publishes?(pending)
201
+
202
+ "CI then publishes @hecks/client #{version} to npm from the tag, which cannot be undone"
203
+ end
204
+
167
205
  def publish_confirmed?(version, pending)
168
206
  targets = []
169
207
  targets << "hecks #{version} to rubygems.org" if pending.include?(:gem)
@@ -179,29 +217,42 @@ module Hecks
179
217
  publish_gem(version, dry_run) if pending.include?(:gem)
180
218
  return 1 if pending.include?(:npm) && !publish_npm!(version, dry_run, ci_publishes?(pending))
181
219
 
220
+ @verified = registries_list?(version, pending) unless dry_run
221
+
182
222
  @console.say(dry_run ? "Dry run complete; nothing was tagged, pushed or published." : "Released hecks #{version}.")
183
223
  0
184
224
  end
185
225
 
186
226
  def publish_gem(version, dry_run)
187
227
  GemPublisher.new(root: @root, commands: @commands, console: @console).publish!(version, dry_run: dry_run)
228
+ @steps << :gem unless dry_run
229
+ end
230
+
231
+ # Asks each registry a step published to whether it now lists the version.
232
+ def registries_list?(version, pending)
233
+ (!pending.include?(:gem) || @published.gem?(version)) && (!pending.include?(:npm) || @published.npm?(version))
234
+ rescue Refusal
235
+ false
188
236
  end
189
237
 
190
238
  def publish_npm!(version, dry_run, via_ci)
191
239
  return wait_for_ci!(version, dry_run) if via_ci
192
240
 
193
241
  NpmPublisher.new(root: @root, commands: @commands, console: @console).publish!(version, dry_run: dry_run)
242
+ @steps << :npm unless dry_run
194
243
  true
195
244
  rescue CommandFailed => e
196
245
  @console.warn("npm publish failed: #{e.message}")
197
246
  unless dry_run
198
- @console.warn("Finish the release with: bin/release --npm-only --npm-local (published steps are not repeated)")
247
+ @console.warn("Finish the release with: hecks publish --npm-only --npm-local --confirm " \
248
+ "(published steps are not repeated)")
199
249
  end
200
250
  raise
201
251
  end
202
252
 
203
253
  def wait_for_ci!(version, dry_run)
204
254
  arrived = @ci.wait!(version, dry_run: dry_run, wait: @options.wait?)
255
+ @steps << :npm if arrived && !dry_run
205
256
  @console.warn(CiPublisher.timeout_hint(version)) unless arrived
206
257
  arrived
207
258
  end
@@ -1,5 +1,5 @@
1
1
  require_relative "../fqn"
2
- require_relative "../facade/handle"
2
+ require_relative "../doors/handle"
3
3
 
4
4
  module Hecks
5
5
  class Router
@@ -100,12 +100,12 @@ module Hecks
100
100
 
101
101
  target.define_singleton_method(:find) do |id|
102
102
  found = dispatcher.registry.repository(domain, ir).find(id)
103
- found && Facade::Handle.new(dispatcher: dispatcher, domain: domain, aggregate: ir, instance: found)
103
+ found && Doors::Handle.new(dispatcher: dispatcher, domain: domain, aggregate: ir, instance: found)
104
104
  end
105
105
 
106
106
  target.define_singleton_method(:all) do
107
107
  dispatcher.registry.repository(domain, ir).all.map do |instance|
108
- Facade::Handle.new(dispatcher: dispatcher, domain: domain, aggregate: ir, instance: instance)
108
+ Doors::Handle.new(dispatcher: dispatcher, domain: domain, aggregate: ir, instance: instance)
109
109
  end
110
110
  end
111
111
  end
@@ -0,0 +1,124 @@
1
+ require_relative "errors"
2
+
3
+ module Hecks
4
+ module Runtime
5
+ # Finds the one adapter this boot loaded for a port name.
6
+ #
7
+ # Shared by the port-operation interpreter (an aggregate asking out) and the query interpreter
8
+ # (a query answered by a port), so both refuse the same way when nothing, or too much,
9
+ # implements the port.
10
+ module AdapterLookup
11
+ @stand_in = nil
12
+
13
+ module_function
14
+
15
+ # Modules whose instance methods every object inherits; a method they own answers no query.
16
+ INHERITED_OWNERS = [Object, Kernel, BasicObject].freeze
17
+
18
+ # Answers every port with a stand-in instead of its adapter while the block runs, for a
19
+ # caller that must not reach what the adapters reach (the fuzzer replays a domain whose
20
+ # adapters run shells and write files). Boot still checks the real adapters' classes; only
21
+ # what `call` hands out changes.
22
+ #
23
+ # @param stand_in [#call] answers `(port_name, asked)` with the object to ask instead
24
+ # @yield the work to run with the stand-in in place
25
+ # @return [Object] what the block answers
26
+ def standing_in(stand_in)
27
+ previous = @stand_in
28
+ @stand_in = stand_in
29
+ yield
30
+ ensure
31
+ @stand_in = previous
32
+ end
33
+
34
+ # @param registry [Runtime::Registry] the booted registry whose adapters are searched
35
+ # @param port_name [String] the port's name as an adapter declares it
36
+ # @param asked [String] what is being asked, worded into a refusal
37
+ # @return [Object] a new instance of the port's only adapter, or the stand-in for it
38
+ # @raise [Runtime::WiringError] if no adapter, or more than one, implements the port
39
+ def call(registry, port_name, asked:)
40
+ adapter_class(registry, port_name, asked: asked)
41
+ return @stand_in.call(port_name, asked) if @stand_in
42
+
43
+ adapter_class(registry, port_name, asked: asked).new
44
+ end
45
+
46
+ # Finds the class of the one adapter this boot loaded for a port name, without building it,
47
+ # so boot can check what the adapter answers before anything asks it.
48
+ #
49
+ # @param registry [Runtime::Registry] the booted registry whose adapters are searched
50
+ # @param port_name [String] the port's name as an adapter declares it
51
+ # @param asked [String] what is being asked, worded into a refusal
52
+ # @return [Class] the port's only adapter
53
+ # @raise [Runtime::WiringError] if no adapter, or more than one, implements the port
54
+ def adapter_class(registry, port_name, asked:)
55
+ implementations = registry.adapters.values.select { |adapter| adapter.port == port_name }
56
+
57
+ case implementations.size
58
+ when 1 then Adapters.const_get(implementations.first.name)
59
+ when 0 then raise WiringError, "no adapter implements the #{port_name} port — nothing can answer #{asked}"
60
+ else raise WiringError,
61
+ "#{implementations.size} adapters implement the #{port_name} port " \
62
+ "(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
63
+ end
64
+ end
65
+
66
+ # Checks that an adapter class can be built with a bare `.new` and answers a query: it
67
+ # defines the method itself (not inherited from Object or Kernel, so a query named `hash` or
68
+ # `display` is not answered by them) and takes exactly the query's arguments as keywords.
69
+ #
70
+ # @param klass [Class] the adapter class {adapter_class} found
71
+ # @param port_name [String] the port's name, worded into a refusal
72
+ # @param method [String] the snake-cased query name the adapter is asked by
73
+ # @param query [Bluebook::Query] the declared query whose arguments the method must take
74
+ # @param asked [String] what is being asked, worded into a refusal
75
+ # @return [void]
76
+ # @raise [Runtime::WiringError] if the constructor needs arguments, the method is missing or
77
+ # inherited, or its keywords do not match the query's arguments
78
+ def check_answers!(klass, port_name, method, query, asked:)
79
+ required = klass.instance_method(:initialize).parameters.filter_map { |kind, name| name if %i[req keyreq].include?(kind) }
80
+ unless required.empty?
81
+ raise WiringError, "#{klass} implements the #{port_name} port but its constructor requires " \
82
+ "#{required.join(', ')} — it is built with no arguments"
83
+ end
84
+
85
+ unless klass.public_method_defined?(method) &&
86
+ !INHERITED_OWNERS.include?(klass.public_instance_method(method).owner)
87
+ raise WiringError, "#{klass} implements the #{port_name} port but not ##{method}, " \
88
+ "which answers #{asked}"
89
+ end
90
+
91
+ check_keywords!(klass, method, query, asked)
92
+ end
93
+
94
+ # @raise [Runtime::WiringError] if the method takes a positional argument, requires a keyword
95
+ # the query does not require, or accepts none of a declared argument
96
+ def check_keywords!(klass, method, query, asked)
97
+ parameters = klass.public_instance_method(method).parameters
98
+ wanted = query.attributes.map { |attribute| attribute.name.to_sym }
99
+ required = query.attributes.reject(&:optional?).map { |attribute| attribute.name.to_sym }
100
+ problems = keyword_problems(parameters, wanted, required)
101
+ return if problems.empty?
102
+
103
+ raise WiringError, "#{klass}##{method} answers #{asked} but #{problems.join(' and ')} — it is asked " \
104
+ "with keywords for the query's arguments (#{wanted.empty? ? 'none' : wanted.join(', ')})"
105
+ end
106
+
107
+ # @return [Array<String>] how `parameters` fails to take exactly the `wanted` keywords
108
+ def keyword_problems(parameters, wanted, required)
109
+ kinds = parameters.map(&:first)
110
+ taken = parameters.select { |kind, _| kind.to_s.start_with?("key") && kind != :keyrest }.map(&:last)
111
+ missing = kinds.include?(:keyrest) ? [] : wanted - taken
112
+ extra = parameters.select { |kind, name| kind == :keyreq && !required.include?(name) }.map(&:last)
113
+ [("takes positional arguments" if kinds.intersect?(%i[req opt rest])),
114
+ keyword_problem("does not take", missing),
115
+ keyword_problem("requires", extra, ", which the query does not")].compact
116
+ end
117
+
118
+ # @return [String, nil] a phrase naming `names` as keywords, or nil when there are none
119
+ def keyword_problem(verb, names, suffix = "")
120
+ "#{verb} #{names.map { |name| "#{name}:" }.join(', ')}#{suffix}" unless names.empty?
121
+ end
122
+ end
123
+ end
124
+ end
@@ -30,6 +30,10 @@ module Hecks
30
30
  @rules.resolve_source(mutation.source,
31
31
  args)
32
32
  end
33
+ # A plain `sets` of a list takes the whole list; only `append:` and `remove:` take
34
+ # one element.
35
+ list_attribute = aggregate.attribute(mutation.target)
36
+ Value.refuse_scalar_list(aggregate, list_attribute, value) if list_attribute && !mutation.source.is_a?(StateRef)
33
37
  instance[mutation.target] = Value.for(aggregate, mutation.target, value)
34
38
  when :append
35
39
  instance[mutation.target] = appended(pre, aggregate, mutation, args)