hecks 2.9.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (600) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +51 -6
  3. data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
  4. data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
  5. data/lib/hecks/adapters/driven/lambda.rb +1 -1
  6. data/lib/hecks/adapters/driven/memory.rb +16 -3
  7. data/lib/hecks/adapters/driven/postgres/reconnect.rb +6 -2
  8. data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
  9. data/lib/hecks/adapters/driven/postgres.rb +4 -2
  10. data/lib/hecks/adapters/driven/prism.rb +41 -8
  11. data/lib/hecks/adapters/driven/tenant_provisioner.rb +73 -16
  12. data/lib/hecks/behaviors/expectations.rb +1 -1
  13. data/lib/hecks/behaviors.rb +1 -1
  14. data/lib/hecks/bench/cli.rb +62 -0
  15. data/lib/hecks/bench/environment.rb +71 -0
  16. data/lib/hecks/bench/postgres_probe.rb +30 -0
  17. data/lib/hecks/bench/report.rb +76 -0
  18. data/lib/hecks/bench/ruby_runner.rb +93 -0
  19. data/lib/hecks/bench/run.rb +55 -0
  20. data/lib/hecks/bench/rust_runner.rb +101 -0
  21. data/lib/hecks/bench/stats.rb +46 -0
  22. data/lib/hecks/bench/suite.rb +98 -0
  23. data/lib/hecks/bench/workload.rb +89 -0
  24. data/lib/hecks/bench.rb +17 -0
  25. data/lib/hecks/bluebook/assembly/contracts.rb +2 -0
  26. data/lib/hecks/bluebook/attribute.rb +1 -1
  27. data/lib/hecks/bluebook/behaviour/aggregate.rb +30 -1
  28. data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -0
  29. data/lib/hecks/bluebook/chapter.rb +6 -2
  30. data/lib/hecks/bluebook/domain_port.rb +36 -5
  31. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +10 -0
  32. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +8 -0
  33. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -1
  34. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +31 -3
  35. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +122 -3
  36. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +1 -1
  37. data/lib/hecks/bluebook/dsl/query_builder.rb +27 -1
  38. data/lib/hecks/bluebook/dsl/rule_reference.rb +1 -1
  39. data/lib/hecks/bluebook/dsl/world_builder.rb +29 -2
  40. data/lib/hecks/bluebook/expression/canonical_form.rb +1 -1
  41. data/lib/hecks/bluebook/expression/resolver.rb +3 -2
  42. data/lib/hecks/bluebook/hexagon.rb +13 -5
  43. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -0
  44. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +4 -1
  45. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
  46. data/lib/hecks/bluebook/meta_validator.rb +9 -4
  47. data/lib/hecks/bluebook/model_check.rb +23 -0
  48. data/lib/hecks/bluebook/pattern_subset.rb +38 -0
  49. data/lib/hecks/bluebook/query.rb +20 -5
  50. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  51. data/lib/hecks/canonical_json.rb +30 -0
  52. data/lib/hecks/chapters.rb +102 -0
  53. data/lib/hecks/cli/behaviors.rb +61 -0
  54. data/lib/hecks/cli/canonicalise.rb +25 -0
  55. data/lib/hecks/cli/check_engine_agreement.rb +32 -0
  56. data/lib/hecks/cli/check_era.rb +88 -0
  57. data/lib/hecks/cli/console.rb +66 -0
  58. data/lib/hecks/cli/deploy_template_diff.rb +40 -0
  59. data/lib/hecks/cli/doc_coverage.rb +32 -0
  60. data/lib/hecks/cli/document.rb +2 -2
  61. data/lib/hecks/cli/expression_projection.rb +41 -0
  62. data/lib/hecks/cli/follow.rb +88 -0
  63. data/lib/hecks/cli/history.rb +44 -0
  64. data/lib/hecks/cli/ir.rb +1 -1
  65. data/lib/hecks/cli/mcp.rb +3 -3
  66. data/lib/hecks/cli/model_check.rb +7 -4
  67. data/lib/hecks/cli/pattern_cases.rb +47 -0
  68. data/lib/hecks/cli/present.rb +39 -0
  69. data/lib/hecks/cli/project_cli.rb +224 -32
  70. data/lib/hecks/cli/project_diagrams.rb +4 -3
  71. data/lib/hecks/cli/project_glossary.rb +44 -0
  72. data/lib/hecks/cli/reference.rb +46 -0
  73. data/lib/hecks/cli/refresh_projections.rb +44 -0
  74. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
  75. data/lib/hecks/cli/release.rb +70 -0
  76. data/lib/hecks/cli/release_gem.rb +59 -0
  77. data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
  78. data/lib/hecks/cli/rspec_shard_files.rb +66 -0
  79. data/lib/hecks/cli/run.rb +3 -3
  80. data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
  81. data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
  82. data/lib/hecks/cli/shape.rb +70 -0
  83. data/lib/hecks/cli/smoke_http.rb +230 -0
  84. data/lib/hecks/cli/smoke_test.rb +5 -3
  85. data/lib/hecks/cli/spec_example.rb +27 -0
  86. data/lib/hecks/cli/statements.rb +32 -0
  87. data/lib/hecks/cli/stores.rb +4 -5
  88. data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
  89. data/lib/hecks/cli.rb +201 -52
  90. data/lib/hecks/codemod.rb +324 -0
  91. data/lib/hecks/corpus.rb +423 -0
  92. data/lib/hecks/deploy/bluebook/deploy.bluebook +591 -23
  93. data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -4
  94. data/lib/hecks/deploy/oidc.json +82 -2
  95. data/lib/hecks/doc/reference.rb +351 -0
  96. data/lib/hecks/doors/cli_door.rb +196 -0
  97. data/lib/hecks/doors/cli_runner.rb +297 -0
  98. data/lib/hecks/{facade → doors}/command_request.rb +1 -1
  99. data/lib/hecks/{facade → doors}/handle.rb +2 -2
  100. data/lib/hecks/{facade → doors}/json_door.rb +4 -4
  101. data/lib/hecks/doors/launcher_options.rb +129 -0
  102. data/lib/hecks/{cli → doors}/mcp_door.rb +7 -7
  103. data/lib/hecks/doors/mcp_door_scope.rb +97 -0
  104. data/lib/hecks/{facade/surface → doors/ruby_door}/aggregate_door.rb +8 -3
  105. data/lib/hecks/{facade/surface → doors/ruby_door}/chapter.rb +3 -3
  106. data/lib/hecks/{facade/surface.rb → doors/ruby_door.rb} +36 -5
  107. data/lib/hecks/doors.rb +61 -0
  108. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +1 -1
  109. data/lib/hecks/embryonaut_bluebook.rb +1 -1
  110. data/lib/hecks/engine_agreement.rb +156 -0
  111. data/lib/hecks/facade.rb +24 -37
  112. data/lib/hecks/forms/app.rb +6 -6
  113. data/lib/hecks/forms/banking_presentation.rb +50 -0
  114. data/lib/hecks/forms/field_shape.rb +2 -1
  115. data/lib/hecks/forms/port_argument.rb +1 -1
  116. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
  117. data/lib/hecks/fuzzing/combination_miner.rb +86 -0
  118. data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
  119. data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
  120. data/lib/hecks/fuzzing/differential.rb +156 -0
  121. data/lib/hecks/fuzzing/domain_generator.rb +615 -0
  122. data/lib/hecks/fuzzing/era_boundary.rb +71 -0
  123. data/lib/hecks/fuzzing/form_census.rb +136 -0
  124. data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
  125. data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
  126. data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
  127. data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
  128. data/lib/hecks/fuzzing/outside_world.rb +45 -0
  129. data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
  130. data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
  131. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
  132. data/lib/hecks/fuzzing/properties/guards.rb +190 -0
  133. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
  134. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
  135. data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
  136. data/lib/hecks/fuzzing/properties/querying.rb +175 -0
  137. data/lib/hecks/fuzzing/properties.rb +162 -0
  138. data/lib/hecks/fuzzing/qa_settings.rb +123 -0
  139. data/lib/hecks/fuzzing/replay.rb +505 -0
  140. data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
  141. data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
  142. data/lib/hecks/fuzzing/self_consistency.rb +339 -0
  143. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
  144. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
  145. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
  146. data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
  147. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
  148. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
  149. data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
  150. data/lib/hecks/fuzzing/shrinker.rb +166 -0
  151. data/lib/hecks/fuzzing/structural_skips.rb +33 -0
  152. data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
  153. data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
  154. data/lib/hecks/fuzzing/value_generator.rb +216 -0
  155. data/lib/hecks/fuzzing.rb +23 -0
  156. data/lib/hecks/grammar/evolve.rb +336 -0
  157. data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
  158. data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
  159. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
  160. data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
  161. data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
  162. data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
  163. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
  164. data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
  165. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
  166. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
  167. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
  168. data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
  169. data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
  170. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
  171. data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
  172. data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
  173. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
  174. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
  175. data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
  176. data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
  177. data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
  178. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
  179. data/lib/hecks/hecks/adapters/git.adapter +7 -0
  180. data/lib/hecks/hecks/adapters/git.rb +102 -0
  181. data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
  182. data/lib/hecks/hecks/adapters/host_http.rb +65 -0
  183. data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
  184. data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
  185. data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
  186. data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
  187. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
  188. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
  189. data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
  190. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
  191. data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
  192. data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
  193. data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
  194. data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
  195. data/lib/hecks/hecks/adapters/local_files.rb +46 -0
  196. data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
  197. data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
  198. data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
  199. data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
  200. data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
  201. data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
  202. data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
  203. data/lib/hecks/hecks/adapters/shell.adapter +7 -0
  204. data/lib/hecks/hecks/adapters/shell.rb +49 -0
  205. data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
  206. data/lib/hecks/hecks/adapters/terminal.rb +74 -0
  207. data/lib/hecks/hecks/codebase.behaviors +172 -0
  208. data/lib/hecks/hecks/codebase.bluebook +2453 -0
  209. data/lib/hecks/hecks/context_map.hecksagon +13 -0
  210. data/lib/hecks/hecks/custodian.behaviors +179 -0
  211. data/lib/hecks/hecks/custodian.bluebook +1847 -0
  212. data/lib/hecks/hecks/deploy.behaviors +74 -0
  213. data/lib/hecks/hecks/deploy.world +12 -0
  214. data/lib/hecks/hecks/environments/memory.world +10 -0
  215. data/lib/hecks/hecks/governance.world +7 -0
  216. data/lib/hecks/hecks/hecks.behaviors +62 -0
  217. data/lib/hecks/hecks/hecks.bluebook +124 -0
  218. data/lib/hecks/hecks/hecks.hecksagon +390 -0
  219. data/lib/hecks/hecks/hecks.world +42 -0
  220. data/lib/hecks/kernel_capabilities.rb +194 -0
  221. data/lib/hecks/language/bluebook/bluebook.bluebook +11 -0
  222. data/lib/hecks/language/bluebook/query.bluebook +7 -0
  223. data/lib/hecks/language/bluebook/vocabulary.bluebook +4 -4
  224. data/lib/hecks/language/hecksagon/domain_port.bluebook +10 -0
  225. data/lib/hecks/language/hecksagon/hecksagon.bluebook +20 -0
  226. data/lib/hecks/mcp_stdio_guard.rb +2 -2
  227. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
  228. data/lib/hecks/persistence_legacy_fixture.rb +113 -0
  229. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +8 -5
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +4 -1
  231. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +15 -2
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +1 -1
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +1 -1
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +32 -15
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +6 -3
  237. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
  238. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +1 -1
  239. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +43 -24
  240. data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
  241. data/lib/hecks/ports/persistence/state_codec.rb +24 -5
  242. data/lib/hecks/projection_files.rb +285 -0
  243. data/lib/hecks/projections/bootstrap_table.rb +1 -1
  244. data/lib/hecks/projections/deploy/fargate.rb +5 -5
  245. data/lib/hecks/projections/deploy/lambda.rb +43 -32
  246. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  247. data/lib/hecks/projections/deploy/preview/template.rb +1 -1
  248. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
  249. data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
  250. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
  251. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
  252. data/lib/hecks/projections/deploy/shared.rb +1 -1
  253. data/lib/hecks/projections/diagrams.rb +3 -3
  254. data/lib/hecks/projections/model/deviations.rb +1 -1
  255. data/lib/hecks/projections/parser_table.rb +4 -4
  256. data/lib/hecks/projections/reference.rb +2 -3
  257. data/lib/hecks/projections/rust_vocabulary.rb +1 -1
  258. data/lib/hecks/projector/cli_projector.rb +106 -37
  259. data/lib/hecks/projector.rb +1 -1
  260. data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
  261. data/lib/hecks/quality_control/adapters/agent.rb +58 -0
  262. data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
  263. data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
  264. data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
  265. data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
  266. data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
  267. data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
  268. data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
  269. data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
  270. data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
  271. data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
  272. data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
  273. data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
  274. data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
  275. data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
  276. data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
  277. data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
  278. data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
  279. data/lib/hecks/quality_control/cli/child.rb +45 -0
  280. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
  281. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
  282. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
  283. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
  284. data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
  285. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
  286. data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
  287. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
  288. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
  289. data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
  290. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
  291. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
  292. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
  293. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
  294. data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
  295. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
  296. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
  297. data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
  298. data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
  299. data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
  300. data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
  301. data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
  302. data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
  303. data/lib/hecks/query_ir.rb +346 -0
  304. data/lib/hecks/query_ir_mcp.rb +198 -0
  305. data/lib/hecks/release/runner/ci_publisher.rb +2 -2
  306. data/lib/hecks/release/runner/clean_tree.rb +42 -0
  307. data/lib/hecks/release/runner/console.rb +6 -0
  308. data/lib/hecks/release/runner/gem_publisher.rb +14 -7
  309. data/lib/hecks/release/runner/npm_publisher.rb +6 -36
  310. data/lib/hecks/release/runner/preflight.rb +26 -2
  311. data/lib/hecks/release/runner/published.rb +8 -26
  312. data/lib/hecks/release/runner/tagger.rb +30 -15
  313. data/lib/hecks/release/runner.rb +57 -6
  314. data/lib/hecks/router/namespace_installer.rb +3 -3
  315. data/lib/hecks/runtime/adapter_lookup.rb +124 -0
  316. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +4 -0
  317. data/lib/hecks/runtime/dependency_planning.rb +20 -2
  318. data/lib/hecks/runtime/dispatcher.rb +53 -3
  319. data/lib/hecks/runtime/interpreting.rb +3 -0
  320. data/lib/hecks/runtime/loader.rb +65 -12
  321. data/lib/hecks/runtime/outbox.rb +1 -1
  322. data/lib/hecks/runtime/policy_interpreter.rb +5 -2
  323. data/lib/hecks/runtime/port_operation_interpreter.rb +2 -10
  324. data/lib/hecks/runtime/query_interpreter.rb +77 -0
  325. data/lib/hecks/runtime/reaction_outcome.rb +89 -0
  326. data/lib/hecks/runtime/refusal_wording.rb +23 -1
  327. data/lib/hecks/runtime/registry/verification.rb +163 -4
  328. data/lib/hecks/runtime/registry.rb +57 -3
  329. data/lib/hecks/runtime/remote_dispatcher.rb +31 -2
  330. data/lib/hecks/runtime/value/coercion.rb +23 -135
  331. data/lib/hecks/runtime/value/field_checks.rb +172 -0
  332. data/lib/hecks/runtime/value.rb +4 -1
  333. data/lib/hecks/runtime.rb +20 -6
  334. data/lib/hecks/rust_build/conformance.rb +158 -0
  335. data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
  336. data/lib/hecks/rust_build/coverage.rb +218 -0
  337. data/lib/hecks/rust_build/native_build.rb +124 -0
  338. data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
  339. data/lib/hecks/rust_build/project_rust.rb +332 -0
  340. data/lib/hecks/rust_build/wasm.rb +105 -0
  341. data/lib/hecks/rust_build/wasm_browser.rb +113 -0
  342. data/lib/hecks/rust_build.rb +144 -0
  343. data/lib/hecks/storehouse.rb +16 -16
  344. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +2 -2
  345. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +1 -1
  346. data/lib/hecks/three_zero/forms.yml +127 -0
  347. data/lib/hecks/three_zero.rb +12 -0
  348. data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
  349. data/lib/hecks/tools/comment_style.rb +1070 -0
  350. data/lib/hecks/tools/corpus_report.rb +87 -0
  351. data/lib/hecks/tools/deploy_recipe.rb +255 -0
  352. data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
  353. data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
  354. data/lib/hecks/tools/era_projection_backfill.rb +94 -0
  355. data/lib/hecks/tools/era_reattest.rb +146 -0
  356. data/lib/hecks/tools/evolve_run.rb +189 -0
  357. data/lib/hecks/tools/fuzz_sweep.rb +373 -0
  358. data/lib/hecks/tools/heki_compaction.rb +100 -0
  359. data/lib/hecks/tools/hoist_local_givens.rb +246 -0
  360. data/lib/hecks/tools/journal_compaction.rb +89 -0
  361. data/lib/hecks/tools/oidc_manifests.rb +70 -0
  362. data/lib/hecks/tools/query_ir_run.rb +81 -0
  363. data/lib/hecks/tools/regeneration_run.rb +166 -0
  364. data/lib/hecks/tools/rust_comment_style.rb +642 -0
  365. data/lib/hecks/tools/sequence_script.rb +62 -0
  366. data/lib/hecks/tools/tail_merge.rb +117 -0
  367. data/lib/hecks/tools/tenant_provisioning.rb +142 -0
  368. data/lib/hecks/tools/translation_audit.rb +208 -0
  369. data/lib/hecks/tools/translation_scaffold.rb +131 -0
  370. data/lib/hecks/tools.rb +86 -0
  371. data/lib/hecks/version.rb +1 -1
  372. data/lib/hecks.rb +32 -15
  373. data/qa/settings.yml +66 -0
  374. data/rust/.cargo/config.toml +6 -0
  375. data/rust/.gitignore +2 -0
  376. data/rust/Cargo.lock +54 -0
  377. data/rust/Cargo.toml +61 -0
  378. data/rust/build/.cargo/config.toml +3 -0
  379. data/rust/build/.gitignore +1 -0
  380. data/rust/build/Cargo.lock +7 -0
  381. data/rust/build/Cargo.toml +19 -0
  382. data/rust/build/src/build_artifact.rs +47 -0
  383. data/rust/build/src/cargo_sync.rs +248 -0
  384. data/rust/build/src/json.rs +421 -0
  385. data/rust/build/src/lineage_pass.rs +285 -0
  386. data/rust/build/src/main.rs +51 -0
  387. data/rust/build/src/optional_pass.rs +150 -0
  388. data/rust/build/src/pipeline.rs +264 -0
  389. data/rust/build/src/reserved_names.rs +76 -0
  390. data/rust/build/src/resolve.rs +124 -0
  391. data/rust/build/src/root.rs +25 -0
  392. data/rust/build/src/sidecars.rs +73 -0
  393. data/rust/build/src/subprocess.rs +51 -0
  394. data/rust/build/src/tmp.rs +31 -0
  395. data/rust/codegen/.cargo/config.toml +3 -0
  396. data/rust/codegen/.gitignore +1 -0
  397. data/rust/codegen/Cargo.lock +7 -0
  398. data/rust/codegen/Cargo.toml +20 -0
  399. data/rust/codegen/src/attr.rs +32 -0
  400. data/rust/codegen/src/bridging.rs +481 -0
  401. data/rust/codegen/src/commands.rs +1226 -0
  402. data/rust/codegen/src/constraints.rs +109 -0
  403. data/rust/codegen/src/dependency_planning.rs +209 -0
  404. data/rust/codegen/src/domain_generator.rs +1288 -0
  405. data/rust/codegen/src/exemplar.rs +230 -0
  406. data/rust/codegen/src/expr_emitter.rs +87 -0
  407. data/rust/codegen/src/fielded.rs +192 -0
  408. data/rust/codegen/src/hecks_naming.rs +33 -0
  409. data/rust/codegen/src/json.rs +335 -0
  410. data/rust/codegen/src/json_codec.rs +697 -0
  411. data/rust/codegen/src/literal.rs +207 -0
  412. data/rust/codegen/src/main.rs +290 -0
  413. data/rust/codegen/src/manifest.rs +227 -0
  414. data/rust/codegen/src/mutations.rs +784 -0
  415. data/rust/codegen/src/naming.rs +286 -0
  416. data/rust/codegen/src/ports.rs +208 -0
  417. data/rust/codegen/src/prelude.rs +116 -0
  418. data/rust/codegen/src/queries.rs +786 -0
  419. data/rust/codegen/src/reactions.rs +532 -0
  420. data/rust/codegen/src/read_models.rs +697 -0
  421. data/rust/codegen/src/reference_specs.rs +40 -0
  422. data/rust/codegen/src/registry.rs +966 -0
  423. data/rust/codegen/src/reserved_names.rs +76 -0
  424. data/rust/codegen/src/shared.rs +93 -0
  425. data/rust/codegen/src/skip_reason.rs +25 -0
  426. data/rust/codegen/src/types.rs +362 -0
  427. data/rust/host/.cargo/config.toml +3 -0
  428. data/rust/host/.gitignore +1 -0
  429. data/rust/host/Cargo.lock +4297 -0
  430. data/rust/host/Cargo.toml +61 -0
  431. data/rust/host/HECKS_RELEASE +1 -0
  432. data/rust/host/rds-ca-bundle.pem +2736 -0
  433. data/rust/host/src/api.rs +1596 -0
  434. data/rust/host/src/approval.rs +652 -0
  435. data/rust/host/src/auth.rs +1287 -0
  436. data/rust/host/src/bin/expr_harness.rs +46 -0
  437. data/rust/host/src/bin/lineage_harness.rs +119 -0
  438. data/rust/host/src/bin/mint_harness.rs +135 -0
  439. data/rust/host/src/checkout.rs +265 -0
  440. data/rust/host/src/dispatch.rs +1443 -0
  441. data/rust/host/src/expr_json.rs +1117 -0
  442. data/rust/host/src/field_hints.rs +52 -0
  443. data/rust/host/src/ir.rs +733 -0
  444. data/rust/host/src/journal.rs +874 -0
  445. data/rust/host/src/lambda_client.rs +378 -0
  446. data/rust/host/src/log.rs +147 -0
  447. data/rust/host/src/main.rs +474 -0
  448. data/rust/host/src/mint.rs +1865 -0
  449. data/rust/host/src/payments/keystore.rs +330 -0
  450. data/rust/host/src/payments/tests.rs +1400 -0
  451. data/rust/host/src/payments.rs +767 -0
  452. data/rust/host/src/presentation.rs +698 -0
  453. data/rust/host/src/presentation_write.rs +1431 -0
  454. data/rust/host/src/query_step.rs +149 -0
  455. data/rust/host/src/rate_limit.rs +904 -0
  456. data/rust/host/src/reference_transform.rs +241 -0
  457. data/rust/host/src/reference_validate.rs +338 -0
  458. data/rust/host/src/resend.rs +296 -0
  459. data/rust/host/src/secrets.rs +106 -0
  460. data/rust/host/src/server.rs +663 -0
  461. data/rust/host/src/storage_shape.rs +175 -0
  462. data/rust/host/src/test_pg.rs +40 -0
  463. data/rust/host/src/ui_schema.rs +1351 -0
  464. data/rust/host/src/wasm_runner.rs +191 -0
  465. data/rust/host/src/web/newsletter/cooldown.rs +126 -0
  466. data/rust/host/src/web/newsletter/tests.rs +104 -0
  467. data/rust/host/src/web/newsletter.rs +310 -0
  468. data/rust/host/src/web/newsletter_send.rs +357 -0
  469. data/rust/host/src/web/registrations.rs +1505 -0
  470. data/rust/host/src/web.rs +2937 -0
  471. data/rust/lsp/.gitignore +1 -0
  472. data/rust/lsp/Cargo.lock +7 -0
  473. data/rust/lsp/Cargo.toml +16 -0
  474. data/rust/lsp/README.md +133 -0
  475. data/rust/lsp/src/diagnostics.rs +165 -0
  476. data/rust/lsp/src/json.rs +385 -0
  477. data/rust/lsp/src/main.rs +399 -0
  478. data/rust/lsp/src/outline.rs +177 -0
  479. data/rust/lsp/src/rpc.rs +67 -0
  480. data/rust/parser/.cargo/config.toml +5 -0
  481. data/rust/parser/.gitignore +1 -0
  482. data/rust/parser/Cargo.lock +7 -0
  483. data/rust/parser/Cargo.toml +21 -0
  484. data/rust/parser/src/build/closed_sets.rs +27 -0
  485. data/rust/parser/src/build/identity.rs +276 -0
  486. data/rust/parser/src/build/mod.rs +12 -0
  487. data/rust/parser/src/build/naming.rs +141 -0
  488. data/rust/parser/src/build/pattern_subset.rs +161 -0
  489. data/rust/parser/src/build/query_derive.rs +72 -0
  490. data/rust/parser/src/build/query_inference.rs +269 -0
  491. data/rust/parser/src/build/query_options.rs +100 -0
  492. data/rust/parser/src/build/read_model.rs +94 -0
  493. data/rust/parser/src/build/references.rs +32 -0
  494. data/rust/parser/src/canonical.rs +114 -0
  495. data/rust/parser/src/diag.rs +55 -0
  496. data/rust/parser/src/emit.rs +953 -0
  497. data/rust/parser/src/expr/ast_json.rs +147 -0
  498. data/rust/parser/src/expr/evaluator.rs +81 -0
  499. data/rust/parser/src/expr/mod.rs +84 -0
  500. data/rust/parser/src/expr/resolver.rs +497 -0
  501. data/rust/parser/src/ir.rs +387 -0
  502. data/rust/parser/src/keywords.rs +585 -0
  503. data/rust/parser/src/lex.rs +662 -0
  504. data/rust/parser/src/main.rs +252 -0
  505. data/rust/parser/src/parse/aggregate.rs +612 -0
  506. data/rust/parser/src/parse/chapter.rs +517 -0
  507. data/rust/parser/src/parse/command.rs +637 -0
  508. data/rust/parser/src/parse/domain_port.rs +127 -0
  509. data/rust/parser/src/parse/entity.rs +527 -0
  510. data/rust/parser/src/parse/file.rs +67 -0
  511. data/rust/parser/src/parse/hecksagon.rs +231 -0
  512. data/rust/parser/src/parse/lifecycle.rs +162 -0
  513. data/rust/parser/src/parse/mod.rs +1262 -0
  514. data/rust/parser/src/parse/policy.rs +87 -0
  515. data/rust/parser/src/parse/process_manager.rs +305 -0
  516. data/rust/parser/src/parse/query.rs +100 -0
  517. data/rust/parser/src/parse/read_model.rs +214 -0
  518. data/rust/parser/src/parse/value_object.rs +250 -0
  519. data/rust/parser/src/ruby_value.rs +422 -0
  520. data/rust/project/bridging.rb +257 -0
  521. data/rust/project/commands.rb +877 -0
  522. data/rust/project/constraints.rb +86 -0
  523. data/rust/project/dependency_planning.rb +178 -0
  524. data/rust/project/domain_generator.rb +980 -0
  525. data/rust/project/exemplar.rb +148 -0
  526. data/rust/project/expr_emitter.rb +72 -0
  527. data/rust/project/fielded.rb +141 -0
  528. data/rust/project/json_codec.rb +641 -0
  529. data/rust/project/mutations.rb +721 -0
  530. data/rust/project/naming.rb +205 -0
  531. data/rust/project/ports.rb +65 -0
  532. data/rust/project/queries.rb +493 -0
  533. data/rust/project/reactions.rb +317 -0
  534. data/rust/project/read_models.rb +690 -0
  535. data/rust/project/reference_specs.rb +27 -0
  536. data/rust/project/registry.rb +396 -0
  537. data/rust/project/skip_reason.rb +29 -0
  538. data/rust/project/types.rb +225 -0
  539. data/rust/project/write_if_changed.rb +83 -0
  540. data/rust/project.rb +30 -0
  541. data/rust/project_rust_pipeline.rb +383 -0
  542. data/rust/src/exemplar/commands.rs +279 -0
  543. data/rust/src/exemplar/constraints.rs +18 -0
  544. data/rust/src/exemplar/fielded.rs +280 -0
  545. data/rust/src/exemplar/json.rs +277 -0
  546. data/rust/src/exemplar/mod.rs +17 -0
  547. data/rust/src/exemplar/mutations.rs +80 -0
  548. data/rust/src/exemplar/queries.rs +33 -0
  549. data/rust/src/exemplar/reactions.rs +88 -0
  550. data/rust/src/exemplar/read_models.rs +48 -0
  551. data/rust/src/exemplar/registry.rs +166 -0
  552. data/rust/src/exemplar/types.rs +44 -0
  553. data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
  554. data/rust/src/kernel/attribute_shapes/list.rs +18 -0
  555. data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
  556. data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
  557. data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
  558. data/rust/src/kernel/cli.rs +585 -0
  559. data/rust/src/kernel/dispatch.rs +1090 -0
  560. data/rust/src/kernel/expr.rs +458 -0
  561. data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
  562. data/rust/src/kernel/expression_operators/comparison.rs +48 -0
  563. data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
  564. data/rust/src/kernel/expression_operators/logical.rs +13 -0
  565. data/rust/src/kernel/expression_operators/membership.rs +18 -0
  566. data/rust/src/kernel/expression_operators/mod.rs +68 -0
  567. data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
  568. data/rust/src/kernel/expression_operators/positional.rs +126 -0
  569. data/rust/src/kernel/expression_operators/presence.rs +101 -0
  570. data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
  571. data/rust/src/kernel/expression_operators/sized.rs +35 -0
  572. data/rust/src/kernel/expression_operators/text.rs +79 -0
  573. data/rust/src/kernel/expression_operators/to_string.rs +18 -0
  574. data/rust/src/kernel/json.rs +779 -0
  575. data/rust/src/kernel/mod.rs +133 -0
  576. data/rust/src/kernel/named_query.rs +219 -0
  577. data/rust/src/kernel/orchestrate.rs +1810 -0
  578. data/rust/src/kernel/pattern.rs +307 -0
  579. data/rust/src/kernel/query_comparators.rs +206 -0
  580. data/rust/src/kernel/query_ordering.rs +103 -0
  581. data/rust/src/kernel/read_model.rs +914 -0
  582. data/rust/src/kernel/reference_lookup.rs +149 -0
  583. data/rust/src/kernel/repository.rs +468 -0
  584. data/rust/src/kernel/routing.rs +612 -0
  585. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
  586. data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
  587. data/rust/src/kernel/vocab/field_hint.rs +70 -0
  588. data/rust/src/kernel/vocab/mod.rs +20 -0
  589. data/rust/src/kernel/vocab/query_comparator.rs +65 -0
  590. data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
  591. data/rust/src/lib.rs +8 -0
  592. data/rust/src/main.rs +16 -0
  593. data/rust/web/.gitignore +1 -0
  594. data/rust/web/Cargo.lock +119 -0
  595. data/rust/web/Cargo.toml +18 -0
  596. data/rust/web/src/lib.rs +8 -0
  597. metadata +477 -12
  598. data/lib/hecks/facade/cli_door.rb +0 -96
  599. data/lib/hecks/facade/cli_runner.rb +0 -127
  600. data/lib/hecks/mcp_door_scope.rb +0 -95
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "../../hecks"
5
+ # The `:shape` projection reads `Runtime::StorageShape`, which lives in the era persistence plugin
6
+ # core does not load by default (ADR 0033).
7
+ require_relative "../ports/persistence/plugins/era"
8
+
9
+ module Hecks
10
+ module CLI
11
+ # The command behind `hecks shape` and `hecks shape`: a bluebook's storage-shape projection, the
12
+ # same hash PostgresEra mints an era's label from, so two runs diff to show whether a bluebook
13
+ # change would bump an era.
14
+ module Shape
15
+ module_function
16
+
17
+ # Prints the projection of a bluebook file as JSON, or one `<Domain> <label>` line per domain
18
+ # of a directory (files in subdirectories are not read).
19
+ #
20
+ # @param argv [Array<String>] the bluebook file or directory, first
21
+ # @param program [String] the name the usage message calls this command by
22
+ # @return [void]
23
+ # @raise [SystemExit] when `argv` is empty, the path is missing or holds no bluebook
24
+ def call(argv, program: "hecks shape")
25
+ target = argv.first or abort "usage: #{program} <bluebook | directory of *.bluebook files>"
26
+ puts render(target)
27
+ rescue Runtime::NotFound => e
28
+ abort e.message
29
+ end
30
+
31
+ # @param target [String] a bluebook file, or a directory of them
32
+ # @return [String] the storage-shape projection as JSON for a file, or one
33
+ # `<Domain> <label>` line per domain for a directory
34
+ # @raise [Runtime::NotFound] if the path does not exist or holds no bluebook
35
+ def render(target)
36
+ raise Runtime::NotFound, "#{target} does not exist" unless File.exist?(target)
37
+
38
+ directory = File.directory?(target)
39
+ files = directory ? Dir[File.join(target, "*.bluebook")] : [target]
40
+ raise Runtime::NotFound, "no *.bluebook files in #{target}" if files.empty?
41
+
42
+ registry = load_files(files)
43
+ return labels(registry) if directory
44
+
45
+ bluebook = registry.bluebooks.values.first or raise Runtime::NotFound, "#{target} declares no bluebook"
46
+ JSON.pretty_generate(Projector.call(:shape, bluebook: bluebook))
47
+ end
48
+
49
+ # @param files [Array<String>] bluebook files
50
+ # @return [Runtime::Registry] a registry holding every bluebook the files declare
51
+ def load_files(files)
52
+ registry = Runtime::Registry.new
53
+ loading = Ports::Loading.bootstrap
54
+ Hecks.with_registry(registry) do
55
+ loading.load_library
56
+ files.each { |file| Kernel.eval(File.read(file), TOPLEVEL_BINDING, File.expand_path(file), 1) }
57
+ end
58
+ registry
59
+ end
60
+
61
+ # @param registry [Runtime::Registry] a loaded registry
62
+ # @return [String] one `<Domain> <label>` line per bluebook, sorted by name
63
+ def labels(registry)
64
+ registry.bluebooks.sort_by { |name, _| name.to_s }
65
+ .map { |name, bluebook| "#{name} #{Runtime::StorageShape.mint_label(bluebook)}" }
66
+ .join("\n")
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,230 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "openssl"
5
+ require "json"
6
+ require "optparse"
7
+
8
+ module Hecks
9
+ module CLI
10
+ # One run of the checks against one service.
11
+ class SmokeHttp
12
+ # The signature header value for a body, per scheme.
13
+ module Signature
14
+ SCHEMES = %w[timestamped sha256 hex].freeze
15
+
16
+ module_function
17
+
18
+ # Signs a body the way the chosen scheme's sender would.
19
+ def header_value(scheme, secret, body, at: Time.now)
20
+ case scheme
21
+ when "timestamped"
22
+ stamp = at.to_i
23
+ "t=#{stamp},v1=#{digest(secret, "#{stamp}.#{body}")}"
24
+ when "sha256" then "sha256=#{digest(secret, body)}"
25
+ when "hex" then digest(secret, body)
26
+ else raise ArgumentError, "unknown scheme #{scheme.inspect}; expected one of #{SCHEMES.join(', ')}"
27
+ end
28
+ end
29
+
30
+ # Computes the keyed digest every scheme is built from.
31
+ def digest(secret, text)
32
+ OpenSSL::HMAC.hexdigest("SHA256", secret, text)
33
+ end
34
+ end
35
+
36
+ # Runs the checks as the `hecks smoke_http` script does: settings from the arguments and the
37
+ # environment, a refusal worded with the program's name.
38
+ #
39
+ # @param argv [Array<String>] the flags, consumed
40
+ # @param env [Hash{String => String}] the environment defaults
41
+ # @param program [String] the name a refusal is prefixed with
42
+ # @return [Integer] the exit status: 1 when any check failed
43
+ # @raise [SystemExit] when the settings are refused
44
+ def self.main(argv, env: ENV, program: "hecks smoke_http")
45
+ new(settings(argv, env)).run
46
+ rescue ArgumentError, OptionParser::ParseError, Errno::ENOENT => e
47
+ abort "#{program}: #{e.message}"
48
+ end
49
+
50
+ # Reads the command line and environment into the settings a run needs.
51
+ #
52
+ # @param argv [Array<String>] the flags, consumed
53
+ # @param env [Hash{String => String}] the environment defaults
54
+ # @return [Hash{Symbol => String}] the settings
55
+ # @raise [ArgumentError] if the path or the secret is missing or the scheme is unknown
56
+ def self.settings(argv, env)
57
+ options = {}
58
+ parser(options).parse!(argv)
59
+ resolve(options, env)
60
+ end
61
+
62
+ # Merges explicit options over the environment's defaults and validates the result.
63
+ #
64
+ # @param options [Hash{Symbol => String}] the settings offered explicitly
65
+ # @param env [Hash{String => String}] the environment defaults
66
+ # @return [Hash{Symbol => String}] the settings
67
+ # @raise [ArgumentError] if the path or the secret is missing or the scheme is unknown
68
+ def self.resolve(options, env)
69
+ settings = {
70
+ url: env.fetch("SMOKE_TARGET_URL", "http://127.0.0.1:4322"), path: env["SMOKE_WEBHOOK_PATH"],
71
+ secret: env["SMOKE_WEBHOOK_SECRET"], header: env.fetch("SMOKE_SIGNATURE_HEADER", "X-Signature"),
72
+ scheme: env.fetch("SMOKE_SIGNATURE_SCHEME", "timestamped")
73
+ }.merge(options)
74
+ validate!(settings)
75
+ settings
76
+ end
77
+
78
+ # @api private
79
+ def self.parser(options)
80
+ OptionParser.new do |parser|
81
+ %i[url path secret header scheme payload health_path state_path].each do |name|
82
+ flag = "--#{name.to_s.tr('_', '-')}"
83
+ parser.on("#{flag} VALUE") { |value| options[name] = value }
84
+ end
85
+ parser.on("--payload-file FILE") { |file| options[:payload] = File.read(file) }
86
+ end
87
+ end
88
+
89
+ # Refuses settings a run cannot start from.
90
+ #
91
+ # @param settings [Hash{Symbol => String}] the settings to judge
92
+ # @return [void]
93
+ # @raise [ArgumentError] if the path or the secret is missing or the scheme is unknown
94
+ def self.validate!(settings)
95
+ raise ArgumentError, "no webhook path: pass --path or set SMOKE_WEBHOOK_PATH" if settings[:path].to_s.empty?
96
+ raise ArgumentError, "no signing secret: set SMOKE_WEBHOOK_SECRET (or pass --secret)" if settings[:secret].to_s.empty?
97
+ return if Signature::SCHEMES.include?(settings[:scheme])
98
+
99
+ raise ArgumentError, "unknown scheme #{settings[:scheme].inspect}; expected one of #{Signature::SCHEMES.join(', ')}"
100
+ end
101
+
102
+ # @param settings [Hash{Symbol => String}] validated settings
103
+ # @param out [IO] where the checks are reported
104
+ def initialize(settings, out: $stdout)
105
+ @settings = settings
106
+ @out = out
107
+ @failures = []
108
+ @target = URI(settings.fetch(:url))
109
+ end
110
+
111
+ def run
112
+ @out.puts "== signed webhook checks against #{@target}#{@settings[:path]} =="
113
+ check_health
114
+ check_unsigned
115
+ check_wrong_secret
116
+ check_tampered_body
117
+ check_signed
118
+ check_repeat
119
+ summarize
120
+ end
121
+
122
+ private
123
+
124
+ def check(label)
125
+ @out.print " #{label}... "
126
+ yield
127
+ @out.puts "ok"
128
+ rescue StandardError => e
129
+ @out.puts "FAILED: #{e.message}"
130
+ @failures << label
131
+ end
132
+
133
+ def check_health
134
+ return unless @settings[:health_path]
135
+
136
+ check("GET #{@settings[:health_path]} answers 200") { expect_status(get(@settings[:health_path]), 200) }
137
+ end
138
+
139
+ def check_unsigned
140
+ check("a delivery with no signature is refused") { expect_refused(post(payload, {})) }
141
+ end
142
+
143
+ def check_wrong_secret
144
+ check("a delivery signed with the wrong secret is refused") do
145
+ expect_refused(post(payload, signature_header("not-the-secret-#{run_id}", payload)))
146
+ end
147
+ end
148
+
149
+ # A trailing space is enough: the signature covers the exact bytes.
150
+ def check_tampered_body
151
+ check("a delivery whose body changed after signing is refused") do
152
+ expect_refused(post("#{payload} ", signature_header(@settings.fetch(:secret), payload)))
153
+ end
154
+ end
155
+
156
+ def check_signed
157
+ check("a correctly signed delivery is accepted") { expect_success(post(payload, signed_headers)) }
158
+ end
159
+
160
+ def check_repeat
161
+ check("a repeated delivery of the same payload is accepted again, not an error") do
162
+ before = state
163
+ expect_success(post(payload, signed_headers))
164
+ after = state
165
+ raise "the state changed on a repeated delivery:\n before #{before}\n after #{after}" unless before == after
166
+ end
167
+ end
168
+
169
+ def summarize
170
+ if @failures.empty?
171
+ @out.puts "\nSMOKE HTTP PASSED"
172
+ 0
173
+ else
174
+ @out.puts "\nSMOKE HTTP FAILED (#{@failures.size}):"
175
+ @failures.each { |label| @out.puts " - #{label}" }
176
+ 1
177
+ end
178
+ end
179
+
180
+ def run_id
181
+ @run_id ||= "smoke-#{Process.pid}-#{rand(1_000_000)}"
182
+ end
183
+
184
+ def payload
185
+ @payload ||= @settings[:payload] || JSON.generate(id: run_id, type: "smoke.ping")
186
+ end
187
+
188
+ def signed_headers
189
+ signature_header(@settings.fetch(:secret), payload)
190
+ end
191
+
192
+ def signature_header(secret, body)
193
+ { @settings.fetch(:header) => Signature.header_value(@settings.fetch(:scheme), secret, body) }
194
+ end
195
+
196
+ def state
197
+ @settings[:state_path] ? get(@settings[:state_path]).body : nil
198
+ end
199
+
200
+ def get(path)
201
+ request(Net::HTTP::Get.new(URI.join(@target, path)))
202
+ end
203
+
204
+ def post(body, headers)
205
+ req = Net::HTTP::Post.new(URI.join(@target, @settings.fetch(:path)))
206
+ headers.each { |name, value| req[name] = value }
207
+ req["Content-Type"] = "application/json"
208
+ req.body = body
209
+ request(req)
210
+ end
211
+
212
+ def request(req)
213
+ options = { use_ssl: req.uri.scheme == "https", read_timeout: 8, open_timeout: 8 }
214
+ Net::HTTP.start(req.uri.host, req.uri.port, **options) { |http| http.request(req) }
215
+ end
216
+
217
+ def expect_status(res, code)
218
+ raise "expected #{code}, got #{res.code}" unless res.code.to_i == code
219
+ end
220
+
221
+ def expect_success(res)
222
+ raise "expected 2xx, got #{res.code}: #{res.body.to_s[0, 200]}" unless res.code.to_i.between?(200, 299)
223
+ end
224
+
225
+ def expect_refused(res)
226
+ raise "expected a 4xx refusal, got #{res.code}" unless res.code.to_i.between?(400, 499)
227
+ end
228
+ end
229
+ end
230
+ end
@@ -3,7 +3,7 @@ require_relative "../bluebook/smoke_test"
3
3
 
4
4
  module Hecks
5
5
  module CLI
6
- # The command behind `bin/smoke_test` and `hecks smoke_test`: boots a real domain
6
+ # The command behind `hecks smoke_test`: boots a real domain
7
7
  # and dispatches one synthesized call per declared command and report
8
8
  # (`Bluebook::SmokeTest`), printing every failure rather than the first.
9
9
  #
@@ -14,8 +14,10 @@ module Hecks
14
14
  module_function
15
15
 
16
16
  def call(argv, root:)
17
- dir_arg = argv.first
18
- targets = dir_arg ? [dir_arg] : Dir.glob("#{root}/examples/*/").map { |d| d.chomp("/") }.select { |d| wired?(d) }
17
+ missing = argv.reject { |dir| File.directory?(dir) }
18
+ abort "smoke_test: no such domain #{missing.first.inspect}" unless missing.empty?
19
+
20
+ targets = argv.empty? ? Dir.glob("#{root}/examples/*/").map { |d| d.chomp("/") }.select { |d| wired?(d) } : argv
19
21
  abort "no wired example domains found (none of examples/* has a .hecksagon)" if targets.empty?
20
22
 
21
23
  ok = targets.reduce(true) { |all_ok, dir| clean?(dir) && all_ok }
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec/core"
4
+
5
+ module Hecks
6
+ module CLI
7
+ # The command behind `hecks run_spec_example`: runs one spec file filtered to one example
8
+ # through `RSpec::Core::Runner`, so a `hecks quality_control log --demonstration` string
9
+ # needs no `-e` quoting.
10
+ module SpecExample
11
+ USAGE = "usage: hecks run_spec_example <spec_file> <example description substring>"
12
+
13
+ module_function
14
+
15
+ # Runs the example whose description contains the substring.
16
+ #
17
+ # @param argv [Array<String>] the spec file, then the description substring
18
+ # @return [Integer] RSpec's own exit status: 0 when every example passed
19
+ # @raise [SystemExit] with the usage line unless exactly two arguments are given
20
+ def call(argv)
21
+ abort USAGE if argv.length != 2
22
+
23
+ RSpec::Core::Runner.run(["--example", argv[1], argv[0]])
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../hecks"
4
+
5
+ module Hecks
6
+ module CLI
7
+ # The command behind `hecks statements`: prints a booted domain's declared facts as plain
8
+ # English sentences, through `Projections::Statements`.
9
+ module Statements
10
+ USAGE = "usage: hecks statements <domain-path> <ChapterName>"
11
+
12
+ module_function
13
+
14
+ # Boots the domain and prints one sentence per declared fact of the named chapter.
15
+ #
16
+ # @param argv [Array<String>] the domain path, then the chapter's name
17
+ # @param cwd [String] what a relative domain path resolves against
18
+ # @param out [IO] where the sentences go
19
+ # @return [Integer] the exit status, 0 once printed
20
+ # @raise [SystemExit] with the usage line when an argument is missing
21
+ def call(argv, cwd: Dir.pwd, out: $stdout)
22
+ domain_path, chapter_name = argv
23
+ abort USAGE unless domain_path && chapter_name
24
+
25
+ runtime = Hecks.boot(File.expand_path(domain_path, cwd))
26
+ chapter = runtime.registry.bluebook(chapter_name)
27
+ Hecks::Projector.call(:statements, bluebook: chapter).each { |statement| out.puts statement }
28
+ 0
29
+ end
30
+ end
31
+ end
32
+ end
@@ -3,8 +3,8 @@ require_relative "../../hecks"
3
3
 
4
4
  module Hecks
5
5
  module CLI
6
- # The command behind `bin/stores` and `hecks stores`: every aggregate's current
7
- # records as JSON (the head, not the journal — see `bin/history` for that).
6
+ # The command behind `hecks stores`: every aggregate's current
7
+ # records as JSON (the head, not the journal — see `hecks history` for that).
8
8
  module Stores
9
9
  module_function
10
10
 
@@ -13,10 +13,9 @@ module Hecks
13
13
  # @param argv [Array<String>] the domain directory, first
14
14
  # @param program [String] the name the error message calls this command by
15
15
  # @return [void]
16
- # @raise [IndexError] when `argv` is empty
17
- # @raise [SystemExit] when the domain directory does not exist
16
+ # @raise [SystemExit] when `argv` is empty or the domain directory does not exist
18
17
  def call(argv, program:)
19
- domain = argv.fetch(0)
18
+ domain = argv.first or abort "usage: #{program} <domain>"
20
19
  unless Dir.exist?(domain)
21
20
  warn "#{program}: no such domain #{domain.inspect}"
22
21
  exit 1
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "etc"
4
+ require "open3"
5
+ require "fileutils"
6
+
7
+ module Hecks
8
+ module CLI
9
+ # The command behind `hecks stress_concurrency`: reruns the thread-safety specs under many
10
+ # seeds, several OS processes at a time. A clean run shows only "not in this many tries", never
11
+ # "impossible".
12
+ #
13
+ # CI can call it gated to pull requests that touch `lib/hecks/runtime/**`, as its own job so the
14
+ # repetition does not tax the single-pass suite; a GitHub Actions matrix leg can also run
15
+ # `--runs 1 --parallel 1 --seed-start N`.
16
+ module StressConcurrencySpecs
17
+ # The specs that prove a shared-state race is closed, listed by hand: a glob would also
18
+ # catch specs that only use threads for fixture setup. In-process specs share no external
19
+ # resource, so concurrent copies are safe.
20
+ PARALLEL_SAFE_SPEC_FILES = [
21
+ "spec/runtime/dispatcher_spec.rb",
22
+ "spec/adapters/driven/in_process_concurrent_dispatch_spec.rb"
23
+ ].freeze
24
+
25
+ # The Postgres spec drops and recreates one fixed database, so overlapping copies would fail
26
+ # with `PG::ConnectionBad`; it runs one process at a time.
27
+ SERIAL_ONLY_SPEC_FILES = [
28
+ "spec/adapters/driven/postgres_concurrent_dispatch_spec.rb"
29
+ ].freeze
30
+
31
+ USAGE = <<~TEXT
32
+ Usage: hecks stress_concurrency [--runs N] [--parallel N] [--seed-start N]
33
+
34
+ --runs N How many times to run EACH group below (default 30).
35
+ Each run uses a different --seed (seed-start + run
36
+ index).
37
+ --parallel N How many parallel-safe-group runs to have going as
38
+ separate OS processes AT THE SAME TIME (default: this
39
+ machine's own core count, via Etc.nprocessors) — real
40
+ concurrent scheduler/CPU contention, not just varied
41
+ seeds one after another. The Postgres-backed group
42
+ always runs one process at a time regardless of this
43
+ flag.
44
+ --seed-start N First --seed value (default 1); runs use seed_start,
45
+ seed_start + 1, ... seed_start + runs - 1.
46
+
47
+ Exits 0 if every run's every example passed, 1 if anything failed -
48
+ failing runs' full output is saved under tmp/stress-failures/
49
+ for a real repro (same seed, same command, just add CI=true).
50
+ TEXT
51
+
52
+ module_function
53
+
54
+ # Runs both groups under every seed and reports.
55
+ #
56
+ # @param argv [Array<String>] `--runs N`, `--parallel N`, `--seed-start N`, `--help`
57
+ # @param root [String] the checkout the specs run in
58
+ # @param out [IO] where the progress and verdict go
59
+ # @return [Integer] 0 when every run passed (or for `--help`), 1 on flakiness, 64 for an
60
+ # unknown argument
61
+ def call(argv, root:, out: $stdout)
62
+ argv = argv.dup
63
+ settings = { runs: 30, parallel: Etc.nprocessors, seed_start: 1 }
64
+ status = parse!(argv, settings, out)
65
+ return status if status
66
+
67
+ results = run_all(root, settings, out)
68
+ report(root, results, out)
69
+ end
70
+
71
+ # @param argv [Array<String>] the arguments; consumed
72
+ # @param settings [Hash{Symbol => Integer}] filled in from the flags
73
+ # @param out [IO] where the usage goes for `--help`
74
+ # @return [Integer, nil] an exit status to stop with, or nil to go on
75
+ def parse!(argv, settings, out)
76
+ flags = { "--runs" => :runs, "--parallel" => :parallel, "--seed-start" => :seed_start }
77
+ until argv.empty?
78
+ arg = argv.shift
79
+ if flags.key?(arg)
80
+ settings[flags[arg]] = Integer(argv.shift)
81
+ elsif %w[--help -h].include?(arg)
82
+ out.puts USAGE
83
+ return 0
84
+ else
85
+ warn "unrecognized argument: #{arg.inspect} (--help for usage)"
86
+ return 64
87
+ end
88
+ end
89
+ nil
90
+ end
91
+
92
+ # @param root [String] the checkout the specs run in
93
+ # @param settings [Hash{Symbol => Integer}] `runs`, `parallel` and `seed_start`
94
+ # @param out [IO] where the progress goes
95
+ # @return [Array<Hash>] one result per run
96
+ def run_all(root, settings, out)
97
+ runs = settings[:runs]
98
+ seed_start = settings[:seed_start]
99
+ seeds = (seed_start...(seed_start + runs)).to_a
100
+ out.puts "Stress-running the parallel-safe group #{runs} time(s) (#{settings[:parallel]} at a time), " \
101
+ "seeds #{seed_start}..#{seed_start + runs - 1}:"
102
+ PARALLEL_SAFE_SPEC_FILES.each { |f| out.puts " - #{f}" }
103
+ out.puts "...and the Postgres-backed group #{runs} time(s), ONE PROCESS AT A TIME:"
104
+ SERIAL_ONLY_SPEC_FILES.each { |f| out.puts " - #{f}" }
105
+ out.puts
106
+
107
+ results = []
108
+ seeds.each_slice(settings[:parallel]) do |batch|
109
+ batch_results = run_batch(root, PARALLEL_SAFE_SPEC_FILES, batch, label: "parallel-safe")
110
+ results.concat(batch_results)
111
+ batch_results.each { |result| out.print result[:success] ? "." : "F" }
112
+ out.flush
113
+ end
114
+ seeds.each do |seed|
115
+ result = run_once(root, SERIAL_ONLY_SPEC_FILES, seed, label: "postgres (serial)")
116
+ results << result
117
+ out.print result[:success] ? "." : "F"
118
+ out.flush
119
+ end
120
+ out.puts
121
+ out.puts
122
+ results
123
+ end
124
+
125
+ # `CI=true` un-excludes the `io: true` Postgres spec, which self-skips without a reachable
126
+ # server.
127
+ #
128
+ # @param root [String] the checkout to run in
129
+ # @param spec_files [Array<String>] the spec files
130
+ # @param seed [Integer] the `--seed` value
131
+ # @param label [String] which group this run belongs to
132
+ # @return [Hash] `label`, `seed`, `success`, `output` and how to `reproduce` it
133
+ def run_once(root, spec_files, seed, label:)
134
+ command = ["bundle", "exec", "rspec", *spec_files, "--seed", seed.to_s, "--format", "progress"]
135
+ stdout, status = Open3.capture2e({ "CI" => "true" }, *command, chdir: root)
136
+ { label: label, seed: seed, success: status.success?, output: stdout,
137
+ reproduce: "CI=true bundle exec rspec #{spec_files.join(' ')} --seed #{seed}" }
138
+ end
139
+
140
+ # One thread per child process: `Open3.capture2e` already spawns a real process per call.
141
+ #
142
+ # @param root [String] the checkout to run in
143
+ # @param spec_files [Array<String>] the spec files
144
+ # @param seeds [Array<Integer>] the seeds to run at the same time
145
+ # @param label [String] which group these runs belong to
146
+ # @return [Array<Hash>] one result per seed
147
+ def run_batch(root, spec_files, seeds, label:)
148
+ seeds.map { |seed| Thread.new { run_once(root, spec_files, seed, label: label) } }.map(&:value)
149
+ end
150
+
151
+ # @param root [String] the checkout; failing runs' output is saved under its `tmp/`
152
+ # @param results [Array<Hash>] every run's result
153
+ # @param out [IO] where the verdict goes
154
+ # @return [Integer] 0 when every run passed, else 1
155
+ def report(root, results, out)
156
+ failures = results.reject { |r| r[:success] }
157
+ if failures.empty?
158
+ out.puts "CLEAN — #{results.size}/#{results.size} runs passed. " \
159
+ "No new flakiness beyond a single ordinary `rspec` run found in this many tries."
160
+ return 0
161
+ end
162
+
163
+ dir = File.join(root, "tmp/stress-failures")
164
+ FileUtils.mkdir_p(dir)
165
+ out.puts "FOUND FLAKINESS — #{failures.size}/#{results.size} runs failed:"
166
+ failures.each do |failure|
167
+ path = File.join(dir, "#{failure[:label].tr(' ', '_').gsub(/[()]/, '')}-seed#{failure[:seed]}.log")
168
+ File.write(path, failure[:output])
169
+ out.puts " [#{failure[:label]}] seed #{failure[:seed]} — output saved to #{path.sub("#{root}/", '')}"
170
+ out.puts " reproduce: #{failure[:reproduce]}"
171
+ end
172
+ out.puts
173
+ 1
174
+ end
175
+ end
176
+ end
177
+ end