hecks 2.9.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (600) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +51 -6
  3. data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
  4. data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
  5. data/lib/hecks/adapters/driven/lambda.rb +1 -1
  6. data/lib/hecks/adapters/driven/memory.rb +16 -3
  7. data/lib/hecks/adapters/driven/postgres/reconnect.rb +6 -2
  8. data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
  9. data/lib/hecks/adapters/driven/postgres.rb +13 -4
  10. data/lib/hecks/adapters/driven/prism.rb +41 -8
  11. data/lib/hecks/adapters/driven/tenant_provisioner.rb +73 -16
  12. data/lib/hecks/behaviors/expectations.rb +1 -1
  13. data/lib/hecks/behaviors.rb +1 -1
  14. data/lib/hecks/bench/cli.rb +62 -0
  15. data/lib/hecks/bench/environment.rb +71 -0
  16. data/lib/hecks/bench/postgres_probe.rb +30 -0
  17. data/lib/hecks/bench/report.rb +76 -0
  18. data/lib/hecks/bench/ruby_runner.rb +93 -0
  19. data/lib/hecks/bench/run.rb +55 -0
  20. data/lib/hecks/bench/rust_runner.rb +101 -0
  21. data/lib/hecks/bench/stats.rb +46 -0
  22. data/lib/hecks/bench/suite.rb +98 -0
  23. data/lib/hecks/bench/workload.rb +89 -0
  24. data/lib/hecks/bench.rb +17 -0
  25. data/lib/hecks/bluebook/assembly/contracts.rb +2 -0
  26. data/lib/hecks/bluebook/attribute.rb +1 -1
  27. data/lib/hecks/bluebook/behaviour/aggregate.rb +30 -1
  28. data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -0
  29. data/lib/hecks/bluebook/chapter.rb +6 -2
  30. data/lib/hecks/bluebook/domain_port.rb +36 -5
  31. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +10 -0
  32. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +8 -0
  33. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -1
  34. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +31 -3
  35. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +122 -3
  36. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +1 -1
  37. data/lib/hecks/bluebook/dsl/query_builder.rb +27 -1
  38. data/lib/hecks/bluebook/dsl/rule_reference.rb +1 -1
  39. data/lib/hecks/bluebook/dsl/world_builder.rb +29 -2
  40. data/lib/hecks/bluebook/expression/canonical_form.rb +1 -1
  41. data/lib/hecks/bluebook/expression/resolver.rb +3 -2
  42. data/lib/hecks/bluebook/hexagon.rb +13 -5
  43. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -0
  44. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +4 -1
  45. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
  46. data/lib/hecks/bluebook/meta_validator.rb +9 -4
  47. data/lib/hecks/bluebook/model_check.rb +23 -0
  48. data/lib/hecks/bluebook/pattern_subset.rb +38 -0
  49. data/lib/hecks/bluebook/query.rb +20 -5
  50. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  51. data/lib/hecks/canonical_json.rb +30 -0
  52. data/lib/hecks/chapters.rb +102 -0
  53. data/lib/hecks/cli/behaviors.rb +61 -0
  54. data/lib/hecks/cli/canonicalise.rb +25 -0
  55. data/lib/hecks/cli/check_engine_agreement.rb +32 -0
  56. data/lib/hecks/cli/check_era.rb +88 -0
  57. data/lib/hecks/cli/console.rb +66 -0
  58. data/lib/hecks/cli/deploy_template_diff.rb +40 -0
  59. data/lib/hecks/cli/doc_coverage.rb +32 -0
  60. data/lib/hecks/cli/document.rb +2 -2
  61. data/lib/hecks/cli/expression_projection.rb +41 -0
  62. data/lib/hecks/cli/follow.rb +88 -0
  63. data/lib/hecks/cli/history.rb +44 -0
  64. data/lib/hecks/cli/ir.rb +1 -1
  65. data/lib/hecks/cli/mcp.rb +3 -3
  66. data/lib/hecks/cli/model_check.rb +15 -8
  67. data/lib/hecks/cli/pattern_cases.rb +47 -0
  68. data/lib/hecks/cli/present.rb +39 -0
  69. data/lib/hecks/cli/project_cli.rb +224 -32
  70. data/lib/hecks/cli/project_diagrams.rb +4 -3
  71. data/lib/hecks/cli/project_glossary.rb +44 -0
  72. data/lib/hecks/cli/reference.rb +46 -0
  73. data/lib/hecks/cli/refresh_projections.rb +44 -0
  74. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
  75. data/lib/hecks/cli/release.rb +70 -0
  76. data/lib/hecks/cli/release_gem.rb +59 -0
  77. data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
  78. data/lib/hecks/cli/rspec_shard_files.rb +66 -0
  79. data/lib/hecks/cli/run.rb +3 -3
  80. data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
  81. data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
  82. data/lib/hecks/cli/shape.rb +70 -0
  83. data/lib/hecks/cli/smoke_http.rb +230 -0
  84. data/lib/hecks/cli/smoke_test.rb +5 -3
  85. data/lib/hecks/cli/spec_example.rb +27 -0
  86. data/lib/hecks/cli/statements.rb +32 -0
  87. data/lib/hecks/cli/stores.rb +4 -5
  88. data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
  89. data/lib/hecks/cli.rb +201 -52
  90. data/lib/hecks/codemod.rb +324 -0
  91. data/lib/hecks/corpus.rb +423 -0
  92. data/lib/hecks/deploy/bluebook/deploy.bluebook +591 -23
  93. data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -4
  94. data/lib/hecks/deploy/oidc.json +82 -2
  95. data/lib/hecks/doc/reference.rb +351 -0
  96. data/lib/hecks/doors/cli_door.rb +196 -0
  97. data/lib/hecks/doors/cli_runner.rb +297 -0
  98. data/lib/hecks/{facade → doors}/command_request.rb +1 -1
  99. data/lib/hecks/{facade → doors}/handle.rb +2 -2
  100. data/lib/hecks/{facade → doors}/json_door.rb +4 -4
  101. data/lib/hecks/doors/launcher_options.rb +129 -0
  102. data/lib/hecks/{cli → doors}/mcp_door.rb +7 -7
  103. data/lib/hecks/doors/mcp_door_scope.rb +97 -0
  104. data/lib/hecks/{facade/surface → doors/ruby_door}/aggregate_door.rb +8 -3
  105. data/lib/hecks/{facade/surface → doors/ruby_door}/chapter.rb +3 -3
  106. data/lib/hecks/{facade/surface.rb → doors/ruby_door.rb} +36 -5
  107. data/lib/hecks/doors.rb +61 -0
  108. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +1 -1
  109. data/lib/hecks/embryonaut_bluebook.rb +1 -1
  110. data/lib/hecks/engine_agreement.rb +156 -0
  111. data/lib/hecks/facade.rb +24 -37
  112. data/lib/hecks/forms/app.rb +6 -6
  113. data/lib/hecks/forms/banking_presentation.rb +50 -0
  114. data/lib/hecks/forms/field_shape.rb +2 -1
  115. data/lib/hecks/forms/port_argument.rb +1 -1
  116. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
  117. data/lib/hecks/fuzzing/combination_miner.rb +86 -0
  118. data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
  119. data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
  120. data/lib/hecks/fuzzing/differential.rb +156 -0
  121. data/lib/hecks/fuzzing/domain_generator.rb +615 -0
  122. data/lib/hecks/fuzzing/era_boundary.rb +71 -0
  123. data/lib/hecks/fuzzing/form_census.rb +136 -0
  124. data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
  125. data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
  126. data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
  127. data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
  128. data/lib/hecks/fuzzing/outside_world.rb +45 -0
  129. data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
  130. data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
  131. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
  132. data/lib/hecks/fuzzing/properties/guards.rb +190 -0
  133. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
  134. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
  135. data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
  136. data/lib/hecks/fuzzing/properties/querying.rb +175 -0
  137. data/lib/hecks/fuzzing/properties.rb +162 -0
  138. data/lib/hecks/fuzzing/qa_settings.rb +123 -0
  139. data/lib/hecks/fuzzing/replay.rb +505 -0
  140. data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
  141. data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
  142. data/lib/hecks/fuzzing/self_consistency.rb +339 -0
  143. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
  144. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
  145. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
  146. data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
  147. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
  148. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
  149. data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
  150. data/lib/hecks/fuzzing/shrinker.rb +166 -0
  151. data/lib/hecks/fuzzing/structural_skips.rb +33 -0
  152. data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
  153. data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
  154. data/lib/hecks/fuzzing/value_generator.rb +216 -0
  155. data/lib/hecks/fuzzing.rb +23 -0
  156. data/lib/hecks/grammar/evolve.rb +336 -0
  157. data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
  158. data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
  159. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
  160. data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
  161. data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
  162. data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
  163. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
  164. data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
  165. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
  166. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
  167. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
  168. data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
  169. data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
  170. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
  171. data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
  172. data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
  173. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
  174. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
  175. data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
  176. data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
  177. data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
  178. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
  179. data/lib/hecks/hecks/adapters/git.adapter +7 -0
  180. data/lib/hecks/hecks/adapters/git.rb +102 -0
  181. data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
  182. data/lib/hecks/hecks/adapters/host_http.rb +65 -0
  183. data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
  184. data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
  185. data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
  186. data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
  187. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
  188. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
  189. data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
  190. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
  191. data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
  192. data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
  193. data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
  194. data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
  195. data/lib/hecks/hecks/adapters/local_files.rb +46 -0
  196. data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
  197. data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
  198. data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
  199. data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
  200. data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
  201. data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
  202. data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
  203. data/lib/hecks/hecks/adapters/shell.adapter +7 -0
  204. data/lib/hecks/hecks/adapters/shell.rb +49 -0
  205. data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
  206. data/lib/hecks/hecks/adapters/terminal.rb +74 -0
  207. data/lib/hecks/hecks/codebase.behaviors +172 -0
  208. data/lib/hecks/hecks/codebase.bluebook +2453 -0
  209. data/lib/hecks/hecks/context_map.hecksagon +13 -0
  210. data/lib/hecks/hecks/custodian.behaviors +179 -0
  211. data/lib/hecks/hecks/custodian.bluebook +1847 -0
  212. data/lib/hecks/hecks/deploy.behaviors +74 -0
  213. data/lib/hecks/hecks/deploy.world +12 -0
  214. data/lib/hecks/hecks/environments/memory.world +10 -0
  215. data/lib/hecks/hecks/governance.world +7 -0
  216. data/lib/hecks/hecks/hecks.behaviors +62 -0
  217. data/lib/hecks/hecks/hecks.bluebook +124 -0
  218. data/lib/hecks/hecks/hecks.hecksagon +390 -0
  219. data/lib/hecks/hecks/hecks.world +42 -0
  220. data/lib/hecks/kernel_capabilities.rb +194 -0
  221. data/lib/hecks/language/bluebook/bluebook.bluebook +11 -0
  222. data/lib/hecks/language/bluebook/query.bluebook +7 -0
  223. data/lib/hecks/language/bluebook/vocabulary.bluebook +4 -4
  224. data/lib/hecks/language/hecksagon/domain_port.bluebook +10 -0
  225. data/lib/hecks/language/hecksagon/hecksagon.bluebook +20 -0
  226. data/lib/hecks/mcp_stdio_guard.rb +2 -2
  227. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
  228. data/lib/hecks/persistence_legacy_fixture.rb +113 -0
  229. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +8 -5
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +4 -1
  231. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +15 -2
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +1 -1
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +1 -1
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +32 -15
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +15 -5
  237. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
  238. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +1 -1
  239. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +43 -24
  240. data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
  241. data/lib/hecks/ports/persistence/state_codec.rb +24 -5
  242. data/lib/hecks/projection_files.rb +285 -0
  243. data/lib/hecks/projections/bootstrap_table.rb +1 -1
  244. data/lib/hecks/projections/deploy/fargate.rb +5 -5
  245. data/lib/hecks/projections/deploy/lambda.rb +43 -32
  246. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  247. data/lib/hecks/projections/deploy/preview/template.rb +1 -1
  248. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
  249. data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
  250. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
  251. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
  252. data/lib/hecks/projections/deploy/shared.rb +1 -1
  253. data/lib/hecks/projections/diagrams.rb +3 -3
  254. data/lib/hecks/projections/model/deviations.rb +1 -1
  255. data/lib/hecks/projections/parser_table.rb +4 -4
  256. data/lib/hecks/projections/reference.rb +2 -3
  257. data/lib/hecks/projections/rust_vocabulary.rb +1 -1
  258. data/lib/hecks/projector/cli_projector.rb +106 -37
  259. data/lib/hecks/projector.rb +1 -1
  260. data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
  261. data/lib/hecks/quality_control/adapters/agent.rb +58 -0
  262. data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
  263. data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
  264. data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
  265. data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
  266. data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
  267. data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
  268. data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
  269. data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
  270. data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
  271. data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
  272. data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
  273. data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
  274. data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
  275. data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
  276. data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
  277. data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
  278. data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
  279. data/lib/hecks/quality_control/cli/child.rb +45 -0
  280. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
  281. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
  282. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
  283. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
  284. data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
  285. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
  286. data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
  287. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
  288. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
  289. data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
  290. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
  291. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
  292. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
  293. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
  294. data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
  295. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
  296. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
  297. data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
  298. data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
  299. data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
  300. data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
  301. data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
  302. data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
  303. data/lib/hecks/query_ir.rb +346 -0
  304. data/lib/hecks/query_ir_mcp.rb +198 -0
  305. data/lib/hecks/release/runner/ci_publisher.rb +2 -2
  306. data/lib/hecks/release/runner/clean_tree.rb +42 -0
  307. data/lib/hecks/release/runner/console.rb +6 -0
  308. data/lib/hecks/release/runner/gem_publisher.rb +14 -7
  309. data/lib/hecks/release/runner/npm_publisher.rb +6 -36
  310. data/lib/hecks/release/runner/preflight.rb +26 -2
  311. data/lib/hecks/release/runner/published.rb +8 -26
  312. data/lib/hecks/release/runner/tagger.rb +30 -15
  313. data/lib/hecks/release/runner.rb +57 -6
  314. data/lib/hecks/router/namespace_installer.rb +3 -3
  315. data/lib/hecks/runtime/adapter_lookup.rb +124 -0
  316. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +4 -0
  317. data/lib/hecks/runtime/dependency_planning.rb +20 -2
  318. data/lib/hecks/runtime/dispatcher.rb +53 -3
  319. data/lib/hecks/runtime/interpreting.rb +3 -0
  320. data/lib/hecks/runtime/loader.rb +65 -12
  321. data/lib/hecks/runtime/outbox.rb +1 -1
  322. data/lib/hecks/runtime/policy_interpreter.rb +5 -2
  323. data/lib/hecks/runtime/port_operation_interpreter.rb +2 -10
  324. data/lib/hecks/runtime/query_interpreter.rb +77 -0
  325. data/lib/hecks/runtime/reaction_outcome.rb +89 -0
  326. data/lib/hecks/runtime/refusal_wording.rb +23 -1
  327. data/lib/hecks/runtime/registry/verification.rb +163 -4
  328. data/lib/hecks/runtime/registry.rb +57 -3
  329. data/lib/hecks/runtime/remote_dispatcher.rb +31 -2
  330. data/lib/hecks/runtime/value/coercion.rb +23 -135
  331. data/lib/hecks/runtime/value/field_checks.rb +172 -0
  332. data/lib/hecks/runtime/value.rb +4 -1
  333. data/lib/hecks/runtime.rb +20 -6
  334. data/lib/hecks/rust_build/conformance.rb +158 -0
  335. data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
  336. data/lib/hecks/rust_build/coverage.rb +218 -0
  337. data/lib/hecks/rust_build/native_build.rb +124 -0
  338. data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
  339. data/lib/hecks/rust_build/project_rust.rb +332 -0
  340. data/lib/hecks/rust_build/wasm.rb +105 -0
  341. data/lib/hecks/rust_build/wasm_browser.rb +113 -0
  342. data/lib/hecks/rust_build.rb +144 -0
  343. data/lib/hecks/storehouse.rb +16 -16
  344. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +2 -2
  345. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +1 -1
  346. data/lib/hecks/three_zero/forms.yml +127 -0
  347. data/lib/hecks/three_zero.rb +12 -0
  348. data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
  349. data/lib/hecks/tools/comment_style.rb +1070 -0
  350. data/lib/hecks/tools/corpus_report.rb +87 -0
  351. data/lib/hecks/tools/deploy_recipe.rb +255 -0
  352. data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
  353. data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
  354. data/lib/hecks/tools/era_projection_backfill.rb +94 -0
  355. data/lib/hecks/tools/era_reattest.rb +146 -0
  356. data/lib/hecks/tools/evolve_run.rb +189 -0
  357. data/lib/hecks/tools/fuzz_sweep.rb +373 -0
  358. data/lib/hecks/tools/heki_compaction.rb +100 -0
  359. data/lib/hecks/tools/hoist_local_givens.rb +246 -0
  360. data/lib/hecks/tools/journal_compaction.rb +89 -0
  361. data/lib/hecks/tools/oidc_manifests.rb +70 -0
  362. data/lib/hecks/tools/query_ir_run.rb +81 -0
  363. data/lib/hecks/tools/regeneration_run.rb +166 -0
  364. data/lib/hecks/tools/rust_comment_style.rb +642 -0
  365. data/lib/hecks/tools/sequence_script.rb +62 -0
  366. data/lib/hecks/tools/tail_merge.rb +117 -0
  367. data/lib/hecks/tools/tenant_provisioning.rb +142 -0
  368. data/lib/hecks/tools/translation_audit.rb +208 -0
  369. data/lib/hecks/tools/translation_scaffold.rb +131 -0
  370. data/lib/hecks/tools.rb +86 -0
  371. data/lib/hecks/version.rb +1 -1
  372. data/lib/hecks.rb +32 -15
  373. data/qa/settings.yml +66 -0
  374. data/rust/.cargo/config.toml +6 -0
  375. data/rust/.gitignore +2 -0
  376. data/rust/Cargo.lock +54 -0
  377. data/rust/Cargo.toml +61 -0
  378. data/rust/build/.cargo/config.toml +3 -0
  379. data/rust/build/.gitignore +1 -0
  380. data/rust/build/Cargo.lock +7 -0
  381. data/rust/build/Cargo.toml +19 -0
  382. data/rust/build/src/build_artifact.rs +47 -0
  383. data/rust/build/src/cargo_sync.rs +248 -0
  384. data/rust/build/src/json.rs +421 -0
  385. data/rust/build/src/lineage_pass.rs +285 -0
  386. data/rust/build/src/main.rs +51 -0
  387. data/rust/build/src/optional_pass.rs +150 -0
  388. data/rust/build/src/pipeline.rs +264 -0
  389. data/rust/build/src/reserved_names.rs +76 -0
  390. data/rust/build/src/resolve.rs +124 -0
  391. data/rust/build/src/root.rs +25 -0
  392. data/rust/build/src/sidecars.rs +73 -0
  393. data/rust/build/src/subprocess.rs +51 -0
  394. data/rust/build/src/tmp.rs +31 -0
  395. data/rust/codegen/.cargo/config.toml +3 -0
  396. data/rust/codegen/.gitignore +1 -0
  397. data/rust/codegen/Cargo.lock +7 -0
  398. data/rust/codegen/Cargo.toml +20 -0
  399. data/rust/codegen/src/attr.rs +32 -0
  400. data/rust/codegen/src/bridging.rs +481 -0
  401. data/rust/codegen/src/commands.rs +1226 -0
  402. data/rust/codegen/src/constraints.rs +109 -0
  403. data/rust/codegen/src/dependency_planning.rs +209 -0
  404. data/rust/codegen/src/domain_generator.rs +1288 -0
  405. data/rust/codegen/src/exemplar.rs +230 -0
  406. data/rust/codegen/src/expr_emitter.rs +87 -0
  407. data/rust/codegen/src/fielded.rs +192 -0
  408. data/rust/codegen/src/hecks_naming.rs +33 -0
  409. data/rust/codegen/src/json.rs +335 -0
  410. data/rust/codegen/src/json_codec.rs +697 -0
  411. data/rust/codegen/src/literal.rs +207 -0
  412. data/rust/codegen/src/main.rs +290 -0
  413. data/rust/codegen/src/manifest.rs +227 -0
  414. data/rust/codegen/src/mutations.rs +784 -0
  415. data/rust/codegen/src/naming.rs +286 -0
  416. data/rust/codegen/src/ports.rs +208 -0
  417. data/rust/codegen/src/prelude.rs +116 -0
  418. data/rust/codegen/src/queries.rs +786 -0
  419. data/rust/codegen/src/reactions.rs +532 -0
  420. data/rust/codegen/src/read_models.rs +697 -0
  421. data/rust/codegen/src/reference_specs.rs +40 -0
  422. data/rust/codegen/src/registry.rs +966 -0
  423. data/rust/codegen/src/reserved_names.rs +76 -0
  424. data/rust/codegen/src/shared.rs +93 -0
  425. data/rust/codegen/src/skip_reason.rs +25 -0
  426. data/rust/codegen/src/types.rs +362 -0
  427. data/rust/host/.cargo/config.toml +3 -0
  428. data/rust/host/.gitignore +1 -0
  429. data/rust/host/Cargo.lock +4297 -0
  430. data/rust/host/Cargo.toml +61 -0
  431. data/rust/host/HECKS_RELEASE +1 -0
  432. data/rust/host/rds-ca-bundle.pem +2736 -0
  433. data/rust/host/src/api.rs +1596 -0
  434. data/rust/host/src/approval.rs +652 -0
  435. data/rust/host/src/auth.rs +1287 -0
  436. data/rust/host/src/bin/expr_harness.rs +46 -0
  437. data/rust/host/src/bin/lineage_harness.rs +119 -0
  438. data/rust/host/src/bin/mint_harness.rs +135 -0
  439. data/rust/host/src/checkout.rs +265 -0
  440. data/rust/host/src/dispatch.rs +1443 -0
  441. data/rust/host/src/expr_json.rs +1117 -0
  442. data/rust/host/src/field_hints.rs +52 -0
  443. data/rust/host/src/ir.rs +733 -0
  444. data/rust/host/src/journal.rs +874 -0
  445. data/rust/host/src/lambda_client.rs +378 -0
  446. data/rust/host/src/log.rs +147 -0
  447. data/rust/host/src/main.rs +474 -0
  448. data/rust/host/src/mint.rs +1865 -0
  449. data/rust/host/src/payments/keystore.rs +330 -0
  450. data/rust/host/src/payments/tests.rs +1400 -0
  451. data/rust/host/src/payments.rs +767 -0
  452. data/rust/host/src/presentation.rs +698 -0
  453. data/rust/host/src/presentation_write.rs +1431 -0
  454. data/rust/host/src/query_step.rs +149 -0
  455. data/rust/host/src/rate_limit.rs +904 -0
  456. data/rust/host/src/reference_transform.rs +241 -0
  457. data/rust/host/src/reference_validate.rs +338 -0
  458. data/rust/host/src/resend.rs +296 -0
  459. data/rust/host/src/secrets.rs +106 -0
  460. data/rust/host/src/server.rs +663 -0
  461. data/rust/host/src/storage_shape.rs +175 -0
  462. data/rust/host/src/test_pg.rs +40 -0
  463. data/rust/host/src/ui_schema.rs +1351 -0
  464. data/rust/host/src/wasm_runner.rs +191 -0
  465. data/rust/host/src/web/newsletter/cooldown.rs +126 -0
  466. data/rust/host/src/web/newsletter/tests.rs +104 -0
  467. data/rust/host/src/web/newsletter.rs +310 -0
  468. data/rust/host/src/web/newsletter_send.rs +357 -0
  469. data/rust/host/src/web/registrations.rs +1505 -0
  470. data/rust/host/src/web.rs +2937 -0
  471. data/rust/lsp/.gitignore +1 -0
  472. data/rust/lsp/Cargo.lock +7 -0
  473. data/rust/lsp/Cargo.toml +16 -0
  474. data/rust/lsp/README.md +133 -0
  475. data/rust/lsp/src/diagnostics.rs +165 -0
  476. data/rust/lsp/src/json.rs +385 -0
  477. data/rust/lsp/src/main.rs +399 -0
  478. data/rust/lsp/src/outline.rs +177 -0
  479. data/rust/lsp/src/rpc.rs +67 -0
  480. data/rust/parser/.cargo/config.toml +5 -0
  481. data/rust/parser/.gitignore +1 -0
  482. data/rust/parser/Cargo.lock +7 -0
  483. data/rust/parser/Cargo.toml +21 -0
  484. data/rust/parser/src/build/closed_sets.rs +27 -0
  485. data/rust/parser/src/build/identity.rs +276 -0
  486. data/rust/parser/src/build/mod.rs +12 -0
  487. data/rust/parser/src/build/naming.rs +141 -0
  488. data/rust/parser/src/build/pattern_subset.rs +161 -0
  489. data/rust/parser/src/build/query_derive.rs +72 -0
  490. data/rust/parser/src/build/query_inference.rs +269 -0
  491. data/rust/parser/src/build/query_options.rs +100 -0
  492. data/rust/parser/src/build/read_model.rs +94 -0
  493. data/rust/parser/src/build/references.rs +32 -0
  494. data/rust/parser/src/canonical.rs +114 -0
  495. data/rust/parser/src/diag.rs +55 -0
  496. data/rust/parser/src/emit.rs +953 -0
  497. data/rust/parser/src/expr/ast_json.rs +147 -0
  498. data/rust/parser/src/expr/evaluator.rs +81 -0
  499. data/rust/parser/src/expr/mod.rs +84 -0
  500. data/rust/parser/src/expr/resolver.rs +497 -0
  501. data/rust/parser/src/ir.rs +387 -0
  502. data/rust/parser/src/keywords.rs +585 -0
  503. data/rust/parser/src/lex.rs +662 -0
  504. data/rust/parser/src/main.rs +252 -0
  505. data/rust/parser/src/parse/aggregate.rs +612 -0
  506. data/rust/parser/src/parse/chapter.rs +517 -0
  507. data/rust/parser/src/parse/command.rs +637 -0
  508. data/rust/parser/src/parse/domain_port.rs +127 -0
  509. data/rust/parser/src/parse/entity.rs +527 -0
  510. data/rust/parser/src/parse/file.rs +67 -0
  511. data/rust/parser/src/parse/hecksagon.rs +231 -0
  512. data/rust/parser/src/parse/lifecycle.rs +162 -0
  513. data/rust/parser/src/parse/mod.rs +1262 -0
  514. data/rust/parser/src/parse/policy.rs +87 -0
  515. data/rust/parser/src/parse/process_manager.rs +305 -0
  516. data/rust/parser/src/parse/query.rs +100 -0
  517. data/rust/parser/src/parse/read_model.rs +214 -0
  518. data/rust/parser/src/parse/value_object.rs +250 -0
  519. data/rust/parser/src/ruby_value.rs +422 -0
  520. data/rust/project/bridging.rb +257 -0
  521. data/rust/project/commands.rb +877 -0
  522. data/rust/project/constraints.rb +86 -0
  523. data/rust/project/dependency_planning.rb +178 -0
  524. data/rust/project/domain_generator.rb +980 -0
  525. data/rust/project/exemplar.rb +148 -0
  526. data/rust/project/expr_emitter.rb +72 -0
  527. data/rust/project/fielded.rb +141 -0
  528. data/rust/project/json_codec.rb +641 -0
  529. data/rust/project/mutations.rb +721 -0
  530. data/rust/project/naming.rb +205 -0
  531. data/rust/project/ports.rb +65 -0
  532. data/rust/project/queries.rb +493 -0
  533. data/rust/project/reactions.rb +317 -0
  534. data/rust/project/read_models.rb +690 -0
  535. data/rust/project/reference_specs.rb +27 -0
  536. data/rust/project/registry.rb +396 -0
  537. data/rust/project/skip_reason.rb +29 -0
  538. data/rust/project/types.rb +225 -0
  539. data/rust/project/write_if_changed.rb +83 -0
  540. data/rust/project.rb +30 -0
  541. data/rust/project_rust_pipeline.rb +383 -0
  542. data/rust/src/exemplar/commands.rs +279 -0
  543. data/rust/src/exemplar/constraints.rs +18 -0
  544. data/rust/src/exemplar/fielded.rs +280 -0
  545. data/rust/src/exemplar/json.rs +277 -0
  546. data/rust/src/exemplar/mod.rs +17 -0
  547. data/rust/src/exemplar/mutations.rs +80 -0
  548. data/rust/src/exemplar/queries.rs +33 -0
  549. data/rust/src/exemplar/reactions.rs +88 -0
  550. data/rust/src/exemplar/read_models.rs +48 -0
  551. data/rust/src/exemplar/registry.rs +166 -0
  552. data/rust/src/exemplar/types.rs +44 -0
  553. data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
  554. data/rust/src/kernel/attribute_shapes/list.rs +18 -0
  555. data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
  556. data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
  557. data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
  558. data/rust/src/kernel/cli.rs +585 -0
  559. data/rust/src/kernel/dispatch.rs +1090 -0
  560. data/rust/src/kernel/expr.rs +458 -0
  561. data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
  562. data/rust/src/kernel/expression_operators/comparison.rs +48 -0
  563. data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
  564. data/rust/src/kernel/expression_operators/logical.rs +13 -0
  565. data/rust/src/kernel/expression_operators/membership.rs +18 -0
  566. data/rust/src/kernel/expression_operators/mod.rs +68 -0
  567. data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
  568. data/rust/src/kernel/expression_operators/positional.rs +126 -0
  569. data/rust/src/kernel/expression_operators/presence.rs +101 -0
  570. data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
  571. data/rust/src/kernel/expression_operators/sized.rs +35 -0
  572. data/rust/src/kernel/expression_operators/text.rs +79 -0
  573. data/rust/src/kernel/expression_operators/to_string.rs +18 -0
  574. data/rust/src/kernel/json.rs +779 -0
  575. data/rust/src/kernel/mod.rs +133 -0
  576. data/rust/src/kernel/named_query.rs +219 -0
  577. data/rust/src/kernel/orchestrate.rs +1810 -0
  578. data/rust/src/kernel/pattern.rs +307 -0
  579. data/rust/src/kernel/query_comparators.rs +206 -0
  580. data/rust/src/kernel/query_ordering.rs +103 -0
  581. data/rust/src/kernel/read_model.rs +914 -0
  582. data/rust/src/kernel/reference_lookup.rs +149 -0
  583. data/rust/src/kernel/repository.rs +468 -0
  584. data/rust/src/kernel/routing.rs +612 -0
  585. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
  586. data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
  587. data/rust/src/kernel/vocab/field_hint.rs +70 -0
  588. data/rust/src/kernel/vocab/mod.rs +20 -0
  589. data/rust/src/kernel/vocab/query_comparator.rs +65 -0
  590. data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
  591. data/rust/src/lib.rs +8 -0
  592. data/rust/src/main.rs +16 -0
  593. data/rust/web/.gitignore +1 -0
  594. data/rust/web/Cargo.lock +119 -0
  595. data/rust/web/Cargo.toml +18 -0
  596. data/rust/web/src/lib.rs +8 -0
  597. metadata +477 -12
  598. data/lib/hecks/facade/cli_door.rb +0 -96
  599. data/lib/hecks/facade/cli_runner.rb +0 -127
  600. data/lib/hecks/mcp_door_scope.rb +0 -95
@@ -0,0 +1,611 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+ require "open3"
6
+ require "securerandom"
7
+ require "tempfile"
8
+ require_relative "../../../hecks"
9
+ # The era/lineage plugin does not load with core (ADR 0033); the ledger's binding needs it.
10
+ require_relative "../../ports/persistence/plugins/era"
11
+ require_relative "../../fuzzing"
12
+ require_relative "../../fuzzing/self_consistency"
13
+ require_relative "../../fuzzing/differential"
14
+ require_relative "../../fuzzing/era_boundary"
15
+ require_relative "../../fuzzing/concurrent_dispatch"
16
+ require_relative "qa_sweep/parsing"
17
+ require_relative "qa_sweep/all_mode"
18
+ require_relative "qa_sweep/checks"
19
+ require_relative "qa_sweep/dials"
20
+ require_relative "qa_sweep/release_mode"
21
+ require_relative "qa_sweep/finding_report"
22
+
23
+ module Hecks
24
+ module QualityControlCli
25
+ # The command behind `hecks quality_control ask run`: runs one claim, sweep, conclude and
26
+ # release cycle against a `QualityControl` target (`USAGE` lists the forms, from a bare sweep
27
+ # of the least recently swept target to `--all` and `--release`). It stops with a report and
28
+ # exit 2 on the first surprised check, and never logs a `Bug` itself.
29
+ #
30
+ # Exit codes: 0 clean; 1 operational error; 2 found something (the sweep stays open and the
31
+ # target is suspended by the ledger's `SuspendOnSurprise` policy until `--release`).
32
+ #
33
+ # `QA_SWEEP_DOMAIN_DIR` boots a throwaway ledger instead of `qa/bluebook` and is inherited by
34
+ # `--all` children; `QA_SWEEP_RUST_DIR` points differential mode at a fixture crate;
35
+ # `QA_SWEEP_COVERAGE_CORPUS_DIR` points the coverage corpus at a throwaway directory;
36
+ # `QA_SWEEP_TRACE=1` prints a per-phase stderr timer.
37
+ class QaSweep
38
+ EXIT_OK = 0
39
+ EXIT_ERROR = 1
40
+ EXIT_FOUND_SOMETHING = 2
41
+
42
+ USAGE = "usage: hecks quality_control ask run [target=<reference>] arguments=\"[--seeds N] [--steps N] " \
43
+ "[--adversarial FRACTION] [--role-draw FRACTION] [--dry-run FRACTION] " \
44
+ "[--self-consistency true|false] [--modes a,b,c]\"\n " \
45
+ "arguments=\"--all [--seeds N] [--steps N] [--adversarial FRACTION] [--role-draw FRACTION] " \
46
+ "[--dry-run FRACTION] [--self-consistency true|false] [--modes a,b,c] [--no-parity]\"\n " \
47
+ "target=<reference> arguments=\"--persistence-parity [--seeds N]\"\n " \
48
+ "target=<reference> arguments=\"--release --notes 'what a person concluded, 40+ chars'\""
49
+
50
+ # Modes run when the ledger declares no `QualityControlDials::MODES` (an isolated spec's
51
+ # fixture).
52
+ DEFAULT_MODES = %i[differential ruby_only self_consistency properties_in_differential
53
+ structural_skip_report persistence_parity].freeze
54
+
55
+ DEFAULT_TARGETS = { "pizzas" => "examples/pizzas", "banking" => "examples/banking" }.freeze
56
+
57
+ # Modes with no per-seed loop; when only these are active the seed machinery is skipped.
58
+ SEEDLESS_MODES = %i[era_boundary].freeze
59
+
60
+ # The disposable scratch database the parity and concurrency modes create schemas in.
61
+ SCRATCH_DATABASE = "hecks_qa_persistence_parity"
62
+
63
+ include Parsing
64
+ include AllMode
65
+ include Checks
66
+ include Dials
67
+ include ReleaseMode
68
+ include FindingReport
69
+
70
+ # Sweeps.
71
+ #
72
+ # @param argv [Array<String>] the flags in the usage line
73
+ # @param root [String] the repository root
74
+ # @param env [Hash{String => String}] the `QA_SWEEP_*` variables
75
+ # @return [Integer] 0 clean, 2 found something, 1 an operational error
76
+ # @raise [SystemExit] on a bad argument or a refused sweep
77
+ def self.call(argv, root:, env: ENV)
78
+ new(root: root, env: env).call(argv)
79
+ end
80
+
81
+ # @param root [String] the repository root
82
+ # @param env [Hash{String => String}] the `QA_SWEEP_*` variables
83
+ def initialize(root:, env: ENV)
84
+ @root = root
85
+ @domain_dir = env.fetch("QA_SWEEP_DOMAIN_DIR", File.join(root, "qa/bluebook"))
86
+ @rust_dir = env.fetch("QA_SWEEP_RUST_DIR", File.join(root, "rust"))
87
+ @coverage_corpus_dir = env.fetch("QA_SWEEP_COVERAGE_CORPUS_DIR", File.join(root, "tmp/qa-coverage-corpus"))
88
+ @trace_enabled = env["QA_SWEEP_TRACE"] == "1"
89
+ end
90
+
91
+ # @param argv [Array<String>] the flags in the usage line
92
+ # @return [Integer] the exit status
93
+ # @raise [SystemExit] on a bad argument or a refused sweep
94
+ def call(argv)
95
+ @trace_last = Process.clock_gettime(Process::CLOCK_MONOTONIC)
96
+ trace("require")
97
+ return EXIT_OK if parse_arguments(argv.dup) == :help
98
+
99
+ @runtime = boot_ledger
100
+ trace("ledger boot")
101
+ read_dials
102
+ resolve_enabled_modes
103
+ return run_all_mode(modes: @enabled_modes, parity_wave: parity_wave?, deferred_wave_modes: deferred_waves) if @all_mode
104
+
105
+ sweep_one_target
106
+ end
107
+
108
+ private
109
+
110
+ # Prints a per-phase stderr timer under `QA_SWEEP_TRACE=1`; each print is the delta since the
111
+ # previous mark.
112
+ def trace(label)
113
+ return unless @trace_enabled
114
+
115
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
116
+ warn format("[qa_sweep_trace pid=%<pid>d] %<label>-24s +%<delta>6.3fs",
117
+ pid: Process.pid, label: label, delta: now - @trace_last)
118
+ @trace_last = now
119
+ end
120
+
121
+ # Default `install_doors` so `QualityControl::Target` and friends are top-level constants.
122
+ def boot_ledger
123
+ Hecks.boot(@domain_dir)
124
+ rescue StandardError => e
125
+ abort "the QualityControl ledger did not boot — fix the ledger itself before sweeping against it " \
126
+ "(#{e.class}: #{e.message})"
127
+ end
128
+
129
+ def query(name, **args) = @runtime.query("QualityControl::#{name}", **args)
130
+
131
+ # A stored `Target.path` is either relative to this repo's root (the common case) or an
132
+ # absolute path to a real external checkout. `File.join(root, path)` does not special-case an
133
+ # absolute second argument the way `File.expand_path` does, so an absolute path would
134
+ # otherwise be silently mangled. It matches `QaDiscoverExternalDomains`'s own normalization.
135
+ def resolve_target_path(path)
136
+ path.start_with?("/") ? File.expand_path(path) : File.expand_path(path, @root)
137
+ end
138
+
139
+ # A target reference may itself contain `/` (`hecks quality_control discover_external_domains`
140
+ # suggests `repo/entity`-shaped references for an external domain), but every reference also
141
+ # gets folded into a single filename component: a log prefix, a shrunk-repro filename, a
142
+ # coverage-corpus filename. Left raw, an embedded `/` is read as an extra path segment that
143
+ # nothing creates, breaking the write. This never changes what is stored as the
144
+ # `Target`/`Sweep` reference itself.
145
+ def filesystem_safe_component(reference)
146
+ reference.gsub(/[^A-Za-z0-9_.-]/, "-")
147
+ end
148
+
149
+ # Reads the sticky per-check `ever_surprised` bit rather than a loop's local, so a surprise
150
+ # resolved before concluding never reads as clean. `&.` because rows logged before that
151
+ # attribute existed lack the key; missing means no sticky surprise.
152
+ def sweep_was_clean?(sweep)
153
+ sweep.checks.none? { |c| c[:ever_surprised]&.[](:value) == "yes" }
154
+ end
155
+
156
+ # Decays the target's yield_score, adds this period's finds, and moves the streak (see
157
+ # `Target.Release`).
158
+ def release_target!(target, capabilities, current_streak, surprises:, clean:)
159
+ next_yield_score = Hecks::Fuzzing::RotationPriority.next_yield_score(
160
+ old_score: target.yield_score.value, surprises_this_period: surprises
161
+ )
162
+ target.release!(now: { value: Time.now.to_i }, yield_score: { value: next_yield_score },
163
+ next_streak: { value: clean ? current_streak + 1 : 0 },
164
+ capabilities: { value: capabilities.join(",") })
165
+ end
166
+
167
+ # Existence-checked: a held or shelved target is invisible to `Rotation`, and re-`Identify`ing
168
+ # an existing id is refused.
169
+ def seed_default_targets!
170
+ existing = query("Target.All").map { |row| row[:reference][:value] }
171
+ DEFAULT_TARGETS.each do |reference, path|
172
+ next if existing.include?(reference)
173
+
174
+ ::QualityControl::Target.identify!(reference: { value: reference }, path: { value: path })
175
+ end
176
+ end
177
+
178
+ # The only pick with no name given, where `RotationPriority.pick`'s yield weighting matters
179
+ # (`--all` sweeps every row regardless of order).
180
+ def pick_target_row
181
+ if @target_ref
182
+ row = query("Target.All").find { |r| r[:reference][:value] == @target_ref }
183
+ return row if row
184
+
185
+ known = query("Target.All").map { |r| r[:reference][:value] }
186
+ abort "no such target: #{@target_ref.inspect} — known targets: #{known.inspect}"
187
+ end
188
+
189
+ rotation = query("Target.Rotation")
190
+ if rotation.empty?
191
+ seed_default_targets!
192
+ rotation = query("Target.Rotation")
193
+ end
194
+ if rotation.empty?
195
+ abort "the rotation is empty even after seeding pizzas/banking — nothing waiting to sweep " \
196
+ "(every known target is currently held or shelved)"
197
+ end
198
+
199
+ Hecks::Fuzzing::RotationPriority.pick(rotation, now: Time.now.to_i)
200
+ end
201
+
202
+ # One target, start to finish.
203
+ def sweep_one_target
204
+ row = pick_target_row
205
+ @target_reference = row[:reference][:value]
206
+ @target_path = row[:path][:value]
207
+ @domain_path = resolve_target_path(@target_path)
208
+ unless File.directory?(@domain_path)
209
+ abort "target #{@target_reference.inspect} names a path that does not exist on disk: #{@domain_path}"
210
+ end
211
+
212
+ # Before the claim: a suspended target cannot be claimed, and must not be until a person
213
+ # runs this.
214
+ return release_suspended_target if @release_mode
215
+
216
+ resolve_target_modes
217
+ claim_target
218
+ resolve_depth
219
+ announce_resolution
220
+ open_sweep
221
+ choose_seat
222
+ prepare_scratch_schemas
223
+ announce_sweep
224
+ surprise = run_seeds
225
+ return report_finding(surprise) if surprise
226
+
227
+ conclude_clean_sweep
228
+ end
229
+
230
+ # Before the claim: an ineligible target is an operational error (exit 1), never a finding,
231
+ # and must not leave the target held. Deferred modes run only when the whole ask is deferred;
232
+ # mixed with others they wait for `--all`'s later wave.
233
+ def resolve_target_modes
234
+ @capabilities = Hecks::Fuzzing::TargetCapabilities.infer(@domain_path, rust_dir: @rust_dir)
235
+ resolved = Hecks::Fuzzing::TargetCapabilities.resolve(@enabled_modes, @capabilities)
236
+ trace("capabilities+resolve")
237
+ explicit_all_deferred = @explicit_modes && !@explicit_modes.empty? &&
238
+ (@explicit_modes - Hecks::Fuzzing::TargetCapabilities::DEFERRED_MODES).empty?
239
+ @deferred_modes = explicit_all_deferred ? [] : resolved & Hecks::Fuzzing::TargetCapabilities::DEFERRED_MODES
240
+ @active_modes = resolved - @deferred_modes
241
+
242
+ if @persistence_parity_mode && !@active_modes.include?(:persistence_parity)
243
+ abort "target #{@target_reference.inspect} (#{@target_path}) declares no persisted_by(\"PostgresEra\") " \
244
+ "binding in its own .hecksagon (capabilities: #{@capabilities.join(',')}) — " \
245
+ "--persistence-parity has nothing to compare Memory against for this domain. " \
246
+ "examples/directory is the known example that qualifies."
247
+ end
248
+
249
+ # `era_boundary` is seedless and meaningful with no primary seat; `concurrency` is a seat,
250
+ # so it counts here too.
251
+ unless @active_modes.intersect?(%i[differential ruby_only persistence_parity concurrency era_boundary])
252
+ abort "target #{@target_reference.inspect} (#{@target_path}) resolves no comparison mode at all — " \
253
+ "enabled #{@enabled_modes.join(',')}, capabilities #{@capabilities.join(',')} " \
254
+ "(deferred: #{@deferred_modes.join(',')}). Nothing here can be swept without at least one of " \
255
+ "differential/ruby_only/persistence_parity/concurrency/era_boundary."
256
+ end
257
+ @seeded_modes = @active_modes - SEEDLESS_MODES
258
+ end
259
+
260
+ def claim_target
261
+ @target = ::QualityControl::Target.find(@target_reference)
262
+ abort "target #{@target_reference.inspect} vanished between being queried and being claimed" unless @target
263
+
264
+ begin
265
+ @target = @target.claim!(held_by: { value: @engineer }, now: { value: Time.now.to_i })
266
+ rescue Hecks::Runtime::GivenNotMet => e
267
+ abort "target #{@target_reference.inspect} is already held and its claim has not gone stale yet " \
268
+ "(#{e.message})"
269
+ end
270
+ trace("claim")
271
+ end
272
+
273
+ # Read once after claiming: nothing before `Target.Release` changes `clean_streak`, and it
274
+ # sizes this sweep and seeds `next_streak`.
275
+ def resolve_depth
276
+ @current_streak = @target.clean_streak.value
277
+ default_seeds, default_steps = Hecks::Fuzzing::SweepDepth.for_streak(@current_streak, tiers: @widening_tiers)
278
+ @seeds = @seeds_override || default_seeds
279
+ @steps_per_sequence = @steps_override || default_steps
280
+
281
+ # Only when depth is auto-resolved from the streak: an explicit `--seeds N` is a person's
282
+ # manual debugging/reproduction run, and stays the predictable `1..N`. Growing this with the
283
+ # streak is what keeps a matured target's sweep moving into seed integers it has never
284
+ # generated before (see `Hecks::Fuzzing::SweepDepth.seed_offset`).
285
+ @seed_offset = @seeds_override ? 0 : Hecks::Fuzzing::SweepDepth.seed_offset(@current_streak, default_seeds)
286
+ clamp_seeds
287
+ end
288
+
289
+ # Clamps down (never up) only for `--persistence-parity`: real PostgresEra I/O per dispatch is
290
+ # expensive even for a target on the widest tier. `concurrency` has a tighter cap: each seed
291
+ # pays for two disposable schemas and a real fork; it is reached via `--modes concurrency`.
292
+ def clamp_seeds
293
+ if @persistence_parity_mode && @seeds > ::QualityControlDials::PERSISTENCE_PARITY_SEED_CAP
294
+ cap = ::QualityControlDials::PERSISTENCE_PARITY_SEED_CAP
295
+ puts "note: --seeds #{@seeds} exceeds QualityControlDials::PERSISTENCE_PARITY_SEED_CAP " \
296
+ "(#{cap}) — clamping down. Real PostgresEra I/O per " \
297
+ "dispatch is genuinely expensive; edit that dial in qa/settings.yml to run more."
298
+ @seeds = cap
299
+ end
300
+ return unless @active_modes.include?(:concurrency) && @seeds > ::QualityControlDials::CONCURRENCY_SEED_CAP
301
+
302
+ cap = ::QualityControlDials::CONCURRENCY_SEED_CAP
303
+ puts "note: --seeds #{@seeds} exceeds QualityControlDials::CONCURRENCY_SEED_CAP " \
304
+ "(#{cap}) — clamping down. Each seed forks two real racers against " \
305
+ "real PostgresEra I/O; edit that dial in qa/settings.yml to run more."
306
+ @seeds = cap
307
+ end
308
+
309
+ def announce_resolution
310
+ puts "resolved depth: seeds=#{@seeds} steps=#{@steps_per_sequence} (clean_streak=#{@current_streak})"
311
+ puts "seed range: #{@seed_offset + 1}..#{@seed_offset + @seeds}" if @seeded_modes.any?
312
+ line = "resolved modes: #{@active_modes.join(',')} (capabilities=#{@capabilities.join(',')}"
313
+ line += "; deferred=#{@deferred_modes.join(',')}" unless @deferred_modes.empty?
314
+ puts "#{line})"
315
+ end
316
+
317
+ # The reference embeds the target: `Sweep` is not scoped to its target, so two sweeps opened
318
+ # in the same second must not collide.
319
+ def open_sweep
320
+ @sweep_reference = "SW-#{@target_reference}-#{Time.now.to_i}"
321
+ @sweep = ::QualityControl::Sweep.open!(target: @target.id, reference: { value: @sweep_reference },
322
+ engineer: { value: @engineer })
323
+ @differ = build_differ
324
+ @feature = File.basename(@domain_path).downcase
325
+ end
326
+
327
+ # Includes `RustConformanceHelpers` outside RSpec; `structural_skips` collects verbs the
328
+ # binary's manifest declares not generated.
329
+ def build_differ
330
+ require File.join(@root, "spec/support/rust_conformance_helpers")
331
+ Class.new do
332
+ include RustConformanceHelpers
333
+
334
+ attr_reader :structural_skips
335
+
336
+ def initialize
337
+ @structural_skips = Set.new
338
+ end
339
+ end.new
340
+ end
341
+
342
+ # The primary seat is the first of persistence_parity, concurrency, differential, ruby_only
343
+ # that is active, or none for a seedless-only sweep; differential degrades to ruby_only if the
344
+ # build fails.
345
+ def choose_seat
346
+ @binary = nil
347
+ return @mode = :seedless if @seeded_modes.empty?
348
+ return @mode = :persistence_parity if @active_modes.include?(:persistence_parity)
349
+ return @mode = :concurrency if @active_modes.include?(:concurrency)
350
+
351
+ @binary = build_rust_binary if @active_modes.include?(:differential)
352
+ return @mode = :differential if @binary
353
+
354
+ degrade_to_ruby_only if @active_modes.include?(:differential)
355
+ @mode = :ruby_only
356
+ end
357
+
358
+ def build_rust_binary
359
+ @differ.build_rust_for(@feature, @rust_dir)
360
+ rescue RustConformanceHelpers::BuildFailed => e
361
+ @rust_build_failure = e
362
+ nil
363
+ end
364
+
365
+ def degrade_to_ruby_only
366
+ # `build_rust_for` answers nil only for an undeclared feature; a declared one that fails
367
+ # raises, so its cargo stderr is printed here.
368
+ reason = @rust_build_failure ? @rust_build_failure.message : "rust/Cargo.toml declares no #{@feature} feature"
369
+ puts "note: no #{@feature} Rust binary — degrading this sweep to ruby_only. Run `hecks project_rust` for it " \
370
+ "and re-sweep.\n#{reason}"
371
+ @active_modes = (@active_modes - %i[differential properties_in_differential structural_skip_report]) |
372
+ [:ruby_only]
373
+ end
374
+
375
+ # Safe under concurrent callers: several sweeps can reach this at once (the parity wave runs
376
+ # up to `QualityControlDials::SWEEP_MAX_PARALLEL` targets in parallel), so the existence check
377
+ # is only a fast-path optimization; the actual safety net is rescuing the create's own race,
378
+ # since two processes can both see the database missing and both attempt `CREATE DATABASE`.
379
+ # Postgres reports that race two different ways: a `duplicate_database` error (42P04) from its
380
+ # own pre-create name check, or, when two backends run that check at nearly the same instant,
381
+ # a raw `unique_violation` (23505) on `pg_database`'s name index once both proceed to insert.
382
+ def ensure_scratch_database!(name)
383
+ admin = PG.connect(dbname: "postgres")
384
+ exists = admin.exec_params("SELECT 1 FROM pg_database WHERE datname = $1", [name]).ntuples.positive?
385
+ admin.exec(%(CREATE DATABASE "#{name}")) unless exists
386
+ rescue PG::DuplicateDatabase, PG::UniqueViolation
387
+ nil
388
+ ensure
389
+ admin&.close
390
+ end
391
+
392
+ # The scratch database is a fixed name, created if missing and never dropped (a concurrent
393
+ # sweep may use another schema in it); only this run's uniquely named schemas are dropped, in
394
+ # `drop_scratch_schemas`. `concurrency` needs two: the real fork race and its sequential
395
+ # oracle.
396
+ def prepare_scratch_schemas
397
+ @parity_database = @concurrency_database = SCRATCH_DATABASE
398
+ @parity_schema = @race_schema = @reference_schema = nil
399
+ if @active_modes.include?(:persistence_parity)
400
+ require "pg"
401
+ @parity_schema = "qa_pp_#{@target_reference}_#{Process.pid}_#{SecureRandom.hex(4)}"
402
+ ensure_scratch_database!(@parity_database)
403
+ end
404
+ return unless @active_modes.include?(:concurrency)
405
+
406
+ require "pg"
407
+ @race_schema = "qa_cc_race_#{@target_reference}_#{Process.pid}_#{SecureRandom.hex(4)}"
408
+ @reference_schema = "qa_cc_ref_#{@target_reference}_#{Process.pid}_#{SecureRandom.hex(4)}"
409
+ ensure_scratch_database!(@concurrency_database)
410
+ end
411
+
412
+ def mode_label
413
+ case @mode
414
+ when :differential then "Ruby vs compiled Rust (#{@feature})"
415
+ when :persistence_parity then "Memory vs real PostgresEra (#{@feature}) — persistence-adapter parity"
416
+ when :concurrency
417
+ "real forked cross-process dispatch vs its own sequential oracle (#{@feature}) — write-lock serialization"
418
+ when :seedless
419
+ "no primary seat (#{@active_modes.join(',')} — audits this target's own real state, generates nothing)"
420
+ else "Ruby-only property/exception check (no compiled Rust binary for #{@feature})"
421
+ end
422
+ end
423
+
424
+ def announce_sweep
425
+ if @mode == :seedless
426
+ puts "sweeping #{@target_reference} (#{@target_path}) — #{mode_label}, " \
427
+ "active modes #{@active_modes.join(',')}"
428
+ else
429
+ puts "sweeping #{@target_reference} (#{@target_path}) — #{mode_label}, #{@seeds} seed(s), " \
430
+ "#{@steps_per_sequence} steps each, adversarial fraction #{@adversarial}, role draw #{@role_draw}, " \
431
+ "dry-run fraction #{@dry_run}, active modes #{@active_modes.join(',')}"
432
+ end
433
+ end
434
+
435
+ # @return [Hash, nil] the first surprise, or nil when every check held
436
+ def run_seeds
437
+ surprise = nil
438
+ begin
439
+ # One campaign per sweep, resumed from a prior tick's corpus for this exact (target, mode)
440
+ # so guided generation's knowledge compounds across ticks instead of rebuilding an
441
+ # identical corpus from an identical trace every time. Nil when guided generation is off,
442
+ # or this is a seedless-only sweep that will never record anything into it.
443
+ @coverage_path = coverage_corpus_path(@target_reference, @mode) if @guided_generation && @seeded_modes.any?
444
+ @campaign = build_campaign
445
+ surprise = seed_loop
446
+ surprise ||= structural_skip_step
447
+ surprise ||= era_boundary_step
448
+ shrink_surprise(surprise)
449
+ ensure
450
+ # Drops this run's disposable schemas whether the sweep ends clean, surprised or crashes;
451
+ # the databases stay. Persists whatever this run's campaign learned, clean, surprised or
452
+ # crashed alike: the corpus a surprised run built up to that point is still real coverage
453
+ # a later tick should not have to rediscover from scratch.
454
+ drop_scratch_schemas
455
+ save_coverage_state!(@coverage_path, @campaign) if @campaign
456
+ end
457
+ surprise
458
+ end
459
+
460
+ def build_campaign
461
+ return unless @coverage_path
462
+
463
+ saved = load_coverage_state(@coverage_path)
464
+ if saved
465
+ Hecks::Fuzzing::CoverageCampaign.load(saved, splice_probability: @corpus_splice_probability,
466
+ favor_count: @favor_rare_verbs)
467
+ else
468
+ Hecks::Fuzzing::CoverageCampaign.new(splice_probability: @corpus_splice_probability,
469
+ favor_count: @favor_rare_verbs)
470
+ end
471
+ end
472
+
473
+ # A seedless-only sweep skips the loop so it never claims to have generated sequences.
474
+ def seed_loop
475
+ return unless @seeded_modes.any?
476
+
477
+ surprise = nil
478
+ ((@seed_offset + 1)..(@seed_offset + @seeds)).each do |seed|
479
+ result = run_one_seed(@mode, seed)
480
+ @campaign.record(seed, result[:plan], result[:trace]) if @campaign && result[:trace]
481
+
482
+ surprised = log_seed_checks(result)
483
+ if surprised.empty?
484
+ puts " seed #{seed}: held (#{result[:checks].map { |c| c[:mode] }.join(', ')})"
485
+ next
486
+ end
487
+
488
+ puts " seed #{seed}: SURPRISED (#{surprised.map { |c| c[:mode] }.join(', ')})"
489
+ surprise = result.merge(seed: seed, checks: surprised)
490
+ break
491
+ end
492
+ puts " #{@campaign.summary}" if @campaign
493
+ surprise
494
+ end
495
+
496
+ # Every mode's check is logged before the loop decides, so a seed that held differentially but
497
+ # surprised on properties records both.
498
+ #
499
+ # @return [Array<Hash>] the checks that surprised
500
+ def log_seed_checks(result)
501
+ result[:checks].reject do |check|
502
+ log_and_mark(check)
503
+ check[:clean]
504
+ end
505
+ end
506
+
507
+ def log_and_mark(check)
508
+ @sweep = log_check!(@sweep, subject: check[:subject], expectation: check[:expectation])
509
+ sequence = @sweep.checks.last[:sequence][:value]
510
+ if check[:clean]
511
+ mark_held!(@sweep, sequence, check[:observation])
512
+ else
513
+ mark_surprised!(@sweep, sequence, check[:observation], @target_reference)
514
+ end
515
+ end
516
+
517
+ # Structural-skip check only after a differential loop that ran clean to the end.
518
+ def structural_skip_step
519
+ return unless @mode == :differential && @active_modes.include?(:structural_skip_report)
520
+
521
+ check = structural_skip_check(@differ, @binary, @structural_boundary)
522
+ log_and_mark(check)
523
+ if check[:clean]
524
+ puts " structural skips: #{check[:observation]}"
525
+ nil
526
+ else
527
+ puts " structural skips: SURPRISED — #{check[:observation]}"
528
+ { seed: nil, steps: [], checks: [check] }
529
+ end
530
+ end
531
+
532
+ # Era-boundary check, once per sweep, independent of the primary seat; skipped once anything
533
+ # surprised. A target with no lineage logs no Check: holding one would report an unaudited
534
+ # target as clean.
535
+ def era_boundary_step
536
+ return unless @active_modes.include?(:era_boundary)
537
+
538
+ check = era_boundary_check(@domain_path)
539
+ if check[:skip]
540
+ puts " era boundary: #{check[:observation]} — no Check logged"
541
+ return nil
542
+ end
543
+
544
+ log_and_mark(check)
545
+ if check[:clean]
546
+ puts " era boundary: #{check[:observation]}"
547
+ nil
548
+ else
549
+ puts " era boundary: SURPRISED — #{check[:observation]}"
550
+ { seed: nil, steps: [], checks: [check] }
551
+ end
552
+ end
553
+
554
+ # Shrinks inside the guarded block: the parity schema is dropped afterwards, and candidates
555
+ # need it.
556
+ def shrink_surprise(surprise)
557
+ return unless surprise && surprise[:seed] && @shrink_budget.positive?
558
+
559
+ surprise[:shrunk] = surprise[:checks].filter_map do |check|
560
+ next unless SHRINKABLE_MODES.include?(check[:mode])
561
+
562
+ puts " shrinking [#{check[:mode]}] (#{surprise[:steps].size} steps, budget #{@shrink_budget})…"
563
+ shrink_check(check, @mode, surprise[:steps])
564
+ end
565
+ end
566
+
567
+ def drop_scratch_schemas
568
+ if @parity_schema
569
+ admin = PG.connect(dbname: @parity_database)
570
+ admin.exec("SET client_min_messages = warning")
571
+ admin.exec("DROP SCHEMA IF EXISTS #{admin.quote_ident(@parity_schema)} CASCADE")
572
+ admin.close
573
+ end
574
+ return unless @race_schema || @reference_schema
575
+
576
+ admin = PG.connect(dbname: @concurrency_database)
577
+ admin.exec("SET client_min_messages = warning")
578
+ [@race_schema, @reference_schema].compact.each do |schema|
579
+ admin.exec("DROP SCHEMA IF EXISTS #{admin.quote_ident(schema)} CASCADE")
580
+ end
581
+ admin.close
582
+ end
583
+
584
+ def report_finding(surprise)
585
+ print_finding(surprise)
586
+ EXIT_FOUND_SOMETHING
587
+ end
588
+
589
+ # Reached only when every seed held; re-reads the sticky `ever_surprised` bit, not the loop's
590
+ # local.
591
+ def conclude_clean_sweep
592
+ notes = "domain=#{@target_reference} path=#{@target_path} mode=#{@mode} modes=#{@active_modes.join(',')} " \
593
+ "capabilities=#{@capabilities.join(',')} feature=#{@feature} seeds=#{@seeds} " \
594
+ "steps=#{@steps_per_sequence} adversarial=#{@adversarial} role_draw=#{@role_draw} " \
595
+ "dry_run=#{@dry_run} — every seed held, no divergence or property violation found."
596
+ @sweep.conclude!(notes: { value: notes })
597
+ trace("seed loop+conclude")
598
+
599
+ target = release_target!(@target, @capabilities, @current_streak, surprises: 0,
600
+ clean: sweep_was_clean?(@sweep))
601
+ trace("release")
602
+
603
+ puts
604
+ puts "clean — #{@target_reference} concluded and released. clean_streak: #{@current_streak} -> " \
605
+ "#{target.clean_streak.value}. capabilities: #{target.capabilities.value.inspect}"
606
+ puts notes
607
+ EXIT_OK
608
+ end
609
+ end
610
+ end
611
+ end