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
data/lib/hecks/facade.rb CHANGED
@@ -1,44 +1,31 @@
1
- module Hecks
2
- # The class-free public surface, installed per boot by `Runtime::Loader.bind_runtime`.
3
- module Facade
4
- end
1
+ # The deprecated alias shim: `Hecks::Facade` is now `Hecks::Doors` and `Doors::Surface` is
2
+ # `Doors::RubyDoor`. Both old constants still resolve and warn; they are removed in
3
+ # `Hecks::Doors::REMOVAL`.
4
+ require_relative "doors"
5
5
 
6
- # Installs facade constants at top level, replacing only what it installed itself.
7
- # A constant owned by user code or the stdlib is left alone with a warning.
8
- module Namespace
9
- # A live registry, mutated by `install`, so it cannot be frozen.
10
- # rubocop:disable-next Style/MutableConstant
11
- GENERATED = {}
12
-
13
- module_function
6
+ # The root namespace; this file holds only the deprecated aliases.
7
+ module Hecks
8
+ # Resolves the constants that now live in `Doors` under their old names, warning on each use.
9
+ #
10
+ # Extended onto `Hecks` and `Hecks::Doors`, ahead of `Module#const_missing`.
11
+ module DoorsAliases
12
+ # Old constant name mapped to the new one, per namespace that carried it.
13
+ RENAMED = { Hecks => { Facade: "Hecks::Doors" }, Hecks::Doors => { Surface: "Hecks::Doors::RubyDoor" } }.freeze
14
14
 
15
- # Sets `container::name` to `value`, replacing only a constant this module installed.
15
+ # Answers a renamed constant with its replacement and a warning; anything else
16
+ # goes to the next `const_missing`.
16
17
  #
17
- # @param container [Module] the namespace to define the constant in
18
- # @param name [String, Symbol] the constant name
19
- # @param value [Module] the facade module to install
20
- # @return [Object] `value` when installed, else the foreign constant left in place
21
- # @raise [NameError] if `name` is not a valid constant name
22
- def install(container, name, value)
23
- name = name.to_s
18
+ # @param name [Symbol] the missing constant
19
+ # @return [Module] the replacement module
20
+ def const_missing(name)
21
+ target = RENAMED.fetch(self, {})[name]
22
+ return super unless target
24
23
 
25
- if container.const_defined?(name, false)
26
- current = container.const_get(name)
27
- unless GENERATED[[container, name]].equal?(current)
28
- warn "[hecks] #{name} is already defined — leaving it alone"
29
- return current
30
- end
31
- container.send(:remove_const, name)
32
- end
33
-
34
- container.const_set(name, value)
35
- GENERATED[[container, name]] = value
36
- value
24
+ warn "[hecks] #{self}::#{name} is deprecated and is removed in #{Hecks::Doors::REMOVAL}; use #{target}"
25
+ Object.const_get(target)
37
26
  end
38
27
  end
39
- end
40
28
 
41
- require_relative "facade/handle"
42
- require_relative "facade/surface"
43
- require_relative "facade/command_request"
44
- require_relative "facade/json_door"
29
+ extend DoorsAliases
30
+ Doors.extend(DoorsAliases)
31
+ end
@@ -7,8 +7,8 @@ require_relative "record_renderer"
7
7
  require_relative "command_form_renderer"
8
8
  require_relative "query_form_renderer"
9
9
  require_relative "params"
10
- require_relative "../facade/command_request"
11
- require_relative "../facade/json_door"
10
+ require_relative "../doors/command_request"
11
+ require_relative "../doors/json_door"
12
12
 
13
13
  module Hecks
14
14
  module Forms
@@ -258,16 +258,16 @@ module Hecks
258
258
  end
259
259
 
260
260
  def command_envelope(command, args)
261
- Facade::CommandRequest.normalize(args, receiver: command_receiver(command), legacy_receiver: :id)
261
+ Doors::CommandRequest.normalize(args, receiver: command_receiver(command), legacy_receiver: :id)
262
262
  end
263
263
 
264
264
  def command_receiver(command) = command.creates? ? nil : :aggregate
265
265
 
266
266
  def submitted_command(request, aggregate, command)
267
267
  if request.media_type == "application/json"
268
- raw = Facade::JsonDoor.parse(request.body.read)
269
- envelope = Facade::JsonDoor.command_request(raw, receiver: command_receiver(command),
270
- legacy_receiver: :id)
268
+ raw = Doors::JsonDoor.parse(request.body.read)
269
+ envelope = Doors::JsonDoor.command_request(raw, receiver: command_receiver(command),
270
+ legacy_receiver: :id)
271
271
  return [raw, envelope]
272
272
  end
273
273
 
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../forms"
4
+ require_relative "port_argument"
5
+
6
+ module Hecks
7
+ module Forms
8
+ # The banking example, wired for the forms app: its bluebook, an in-memory store for every
9
+ # aggregate, and the `expose` sample that renders it.
10
+ #
11
+ # It is what `hecks present` serves. The `expose` sample stays out of `examples/`, since every
12
+ # entry there is scanned as a corpus member and must be a complete, model-checked domain.
13
+ module BankingPresentation
14
+ # The aggregates of the banking example, each held in memory.
15
+ AGGREGATES = %w[Customer Account SafeDepositBox Transfer ATMCard CardPayment ExternalTransfer
16
+ ScheduledPayment OnboardingCase Statement].freeze
17
+
18
+ module_function
19
+
20
+ # Builds the Rack app for the banking example.
21
+ #
22
+ # @param root [String] the checkout's root
23
+ # @return [#call] the app, for a Rack server
24
+ def app(root:)
25
+ registry = Hecks::Runtime::Registry.new
26
+ Hecks.with_registry(registry) { load_into(registry, root) }
27
+ registry.verify!
28
+ Hecks::Forms::App.for(registry: registry, app_name: "BankingConsole")
29
+ end
30
+
31
+ # @api private
32
+ def load_into(_registry, root)
33
+ %w[lib/hecks/ports/persistence.port lib/hecks/ports/extraction.port
34
+ lib/hecks/adapters/driven/memory.adapter lib/hecks/adapters/driven/prism.adapter].each do |file|
35
+ Kernel.load(File.join(root, file))
36
+ end
37
+ Hecks::Adapters::Folder.new.load_bluebooks(File.join(root, "examples/banking/bluebook"))
38
+ Kernel.load(File.join(root, "lib/hecks/forms/examples/banking_console.bluebook"))
39
+ Hecks.hecksagon("Banking") do
40
+ uses_framework "Governance"
41
+ AGGREGATES.each { |name| ::Banking.const_get(name).persisted_by("Memory") }
42
+ end
43
+ Hecks.hecksagon("Governance") do
44
+ ::Governance::RoleAssignment.persisted_by("Memory")
45
+ ::Governance::RoleTransition.persisted_by("Memory")
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -182,7 +182,8 @@ module Hecks
182
182
  end
183
183
 
184
184
  # Vocabulary::FieldHint rows (language/bluebook/vocabulary.bluebook), matched
185
- # case-insensitively. bin/project_field_hints writes the Rust host's copy from the same rows.
185
+ # case-insensitively. hecks project_field_hints writes the Rust host's copy from the same
186
+ # rows.
186
187
  HINTS = Hecks::Vocabulary.rows("FieldHint")
187
188
  .to_h { |row| [row["name"], Regexp.new(row["pattern"], Regexp::IGNORECASE)] }
188
189
  .freeze
@@ -1,6 +1,6 @@
1
1
  module Hecks
2
2
  module Forms
3
- # Reads bin/present's `-p PORT` / `--port PORT` / `--port=PORT` argument.
3
+ # Reads hecks present's `-p PORT` / `--port PORT` / `--port=PORT` argument.
4
4
  # Returns `[port, nil]` or `[nil, message]`; a non-numeric port is refused, not read as 0.
5
5
  module PortArgument
6
6
  module_function
@@ -0,0 +1,393 @@
1
+ require_relative "../bluebook/expression/resolver"
2
+ require_relative "../bluebook/expression/evaluator"
3
+
4
+ module Hecks
5
+ module Fuzzing
6
+ # Generates every well-typed expression up to MAX_DEPTH and checks that
7
+ # `Expression::Evaluator` never raises anything but `EvaluationError` on them.
8
+ #
9
+ # Generation is type-directed: a sub-expression is only built where its parent
10
+ # accepts its type, with a small literal palette and a depth bound to stay tractable.
11
+ module BoundedExhaustiveExpressions
12
+ module_function
13
+
14
+ # Integer and Float are one type: the resolver never distinguishes them.
15
+ TYPES = %i[numeric string boolean array nil_type].freeze
16
+
17
+ # A small literal palette; boundary values (Bignum, NaN, unicode) are covered elsewhere.
18
+ TYPE_LEAVES = {
19
+ numeric: ["0", "1", "-1"].freeze,
20
+ string: ['""', '"a"'].freeze,
21
+ boolean: %w[true false].freeze,
22
+ nil_type: %w[nil].freeze
23
+ }.freeze
24
+
25
+ # Names must not collide with BLOCK_PARAM: a block parameter shadows a same-named attribute.
26
+ SYNTHETIC_ATTRS = {
27
+ numeric: %w[num_a num_b].freeze,
28
+ string: %w[str_a str_b].freeze,
29
+ boolean: %w[bool_a bool_b].freeze,
30
+ array: %w[arr_num arr_str].freeze
31
+ }.freeze
32
+
33
+ # Element type per array attribute, so a block parameter gets the right leaf set.
34
+ ARRAY_ELEMENT_TYPE = { "arr_num" => :numeric, "arr_str" => :string }.freeze
35
+
36
+ BLOCK_PARAM = "el".freeze
37
+
38
+ MAX_DEPTH = 3
39
+
40
+ # Half the attributes are bare scalars, half wrapped in `SingleFieldVO`, so the
41
+ # value-object unwrap path in `Resolver#unwrap_scalar` is exercised.
42
+ #
43
+ # Not a plain Hash: `unwrap_scalar` leaves a Hash wrapped, so a `{value: X}` Hash
44
+ # would fail every scalar-typed operation.
45
+ SingleFieldVO = Struct.new(:value) do
46
+ def to_h = { value: value }
47
+ end
48
+
49
+ # Builds the fixed state every generated predicate is interpreted against.
50
+ #
51
+ # @return [Hash{Symbol => Object}] symbol-keyed synthetic state
52
+ def synthetic_state
53
+ {
54
+ num_a: 3,
55
+ num_b: SingleFieldVO.new(5),
56
+ str_a: "hello",
57
+ str_b: SingleFieldVO.new("world"),
58
+ bool_a: true,
59
+ bool_b: SingleFieldVO.new(false),
60
+ arr_num: [1, SingleFieldVO.new(2), 3],
61
+ arr_str: ["x", SingleFieldVO.new("y"), "z"]
62
+ }
63
+ end
64
+
65
+ # The `attrs` half of the interpret call; every name lives in `synthetic_state`.
66
+ #
67
+ # @return [Hash] always `{}`
68
+ def synthetic_attrs = {}
69
+
70
+ # Lists every terminal expression of `type`: literals, synthetic attribute names,
71
+ # and any bound block-parameter leaf.
72
+ #
73
+ # @param type [Symbol] one of `TYPES`
74
+ # @return [Array<String>] the terminal expressions for `type`
75
+ def leaves(type)
76
+ (TYPE_LEAVES[type] || []) + (SYNTHETIC_ATTRS[type] || []) + Array(bound_leaves[type])
77
+ end
78
+
79
+ # The per-type stack of bound block-parameter leaf names.
80
+ #
81
+ # A stack because nested block predicates push a second `BLOCK_PARAM`; the inner one
82
+ # shadows the outer, which the interpreter handles by rebinding per level.
83
+ #
84
+ # @return [Hash{Symbol => Array<String>}] each type's stack, usually `[]` or `["el"]`
85
+ def bound_leaves = @bound_leaves ||= Hash.new { |h, k| h[k] = [] }
86
+
87
+ # Runs the block with `BLOCK_PARAM` admitted as a leaf of `element_type`.
88
+ #
89
+ # @param element_type [Symbol] the type `BLOCK_PARAM` is bound to
90
+ # @yield runs with the leaf pushed and affected cache entries invalidated
91
+ # @return [Object] the block's result
92
+ def with_element_leaf(element_type)
93
+ bound_leaves[element_type] << BLOCK_PARAM
94
+ cache.delete_if { |(type, _depth), _| type == element_type }
95
+ yield
96
+ ensure
97
+ bound_leaves[element_type].pop
98
+ cache.delete_if { |(type, _depth), _| type == element_type }
99
+ end
100
+
101
+ # Lists every expression of `type` reachable in at most `depth` steps, memoized
102
+ # so cost grows additively per level rather than multiplicatively.
103
+ #
104
+ # @param type [Symbol] one of `TYPES`
105
+ # @param depth [Integer] maximum recursive steps remaining
106
+ # @return [Array<String>] the deduplicated expressions; `leaves(type)` at depth 0
107
+ def productions(type, depth)
108
+ cache[[type, depth]] ||= begin
109
+ base = leaves(type)
110
+ depth <= 0 ? base : (base + recursive_productions(type, depth)).uniq
111
+ end
112
+ end
113
+
114
+ # The `productions` memo, keyed by `[type, depth]`.
115
+ #
116
+ # @return [Hash{Array(Symbol, Integer) => Array<String>}] the cache
117
+ def cache = @cache ||= {}
118
+
119
+ # Dispatches to the recursive production rule for `type`.
120
+ #
121
+ # @param type [Symbol] one of `TYPES`
122
+ # @param depth [Integer] maximum recursive steps remaining
123
+ # @return [Array<String>] the non-leaf expressions; `[]` for `:nil_type`
124
+ # @raise [ArgumentError] if `type` is outside `TYPES`
125
+ def recursive_productions(type, depth)
126
+ case type
127
+ when :numeric then numeric_productions(depth)
128
+ when :string then string_productions(depth)
129
+ when :boolean then boolean_productions(depth)
130
+ when :array then array_productions(depth)
131
+ # nothing produces nil as source text; Find's "not found" is a runtime outcome
132
+ when :nil_type then []
133
+ else raise ArgumentError, "no production rule for type #{type.inspect}"
134
+ end
135
+ end
136
+
137
+ # Samples `productions(type, depth)` down to `SAMPLE_CAP` entries.
138
+ #
139
+ # Every internal sub-expression list goes through this: bounding the inputs (not the
140
+ # final output) is what keeps growth near-linear in depth instead of combinatorial.
141
+ #
142
+ # @param type [Symbol] one of `TYPES`
143
+ # @param depth [Integer] maximum recursive steps remaining
144
+ # @return [Array<String>] at most `SAMPLE_CAP` expressions
145
+ def bounded(type, depth) = sample(productions(type, depth))
146
+
147
+ # Numeric from Addition, Modulo, Size (string or array), and First/Last of a numeric array.
148
+ #
149
+ # @param depth [Integer] maximum recursive steps remaining
150
+ # @return [Array<String>] numeric expressions one construct deep
151
+ def numeric_productions(depth)
152
+ sub = bounded(:numeric, depth - 1)
153
+ # Modulo's receiver is restricted to `resolver_numeric_leaves`: `Resolver.parse` splits
154
+ # on `+` before the `.modulo` suffix, so "0 + 0.modulo(1)" parses as `0 + (0.modulo(1))`.
155
+ pairs(sub).map { |a, b| "#{a} + #{b}" } +
156
+ cross(resolver_numeric_leaves(depth - 1), sub).map { |a, b| "#{a}.modulo(#{b})" } +
157
+ bounded(:string, depth - 1).map { |s| "#{s}.size" } +
158
+ bounded(:array, depth - 1).map { |a| "#{a}.size" } +
159
+ sample(numeric_array_productions(depth - 1)).flat_map { |a| ["#{a}.first", "#{a}.last"] }
160
+ end
161
+
162
+ # Booleans safe as the receiver of a Resolver-level suffix such as `.to_s` (a boolean-
163
+ # typed expression, is wanted). Found live, the same way the
164
+ # nested `.modulo` bug was: `Compare`, `Include`, `Or`, `And` and `Not` are boolean-
165
+ # typed at `interpret` time, but `Evaluator`-level syntax, not
166
+ # something `Resolver.parse` can take as a receiver, even parenthesized.
167
+ #
168
+ # @param depth [Integer] maximum recursive steps remaining
169
+ # @return [Array<String>] boolean expressions parsed by a Resolver suffix rule
170
+ def resolver_boolean_leaves(depth)
171
+ leaves(:boolean) +
172
+ resolver_numeric_leaves(depth).flat_map { |n| ["#{n}.positive?", "#{n}.negative?", "#{n}.zero?"] } +
173
+ bounded(:string, depth).flat_map do |s|
174
+ ["#{s}.present?", "#{s}.blank?", "#{s}.match?(/a/)", "#{s}.start_with?(\"a\")", "#{s}.end_with?(\"a\")"]
175
+ end +
176
+ (sample(numeric_array_productions(depth)) + sample(string_array_productions(depth)) + bounded(:array, depth)).map do |x|
177
+ "#{x}.empty?"
178
+ end +
179
+ sample(block_predicate_productions(depth))
180
+ end
181
+
182
+ # Numerics safe to embed as the receiver of a Resolver-level suffix such as `.to_s`.
183
+ #
184
+ # A bare `Addition` is excluded: `Resolver.parse` splits on `+` before matching the
185
+ # suffix, so "0 + 0.to_s" parses as `0 + (0.to_s)`, not `(0 + 0).to_s`.
186
+ #
187
+ # @param depth [Integer] maximum recursive steps remaining
188
+ # @return [Array<String>] numeric expressions that are never a bare top-level `Addition`
189
+ def resolver_numeric_leaves(depth)
190
+ return leaves(:numeric) if depth <= 0
191
+
192
+ # Self-referential so Modulo's receiver stays restricted; its argument is unrestricted.
193
+ leaves(:numeric) +
194
+ cross(resolver_numeric_leaves(depth - 1), bounded(:numeric, depth - 1)).map { |a, b| "#{a}.modulo(#{b})" } +
195
+ bounded(:string, depth).map { |s| "#{s}.size" } +
196
+ bounded(:array, depth).map { |a| "#{a}.size" } +
197
+ sample(numeric_array_productions(depth)).flat_map { |a| ["#{a}.first", "#{a}.last"] }
198
+ end
199
+
200
+ # String from ToS of numeric, boolean, nil or string, and First/Last of a string array.
201
+ #
202
+ # `Split` is not listed: it produces an array, never a String.
203
+ #
204
+ # @param depth [Integer] maximum recursive steps remaining
205
+ # @return [Array<String>] string expressions one construct deep
206
+ def string_productions(depth)
207
+ sample(resolver_numeric_leaves(depth - 1)).map { |n| "#{n}.to_s" } +
208
+ sample(resolver_boolean_leaves(depth - 1)).map { |b| "#{b}.to_s" } +
209
+ bounded(:nil_type, depth - 1).map { |n| "#{n}.to_s" } +
210
+ bounded(:string, depth - 1).map { |s| "#{s}.to_s" } +
211
+ sample(string_array_productions(depth - 1)).flat_map { |a| ["#{a}.first", "#{a}.last"] }
212
+ end
213
+
214
+ # Array from Split and same-type array literals; array is mostly a receiver type, so
215
+ # few producers are needed.
216
+ #
217
+ # @param depth [Integer] maximum recursive steps remaining
218
+ # @return [Array<String>] array expressions one construct deep; `[]` at depth 0
219
+ def array_productions(depth)
220
+ return [] if depth <= 0
221
+
222
+ bounded(:string, depth - 1).map { |s| "#{s}.split(\",\")" } +
223
+ [numeric_array_literal(depth - 1), string_array_literal(depth - 1)]
224
+ end
225
+
226
+ # Builds a two-element numeric array literal at `depth`.
227
+ #
228
+ # @param depth [Integer] maximum recursive steps remaining for its elements
229
+ # @return [String] the literal's source text
230
+ def numeric_array_literal(depth) = "[#{bounded(:numeric, depth).first(2).join(', ')}]"
231
+
232
+ # Builds a two-element string array literal at `depth`.
233
+ #
234
+ # @param depth [Integer] maximum recursive steps remaining for its elements
235
+ # @return [String] the literal's source text
236
+ def string_array_literal(depth) = "[#{bounded(:string, depth).first(2).join(', ')}]"
237
+
238
+ # Arrays known by construction to hold numeric elements, so `.first`/`.last` and block
239
+ # predicates know the element type without a richer AST.
240
+ #
241
+ # @param depth [Integer] maximum recursive steps remaining
242
+ # @return [Array<String>] `"arr_num"` plus a numeric array literal at `depth`
243
+ def numeric_array_productions(depth)
244
+ ["arr_num", numeric_array_literal(depth)]
245
+ # no Split entry: a Split of a string never yields numeric elements
246
+ end
247
+
248
+ # Arrays known by construction to hold string elements; the twin of
249
+ # `numeric_array_productions`.
250
+ #
251
+ # @param depth [Integer] maximum recursive steps remaining
252
+ # @return [Array<String>] `"arr_str"`, a string array literal, and every string `Split`
253
+ def string_array_productions(depth)
254
+ ["arr_str", string_array_literal(depth)] + bounded(:string, depth).map { |s| "#{s}.split(\",\")" }
255
+ end
256
+
257
+ # Boolean from every comparison and predicate construct; a `given` body is boolean-
258
+ # typed at its own top level (`Evaluator.truthy?`), so this is also
259
+ # the set `all_predicates` draws from.
260
+ #
261
+ # One table rather than helper methods: each term's rationale sits beside it.
262
+ # rubocop:disable-next Metrics/AbcSize
263
+ #
264
+ # @param depth [Integer] maximum recursive steps remaining
265
+ # @return [Array<String>] boolean expressions one construct deep
266
+ def boolean_productions(depth)
267
+ sub = bounded(:boolean, depth - 1)
268
+ num = bounded(:numeric, depth - 1)
269
+ str = bounded(:string, depth - 1)
270
+
271
+ pairs(num).map { |a, b| "#{a} == #{b}" } +
272
+ pairs(num).map { |a, b| "#{a} > #{b}" } +
273
+ pairs(str).map { |a, b| "#{a} == #{b}" } +
274
+ pairs(str).map { |a, b| "#{a} < #{b}" } +
275
+ # `resolver_numeric_leaves`, not `num`: a suffix match is a plain trailing strip, so
276
+ # an `Addition` would mis-split ("0 + 0.positive?" parses as `0 + (0.positive?)`).
277
+ resolver_numeric_leaves(depth - 1).flat_map { |n| ["#{n}.positive?", "#{n}.negative?", "#{n}.zero?"] } +
278
+ (str + sample(numeric_array_productions(depth - 1)) + sample(string_array_productions(depth - 1)) + bounded(:array,
279
+ depth - 1))
280
+ .map do |x|
281
+ "#{x}.empty?"
282
+ end +
283
+ str.map { |s| "#{s}.match?(/a/)" } +
284
+ str.flat_map { |s| ["#{s}.present?", "#{s}.blank?"] } +
285
+ str.flat_map { |s| ["#{s}.start_with?(\"a\")", "#{s}.end_with?(\"a\")"] } +
286
+ pairs(sub).flat_map { |a, b| ["#{a} && #{b}", "#{a} || #{b}"] } +
287
+ sub.map { |b| "!#{b}" } +
288
+ block_predicate_productions(depth - 1) +
289
+ include_productions(depth - 1)
290
+ end
291
+
292
+ # `.all?`/`.any?`/`.none?` over each known-element-typed array, with a predicate body
293
+ # bound to `BLOCK_PARAM`.
294
+ #
295
+ # The only construct where Resolver and Evaluator recurse into each other, and the
296
+ # only one that can nest into another block predicate.
297
+ #
298
+ # @param depth [Integer] maximum recursive steps remaining for the predicate body
299
+ # @return [Array<String>] `.all?`/`.any?`/`.none?` calls over each
300
+ # known-element-typed array source, one per predicate body `predicate_bodies`
301
+ # produces; `[]` if `depth` is negative
302
+ def block_predicate_productions(depth)
303
+ return [] if depth.negative?
304
+
305
+ [
306
+ ["arr_num", :numeric], [numeric_array_literal(depth), :numeric],
307
+ ["arr_str", :string], [string_array_literal(depth), :string]
308
+ ].flat_map do |array_text, element_type|
309
+ predicate_bodies(element_type, depth).flat_map do |body|
310
+ %w[all? any? none?].map { |mode| "#{array_text}.#{mode} { |#{BLOCK_PARAM}| #{body} }" }
311
+ end
312
+ end
313
+ end
314
+
315
+ # The predicate body a block gets: `boolean_productions` with `BLOCK_PARAM` also a leaf.
316
+ # Sampled because each body is multiplied by three modes and four array sources.
317
+ #
318
+ # @param element_type [Symbol] the type `BLOCK_PARAM` is bound to inside the block
319
+ # @param depth [Integer] maximum recursive steps remaining for the body
320
+ # @return [Array<String>] a sampled set of boolean expressions
321
+ def predicate_bodies(element_type, depth)
322
+ sample(with_element_leaf(element_type) { boolean_productions(depth) })
323
+ end
324
+
325
+ # `haystack.include?(needle)`: a String haystack needs a String needle, an Array
326
+ # haystack admits any needle type.
327
+ #
328
+ # @param depth [Integer] maximum recursive steps remaining
329
+ # @return [Array<String>] `include?` expressions for string and array haystacks
330
+ def include_productions(depth)
331
+ str = bounded(:string, depth)
332
+ pairs(str).map { |haystack, needle| "#{haystack}.include?(#{needle})" } +
333
+ sample(numeric_array_productions(depth)).product(bounded(:numeric, depth)).map do |arr, needle|
334
+ "#{arr}.include?(#{needle})"
335
+ end +
336
+ sample(string_array_productions(depth)).product(bounded(:string, depth)).map do |arr, needle|
337
+ "#{arr}.include?(#{needle})"
338
+ end
339
+ end
340
+
341
+ # Cap on any sampled list. A full cross product explodes to tens of millions of cases,
342
+ # and `Or`/`And`/`Not` do no type-dependent work, so sampled operands cover them.
343
+ SAMPLE_CAP = 14
344
+
345
+ # Evenly samples `list` down to at most `SAMPLE_CAP` entries.
346
+ #
347
+ # @param list [Array<String>] the list to sample
348
+ # @return [Array<String>] `list` if short enough, else every n-th entry, evenly spaced
349
+ def sample(list) = list.size <= SAMPLE_CAP ? list : list.each_slice(list.size.fdiv(SAMPLE_CAP).ceil).map(&:first)
350
+
351
+ # Builds every pair of one sampled list with itself.
352
+ #
353
+ # @param list [Array<String>] the list to sample and pair
354
+ # @return [Array<Array(String, String)>] the sampled list's self cross product
355
+ def pairs(list)
356
+ sampled = sample(list)
357
+ sampled.product(sampled)
358
+ end
359
+
360
+ # The two-list twin of `pairs`, for constructs whose sides need different operand sets.
361
+ #
362
+ # @param left [Array<String>] the left-hand list to sample
363
+ # @param right [Array<String>] the right-hand list to sample
364
+ # @return [Array<Array(String, String)>] the cross product of the sampled lists
365
+ def cross(left, right) = sample(left).product(sample(right))
366
+
367
+ # Every boolean-typed expression up to `max_depth`, deduplicated.
368
+ #
369
+ # @param max_depth [Integer] maximum recursive depth to generate up to
370
+ # @return [Array<String>] the deduplicated expressions
371
+ def all_predicates(max_depth = MAX_DEPTH)
372
+ productions(:boolean, max_depth).uniq
373
+ end
374
+
375
+ # Interprets one predicate against the synthetic state.
376
+ #
377
+ # A true/false answer or a clean `EvaluationError` is the sublanguage working; only
378
+ # any other escaping error is a finding.
379
+ #
380
+ # @param expr [String] a generated or hand-written expression source
381
+ # @return [Hash{Symbol => Object}] `{ok: true, result:}`, `{ok: true, result: :refused,
382
+ # message:}` for `EvaluationError`, or `{ok: false, error:}` for anything else
383
+ def check(expr)
384
+ result = Hecks::Bluebook::Expression::Evaluator.call(expr, synthetic_state, synthetic_attrs)
385
+ { ok: true, result: result }
386
+ rescue Hecks::Bluebook::Expression::EvaluationError => e
387
+ { ok: true, result: :refused, message: e.message }
388
+ rescue StandardError => e
389
+ { ok: false, error: e }
390
+ end
391
+ end
392
+ end
393
+ end
@@ -0,0 +1,86 @@
1
+ require "json"
2
+ require "open3"
3
+ require_relative "form_census"
4
+
5
+ module Hecks
6
+ module Fuzzing
7
+ # Asks an agent to write candidate bluebooks from the corpus's form-pair census.
8
+ # Opt-in: `hecks quality_control tick` never runs it; an agent call is costly and
9
+ # nondeterministic.
10
+ module CombinationMiner
11
+ PROMPT_TEMPLATE = "qa/combination_miner/prompt.md".freeze
12
+ HYPOTHESIS_FILE = "HYPOTHESIS.md".freeze
13
+
14
+ module_function
15
+
16
+ def corpus_paths(root)
17
+ Hecks::Corpus.members(:stress, :example, root: root).map(&:path)
18
+ .select { |path| Hecks::Corpus.bluebook_files(path) }
19
+ .sort
20
+ end
21
+
22
+ def recent_bug_titles(root, limit: 60)
23
+ out, status = Open3.capture2("git", "log", "--format=%s", "--grep=BUG#", "-n", limit.to_s, chdir: root)
24
+ status.success? ? out.lines.map(&:strip).reject(&:empty?) : []
25
+ end
26
+
27
+ def brief(paths, root:, bug_titles:)
28
+ covered = Hash.new { |hash, key| hash[key] = [] }
29
+ skipped = []
30
+ paths.each do |path|
31
+ FormCensus.covered_pairs(FormCensus.census(path)).each { |pair, names| covered[pair].concat(names) }
32
+ rescue StandardError, ScriptError => e
33
+ skipped << "#{relative(path, root)} (#{e.class}: #{e.message.lines.first&.strip})"
34
+ end
35
+ { "forms" => FormCensus::FORMS.keys, "corpus" => paths.map { |path| relative(path, root) },
36
+ "unmet_pairs" => (FormCensus.pairs - covered.keys).sort,
37
+ "single_carrier_pairs" => covered.select { |_, names| names.uniq.size == 1 }
38
+ .map { |pair, names| "#{pair} (only #{names.first})" }.sort,
39
+ "skipped" => skipped, "recent_bugs" => bug_titles, "covered" => covered }
40
+ end
41
+
42
+ def prompt(root, brief, count:, out_dir:)
43
+ substitutions = {
44
+ "count" => count.to_s, "out_dir" => out_dir, "forms" => brief["forms"].join(", "),
45
+ "corpus" => bulleted(brief["corpus"]), "unmet_pairs" => bulleted(brief["unmet_pairs"]),
46
+ "single_carrier_pairs" => bulleted(brief["single_carrier_pairs"]),
47
+ "recent_bugs" => bulleted(brief["recent_bugs"]), "skipped" => bulleted(brief["skipped"])
48
+ }
49
+ File.read(File.join(root, PROMPT_TEMPLATE)).gsub(/\{\{(\w+)\}\}/) { substitutions.fetch(Regexp.last_match(1)) }
50
+ end
51
+
52
+ def repair_prompt(failures, out_dir:)
53
+ listed = failures.map { |candidate, error| "- #{candidate[:bluebook]}\n boot error: #{error}" }.join("\n")
54
+ <<~PROMPT
55
+ REPAIR ROUND. You wrote candidate Hecks bluebooks under #{out_dir} for the QA combination miner.
56
+ These did not boot. Edit each file IN PLACE so it boots, keeping the construct combination its
57
+ #{HYPOTHESIS_FILE} names (update the hypothesis if the fix changes the shape). Do not create new
58
+ candidates and do not touch anything outside #{out_dir}.
59
+
60
+ #{listed}
61
+ PROMPT
62
+ end
63
+
64
+ # One bluebook per candidate: the first, alphabetically, if an agent wrote more.
65
+ def candidates(out_dir)
66
+ Dir[File.join(out_dir, "*")].select { |dir| File.directory?(dir) }.sort.filter_map do |dir|
67
+ bluebook = Dir[File.join(dir, "*.bluebook")].min
68
+ next unless bluebook
69
+
70
+ hypothesis = File.join(dir, HYPOTHESIS_FILE)
71
+ { slug: File.basename(dir), dir: dir, bluebook: bluebook,
72
+ hypothesis: File.exist?(hypothesis) ? File.read(hypothesis).strip : nil }
73
+ end
74
+ end
75
+
76
+ # Informational, never a gate: an agent may aim at a shape the census does not name yet.
77
+ def new_pairs(candidate_dir, covered)
78
+ FormCensus.covered_pairs(FormCensus.census(candidate_dir)).keys.reject { |pair| covered.key?(pair) }.sort
79
+ end
80
+
81
+ def relative(path, root) = path.delete_prefix("#{root}/")
82
+
83
+ def bulleted(items) = items.empty? ? "(none)" : items.map { |item| "- #{item}" }.join("\n")
84
+ end
85
+ end
86
+ end