hecks 2.9.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (600) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +51 -6
  3. data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
  4. data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
  5. data/lib/hecks/adapters/driven/lambda.rb +1 -1
  6. data/lib/hecks/adapters/driven/memory.rb +16 -3
  7. data/lib/hecks/adapters/driven/postgres/reconnect.rb +6 -2
  8. data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
  9. data/lib/hecks/adapters/driven/postgres.rb +4 -2
  10. data/lib/hecks/adapters/driven/prism.rb +41 -8
  11. data/lib/hecks/adapters/driven/tenant_provisioner.rb +73 -16
  12. data/lib/hecks/behaviors/expectations.rb +1 -1
  13. data/lib/hecks/behaviors.rb +1 -1
  14. data/lib/hecks/bench/cli.rb +62 -0
  15. data/lib/hecks/bench/environment.rb +71 -0
  16. data/lib/hecks/bench/postgres_probe.rb +30 -0
  17. data/lib/hecks/bench/report.rb +76 -0
  18. data/lib/hecks/bench/ruby_runner.rb +93 -0
  19. data/lib/hecks/bench/run.rb +55 -0
  20. data/lib/hecks/bench/rust_runner.rb +101 -0
  21. data/lib/hecks/bench/stats.rb +46 -0
  22. data/lib/hecks/bench/suite.rb +98 -0
  23. data/lib/hecks/bench/workload.rb +89 -0
  24. data/lib/hecks/bench.rb +17 -0
  25. data/lib/hecks/bluebook/assembly/contracts.rb +2 -0
  26. data/lib/hecks/bluebook/attribute.rb +1 -1
  27. data/lib/hecks/bluebook/behaviour/aggregate.rb +30 -1
  28. data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -0
  29. data/lib/hecks/bluebook/chapter.rb +6 -2
  30. data/lib/hecks/bluebook/domain_port.rb +36 -5
  31. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +10 -0
  32. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +8 -0
  33. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -1
  34. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +31 -3
  35. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +122 -3
  36. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +1 -1
  37. data/lib/hecks/bluebook/dsl/query_builder.rb +27 -1
  38. data/lib/hecks/bluebook/dsl/rule_reference.rb +1 -1
  39. data/lib/hecks/bluebook/dsl/world_builder.rb +29 -2
  40. data/lib/hecks/bluebook/expression/canonical_form.rb +1 -1
  41. data/lib/hecks/bluebook/expression/resolver.rb +3 -2
  42. data/lib/hecks/bluebook/hexagon.rb +13 -5
  43. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -0
  44. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +4 -1
  45. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
  46. data/lib/hecks/bluebook/meta_validator.rb +9 -4
  47. data/lib/hecks/bluebook/model_check.rb +23 -0
  48. data/lib/hecks/bluebook/pattern_subset.rb +38 -0
  49. data/lib/hecks/bluebook/query.rb +20 -5
  50. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  51. data/lib/hecks/canonical_json.rb +30 -0
  52. data/lib/hecks/chapters.rb +102 -0
  53. data/lib/hecks/cli/behaviors.rb +61 -0
  54. data/lib/hecks/cli/canonicalise.rb +25 -0
  55. data/lib/hecks/cli/check_engine_agreement.rb +32 -0
  56. data/lib/hecks/cli/check_era.rb +88 -0
  57. data/lib/hecks/cli/console.rb +66 -0
  58. data/lib/hecks/cli/deploy_template_diff.rb +40 -0
  59. data/lib/hecks/cli/doc_coverage.rb +32 -0
  60. data/lib/hecks/cli/document.rb +2 -2
  61. data/lib/hecks/cli/expression_projection.rb +41 -0
  62. data/lib/hecks/cli/follow.rb +88 -0
  63. data/lib/hecks/cli/history.rb +44 -0
  64. data/lib/hecks/cli/ir.rb +1 -1
  65. data/lib/hecks/cli/mcp.rb +3 -3
  66. data/lib/hecks/cli/model_check.rb +7 -4
  67. data/lib/hecks/cli/pattern_cases.rb +47 -0
  68. data/lib/hecks/cli/present.rb +39 -0
  69. data/lib/hecks/cli/project_cli.rb +224 -32
  70. data/lib/hecks/cli/project_diagrams.rb +4 -3
  71. data/lib/hecks/cli/project_glossary.rb +44 -0
  72. data/lib/hecks/cli/reference.rb +46 -0
  73. data/lib/hecks/cli/refresh_projections.rb +44 -0
  74. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
  75. data/lib/hecks/cli/release.rb +70 -0
  76. data/lib/hecks/cli/release_gem.rb +59 -0
  77. data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
  78. data/lib/hecks/cli/rspec_shard_files.rb +66 -0
  79. data/lib/hecks/cli/run.rb +3 -3
  80. data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
  81. data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
  82. data/lib/hecks/cli/shape.rb +70 -0
  83. data/lib/hecks/cli/smoke_http.rb +230 -0
  84. data/lib/hecks/cli/smoke_test.rb +5 -3
  85. data/lib/hecks/cli/spec_example.rb +27 -0
  86. data/lib/hecks/cli/statements.rb +32 -0
  87. data/lib/hecks/cli/stores.rb +4 -5
  88. data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
  89. data/lib/hecks/cli.rb +201 -52
  90. data/lib/hecks/codemod.rb +324 -0
  91. data/lib/hecks/corpus.rb +423 -0
  92. data/lib/hecks/deploy/bluebook/deploy.bluebook +591 -23
  93. data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -4
  94. data/lib/hecks/deploy/oidc.json +82 -2
  95. data/lib/hecks/doc/reference.rb +351 -0
  96. data/lib/hecks/doors/cli_door.rb +196 -0
  97. data/lib/hecks/doors/cli_runner.rb +297 -0
  98. data/lib/hecks/{facade → doors}/command_request.rb +1 -1
  99. data/lib/hecks/{facade → doors}/handle.rb +2 -2
  100. data/lib/hecks/{facade → doors}/json_door.rb +4 -4
  101. data/lib/hecks/doors/launcher_options.rb +129 -0
  102. data/lib/hecks/{cli → doors}/mcp_door.rb +7 -7
  103. data/lib/hecks/doors/mcp_door_scope.rb +97 -0
  104. data/lib/hecks/{facade/surface → doors/ruby_door}/aggregate_door.rb +8 -3
  105. data/lib/hecks/{facade/surface → doors/ruby_door}/chapter.rb +3 -3
  106. data/lib/hecks/{facade/surface.rb → doors/ruby_door.rb} +36 -5
  107. data/lib/hecks/doors.rb +61 -0
  108. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +1 -1
  109. data/lib/hecks/embryonaut_bluebook.rb +1 -1
  110. data/lib/hecks/engine_agreement.rb +156 -0
  111. data/lib/hecks/facade.rb +24 -37
  112. data/lib/hecks/forms/app.rb +6 -6
  113. data/lib/hecks/forms/banking_presentation.rb +50 -0
  114. data/lib/hecks/forms/field_shape.rb +2 -1
  115. data/lib/hecks/forms/port_argument.rb +1 -1
  116. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
  117. data/lib/hecks/fuzzing/combination_miner.rb +86 -0
  118. data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
  119. data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
  120. data/lib/hecks/fuzzing/differential.rb +156 -0
  121. data/lib/hecks/fuzzing/domain_generator.rb +615 -0
  122. data/lib/hecks/fuzzing/era_boundary.rb +71 -0
  123. data/lib/hecks/fuzzing/form_census.rb +136 -0
  124. data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
  125. data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
  126. data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
  127. data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
  128. data/lib/hecks/fuzzing/outside_world.rb +45 -0
  129. data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
  130. data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
  131. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
  132. data/lib/hecks/fuzzing/properties/guards.rb +190 -0
  133. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
  134. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
  135. data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
  136. data/lib/hecks/fuzzing/properties/querying.rb +175 -0
  137. data/lib/hecks/fuzzing/properties.rb +162 -0
  138. data/lib/hecks/fuzzing/qa_settings.rb +123 -0
  139. data/lib/hecks/fuzzing/replay.rb +505 -0
  140. data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
  141. data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
  142. data/lib/hecks/fuzzing/self_consistency.rb +339 -0
  143. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
  144. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
  145. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
  146. data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
  147. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
  148. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
  149. data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
  150. data/lib/hecks/fuzzing/shrinker.rb +166 -0
  151. data/lib/hecks/fuzzing/structural_skips.rb +33 -0
  152. data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
  153. data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
  154. data/lib/hecks/fuzzing/value_generator.rb +216 -0
  155. data/lib/hecks/fuzzing.rb +23 -0
  156. data/lib/hecks/grammar/evolve.rb +336 -0
  157. data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
  158. data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
  159. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
  160. data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
  161. data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
  162. data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
  163. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
  164. data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
  165. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
  166. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
  167. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
  168. data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
  169. data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
  170. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
  171. data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
  172. data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
  173. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
  174. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
  175. data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
  176. data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
  177. data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
  178. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
  179. data/lib/hecks/hecks/adapters/git.adapter +7 -0
  180. data/lib/hecks/hecks/adapters/git.rb +102 -0
  181. data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
  182. data/lib/hecks/hecks/adapters/host_http.rb +65 -0
  183. data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
  184. data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
  185. data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
  186. data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
  187. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
  188. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
  189. data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
  190. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
  191. data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
  192. data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
  193. data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
  194. data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
  195. data/lib/hecks/hecks/adapters/local_files.rb +46 -0
  196. data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
  197. data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
  198. data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
  199. data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
  200. data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
  201. data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
  202. data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
  203. data/lib/hecks/hecks/adapters/shell.adapter +7 -0
  204. data/lib/hecks/hecks/adapters/shell.rb +49 -0
  205. data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
  206. data/lib/hecks/hecks/adapters/terminal.rb +74 -0
  207. data/lib/hecks/hecks/codebase.behaviors +172 -0
  208. data/lib/hecks/hecks/codebase.bluebook +2453 -0
  209. data/lib/hecks/hecks/context_map.hecksagon +13 -0
  210. data/lib/hecks/hecks/custodian.behaviors +179 -0
  211. data/lib/hecks/hecks/custodian.bluebook +1847 -0
  212. data/lib/hecks/hecks/deploy.behaviors +74 -0
  213. data/lib/hecks/hecks/deploy.world +12 -0
  214. data/lib/hecks/hecks/environments/memory.world +10 -0
  215. data/lib/hecks/hecks/governance.world +7 -0
  216. data/lib/hecks/hecks/hecks.behaviors +62 -0
  217. data/lib/hecks/hecks/hecks.bluebook +124 -0
  218. data/lib/hecks/hecks/hecks.hecksagon +390 -0
  219. data/lib/hecks/hecks/hecks.world +42 -0
  220. data/lib/hecks/kernel_capabilities.rb +194 -0
  221. data/lib/hecks/language/bluebook/bluebook.bluebook +11 -0
  222. data/lib/hecks/language/bluebook/query.bluebook +7 -0
  223. data/lib/hecks/language/bluebook/vocabulary.bluebook +4 -4
  224. data/lib/hecks/language/hecksagon/domain_port.bluebook +10 -0
  225. data/lib/hecks/language/hecksagon/hecksagon.bluebook +20 -0
  226. data/lib/hecks/mcp_stdio_guard.rb +2 -2
  227. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
  228. data/lib/hecks/persistence_legacy_fixture.rb +113 -0
  229. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +8 -5
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +4 -1
  231. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +15 -2
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +1 -1
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +1 -1
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +32 -15
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +6 -3
  237. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
  238. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +1 -1
  239. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +43 -24
  240. data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
  241. data/lib/hecks/ports/persistence/state_codec.rb +24 -5
  242. data/lib/hecks/projection_files.rb +285 -0
  243. data/lib/hecks/projections/bootstrap_table.rb +1 -1
  244. data/lib/hecks/projections/deploy/fargate.rb +5 -5
  245. data/lib/hecks/projections/deploy/lambda.rb +43 -32
  246. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  247. data/lib/hecks/projections/deploy/preview/template.rb +1 -1
  248. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
  249. data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
  250. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
  251. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
  252. data/lib/hecks/projections/deploy/shared.rb +1 -1
  253. data/lib/hecks/projections/diagrams.rb +3 -3
  254. data/lib/hecks/projections/model/deviations.rb +1 -1
  255. data/lib/hecks/projections/parser_table.rb +4 -4
  256. data/lib/hecks/projections/reference.rb +2 -3
  257. data/lib/hecks/projections/rust_vocabulary.rb +1 -1
  258. data/lib/hecks/projector/cli_projector.rb +106 -37
  259. data/lib/hecks/projector.rb +1 -1
  260. data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
  261. data/lib/hecks/quality_control/adapters/agent.rb +58 -0
  262. data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
  263. data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
  264. data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
  265. data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
  266. data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
  267. data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
  268. data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
  269. data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
  270. data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
  271. data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
  272. data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
  273. data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
  274. data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
  275. data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
  276. data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
  277. data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
  278. data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
  279. data/lib/hecks/quality_control/cli/child.rb +45 -0
  280. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
  281. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
  282. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
  283. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
  284. data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
  285. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
  286. data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
  287. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
  288. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
  289. data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
  290. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
  291. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
  292. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
  293. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
  294. data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
  295. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
  296. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
  297. data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
  298. data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
  299. data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
  300. data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
  301. data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
  302. data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
  303. data/lib/hecks/query_ir.rb +346 -0
  304. data/lib/hecks/query_ir_mcp.rb +198 -0
  305. data/lib/hecks/release/runner/ci_publisher.rb +2 -2
  306. data/lib/hecks/release/runner/clean_tree.rb +42 -0
  307. data/lib/hecks/release/runner/console.rb +6 -0
  308. data/lib/hecks/release/runner/gem_publisher.rb +14 -7
  309. data/lib/hecks/release/runner/npm_publisher.rb +6 -36
  310. data/lib/hecks/release/runner/preflight.rb +26 -2
  311. data/lib/hecks/release/runner/published.rb +8 -26
  312. data/lib/hecks/release/runner/tagger.rb +30 -15
  313. data/lib/hecks/release/runner.rb +57 -6
  314. data/lib/hecks/router/namespace_installer.rb +3 -3
  315. data/lib/hecks/runtime/adapter_lookup.rb +124 -0
  316. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +4 -0
  317. data/lib/hecks/runtime/dependency_planning.rb +20 -2
  318. data/lib/hecks/runtime/dispatcher.rb +53 -3
  319. data/lib/hecks/runtime/interpreting.rb +3 -0
  320. data/lib/hecks/runtime/loader.rb +65 -12
  321. data/lib/hecks/runtime/outbox.rb +1 -1
  322. data/lib/hecks/runtime/policy_interpreter.rb +5 -2
  323. data/lib/hecks/runtime/port_operation_interpreter.rb +2 -10
  324. data/lib/hecks/runtime/query_interpreter.rb +77 -0
  325. data/lib/hecks/runtime/reaction_outcome.rb +89 -0
  326. data/lib/hecks/runtime/refusal_wording.rb +23 -1
  327. data/lib/hecks/runtime/registry/verification.rb +163 -4
  328. data/lib/hecks/runtime/registry.rb +57 -3
  329. data/lib/hecks/runtime/remote_dispatcher.rb +31 -2
  330. data/lib/hecks/runtime/value/coercion.rb +23 -135
  331. data/lib/hecks/runtime/value/field_checks.rb +172 -0
  332. data/lib/hecks/runtime/value.rb +4 -1
  333. data/lib/hecks/runtime.rb +20 -6
  334. data/lib/hecks/rust_build/conformance.rb +158 -0
  335. data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
  336. data/lib/hecks/rust_build/coverage.rb +218 -0
  337. data/lib/hecks/rust_build/native_build.rb +124 -0
  338. data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
  339. data/lib/hecks/rust_build/project_rust.rb +332 -0
  340. data/lib/hecks/rust_build/wasm.rb +105 -0
  341. data/lib/hecks/rust_build/wasm_browser.rb +113 -0
  342. data/lib/hecks/rust_build.rb +144 -0
  343. data/lib/hecks/storehouse.rb +16 -16
  344. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +2 -2
  345. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +1 -1
  346. data/lib/hecks/three_zero/forms.yml +127 -0
  347. data/lib/hecks/three_zero.rb +12 -0
  348. data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
  349. data/lib/hecks/tools/comment_style.rb +1070 -0
  350. data/lib/hecks/tools/corpus_report.rb +87 -0
  351. data/lib/hecks/tools/deploy_recipe.rb +255 -0
  352. data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
  353. data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
  354. data/lib/hecks/tools/era_projection_backfill.rb +94 -0
  355. data/lib/hecks/tools/era_reattest.rb +146 -0
  356. data/lib/hecks/tools/evolve_run.rb +189 -0
  357. data/lib/hecks/tools/fuzz_sweep.rb +373 -0
  358. data/lib/hecks/tools/heki_compaction.rb +100 -0
  359. data/lib/hecks/tools/hoist_local_givens.rb +246 -0
  360. data/lib/hecks/tools/journal_compaction.rb +89 -0
  361. data/lib/hecks/tools/oidc_manifests.rb +70 -0
  362. data/lib/hecks/tools/query_ir_run.rb +81 -0
  363. data/lib/hecks/tools/regeneration_run.rb +166 -0
  364. data/lib/hecks/tools/rust_comment_style.rb +642 -0
  365. data/lib/hecks/tools/sequence_script.rb +62 -0
  366. data/lib/hecks/tools/tail_merge.rb +117 -0
  367. data/lib/hecks/tools/tenant_provisioning.rb +142 -0
  368. data/lib/hecks/tools/translation_audit.rb +208 -0
  369. data/lib/hecks/tools/translation_scaffold.rb +131 -0
  370. data/lib/hecks/tools.rb +86 -0
  371. data/lib/hecks/version.rb +1 -1
  372. data/lib/hecks.rb +32 -15
  373. data/qa/settings.yml +66 -0
  374. data/rust/.cargo/config.toml +6 -0
  375. data/rust/.gitignore +2 -0
  376. data/rust/Cargo.lock +54 -0
  377. data/rust/Cargo.toml +61 -0
  378. data/rust/build/.cargo/config.toml +3 -0
  379. data/rust/build/.gitignore +1 -0
  380. data/rust/build/Cargo.lock +7 -0
  381. data/rust/build/Cargo.toml +19 -0
  382. data/rust/build/src/build_artifact.rs +47 -0
  383. data/rust/build/src/cargo_sync.rs +248 -0
  384. data/rust/build/src/json.rs +421 -0
  385. data/rust/build/src/lineage_pass.rs +285 -0
  386. data/rust/build/src/main.rs +51 -0
  387. data/rust/build/src/optional_pass.rs +150 -0
  388. data/rust/build/src/pipeline.rs +264 -0
  389. data/rust/build/src/reserved_names.rs +76 -0
  390. data/rust/build/src/resolve.rs +124 -0
  391. data/rust/build/src/root.rs +25 -0
  392. data/rust/build/src/sidecars.rs +73 -0
  393. data/rust/build/src/subprocess.rs +51 -0
  394. data/rust/build/src/tmp.rs +31 -0
  395. data/rust/codegen/.cargo/config.toml +3 -0
  396. data/rust/codegen/.gitignore +1 -0
  397. data/rust/codegen/Cargo.lock +7 -0
  398. data/rust/codegen/Cargo.toml +20 -0
  399. data/rust/codegen/src/attr.rs +32 -0
  400. data/rust/codegen/src/bridging.rs +481 -0
  401. data/rust/codegen/src/commands.rs +1226 -0
  402. data/rust/codegen/src/constraints.rs +109 -0
  403. data/rust/codegen/src/dependency_planning.rs +209 -0
  404. data/rust/codegen/src/domain_generator.rs +1288 -0
  405. data/rust/codegen/src/exemplar.rs +230 -0
  406. data/rust/codegen/src/expr_emitter.rs +87 -0
  407. data/rust/codegen/src/fielded.rs +192 -0
  408. data/rust/codegen/src/hecks_naming.rs +33 -0
  409. data/rust/codegen/src/json.rs +335 -0
  410. data/rust/codegen/src/json_codec.rs +697 -0
  411. data/rust/codegen/src/literal.rs +207 -0
  412. data/rust/codegen/src/main.rs +290 -0
  413. data/rust/codegen/src/manifest.rs +227 -0
  414. data/rust/codegen/src/mutations.rs +784 -0
  415. data/rust/codegen/src/naming.rs +286 -0
  416. data/rust/codegen/src/ports.rs +208 -0
  417. data/rust/codegen/src/prelude.rs +116 -0
  418. data/rust/codegen/src/queries.rs +786 -0
  419. data/rust/codegen/src/reactions.rs +532 -0
  420. data/rust/codegen/src/read_models.rs +697 -0
  421. data/rust/codegen/src/reference_specs.rs +40 -0
  422. data/rust/codegen/src/registry.rs +966 -0
  423. data/rust/codegen/src/reserved_names.rs +76 -0
  424. data/rust/codegen/src/shared.rs +93 -0
  425. data/rust/codegen/src/skip_reason.rs +25 -0
  426. data/rust/codegen/src/types.rs +362 -0
  427. data/rust/host/.cargo/config.toml +3 -0
  428. data/rust/host/.gitignore +1 -0
  429. data/rust/host/Cargo.lock +4297 -0
  430. data/rust/host/Cargo.toml +61 -0
  431. data/rust/host/HECKS_RELEASE +1 -0
  432. data/rust/host/rds-ca-bundle.pem +2736 -0
  433. data/rust/host/src/api.rs +1596 -0
  434. data/rust/host/src/approval.rs +652 -0
  435. data/rust/host/src/auth.rs +1287 -0
  436. data/rust/host/src/bin/expr_harness.rs +46 -0
  437. data/rust/host/src/bin/lineage_harness.rs +119 -0
  438. data/rust/host/src/bin/mint_harness.rs +135 -0
  439. data/rust/host/src/checkout.rs +265 -0
  440. data/rust/host/src/dispatch.rs +1443 -0
  441. data/rust/host/src/expr_json.rs +1117 -0
  442. data/rust/host/src/field_hints.rs +52 -0
  443. data/rust/host/src/ir.rs +733 -0
  444. data/rust/host/src/journal.rs +874 -0
  445. data/rust/host/src/lambda_client.rs +378 -0
  446. data/rust/host/src/log.rs +147 -0
  447. data/rust/host/src/main.rs +474 -0
  448. data/rust/host/src/mint.rs +1865 -0
  449. data/rust/host/src/payments/keystore.rs +330 -0
  450. data/rust/host/src/payments/tests.rs +1400 -0
  451. data/rust/host/src/payments.rs +767 -0
  452. data/rust/host/src/presentation.rs +698 -0
  453. data/rust/host/src/presentation_write.rs +1431 -0
  454. data/rust/host/src/query_step.rs +149 -0
  455. data/rust/host/src/rate_limit.rs +904 -0
  456. data/rust/host/src/reference_transform.rs +241 -0
  457. data/rust/host/src/reference_validate.rs +338 -0
  458. data/rust/host/src/resend.rs +296 -0
  459. data/rust/host/src/secrets.rs +106 -0
  460. data/rust/host/src/server.rs +663 -0
  461. data/rust/host/src/storage_shape.rs +175 -0
  462. data/rust/host/src/test_pg.rs +40 -0
  463. data/rust/host/src/ui_schema.rs +1351 -0
  464. data/rust/host/src/wasm_runner.rs +191 -0
  465. data/rust/host/src/web/newsletter/cooldown.rs +126 -0
  466. data/rust/host/src/web/newsletter/tests.rs +104 -0
  467. data/rust/host/src/web/newsletter.rs +310 -0
  468. data/rust/host/src/web/newsletter_send.rs +357 -0
  469. data/rust/host/src/web/registrations.rs +1505 -0
  470. data/rust/host/src/web.rs +2937 -0
  471. data/rust/lsp/.gitignore +1 -0
  472. data/rust/lsp/Cargo.lock +7 -0
  473. data/rust/lsp/Cargo.toml +16 -0
  474. data/rust/lsp/README.md +133 -0
  475. data/rust/lsp/src/diagnostics.rs +165 -0
  476. data/rust/lsp/src/json.rs +385 -0
  477. data/rust/lsp/src/main.rs +399 -0
  478. data/rust/lsp/src/outline.rs +177 -0
  479. data/rust/lsp/src/rpc.rs +67 -0
  480. data/rust/parser/.cargo/config.toml +5 -0
  481. data/rust/parser/.gitignore +1 -0
  482. data/rust/parser/Cargo.lock +7 -0
  483. data/rust/parser/Cargo.toml +21 -0
  484. data/rust/parser/src/build/closed_sets.rs +27 -0
  485. data/rust/parser/src/build/identity.rs +276 -0
  486. data/rust/parser/src/build/mod.rs +12 -0
  487. data/rust/parser/src/build/naming.rs +141 -0
  488. data/rust/parser/src/build/pattern_subset.rs +161 -0
  489. data/rust/parser/src/build/query_derive.rs +72 -0
  490. data/rust/parser/src/build/query_inference.rs +269 -0
  491. data/rust/parser/src/build/query_options.rs +100 -0
  492. data/rust/parser/src/build/read_model.rs +94 -0
  493. data/rust/parser/src/build/references.rs +32 -0
  494. data/rust/parser/src/canonical.rs +114 -0
  495. data/rust/parser/src/diag.rs +55 -0
  496. data/rust/parser/src/emit.rs +953 -0
  497. data/rust/parser/src/expr/ast_json.rs +147 -0
  498. data/rust/parser/src/expr/evaluator.rs +81 -0
  499. data/rust/parser/src/expr/mod.rs +84 -0
  500. data/rust/parser/src/expr/resolver.rs +497 -0
  501. data/rust/parser/src/ir.rs +387 -0
  502. data/rust/parser/src/keywords.rs +585 -0
  503. data/rust/parser/src/lex.rs +662 -0
  504. data/rust/parser/src/main.rs +252 -0
  505. data/rust/parser/src/parse/aggregate.rs +612 -0
  506. data/rust/parser/src/parse/chapter.rs +517 -0
  507. data/rust/parser/src/parse/command.rs +637 -0
  508. data/rust/parser/src/parse/domain_port.rs +127 -0
  509. data/rust/parser/src/parse/entity.rs +527 -0
  510. data/rust/parser/src/parse/file.rs +67 -0
  511. data/rust/parser/src/parse/hecksagon.rs +231 -0
  512. data/rust/parser/src/parse/lifecycle.rs +162 -0
  513. data/rust/parser/src/parse/mod.rs +1262 -0
  514. data/rust/parser/src/parse/policy.rs +87 -0
  515. data/rust/parser/src/parse/process_manager.rs +305 -0
  516. data/rust/parser/src/parse/query.rs +100 -0
  517. data/rust/parser/src/parse/read_model.rs +214 -0
  518. data/rust/parser/src/parse/value_object.rs +250 -0
  519. data/rust/parser/src/ruby_value.rs +422 -0
  520. data/rust/project/bridging.rb +257 -0
  521. data/rust/project/commands.rb +877 -0
  522. data/rust/project/constraints.rb +86 -0
  523. data/rust/project/dependency_planning.rb +178 -0
  524. data/rust/project/domain_generator.rb +980 -0
  525. data/rust/project/exemplar.rb +148 -0
  526. data/rust/project/expr_emitter.rb +72 -0
  527. data/rust/project/fielded.rb +141 -0
  528. data/rust/project/json_codec.rb +641 -0
  529. data/rust/project/mutations.rb +721 -0
  530. data/rust/project/naming.rb +205 -0
  531. data/rust/project/ports.rb +65 -0
  532. data/rust/project/queries.rb +493 -0
  533. data/rust/project/reactions.rb +317 -0
  534. data/rust/project/read_models.rb +690 -0
  535. data/rust/project/reference_specs.rb +27 -0
  536. data/rust/project/registry.rb +396 -0
  537. data/rust/project/skip_reason.rb +29 -0
  538. data/rust/project/types.rb +225 -0
  539. data/rust/project/write_if_changed.rb +83 -0
  540. data/rust/project.rb +30 -0
  541. data/rust/project_rust_pipeline.rb +383 -0
  542. data/rust/src/exemplar/commands.rs +279 -0
  543. data/rust/src/exemplar/constraints.rs +18 -0
  544. data/rust/src/exemplar/fielded.rs +280 -0
  545. data/rust/src/exemplar/json.rs +277 -0
  546. data/rust/src/exemplar/mod.rs +17 -0
  547. data/rust/src/exemplar/mutations.rs +80 -0
  548. data/rust/src/exemplar/queries.rs +33 -0
  549. data/rust/src/exemplar/reactions.rs +88 -0
  550. data/rust/src/exemplar/read_models.rs +48 -0
  551. data/rust/src/exemplar/registry.rs +166 -0
  552. data/rust/src/exemplar/types.rs +44 -0
  553. data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
  554. data/rust/src/kernel/attribute_shapes/list.rs +18 -0
  555. data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
  556. data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
  557. data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
  558. data/rust/src/kernel/cli.rs +585 -0
  559. data/rust/src/kernel/dispatch.rs +1090 -0
  560. data/rust/src/kernel/expr.rs +458 -0
  561. data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
  562. data/rust/src/kernel/expression_operators/comparison.rs +48 -0
  563. data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
  564. data/rust/src/kernel/expression_operators/logical.rs +13 -0
  565. data/rust/src/kernel/expression_operators/membership.rs +18 -0
  566. data/rust/src/kernel/expression_operators/mod.rs +68 -0
  567. data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
  568. data/rust/src/kernel/expression_operators/positional.rs +126 -0
  569. data/rust/src/kernel/expression_operators/presence.rs +101 -0
  570. data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
  571. data/rust/src/kernel/expression_operators/sized.rs +35 -0
  572. data/rust/src/kernel/expression_operators/text.rs +79 -0
  573. data/rust/src/kernel/expression_operators/to_string.rs +18 -0
  574. data/rust/src/kernel/json.rs +779 -0
  575. data/rust/src/kernel/mod.rs +133 -0
  576. data/rust/src/kernel/named_query.rs +219 -0
  577. data/rust/src/kernel/orchestrate.rs +1810 -0
  578. data/rust/src/kernel/pattern.rs +307 -0
  579. data/rust/src/kernel/query_comparators.rs +206 -0
  580. data/rust/src/kernel/query_ordering.rs +103 -0
  581. data/rust/src/kernel/read_model.rs +914 -0
  582. data/rust/src/kernel/reference_lookup.rs +149 -0
  583. data/rust/src/kernel/repository.rs +468 -0
  584. data/rust/src/kernel/routing.rs +612 -0
  585. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
  586. data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
  587. data/rust/src/kernel/vocab/field_hint.rs +70 -0
  588. data/rust/src/kernel/vocab/mod.rs +20 -0
  589. data/rust/src/kernel/vocab/query_comparator.rs +65 -0
  590. data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
  591. data/rust/src/lib.rs +8 -0
  592. data/rust/src/main.rs +16 -0
  593. data/rust/web/.gitignore +1 -0
  594. data/rust/web/Cargo.lock +119 -0
  595. data/rust/web/Cargo.toml +18 -0
  596. data/rust/web/src/lib.rs +8 -0
  597. metadata +477 -12
  598. data/lib/hecks/facade/cli_door.rb +0 -96
  599. data/lib/hecks/facade/cli_runner.rb +0 -127
  600. data/lib/hecks/mcp_door_scope.rb +0 -95
@@ -0,0 +1,129 @@
1
+ require_relative "../ports/identity_generation"
2
+ require_relative "../runtime/errors"
3
+ require_relative "cli_door"
4
+
5
+ module Hecks
6
+ module Doors
7
+ # What a domain's world file adds to its generated launcher, all of it opt-in.
8
+ #
9
+ # A `.world` that declares a `launcher` setting switches these on for that chapter; a
10
+ # domain without one keeps the launcher's plain forms exactly:
11
+ #
12
+ # launcher "Launcher", run_keys: true, failure_states: %w[flagged failed],
13
+ # names: { "mcp" => "serve_mcp" }
14
+ #
15
+ # - **run_keys** mints the `run` key of a creating command that was given none.
16
+ # - **failure_states** are the lifecycle states `--wait` reports as a failure (exit 1).
17
+ # - **names** maps a launcher name to the command it stands for (see `CliProjector`).
18
+ module LauncherOptions
19
+ SETTING = "launcher".freeze
20
+ WAIT = "--wait".freeze
21
+ RUN_KEY = "run.value".freeze
22
+
23
+ module_function
24
+
25
+ # Reads a chapter's `launcher` setting.
26
+ #
27
+ # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted domain
28
+ # @param chapter [String] the chapter the command line speaks to
29
+ # @return [Hash{Symbol => Object}, nil] the setting, or nil when the chapter did not opt in
30
+ def settings(runtime, chapter)
31
+ return unless runtime.respond_to?(:registry)
32
+
33
+ found = runtime.registry.world(chapter)&.for_verb(SETTING)
34
+ found.nil? || found.empty? ? nil : found
35
+ end
36
+
37
+ # The options the launcher's projection is asked for: the program's name, the chapter's
38
+ # launcher names, and whether run keys are minted (so help shows them optional).
39
+ #
40
+ # @param settings [Hash, nil] the chapter's `launcher` setting
41
+ # @param program [String] how the caller was invoked
42
+ # @return [Hash{Symbol => Object}] the options `Projector::CliProjector` reads
43
+ def projection(settings, program)
44
+ { program: program, names: settings && settings[:names], mint_run_keys: settings && settings[:run_keys] }
45
+ end
46
+
47
+ # Takes `--wait` out of a verb's words, unless the verb declares a `wait` argument of its own.
48
+ #
49
+ # `--wait=false` (or `no`, `0`, `off`) is a `--wait` that was switched off; a bare `--wait`
50
+ # may be followed by its Boolean word.
51
+ #
52
+ # @param spec [Hash] the verb's projected spec
53
+ # @param words [Array<String>] the words after the verb
54
+ # @return [Array(Array<String>, Boolean)] the remaining words, and whether `--wait` was given
55
+ # @raise [Runtime::TypeMismatch] if `--wait=` carries something that is not a Boolean word
56
+ def take_wait(spec, words)
57
+ return [words, false] unless words.any? { |word| wait_word?(word) }
58
+ return [words, false] if spec[:arguments].any? { |argument| argument[:path].split(".").first == "wait" }
59
+
60
+ wait = false
61
+ rest = []
62
+ queue = words.dup
63
+ until queue.empty?
64
+ word = queue.shift
65
+ wait_word?(word) ? wait ||= CliDoor.boolean(wait_value(word, queue)) : rest << word
66
+ end
67
+ [rest, wait]
68
+ end
69
+
70
+ # @api private
71
+ def wait_word?(word) = word == WAIT || word.start_with?("#{WAIT}=")
72
+
73
+ # The Boolean word a `--wait` carries: after `=`, or the next word when it is one.
74
+ # @api private
75
+ def wait_value(word, queue)
76
+ return word.split("=", 2).last if word.include?("=")
77
+
78
+ CliDoor::BOOLEAN_WORDS.key?(queue.first.to_s.downcase) ? queue.shift : "true"
79
+ end
80
+
81
+ # Whether a question's text answer reports a gap, which `--wait` turns into a failing exit.
82
+ #
83
+ # The coverage checks end their report with a `GAP (n)` heading; a non-zero count is a
84
+ # finding, and a CI stage that waits on the question fails on it.
85
+ #
86
+ # @param text [String, nil] the question's text answer
87
+ # @return [Boolean] whether a `GAP (n)` heading with n above zero is in the text
88
+ def gap_reported?(text)
89
+ text.is_a?(String) && text.match?(/^GAP \((?!0\))\d+\)/)
90
+ end
91
+
92
+ # Mints the `run` key a creating command was not given, when the domain opted in.
93
+ #
94
+ # An explicit key always wins. Nothing is minted for a command that acts on an existing
95
+ # record. When no identity adapter is bound the run is refused, saying to name a key,
96
+ # rather than sent on without one.
97
+ #
98
+ # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted domain
99
+ # @param spec [Hash] the verb's projected spec
100
+ # @param args [Hash] the parsed arguments
101
+ # @param settings [Hash, nil] the chapter's `launcher` setting
102
+ # @return [Array(Hash, String)] the arguments, and the key minted (nil when none was)
103
+ # @raise [Runtime::NotFound] if a key is owed but no identity adapter is bound
104
+ def run_key(runtime, spec, args, settings)
105
+ return [args, nil] unless settings && settings[:run_keys] && spec[:creates]
106
+ return [args, nil] unless spec[:arguments].any? { |argument| argument[:path] == RUN_KEY }
107
+ return [args, nil] if args.key?(:run) || !runtime.respond_to?(:registry)
108
+
109
+ key = Ports::IdentityGeneration.uuid(runtime.registry)
110
+ [args.merge(run: { value: key }), key]
111
+ rescue Runtime::WiringError => e
112
+ raise Runtime::NotFound, "cannot mint a run key (#{e.message.lines.first.strip}); name one with run=<key>"
113
+ end
114
+
115
+ # Whether a settled record sits in one of the chapter's failure states.
116
+ #
117
+ # @param aggregate [Bluebook::Aggregate, nil] the record's aggregate
118
+ # @param state [Hash, nil] the record's state
119
+ # @param settings [Hash, nil] the chapter's `launcher` setting
120
+ # @return [Boolean] false when the aggregate has no lifecycle
121
+ def failed?(aggregate, state, settings)
122
+ field = aggregate&.lifecycle&.field
123
+ return false unless field && state
124
+
125
+ Array(settings && settings[:failure_states]).map(&:to_s).include?(state[field.to_sym].to_s)
126
+ end
127
+ end
128
+ end
129
+ end
@@ -1,9 +1,9 @@
1
1
  require "json"
2
2
  require_relative "../../hecks"
3
- require_relative "mcp"
3
+ require_relative "../cli/mcp"
4
4
 
5
5
  module Hecks
6
- module CLI
6
+ module Doors
7
7
  # The MCP door onto the storehouse bus: hand-rolled JSON-RPC over stdio, one
8
8
  # server per booted domain. All logic and the JSON shape answered with live in
9
9
  # `Storehouse`; this is plumbing only. A `role:`/`actor_id:` a caller sends is
@@ -235,7 +235,7 @@ module Hecks
235
235
  # Read once, so a bad setting refuses to start the door before anything is
236
236
  # written to stdout.
237
237
  def scope
238
- @scope ||= McpDoorScope.start!(server: Mcp::SERVER)
238
+ @scope ||= McpDoorScope.start!(server: CLI::Mcp::SERVER)
239
239
  end
240
240
 
241
241
  # The standing notes, then what this door's scope says about `domain:`.
@@ -244,7 +244,7 @@ module Hecks
244
244
  end
245
245
 
246
246
  def serve
247
- McpStdioGuard.warn!(server: Mcp::SERVER, notes: warning_notes)
247
+ McpStdioGuard.warn!(server: CLI::Mcp::SERVER, notes: warning_notes)
248
248
  $stdout.sync = true
249
249
 
250
250
  $stdin.each_line do |line|
@@ -269,7 +269,7 @@ module Hecks
269
269
  # `domain:` is confined to `Storehouse::BOOT_ROOT`, and further to a reader
270
270
  # door's `HECKS_DOOR_DOMAINS`, before anything boots.
271
271
  def boot(domain)
272
- Hecks.boot(Storehouse.confine!(scope.admit_domain!(domain), "domain"), install_facade: false)
272
+ Hecks.boot(Storehouse.confine!(scope.admit_domain!(domain), "domain"), install_doors: false)
273
273
  end
274
274
 
275
275
  # One `when` per tool, so the shared `rescue` catches every branch's escape
@@ -315,7 +315,7 @@ module Hecks
315
315
  end
316
316
  rescue StandardError => e
317
317
  # A defect, not a refusal: reaching here means a bad domain path or an
318
- # argument shape `Facade::JsonDoor` could not symbolize.
318
+ # argument shape `Doors::JsonDoor` could not symbolize.
319
319
  { ok: false, error: "#{e.class}: #{e.message}" }
320
320
  end
321
321
 
@@ -347,7 +347,7 @@ module Hecks
347
347
  send_response(id, {
348
348
  protocolVersion: PROTOCOL_VERSION,
349
349
  capabilities: { tools: {} },
350
- serverInfo: { name: Mcp::SERVER, version: "1.1.0" }
350
+ serverInfo: { name: CLI::Mcp::SERVER, version: "1.1.0" }
351
351
  })
352
352
  when "notifications/initialized"
353
353
  nil # a notification — no id, no response
@@ -0,0 +1,97 @@
1
+ require_relative "../runtime/errors"
2
+
3
+ module Hecks
4
+ module Doors
5
+ # What a spawned hecks mcp may do: every tool by default, or only
6
+ # reader tools against named domains in reader mode — reach, not identity (ADR 0072).
7
+ class McpDoorScope
8
+ ENV_PREFIX = "HECKS_DOOR_".freeze
9
+ TOOLS_VARIABLE = "HECKS_DOOR_TOOLS".freeze
10
+ DOMAINS_VARIABLE = "HECKS_DOOR_DOMAINS".freeze
11
+ READERS = "readers".freeze
12
+ EXIT_STATUS = 2
13
+
14
+ # The tools a reader door serves — each reads a domain or the door's own
15
+ # audit log and writes no domain state. `validate` still boots its
16
+ # domain, so it stays bound to the allowed set like every other tool.
17
+ READER_TOOLS = %w[query events state catalog describe validate domains history follow].freeze
18
+
19
+ # Unrestricted when no HECKS_DOOR_* variable is set.
20
+ def self.from_env(env = ENV)
21
+ problems = setting_problems(env)
22
+ raise ArgumentError, problems.join("\n") unless problems.empty?
23
+ return new(allowed_domains: nil) unless env[TOOLS_VARIABLE]
24
+
25
+ new(allowed_domains: env[DOMAINS_VARIABLE].split(File::PATH_SEPARATOR).reject(&:empty?))
26
+ end
27
+
28
+ # Refuses to stderr, not stdout, which carries the MCP protocol.
29
+ def self.start!(server:, env: ENV, stderr: $stderr)
30
+ from_env(env)
31
+ rescue ArgumentError, Runtime::TypeMismatch => e
32
+ e.message.each_line { |line| stderr.puts("#{server}: refusing to start: #{line.chomp}") }
33
+ exit(EXIT_STATUS)
34
+ end
35
+
36
+ def self.setting_problems(env)
37
+ unknown = env.keys.select { |name| name.start_with?(ENV_PREFIX) } - [TOOLS_VARIABLE, DOMAINS_VARIABLE]
38
+ problems = unknown.map do |name|
39
+ "environment #{name} is not accepted; the door takes only #{TOOLS_VARIABLE} and #{DOMAINS_VARIABLE}"
40
+ end
41
+ problems + mode_problems(env[TOOLS_VARIABLE], env[DOMAINS_VARIABLE])
42
+ end
43
+
44
+ def self.mode_problems(tools, domains)
45
+ if tools.nil?
46
+ domains.nil? ? [] : ["#{DOMAINS_VARIABLE} is set without #{TOOLS_VARIABLE}=#{READERS}; it only narrows a reader door"]
47
+ elsif tools != READERS
48
+ ["#{TOOLS_VARIABLE}=#{tools.inspect} is not accepted; the only value is #{READERS.inspect}"]
49
+ elsif domains.to_s.split(File::PATH_SEPARATOR).none? { |path| !path.empty? }
50
+ ["#{TOOLS_VARIABLE}=#{READERS} needs #{DOMAINS_VARIABLE}, the domains this door may read"]
51
+ else
52
+ []
53
+ end
54
+ end
55
+
56
+ # The named domains, resolved against Storehouse::BOOT_ROOT; nil when unrestricted.
57
+ attr_reader :allowed_domains
58
+
59
+ def initialize(allowed_domains:)
60
+ @allowed_domains = allowed_domains&.map { |path| Storehouse.confine!(path, DOMAINS_VARIABLE) }.freeze
61
+ end
62
+
63
+ def restricted?
64
+ !@allowed_domains.nil?
65
+ end
66
+
67
+ def permits_tool?(name)
68
+ !restricted? || READER_TOOLS.include?(name)
69
+ end
70
+
71
+ def refusal(name)
72
+ { ok: false,
73
+ error: "#{name.inspect} is refused: this door runs in reader mode (#{TOOLS_VARIABLE}=#{READERS}), " \
74
+ "which serves only #{READER_TOOLS.join(', ')}" }
75
+ end
76
+
77
+ # Checked before anything boots, so a path outside the allowed set never reaches Kernel.load.
78
+ def admit_domain!(domain)
79
+ return domain unless restricted?
80
+ return domain if @allowed_domains.include?(Storehouse.confine!(domain, "domain"))
81
+
82
+ raise Runtime::TypeMismatch,
83
+ "domain: #{domain.inspect} is refused: this door runs in reader mode (#{TOOLS_VARIABLE}=#{READERS}) " \
84
+ "and reads only #{DOMAINS_VARIABLE}: #{@allowed_domains.join(', ')}"
85
+ end
86
+
87
+ def notes
88
+ return [] unless restricted?
89
+
90
+ ["Reader mode (#{TOOLS_VARIABLE}=#{READERS}): serves #{READER_TOOLS.join(', ')}; " \
91
+ "refuses dispatch (with dry_run and steps), behaviors and every other tool.",
92
+ "domain: boots only #{DOMAINS_VARIABLE}: #{@allowed_domains.join(', ')}.",
93
+ "Reader mode limits reach and identifies no one; it is not authentication."]
94
+ end
95
+ end
96
+ end
97
+ end
@@ -1,4 +1,5 @@
1
1
  require_relative "../../bluebook/dsl/hecksagon_builder"
2
+ require_relative "../../bluebook/dsl/world_builder"
2
3
  require_relative "../../bluebook/dsl/domain_port_builder"
3
4
  require_relative "../../bluebook/dsl/const_shim"
4
5
  require_relative "../../bluebook/dsl/binding_proxy"
@@ -7,8 +8,8 @@ require_relative "../handle"
7
8
  require_relative "../../naming"
8
9
 
9
10
  module Hecks
10
- module Facade
11
- module Surface
11
+ module Doors
12
+ module RubyDoor
12
13
  # One aggregate's door: creating verbs and queries as module methods, CRUD
13
14
  # delegation, and the `.hecksagon` binding/port hooks it lands on.
14
15
  module AggregateDoor
@@ -123,6 +124,9 @@ module Hecks
123
124
  return Bluebook::DSL::AttributePath.new(fqn, [verb.to_s]) if args.empty? && kwargs.empty? && !block
124
125
 
125
126
  collector = Bluebook::DSL::HecksagonBuilder.collector
127
+ world = Bluebook::DSL::WorldBuilder.current
128
+ # Inside a `.world` block the same qualified spelling records a world setting.
129
+ return world.record_binding(verb, args, kwargs, block) if world && !collector
126
130
  return super(verb, *args, **kwargs, &block) unless collector
127
131
 
128
132
  collector << Bluebook::Bind.new(
@@ -136,7 +140,8 @@ module Hecks
136
140
  end
137
141
 
138
142
  door.define_singleton_method(:respond_to_missing?) do |name, include_private = false|
139
- !Bluebook::DSL::HecksagonBuilder.collector.nil? || super(name, include_private)
143
+ !Bluebook::DSL::HecksagonBuilder.collector.nil? || !Bluebook::DSL::WorldBuilder.current.nil? ||
144
+ super(name, include_private)
140
145
  end
141
146
 
142
147
  # Uses `aggregate.hecks_name`, not the qualified `fqn`: qualifying it here
@@ -3,8 +3,8 @@ require_relative "../../bluebook/dsl/const_shim"
3
3
  require_relative "../../bluebook/dsl/hecksagon_builder"
4
4
 
5
5
  module Hecks
6
- module Facade
7
- module Surface
6
+ module Doors
7
+ module RubyDoor
8
8
  # One chapter's module: vision and aggregate list on the singleton, one
9
9
  # aggregate door per declared head, and a `const_missing` hook for undeclared names.
10
10
  module Chapter
@@ -15,7 +15,7 @@ module Hecks
15
15
  # @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted
16
16
  # dispatcher each aggregate door closes over
17
17
  # @param bluebook [Bluebook::Chapter] the chapter to project into a module
18
- # @return [Module] a fresh, unnamed module; `Surface.install` gives it its
18
+ # @return [Module] a fresh, unnamed module; `RubyDoor.install` gives it its
19
19
  # top-level name
20
20
  # @raise [NameError] if an aggregate's name is not a valid constant name
21
21
  def chapter_module(dispatcher, bluebook)
@@ -1,14 +1,15 @@
1
- require_relative "surface/chapter"
2
- require_relative "surface/aggregate_door"
1
+ require_relative "../chapters"
2
+ require_relative "ruby_door/chapter"
3
+ require_relative "ruby_door/aggregate_door"
3
4
 
4
5
  module Hecks
5
- module Facade
6
+ module Doors
6
7
  # The door without classes: anonymous per-boot modules whose singleton methods
7
8
  # dispatch by FQN, installed by `Loader.bind_runtime` and replaced whole on the next boot.
8
9
  #
9
10
  # `persisted_by("Heki")` in a `.hecksagon` file lands on the module's `method_missing`,
10
11
  # which records a `Bind` into the open `HecksagonBuilder.collector`.
11
- module Surface
12
+ module RubyDoor
12
13
  # Names that would shadow the machinery a Handle runs on; a field with one gets no reader.
13
14
  RESERVED = %i[id state events reload inspect to_h hash class].freeze
14
15
 
@@ -21,6 +22,7 @@ module Hecks
21
22
  # closing over `dispatcher`, so `Pizzas::Pizza` and the bare `Pizza` both open the
22
23
  # door of the boot that ran last.
23
24
  #
25
+ # A gem chapter a hecksagon `attaches` installs nothing (`Query`, `Port` stay free).
24
26
  # An aggregate sharing its chapter's name gets no second constant. A name that user
25
27
  # code or the stdlib already owns is left alone with a warning (see `Namespace.install`).
26
28
  #
@@ -30,8 +32,13 @@ module Hecks
30
32
  # @raise [NameError] if a chapter or aggregate name is not a valid constant name
31
33
  def install(dispatcher)
32
34
  dispatcher.registry.bluebooks.each_value do |bluebook|
35
+ next if attached_chapter?(dispatcher.registry, bluebook)
36
+
33
37
  chapter = chapter_module(dispatcher, bluebook)
34
- Namespace.install(Object, bluebook.name, chapter)
38
+ Namespace.install(*placement(bluebook), chapter)
39
+ # A namespaced chapter keeps its aggregates inside its namespace: top-level
40
+ # shortcuts would bring back the collisions the namespace exists to avoid.
41
+ next if bluebook.namespace
35
42
 
36
43
  bluebook.aggregates.each do |aggregate|
37
44
  next if aggregate.hecks_name == bluebook.name
@@ -41,6 +48,30 @@ module Hecks
41
48
  end
42
49
  dispatcher
43
50
  end
51
+
52
+ # Whether `bluebook` is a gem chapter that a hecksagon attached rather than a domain's own.
53
+ #
54
+ # @param registry [Runtime::Registry] the booted registry
55
+ # @param bluebook [Bluebook::Chapter] the chapter under test
56
+ # @return [Boolean]
57
+ def attached_chapter?(registry, bluebook)
58
+ registry.bounded?(bluebook.name) && Hecks::Chapters.index.key?(bluebook.name)
59
+ end
60
+
61
+ # Where a chapter's module installs: `Object::<name>`, or the constant path its
62
+ # `namespace` names, creating any missing parent modules along the way.
63
+ #
64
+ # @param bluebook [Bluebook::Chapter] the chapter being installed
65
+ # @return [Array(Module, String)] the parent module and the constant name within it
66
+ def placement(bluebook)
67
+ return [Object, bluebook.name] unless bluebook.namespace
68
+
69
+ *parents, leaf = bluebook.namespace.split("::")
70
+ parent = parents.reduce(Object) do |outer, name|
71
+ outer.const_defined?(name, false) ? outer.const_get(name, false) : outer.const_set(name, Module.new)
72
+ end
73
+ [parent, leaf]
74
+ end
44
75
  end
45
76
  end
46
77
  end
@@ -0,0 +1,61 @@
1
+ module Hecks
2
+ # The class-free public surface, installed per boot by `Runtime::Loader.bind_runtime`.
3
+ module Doors
4
+ # The removal release of the `Facade` and `install_facade:` spellings.
5
+ REMOVAL = "3.1.0".freeze
6
+
7
+ module_function
8
+
9
+ # Resolves the boot switch from the current keyword and its deprecated spelling.
10
+ #
11
+ # @param install_doors [Boolean] the `install_doors:` keyword
12
+ # @param install_facade [Boolean, nil] the deprecated `install_facade:` keyword; nil when
13
+ # the caller did not pass it
14
+ # @return [Boolean] whether to install the Ruby door constants
15
+ def install?(install_doors, install_facade = nil)
16
+ return install_doors if install_facade.nil?
17
+
18
+ warn "[hecks] `install_facade:` is deprecated and is removed in #{REMOVAL}; use `install_doors:`"
19
+ install_facade
20
+ end
21
+ end
22
+
23
+ # Installs facade constants at top level, replacing only what it installed itself.
24
+ # A constant owned by user code or the stdlib is left alone with a warning.
25
+ module Namespace
26
+ # A live registry, mutated by `install`, so it cannot be frozen.
27
+ # rubocop:disable-next Style/MutableConstant
28
+ GENERATED = {}
29
+
30
+ module_function
31
+
32
+ # Sets `container::name` to `value`, replacing only a constant this module installed.
33
+ #
34
+ # @param container [Module] the namespace to define the constant in
35
+ # @param name [String, Symbol] the constant name
36
+ # @param value [Module] the facade module to install
37
+ # @return [Object] `value` when installed, else the foreign constant left in place
38
+ # @raise [NameError] if `name` is not a valid constant name
39
+ def install(container, name, value)
40
+ name = name.to_s
41
+
42
+ if container.const_defined?(name, false)
43
+ current = container.const_get(name)
44
+ unless GENERATED[[container, name]].equal?(current)
45
+ warn "[hecks] #{name} is already defined — leaving it alone"
46
+ return current
47
+ end
48
+ container.send(:remove_const, name)
49
+ end
50
+
51
+ container.const_set(name, value)
52
+ GENERATED[[container, name]] = value
53
+ value
54
+ end
55
+ end
56
+ end
57
+
58
+ require_relative "doors/handle"
59
+ require_relative "doors/ruby_door"
60
+ require_relative "doors/command_request"
61
+ require_relative "doors/json_door"
@@ -3,7 +3,7 @@ require_relative "vendor"
3
3
 
4
4
  module Hecks
5
5
  module EmbryonautBluebook
6
- # The command line behind `bin/vendor_bluebook`; lives in the gem so a
6
+ # The command line behind `hecks vendor`; lives in the gem so a
7
7
  # consuming project reaches it through its own bundle, no script to copy.
8
8
  #
9
9
  # vendor_bluebook <package>[@<version-or-commit>] [--from path] [--root path]
@@ -31,7 +31,7 @@ module Hecks
31
31
  if files.empty?
32
32
  raise Runtime::WiringError,
33
33
  "no vendored embryonaut bluebook named #{name.inspect} at #{dir} — " \
34
- "vendor it with bin/vendor_bluebook #{name}"
34
+ "vendor it with hecks vendor #{name}"
35
35
  end
36
36
 
37
37
  # Loaded in the glob's alphabetical order; a package with several
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ # The grep-based gate that keeps the query engines agreeing on what a comparator means.
5
+ #
6
+ # It fails when an engine re-grows its own comparator dispatch, or when a declared comparator
7
+ # lacks a shared `Comparison` case or a cross-engine agreement spec. Every declared comparator
8
+ # is read from `Hecks::Vocabulary`, as `Common::COMPARATORS` is: one live source, no copied list.
9
+ module EngineAgreement
10
+ # The engine files that must route every comparator through `Comparison.holds?`.
11
+ ENGINE_FILES = {
12
+ "in-memory port (Memory/Heki-backed aggregate queries)" => "lib/hecks/ports/query/in_memory.rb",
13
+ "reference interpreter (entity/sub-list queries, no-native-hook fallback)" => "lib/hecks/runtime/query_interpreter.rb"
14
+ }.freeze
15
+
16
+ # Where the shared comparison logic lives.
17
+ SHARED_COMPARISON_FILE = "lib/hecks/query_specification/common/comparison.rb"
18
+
19
+ # Specs that put each comparator in front of both engines. `none_in_state` needs a second
20
+ # aggregate and a registry, so the two dedicated growth specs cover it instead.
21
+ AGREEMENT_SPEC_FILES = %w[
22
+ spec/adapters/query_agreement_spec.rb
23
+ spec/query_none_in_state_aggregate_level_growth_spec.rb
24
+ spec/query_none_in_state_growth_spec.rb
25
+ ].freeze
26
+
27
+ # What a check found.
28
+ #
29
+ # @!attribute [r] declared
30
+ # @return [Array<String>] the comparators the vocabulary declares
31
+ # @!attribute [r] problems
32
+ # @return [Array<String>] each disagreement, in words; empty when the engines agree
33
+ Finding = Struct.new(:declared, :problems)
34
+
35
+ module_function
36
+
37
+ # Checks the engines against the vocabulary.
38
+ #
39
+ # @param root [String] the checkout whose files are read
40
+ # @return [Finding] the declared comparators and every problem found
41
+ def check(root:)
42
+ require "hecks"
43
+ declared = Hecks::Vocabulary.symbols("QueryComparator").map(&:to_s)
44
+ if declared.empty?
45
+ return Finding.new(declared, ["Vocabulary::QueryComparator declared no comparators at all — " \
46
+ "vocabulary.bluebook failed to register, or Hecks::Vocabulary changed " \
47
+ "shape. Refusing to report a clean pass."])
48
+ end
49
+
50
+ problems = vocabulary_problems(declared) + engine_problems(root, declared) + shared_problems(root, declared)
51
+ Finding.new(declared, problems)
52
+ end
53
+
54
+ # @param declared [Array<String>] the declared comparators
55
+ # @return [Array<String>] a problem when the language and the runtime table disagree
56
+ def vocabulary_problems(declared)
57
+ runtime = Hecks::QuerySpecification::Common::COMPARATORS.map(&:to_s)
58
+ differ = (declared - runtime) | (runtime - declared)
59
+ return [] if differ.empty?
60
+
61
+ ["Vocabulary::QueryComparator #{declared.sort.inspect} and " \
62
+ "QuerySpecification::Common::COMPARATORS #{runtime.sort.inspect} disagree " \
63
+ "(only in one: #{differ.sort.inspect}) — the language and the runtime table it drives " \
64
+ "have drifted apart."]
65
+ end
66
+
67
+ # @param root [String] the checkout
68
+ # @param declared [Array<String>] the declared comparators
69
+ # @return [Array<String>] a problem for each engine that lost `Comparison.holds?` or grew a case
70
+ def engine_problems(root, declared)
71
+ ENGINE_FILES.flat_map do |label, relative|
72
+ source = File.read(File.join(root, relative))
73
+ found = []
74
+ unless source =~ /Comparison\.holds\?/
75
+ found << "#{relative} (#{label}) no longer calls Comparison.holds? at all — " \
76
+ "has it grown its own comparator dispatch again?"
77
+ end
78
+ declared.each do |comparator|
79
+ escaped = Regexp.escape(comparator)
80
+ next unless source =~ /when\s+"#{escaped}"/ || source =~ /when\s+:#{escaped}\b/
81
+
82
+ found << "#{relative} (#{label}) has its own `when #{comparator.inspect}` — comparator logic " \
83
+ "belongs ONLY in #{SHARED_COMPARISON_FILE}, not re-implemented per engine."
84
+ end
85
+ found
86
+ end
87
+ end
88
+
89
+ # @param root [String] the checkout
90
+ # @param declared [Array<String>] the declared comparators
91
+ # @return [Array<String>] a problem for a comparator with no shared case, or no agreement spec
92
+ def shared_problems(root, declared)
93
+ comparison = File.read(File.join(root, SHARED_COMPARISON_FILE))
94
+ specs = AGREEMENT_SPEC_FILES.map { |path| File.read(File.join(root, path)) }
95
+ missing_case = declared.reject { |comparator| comparison =~ /when\s+"#{Regexp.escape(comparator)}"/ }
96
+ missing_spec = declared.reject { |comparator| specs.any? { |text| mentions_operator?(text, comparator) } }
97
+
98
+ [missing_case_problem(missing_case), missing_spec_problem(missing_spec)].compact
99
+ end
100
+
101
+ # @param missing [Array<String>] comparators with no `when` case in the shared file
102
+ # @return [String, nil] the problem, or nil when none is missing
103
+ def missing_case_problem(missing)
104
+ return nil if missing.empty?
105
+
106
+ "#{SHARED_COMPARISON_FILE} has no `when` case for: #{missing.sort.inspect} — " \
107
+ "a declared comparator with no shared implementation falls to Comparison.holds?'s own `else`, " \
108
+ "which RAISES (by design), but only the first time anything actually asks for it at runtime. " \
109
+ "Add a `when #{missing.first.inspect} then ...` case before shipping this comparator."
110
+ end
111
+
112
+ # @param missing [Array<String>] comparators no agreement spec exercises
113
+ # @return [String, nil] the problem, or nil when none is missing
114
+ def missing_spec_problem(missing)
115
+ return nil if missing.empty?
116
+
117
+ "no example in #{AGREEMENT_SPEC_FILES.inspect} exercises: #{missing.sort.inspect} — " \
118
+ "a comparator with a case but no cross-engine spec has never actually had both engines asked " \
119
+ "the same question. Add a declared query using it, with a hand-computed expected id list, to one " \
120
+ "of those files (see spec/adapters/query_agreement_spec.rb's own header for why a hand-computed " \
121
+ "oracle, not mere pairwise agreement, is the standard)."
122
+ end
123
+
124
+ # @param text [String] a spec's source
125
+ # @param comparator [String] the comparator name, such as `"eq"`
126
+ # @return [Boolean] whether the spec puts the comparator in front of an engine
127
+ def mentions_operator?(text, comparator)
128
+ # Explicit form: the comparator as a bare hash key, bounded so `in:` cannot match
129
+ # mid-identifier.
130
+ return true if text =~ /(?<![A-Za-z0-9_])#{Regexp.escape(comparator)}(?![A-Za-z0-9_])\s*:/
131
+
132
+ # `eq` is normally implicit (`where(status: "open")`), so an example description naming it
133
+ # counts; no other comparator gets that leniency.
134
+ return false unless comparator == "eq"
135
+
136
+ text =~ /\b(?:it|describe)\s+"[^"]*\beq\b[^"]*"/i ? true : false
137
+ end
138
+
139
+ # @param finding [Finding] what a check found
140
+ # @return [String] the clean-pass line
141
+ def clean_line(finding)
142
+ "check_engine_agreement: #{finding.declared.size} declared comparator(s) " \
143
+ "(#{finding.declared.sort.join(', ')}) — every one has a shared Comparison case, a cross-engine " \
144
+ "agreement spec, and both engine files still route through Comparison.holds? alone. 0 problems."
145
+ end
146
+
147
+ # @param finding [Finding] what a check found
148
+ # @return [String] every problem, numbered, with where the mechanism is explained
149
+ def problem_lines(finding)
150
+ numbered = finding.problems.each_with_index.map { |problem, index| " #{index + 1}. #{problem}\n\n" }
151
+ ["check_engine_agreement: #{finding.problems.size} problem(s) found:\n\n", *numbered,
152
+ "See lib/hecks/query_specification/common/comparison.rb's own header for what this mechanism " \
153
+ "replaced and why a grep-based gate exists here at all."].join
154
+ end
155
+ end
156
+ end