hecks 2.9.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (600) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +51 -6
  3. data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
  4. data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
  5. data/lib/hecks/adapters/driven/lambda.rb +1 -1
  6. data/lib/hecks/adapters/driven/memory.rb +16 -3
  7. data/lib/hecks/adapters/driven/postgres/reconnect.rb +6 -2
  8. data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
  9. data/lib/hecks/adapters/driven/postgres.rb +13 -4
  10. data/lib/hecks/adapters/driven/prism.rb +41 -8
  11. data/lib/hecks/adapters/driven/tenant_provisioner.rb +73 -16
  12. data/lib/hecks/behaviors/expectations.rb +1 -1
  13. data/lib/hecks/behaviors.rb +1 -1
  14. data/lib/hecks/bench/cli.rb +62 -0
  15. data/lib/hecks/bench/environment.rb +71 -0
  16. data/lib/hecks/bench/postgres_probe.rb +30 -0
  17. data/lib/hecks/bench/report.rb +76 -0
  18. data/lib/hecks/bench/ruby_runner.rb +93 -0
  19. data/lib/hecks/bench/run.rb +55 -0
  20. data/lib/hecks/bench/rust_runner.rb +101 -0
  21. data/lib/hecks/bench/stats.rb +46 -0
  22. data/lib/hecks/bench/suite.rb +98 -0
  23. data/lib/hecks/bench/workload.rb +89 -0
  24. data/lib/hecks/bench.rb +17 -0
  25. data/lib/hecks/bluebook/assembly/contracts.rb +2 -0
  26. data/lib/hecks/bluebook/attribute.rb +1 -1
  27. data/lib/hecks/bluebook/behaviour/aggregate.rb +30 -1
  28. data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -0
  29. data/lib/hecks/bluebook/chapter.rb +6 -2
  30. data/lib/hecks/bluebook/domain_port.rb +36 -5
  31. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +10 -0
  32. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +8 -0
  33. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -1
  34. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +31 -3
  35. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +122 -3
  36. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +1 -1
  37. data/lib/hecks/bluebook/dsl/query_builder.rb +27 -1
  38. data/lib/hecks/bluebook/dsl/rule_reference.rb +1 -1
  39. data/lib/hecks/bluebook/dsl/world_builder.rb +29 -2
  40. data/lib/hecks/bluebook/expression/canonical_form.rb +1 -1
  41. data/lib/hecks/bluebook/expression/resolver.rb +3 -2
  42. data/lib/hecks/bluebook/hexagon.rb +13 -5
  43. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -0
  44. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +4 -1
  45. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
  46. data/lib/hecks/bluebook/meta_validator.rb +9 -4
  47. data/lib/hecks/bluebook/model_check.rb +23 -0
  48. data/lib/hecks/bluebook/pattern_subset.rb +38 -0
  49. data/lib/hecks/bluebook/query.rb +20 -5
  50. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  51. data/lib/hecks/canonical_json.rb +30 -0
  52. data/lib/hecks/chapters.rb +102 -0
  53. data/lib/hecks/cli/behaviors.rb +61 -0
  54. data/lib/hecks/cli/canonicalise.rb +25 -0
  55. data/lib/hecks/cli/check_engine_agreement.rb +32 -0
  56. data/lib/hecks/cli/check_era.rb +88 -0
  57. data/lib/hecks/cli/console.rb +66 -0
  58. data/lib/hecks/cli/deploy_template_diff.rb +40 -0
  59. data/lib/hecks/cli/doc_coverage.rb +32 -0
  60. data/lib/hecks/cli/document.rb +2 -2
  61. data/lib/hecks/cli/expression_projection.rb +41 -0
  62. data/lib/hecks/cli/follow.rb +88 -0
  63. data/lib/hecks/cli/history.rb +44 -0
  64. data/lib/hecks/cli/ir.rb +1 -1
  65. data/lib/hecks/cli/mcp.rb +3 -3
  66. data/lib/hecks/cli/model_check.rb +15 -8
  67. data/lib/hecks/cli/pattern_cases.rb +47 -0
  68. data/lib/hecks/cli/present.rb +39 -0
  69. data/lib/hecks/cli/project_cli.rb +224 -32
  70. data/lib/hecks/cli/project_diagrams.rb +4 -3
  71. data/lib/hecks/cli/project_glossary.rb +44 -0
  72. data/lib/hecks/cli/reference.rb +46 -0
  73. data/lib/hecks/cli/refresh_projections.rb +44 -0
  74. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
  75. data/lib/hecks/cli/release.rb +70 -0
  76. data/lib/hecks/cli/release_gem.rb +59 -0
  77. data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
  78. data/lib/hecks/cli/rspec_shard_files.rb +66 -0
  79. data/lib/hecks/cli/run.rb +3 -3
  80. data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
  81. data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
  82. data/lib/hecks/cli/shape.rb +70 -0
  83. data/lib/hecks/cli/smoke_http.rb +230 -0
  84. data/lib/hecks/cli/smoke_test.rb +5 -3
  85. data/lib/hecks/cli/spec_example.rb +27 -0
  86. data/lib/hecks/cli/statements.rb +32 -0
  87. data/lib/hecks/cli/stores.rb +4 -5
  88. data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
  89. data/lib/hecks/cli.rb +201 -52
  90. data/lib/hecks/codemod.rb +324 -0
  91. data/lib/hecks/corpus.rb +423 -0
  92. data/lib/hecks/deploy/bluebook/deploy.bluebook +591 -23
  93. data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -4
  94. data/lib/hecks/deploy/oidc.json +82 -2
  95. data/lib/hecks/doc/reference.rb +351 -0
  96. data/lib/hecks/doors/cli_door.rb +196 -0
  97. data/lib/hecks/doors/cli_runner.rb +297 -0
  98. data/lib/hecks/{facade → doors}/command_request.rb +1 -1
  99. data/lib/hecks/{facade → doors}/handle.rb +2 -2
  100. data/lib/hecks/{facade → doors}/json_door.rb +4 -4
  101. data/lib/hecks/doors/launcher_options.rb +129 -0
  102. data/lib/hecks/{cli → doors}/mcp_door.rb +7 -7
  103. data/lib/hecks/doors/mcp_door_scope.rb +97 -0
  104. data/lib/hecks/{facade/surface → doors/ruby_door}/aggregate_door.rb +8 -3
  105. data/lib/hecks/{facade/surface → doors/ruby_door}/chapter.rb +3 -3
  106. data/lib/hecks/{facade/surface.rb → doors/ruby_door.rb} +36 -5
  107. data/lib/hecks/doors.rb +61 -0
  108. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +1 -1
  109. data/lib/hecks/embryonaut_bluebook.rb +1 -1
  110. data/lib/hecks/engine_agreement.rb +156 -0
  111. data/lib/hecks/facade.rb +24 -37
  112. data/lib/hecks/forms/app.rb +6 -6
  113. data/lib/hecks/forms/banking_presentation.rb +50 -0
  114. data/lib/hecks/forms/field_shape.rb +2 -1
  115. data/lib/hecks/forms/port_argument.rb +1 -1
  116. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
  117. data/lib/hecks/fuzzing/combination_miner.rb +86 -0
  118. data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
  119. data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
  120. data/lib/hecks/fuzzing/differential.rb +156 -0
  121. data/lib/hecks/fuzzing/domain_generator.rb +615 -0
  122. data/lib/hecks/fuzzing/era_boundary.rb +71 -0
  123. data/lib/hecks/fuzzing/form_census.rb +136 -0
  124. data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
  125. data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
  126. data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
  127. data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
  128. data/lib/hecks/fuzzing/outside_world.rb +45 -0
  129. data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
  130. data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
  131. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
  132. data/lib/hecks/fuzzing/properties/guards.rb +190 -0
  133. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
  134. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
  135. data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
  136. data/lib/hecks/fuzzing/properties/querying.rb +175 -0
  137. data/lib/hecks/fuzzing/properties.rb +162 -0
  138. data/lib/hecks/fuzzing/qa_settings.rb +123 -0
  139. data/lib/hecks/fuzzing/replay.rb +505 -0
  140. data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
  141. data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
  142. data/lib/hecks/fuzzing/self_consistency.rb +339 -0
  143. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
  144. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
  145. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
  146. data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
  147. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
  148. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
  149. data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
  150. data/lib/hecks/fuzzing/shrinker.rb +166 -0
  151. data/lib/hecks/fuzzing/structural_skips.rb +33 -0
  152. data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
  153. data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
  154. data/lib/hecks/fuzzing/value_generator.rb +216 -0
  155. data/lib/hecks/fuzzing.rb +23 -0
  156. data/lib/hecks/grammar/evolve.rb +336 -0
  157. data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
  158. data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
  159. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
  160. data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
  161. data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
  162. data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
  163. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
  164. data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
  165. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
  166. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
  167. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
  168. data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
  169. data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
  170. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
  171. data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
  172. data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
  173. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
  174. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
  175. data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
  176. data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
  177. data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
  178. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
  179. data/lib/hecks/hecks/adapters/git.adapter +7 -0
  180. data/lib/hecks/hecks/adapters/git.rb +102 -0
  181. data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
  182. data/lib/hecks/hecks/adapters/host_http.rb +65 -0
  183. data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
  184. data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
  185. data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
  186. data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
  187. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
  188. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
  189. data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
  190. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
  191. data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
  192. data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
  193. data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
  194. data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
  195. data/lib/hecks/hecks/adapters/local_files.rb +46 -0
  196. data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
  197. data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
  198. data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
  199. data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
  200. data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
  201. data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
  202. data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
  203. data/lib/hecks/hecks/adapters/shell.adapter +7 -0
  204. data/lib/hecks/hecks/adapters/shell.rb +49 -0
  205. data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
  206. data/lib/hecks/hecks/adapters/terminal.rb +74 -0
  207. data/lib/hecks/hecks/codebase.behaviors +172 -0
  208. data/lib/hecks/hecks/codebase.bluebook +2453 -0
  209. data/lib/hecks/hecks/context_map.hecksagon +13 -0
  210. data/lib/hecks/hecks/custodian.behaviors +179 -0
  211. data/lib/hecks/hecks/custodian.bluebook +1847 -0
  212. data/lib/hecks/hecks/deploy.behaviors +74 -0
  213. data/lib/hecks/hecks/deploy.world +12 -0
  214. data/lib/hecks/hecks/environments/memory.world +10 -0
  215. data/lib/hecks/hecks/governance.world +7 -0
  216. data/lib/hecks/hecks/hecks.behaviors +62 -0
  217. data/lib/hecks/hecks/hecks.bluebook +124 -0
  218. data/lib/hecks/hecks/hecks.hecksagon +390 -0
  219. data/lib/hecks/hecks/hecks.world +42 -0
  220. data/lib/hecks/kernel_capabilities.rb +194 -0
  221. data/lib/hecks/language/bluebook/bluebook.bluebook +11 -0
  222. data/lib/hecks/language/bluebook/query.bluebook +7 -0
  223. data/lib/hecks/language/bluebook/vocabulary.bluebook +4 -4
  224. data/lib/hecks/language/hecksagon/domain_port.bluebook +10 -0
  225. data/lib/hecks/language/hecksagon/hecksagon.bluebook +20 -0
  226. data/lib/hecks/mcp_stdio_guard.rb +2 -2
  227. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
  228. data/lib/hecks/persistence_legacy_fixture.rb +113 -0
  229. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +8 -5
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +4 -1
  231. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +15 -2
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +1 -1
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +1 -1
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +32 -15
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +15 -5
  237. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
  238. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +1 -1
  239. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +43 -24
  240. data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
  241. data/lib/hecks/ports/persistence/state_codec.rb +24 -5
  242. data/lib/hecks/projection_files.rb +285 -0
  243. data/lib/hecks/projections/bootstrap_table.rb +1 -1
  244. data/lib/hecks/projections/deploy/fargate.rb +5 -5
  245. data/lib/hecks/projections/deploy/lambda.rb +43 -32
  246. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  247. data/lib/hecks/projections/deploy/preview/template.rb +1 -1
  248. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
  249. data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
  250. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
  251. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
  252. data/lib/hecks/projections/deploy/shared.rb +1 -1
  253. data/lib/hecks/projections/diagrams.rb +3 -3
  254. data/lib/hecks/projections/model/deviations.rb +1 -1
  255. data/lib/hecks/projections/parser_table.rb +4 -4
  256. data/lib/hecks/projections/reference.rb +2 -3
  257. data/lib/hecks/projections/rust_vocabulary.rb +1 -1
  258. data/lib/hecks/projector/cli_projector.rb +106 -37
  259. data/lib/hecks/projector.rb +1 -1
  260. data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
  261. data/lib/hecks/quality_control/adapters/agent.rb +58 -0
  262. data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
  263. data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
  264. data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
  265. data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
  266. data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
  267. data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
  268. data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
  269. data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
  270. data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
  271. data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
  272. data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
  273. data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
  274. data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
  275. data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
  276. data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
  277. data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
  278. data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
  279. data/lib/hecks/quality_control/cli/child.rb +45 -0
  280. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
  281. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
  282. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
  283. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
  284. data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
  285. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
  286. data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
  287. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
  288. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
  289. data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
  290. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
  291. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
  292. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
  293. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
  294. data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
  295. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
  296. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
  297. data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
  298. data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
  299. data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
  300. data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
  301. data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
  302. data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
  303. data/lib/hecks/query_ir.rb +346 -0
  304. data/lib/hecks/query_ir_mcp.rb +198 -0
  305. data/lib/hecks/release/runner/ci_publisher.rb +2 -2
  306. data/lib/hecks/release/runner/clean_tree.rb +42 -0
  307. data/lib/hecks/release/runner/console.rb +6 -0
  308. data/lib/hecks/release/runner/gem_publisher.rb +14 -7
  309. data/lib/hecks/release/runner/npm_publisher.rb +6 -36
  310. data/lib/hecks/release/runner/preflight.rb +26 -2
  311. data/lib/hecks/release/runner/published.rb +8 -26
  312. data/lib/hecks/release/runner/tagger.rb +30 -15
  313. data/lib/hecks/release/runner.rb +57 -6
  314. data/lib/hecks/router/namespace_installer.rb +3 -3
  315. data/lib/hecks/runtime/adapter_lookup.rb +124 -0
  316. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +4 -0
  317. data/lib/hecks/runtime/dependency_planning.rb +20 -2
  318. data/lib/hecks/runtime/dispatcher.rb +53 -3
  319. data/lib/hecks/runtime/interpreting.rb +3 -0
  320. data/lib/hecks/runtime/loader.rb +65 -12
  321. data/lib/hecks/runtime/outbox.rb +1 -1
  322. data/lib/hecks/runtime/policy_interpreter.rb +5 -2
  323. data/lib/hecks/runtime/port_operation_interpreter.rb +2 -10
  324. data/lib/hecks/runtime/query_interpreter.rb +77 -0
  325. data/lib/hecks/runtime/reaction_outcome.rb +89 -0
  326. data/lib/hecks/runtime/refusal_wording.rb +23 -1
  327. data/lib/hecks/runtime/registry/verification.rb +163 -4
  328. data/lib/hecks/runtime/registry.rb +57 -3
  329. data/lib/hecks/runtime/remote_dispatcher.rb +31 -2
  330. data/lib/hecks/runtime/value/coercion.rb +23 -135
  331. data/lib/hecks/runtime/value/field_checks.rb +172 -0
  332. data/lib/hecks/runtime/value.rb +4 -1
  333. data/lib/hecks/runtime.rb +20 -6
  334. data/lib/hecks/rust_build/conformance.rb +158 -0
  335. data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
  336. data/lib/hecks/rust_build/coverage.rb +218 -0
  337. data/lib/hecks/rust_build/native_build.rb +124 -0
  338. data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
  339. data/lib/hecks/rust_build/project_rust.rb +332 -0
  340. data/lib/hecks/rust_build/wasm.rb +105 -0
  341. data/lib/hecks/rust_build/wasm_browser.rb +113 -0
  342. data/lib/hecks/rust_build.rb +144 -0
  343. data/lib/hecks/storehouse.rb +16 -16
  344. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +2 -2
  345. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +1 -1
  346. data/lib/hecks/three_zero/forms.yml +127 -0
  347. data/lib/hecks/three_zero.rb +12 -0
  348. data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
  349. data/lib/hecks/tools/comment_style.rb +1070 -0
  350. data/lib/hecks/tools/corpus_report.rb +87 -0
  351. data/lib/hecks/tools/deploy_recipe.rb +255 -0
  352. data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
  353. data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
  354. data/lib/hecks/tools/era_projection_backfill.rb +94 -0
  355. data/lib/hecks/tools/era_reattest.rb +146 -0
  356. data/lib/hecks/tools/evolve_run.rb +189 -0
  357. data/lib/hecks/tools/fuzz_sweep.rb +373 -0
  358. data/lib/hecks/tools/heki_compaction.rb +100 -0
  359. data/lib/hecks/tools/hoist_local_givens.rb +246 -0
  360. data/lib/hecks/tools/journal_compaction.rb +89 -0
  361. data/lib/hecks/tools/oidc_manifests.rb +70 -0
  362. data/lib/hecks/tools/query_ir_run.rb +81 -0
  363. data/lib/hecks/tools/regeneration_run.rb +166 -0
  364. data/lib/hecks/tools/rust_comment_style.rb +642 -0
  365. data/lib/hecks/tools/sequence_script.rb +62 -0
  366. data/lib/hecks/tools/tail_merge.rb +117 -0
  367. data/lib/hecks/tools/tenant_provisioning.rb +142 -0
  368. data/lib/hecks/tools/translation_audit.rb +208 -0
  369. data/lib/hecks/tools/translation_scaffold.rb +131 -0
  370. data/lib/hecks/tools.rb +86 -0
  371. data/lib/hecks/version.rb +1 -1
  372. data/lib/hecks.rb +32 -15
  373. data/qa/settings.yml +66 -0
  374. data/rust/.cargo/config.toml +6 -0
  375. data/rust/.gitignore +2 -0
  376. data/rust/Cargo.lock +54 -0
  377. data/rust/Cargo.toml +61 -0
  378. data/rust/build/.cargo/config.toml +3 -0
  379. data/rust/build/.gitignore +1 -0
  380. data/rust/build/Cargo.lock +7 -0
  381. data/rust/build/Cargo.toml +19 -0
  382. data/rust/build/src/build_artifact.rs +47 -0
  383. data/rust/build/src/cargo_sync.rs +248 -0
  384. data/rust/build/src/json.rs +421 -0
  385. data/rust/build/src/lineage_pass.rs +285 -0
  386. data/rust/build/src/main.rs +51 -0
  387. data/rust/build/src/optional_pass.rs +150 -0
  388. data/rust/build/src/pipeline.rs +264 -0
  389. data/rust/build/src/reserved_names.rs +76 -0
  390. data/rust/build/src/resolve.rs +124 -0
  391. data/rust/build/src/root.rs +25 -0
  392. data/rust/build/src/sidecars.rs +73 -0
  393. data/rust/build/src/subprocess.rs +51 -0
  394. data/rust/build/src/tmp.rs +31 -0
  395. data/rust/codegen/.cargo/config.toml +3 -0
  396. data/rust/codegen/.gitignore +1 -0
  397. data/rust/codegen/Cargo.lock +7 -0
  398. data/rust/codegen/Cargo.toml +20 -0
  399. data/rust/codegen/src/attr.rs +32 -0
  400. data/rust/codegen/src/bridging.rs +481 -0
  401. data/rust/codegen/src/commands.rs +1226 -0
  402. data/rust/codegen/src/constraints.rs +109 -0
  403. data/rust/codegen/src/dependency_planning.rs +209 -0
  404. data/rust/codegen/src/domain_generator.rs +1288 -0
  405. data/rust/codegen/src/exemplar.rs +230 -0
  406. data/rust/codegen/src/expr_emitter.rs +87 -0
  407. data/rust/codegen/src/fielded.rs +192 -0
  408. data/rust/codegen/src/hecks_naming.rs +33 -0
  409. data/rust/codegen/src/json.rs +335 -0
  410. data/rust/codegen/src/json_codec.rs +697 -0
  411. data/rust/codegen/src/literal.rs +207 -0
  412. data/rust/codegen/src/main.rs +290 -0
  413. data/rust/codegen/src/manifest.rs +227 -0
  414. data/rust/codegen/src/mutations.rs +784 -0
  415. data/rust/codegen/src/naming.rs +286 -0
  416. data/rust/codegen/src/ports.rs +208 -0
  417. data/rust/codegen/src/prelude.rs +116 -0
  418. data/rust/codegen/src/queries.rs +786 -0
  419. data/rust/codegen/src/reactions.rs +532 -0
  420. data/rust/codegen/src/read_models.rs +697 -0
  421. data/rust/codegen/src/reference_specs.rs +40 -0
  422. data/rust/codegen/src/registry.rs +966 -0
  423. data/rust/codegen/src/reserved_names.rs +76 -0
  424. data/rust/codegen/src/shared.rs +93 -0
  425. data/rust/codegen/src/skip_reason.rs +25 -0
  426. data/rust/codegen/src/types.rs +362 -0
  427. data/rust/host/.cargo/config.toml +3 -0
  428. data/rust/host/.gitignore +1 -0
  429. data/rust/host/Cargo.lock +4297 -0
  430. data/rust/host/Cargo.toml +61 -0
  431. data/rust/host/HECKS_RELEASE +1 -0
  432. data/rust/host/rds-ca-bundle.pem +2736 -0
  433. data/rust/host/src/api.rs +1596 -0
  434. data/rust/host/src/approval.rs +652 -0
  435. data/rust/host/src/auth.rs +1287 -0
  436. data/rust/host/src/bin/expr_harness.rs +46 -0
  437. data/rust/host/src/bin/lineage_harness.rs +119 -0
  438. data/rust/host/src/bin/mint_harness.rs +135 -0
  439. data/rust/host/src/checkout.rs +265 -0
  440. data/rust/host/src/dispatch.rs +1443 -0
  441. data/rust/host/src/expr_json.rs +1117 -0
  442. data/rust/host/src/field_hints.rs +52 -0
  443. data/rust/host/src/ir.rs +733 -0
  444. data/rust/host/src/journal.rs +874 -0
  445. data/rust/host/src/lambda_client.rs +378 -0
  446. data/rust/host/src/log.rs +147 -0
  447. data/rust/host/src/main.rs +474 -0
  448. data/rust/host/src/mint.rs +1865 -0
  449. data/rust/host/src/payments/keystore.rs +330 -0
  450. data/rust/host/src/payments/tests.rs +1400 -0
  451. data/rust/host/src/payments.rs +767 -0
  452. data/rust/host/src/presentation.rs +698 -0
  453. data/rust/host/src/presentation_write.rs +1431 -0
  454. data/rust/host/src/query_step.rs +149 -0
  455. data/rust/host/src/rate_limit.rs +904 -0
  456. data/rust/host/src/reference_transform.rs +241 -0
  457. data/rust/host/src/reference_validate.rs +338 -0
  458. data/rust/host/src/resend.rs +296 -0
  459. data/rust/host/src/secrets.rs +106 -0
  460. data/rust/host/src/server.rs +663 -0
  461. data/rust/host/src/storage_shape.rs +175 -0
  462. data/rust/host/src/test_pg.rs +40 -0
  463. data/rust/host/src/ui_schema.rs +1351 -0
  464. data/rust/host/src/wasm_runner.rs +191 -0
  465. data/rust/host/src/web/newsletter/cooldown.rs +126 -0
  466. data/rust/host/src/web/newsletter/tests.rs +104 -0
  467. data/rust/host/src/web/newsletter.rs +310 -0
  468. data/rust/host/src/web/newsletter_send.rs +357 -0
  469. data/rust/host/src/web/registrations.rs +1505 -0
  470. data/rust/host/src/web.rs +2937 -0
  471. data/rust/lsp/.gitignore +1 -0
  472. data/rust/lsp/Cargo.lock +7 -0
  473. data/rust/lsp/Cargo.toml +16 -0
  474. data/rust/lsp/README.md +133 -0
  475. data/rust/lsp/src/diagnostics.rs +165 -0
  476. data/rust/lsp/src/json.rs +385 -0
  477. data/rust/lsp/src/main.rs +399 -0
  478. data/rust/lsp/src/outline.rs +177 -0
  479. data/rust/lsp/src/rpc.rs +67 -0
  480. data/rust/parser/.cargo/config.toml +5 -0
  481. data/rust/parser/.gitignore +1 -0
  482. data/rust/parser/Cargo.lock +7 -0
  483. data/rust/parser/Cargo.toml +21 -0
  484. data/rust/parser/src/build/closed_sets.rs +27 -0
  485. data/rust/parser/src/build/identity.rs +276 -0
  486. data/rust/parser/src/build/mod.rs +12 -0
  487. data/rust/parser/src/build/naming.rs +141 -0
  488. data/rust/parser/src/build/pattern_subset.rs +161 -0
  489. data/rust/parser/src/build/query_derive.rs +72 -0
  490. data/rust/parser/src/build/query_inference.rs +269 -0
  491. data/rust/parser/src/build/query_options.rs +100 -0
  492. data/rust/parser/src/build/read_model.rs +94 -0
  493. data/rust/parser/src/build/references.rs +32 -0
  494. data/rust/parser/src/canonical.rs +114 -0
  495. data/rust/parser/src/diag.rs +55 -0
  496. data/rust/parser/src/emit.rs +953 -0
  497. data/rust/parser/src/expr/ast_json.rs +147 -0
  498. data/rust/parser/src/expr/evaluator.rs +81 -0
  499. data/rust/parser/src/expr/mod.rs +84 -0
  500. data/rust/parser/src/expr/resolver.rs +497 -0
  501. data/rust/parser/src/ir.rs +387 -0
  502. data/rust/parser/src/keywords.rs +585 -0
  503. data/rust/parser/src/lex.rs +662 -0
  504. data/rust/parser/src/main.rs +252 -0
  505. data/rust/parser/src/parse/aggregate.rs +612 -0
  506. data/rust/parser/src/parse/chapter.rs +517 -0
  507. data/rust/parser/src/parse/command.rs +637 -0
  508. data/rust/parser/src/parse/domain_port.rs +127 -0
  509. data/rust/parser/src/parse/entity.rs +527 -0
  510. data/rust/parser/src/parse/file.rs +67 -0
  511. data/rust/parser/src/parse/hecksagon.rs +231 -0
  512. data/rust/parser/src/parse/lifecycle.rs +162 -0
  513. data/rust/parser/src/parse/mod.rs +1262 -0
  514. data/rust/parser/src/parse/policy.rs +87 -0
  515. data/rust/parser/src/parse/process_manager.rs +305 -0
  516. data/rust/parser/src/parse/query.rs +100 -0
  517. data/rust/parser/src/parse/read_model.rs +214 -0
  518. data/rust/parser/src/parse/value_object.rs +250 -0
  519. data/rust/parser/src/ruby_value.rs +422 -0
  520. data/rust/project/bridging.rb +257 -0
  521. data/rust/project/commands.rb +877 -0
  522. data/rust/project/constraints.rb +86 -0
  523. data/rust/project/dependency_planning.rb +178 -0
  524. data/rust/project/domain_generator.rb +980 -0
  525. data/rust/project/exemplar.rb +148 -0
  526. data/rust/project/expr_emitter.rb +72 -0
  527. data/rust/project/fielded.rb +141 -0
  528. data/rust/project/json_codec.rb +641 -0
  529. data/rust/project/mutations.rb +721 -0
  530. data/rust/project/naming.rb +205 -0
  531. data/rust/project/ports.rb +65 -0
  532. data/rust/project/queries.rb +493 -0
  533. data/rust/project/reactions.rb +317 -0
  534. data/rust/project/read_models.rb +690 -0
  535. data/rust/project/reference_specs.rb +27 -0
  536. data/rust/project/registry.rb +396 -0
  537. data/rust/project/skip_reason.rb +29 -0
  538. data/rust/project/types.rb +225 -0
  539. data/rust/project/write_if_changed.rb +83 -0
  540. data/rust/project.rb +30 -0
  541. data/rust/project_rust_pipeline.rb +383 -0
  542. data/rust/src/exemplar/commands.rs +279 -0
  543. data/rust/src/exemplar/constraints.rs +18 -0
  544. data/rust/src/exemplar/fielded.rs +280 -0
  545. data/rust/src/exemplar/json.rs +277 -0
  546. data/rust/src/exemplar/mod.rs +17 -0
  547. data/rust/src/exemplar/mutations.rs +80 -0
  548. data/rust/src/exemplar/queries.rs +33 -0
  549. data/rust/src/exemplar/reactions.rs +88 -0
  550. data/rust/src/exemplar/read_models.rs +48 -0
  551. data/rust/src/exemplar/registry.rs +166 -0
  552. data/rust/src/exemplar/types.rs +44 -0
  553. data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
  554. data/rust/src/kernel/attribute_shapes/list.rs +18 -0
  555. data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
  556. data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
  557. data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
  558. data/rust/src/kernel/cli.rs +585 -0
  559. data/rust/src/kernel/dispatch.rs +1090 -0
  560. data/rust/src/kernel/expr.rs +458 -0
  561. data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
  562. data/rust/src/kernel/expression_operators/comparison.rs +48 -0
  563. data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
  564. data/rust/src/kernel/expression_operators/logical.rs +13 -0
  565. data/rust/src/kernel/expression_operators/membership.rs +18 -0
  566. data/rust/src/kernel/expression_operators/mod.rs +68 -0
  567. data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
  568. data/rust/src/kernel/expression_operators/positional.rs +126 -0
  569. data/rust/src/kernel/expression_operators/presence.rs +101 -0
  570. data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
  571. data/rust/src/kernel/expression_operators/sized.rs +35 -0
  572. data/rust/src/kernel/expression_operators/text.rs +79 -0
  573. data/rust/src/kernel/expression_operators/to_string.rs +18 -0
  574. data/rust/src/kernel/json.rs +779 -0
  575. data/rust/src/kernel/mod.rs +133 -0
  576. data/rust/src/kernel/named_query.rs +219 -0
  577. data/rust/src/kernel/orchestrate.rs +1810 -0
  578. data/rust/src/kernel/pattern.rs +307 -0
  579. data/rust/src/kernel/query_comparators.rs +206 -0
  580. data/rust/src/kernel/query_ordering.rs +103 -0
  581. data/rust/src/kernel/read_model.rs +914 -0
  582. data/rust/src/kernel/reference_lookup.rs +149 -0
  583. data/rust/src/kernel/repository.rs +468 -0
  584. data/rust/src/kernel/routing.rs +612 -0
  585. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
  586. data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
  587. data/rust/src/kernel/vocab/field_hint.rs +70 -0
  588. data/rust/src/kernel/vocab/mod.rs +20 -0
  589. data/rust/src/kernel/vocab/query_comparator.rs +65 -0
  590. data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
  591. data/rust/src/lib.rs +8 -0
  592. data/rust/src/main.rs +16 -0
  593. data/rust/web/.gitignore +1 -0
  594. data/rust/web/Cargo.lock +119 -0
  595. data/rust/web/Cargo.toml +18 -0
  596. data/rust/web/src/lib.rs +8 -0
  597. metadata +477 -12
  598. data/lib/hecks/facade/cli_door.rb +0 -96
  599. data/lib/hecks/facade/cli_runner.rb +0 -127
  600. data/lib/hecks/mcp_door_scope.rb +0 -95
@@ -0,0 +1,175 @@
1
+ module Hecks
2
+ module Fuzzing
3
+ module Properties
4
+ # Properties that check a query's answer against an independent recomputation.
5
+ module Querying
6
+ # Differential oracle: each replayed ask is answered natively and by the reference
7
+ # interpreter, and the two must agree.
8
+ #
9
+ # Refusal is read by key presence (`:error` / `:reference_error`), not truthiness;
10
+ # one side refusing while the other answers is a divergence. Read-model asks
11
+ # (no "::" in the verb) have no reference twin and are skipped.
12
+ #
13
+ # @param history [Hash] a replayed history as returned by `Replay.call`
14
+ # @return [true, String] true, or a message naming each disagreeing query
15
+ def query_answers_match_reference(history)
16
+ offenders = history.fetch(:queries).filter_map do |asked|
17
+ next unless asked[:query].is_a?(String) && asked[:query].include?("::")
18
+
19
+ native_refused = asked.key?(:error)
20
+ reference_refused = asked.key?(:reference_error)
21
+
22
+ if native_refused != reference_refused
23
+ next "#{asked[:query]} #{asked[:args].inspect} — native " \
24
+ "#{native_refused ? "refused (#{asked[:error]})" : 'answered'}, " \
25
+ "but the reference interpreter #{reference_refused ? "refused (#{asked[:reference_error]})" : 'answered'} — " \
26
+ "a refusal-shaped divergence, not just a differing row set"
27
+ end
28
+
29
+ next if native_refused
30
+ next if asked[:rows] == asked[:reference_rows]
31
+
32
+ "#{asked[:query]} #{asked[:args].inspect} answered #{asked[:rows].inspect} " \
33
+ "natively but #{asked[:reference_rows].inspect} through the reference interpreter"
34
+ end
35
+
36
+ offenders.empty? || offenders.join("; ")
37
+ end
38
+
39
+ # Recomputes order/offset/limit from `history[:instances]` and compares it with the
40
+ # real answer of every paged query (`order_by` with `offset` or `limit`).
41
+ #
42
+ # Ordering reuses `Ports::Query::Ordering.apply` so "in order" cannot drift from the
43
+ # interpreter; only the offset-then-limit slice is restated. Comparing the interpreter's
44
+ # native and reference paths instead could share one bug.
45
+ #
46
+ # Kept as one method: the steps run once, in order, and share every intermediate.
47
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
48
+ #
49
+ # @param history [Hash] a replayed history as returned by `Replay.call`
50
+ # @return [true, String] true, or a message naming each mismatching query
51
+ def paging_offset_partitions_correctly(history)
52
+ bluebooks = history.fetch(:bluebooks)
53
+
54
+ offenders = history.fetch(:queries).filter_map do |asked|
55
+ next if asked[:error] || !asked[:query].is_a?(String) || !asked[:query].include?("::")
56
+
57
+ declared = query_for_verb(bluebooks, asked[:query])
58
+ next unless declared&.order_by && (declared.offset || declared.limit)
59
+
60
+ domain, aggregate_name, = Naming.split_verb(asked[:query])
61
+ aggregate = bluebooks[domain]&.aggregate(aggregate_name)
62
+ args = normalize_query_args(aggregate, declared, asked[:args] || {})
63
+ rows = query_eligible_rows(asked.fetch(:instances_at), domain, aggregate_name, declared.wheres, args,
64
+ bluebooks: bluebooks)
65
+ ordered = Ports::Query::Ordering.apply(
66
+ rows, declared.order_by, declared.null_semantics, identity: ->(row) { row[:id].to_s }
67
+ ) { |row| Ports::Query::InMemory.comparable(QuerySpecification::FieldPath.dig(row, declared.order_by.field)) }
68
+
69
+ skipped = declared.offset ? ordered.drop(resolve_paging_value(declared.offset.value, args).to_i) : ordered
70
+ expected = declared.limit ? skipped.first(resolve_paging_value(declared.limit.value, args).to_i) : skipped
71
+ actual = asked[:rows]
72
+ next if actual == expected
73
+
74
+ "#{asked[:query]} #{args.inspect} answered #{actual.inspect}, but independently recomputing " \
75
+ "order/offset/limit from #{rows.length} eligible row(s) gives #{expected.inspect}"
76
+ end
77
+
78
+ offenders.empty? || offenders.join("; ")
79
+ end
80
+
81
+ # Real dispatch normalizes a query's own declared arguments
82
+ # (`QueryInterpreter#normalize_args`) before ever evaluating a where clause,
83
+ # filling a composite argument's own declared defaults along the way
84
+ # (`Value.for_attribute` -> `Value.build`). The language's own ambiguous-
85
+ # comparison guard (`refuse_ambiguous_comparison!`) rules out comparing most
86
+ # multi-field value objects whole, but a single-attribute one (still eligible,
87
+ # since it is never ambiguous) can still arrive with that one field omitted —
88
+ # recomputing eligibility against the raw fuzzed args instead of this normalized
89
+ # copy would then disagree with real dispatch — the same false-divergence shape
90
+ # `build_guard_check` guards against for the guard check. `Runtime::QueryInterpreter.
91
+ # new(nil)` is safe: `normalize_args` never reads the registry it would otherwise need.
92
+ def normalize_query_args(aggregate, declared, args)
93
+ return args unless aggregate
94
+
95
+ Runtime::QueryInterpreter.new(nil).send(:normalize_args, aggregate, declared, args)
96
+ rescue StandardError
97
+ args
98
+ end
99
+
100
+ # Resolves the declared Query for a replayed verb.
101
+ #
102
+ # Entity-level queries (dotted query path) return nil: they are not one aggregate's
103
+ # own rows, which #query_eligible_rows assumes.
104
+ #
105
+ # @param bluebooks [Hash{String => Bluebook::Chapter}] every loaded domain
106
+ # @param verb [String] `"Domain::Aggregate.Query"`
107
+ # @return [Bluebook::Query, nil]
108
+ def query_for_verb(bluebooks, verb)
109
+ domain, aggregate_name, query_path = Naming.split_verb(verb)
110
+ return nil unless query_path && !query_path.include?(".")
111
+
112
+ bluebook = bluebooks[domain]
113
+ aggregate = bluebook&.aggregate(aggregate_name)
114
+ aggregate&.query(query_path)
115
+ end
116
+
117
+ # Recomputes which of an aggregate's own stored rows a query's `wheres` admit.
118
+ #
119
+ # Rows get `id:` merged in because Ordering.apply's `identity:` and the real answer
120
+ # both use it. `bluebooks:` is needed only to resolve a `/` hop clause, whose head
121
+ # names a declared reference; without it the clause would be dug as a local path.
122
+ #
123
+ # @param instances [Hash{String => Hash}] `history[:instances]` or an `:instances_at`
124
+ # snapshot
125
+ # @param wheres [Array<QuerySpecification::Common::WhereClause>] clauses every row
126
+ # must satisfy
127
+ # @param args [Hash] the query's call args, for clauses whose value is a Symbol
128
+ # @return [Array<Hash>] each admitted row's state, merged with its `id:`
129
+ def query_eligible_rows(instances, domain, aggregate_name, wheres, args, bluebooks: {})
130
+ aggregate = bluebooks[domain]&.aggregate(aggregate_name)
131
+ prefix = "#{domain}::#{aggregate_name}#"
132
+ instances.filter_map do |key, state|
133
+ next unless key.start_with?(prefix)
134
+
135
+ row = state.merge(id: key.split("#").last)
136
+ next unless wheres.all? do |clause|
137
+ resolved = resolve_hop_clause(instances, domain, aggregate, clause, args, bluebooks)
138
+ held = Ports::Query::InMemory.comparable(QuerySpecification::FieldPath.dig(row, resolved.field))
139
+ Ports::Query::InMemory.holds?(resolved, held, args)
140
+ end
141
+
142
+ row
143
+ end
144
+ end
145
+
146
+ # Resolves one hop of a `/`-chained clause into a local `in` clause over the target's ids.
147
+ #
148
+ # Restates `Runtime::ReferenceHop#fold` over the replay snapshot, so the property never
149
+ # checks the runtime against its own code path. A clause with no resolvable hop head
150
+ # is returned unchanged.
151
+ #
152
+ # @param aggregate [Bluebook::Aggregate, nil] nil skips hop resolution
153
+ # @return [QuerySpecification::Common::WhereClause] `clause`, or the `in` clause
154
+ def resolve_hop_clause(instances, domain, aggregate, clause, args, bluebooks)
155
+ return clause unless aggregate && QuerySpecification::HopPath.hop_head?(clause.field, aggregate.attributes)
156
+
157
+ hop, rest = QuerySpecification::HopPath.next_hop(clause.field, aggregate.attributes)
158
+ target = hop.target
159
+ return clause unless target
160
+
161
+ inner = QuerySpecification::Common::WhereClause.new(field: rest, op: clause.op, value: clause.value)
162
+ ids = query_eligible_rows(instances, domain, target.hecks_name, [inner], args, bluebooks: bluebooks)
163
+ .map { |row| row[:id].to_s }.uniq
164
+
165
+ QuerySpecification::Common::WhereClause.new(field: hop.attribute.name, op: "in", value: ids)
166
+ end
167
+
168
+ # Resolves a declared limit/offset: a literal, or a Symbol naming a call arg.
169
+ def resolve_paging_value(value, args)
170
+ value.is_a?(Symbol) ? args[value] : value
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,162 @@
1
+ require "json"
2
+ require_relative "../bluebook/model_check"
3
+ require_relative "../bluebook/meta_validator"
4
+ require_relative "../ports/query/in_memory"
5
+ require_relative "../query_specification/field_path"
6
+ require_relative "../runtime/value"
7
+ require_relative "properties/lifecycle_and_replay"
8
+ require_relative "properties/querying"
9
+ require_relative "properties/guards"
10
+ require_relative "properties/dispatch_and_mutations"
11
+ require_relative "properties/invariants_and_aggregation"
12
+ require_relative "properties/corrections"
13
+ require_relative "properties/outbox"
14
+
15
+ module Hecks
16
+ module Fuzzing
17
+ # Declared properties, checked over a replayed history: each maps a name
18
+ # to `->(history) { true, or a message string naming what broke }`.
19
+ module Properties
20
+ # Grouped by responsibility across properties/*.rb; `extend`ed so each
21
+ # stays reachable as `Properties.foo(history)`, matching `module_function` below.
22
+ extend LifecycleAndReplay
23
+ extend Querying
24
+ extend Guards
25
+ extend DispatchAndMutations
26
+ extend DryRuns
27
+ extend InvariantsAndAggregation
28
+ extend Corrections
29
+ extend Outbox
30
+
31
+ module_function
32
+
33
+ # Which language feature each property is answerable for — exhaustive of
34
+ # what would go unchecked without it, not of everything its body touches.
35
+ FEATURE_COVERAGE = {
36
+ lifecycle_values_are_declared: %w[Aggregate#state_field Aggregate#state_start Aggregate#transitions
37
+ Entity#state_field Entity#state_start Entity#transitions],
38
+ saga_advances_follow_declared_handlers: %w[Handler#from_state Handler#to_state Handler#event_type],
39
+ query_answers_match_reference: %w[Query#wheres Query#order_field Query#order_way Query#limit],
40
+ paging_offset_partitions_correctly: %w[Query#options],
41
+ authorize_scopes_or_refuses: %w[Query#options],
42
+ guard_refusals_are_declared: %w[Command#givens Command#ensures],
43
+ lifecycle_guard_and_given_violations_are_refused: %w[Command#from Aggregate#preconditions Entity#preconditions],
44
+ # Dispatch#command_name/with_spec aren't claimable feature names — the
45
+ # coverage walk only reaches one level of entity nesting and Dispatch
46
+ # sits two deep — but this property still checks the real behavior
47
+ # (dispatch_args) those names would have covered.
48
+ dispatch_binding_fidelity: %w[Handler#dispatches Policy#with_spec],
49
+ mutations_match_recompute: %w[Command#mutations],
50
+ sagas_rehydrate_cleanly: %w[ProcessManager#states ProcessManager#correlates_by
51
+ ProcessManager#starts_on ProcessManager#ends_on],
52
+ fanout_dispatches_once_per_matching_row: %w[Policy#for_each Policy#where],
53
+ aggregation_matches_recompute: %w[ReadModel#count ReadModel#median_field ReadModel#sum_field
54
+ ReadModel#avg_field ReadModel#min_field ReadModel#max_field
55
+ ReadModel#percentile_field ReadModel#percentile_at
56
+ ReadModel#any_field ReadModel#all_field],
57
+ stored_records_satisfy_declared_invariants: %w[Aggregate#invariants Entity#invariants],
58
+ group_by_matches_recompute: %w[ReadModel#group_by],
59
+ # A runtime door, not a grammar construct — `Dispatcher#dry_run?` is not
60
+ # a word a bluebook declares. Listed empty rather than omitted, so every
61
+ # property still names what it is answerable for.
62
+ dry_runs_leave_no_trace: [],
63
+ # Another runtime door, not a grammar construct — same reasoning as
64
+ # dry_runs_leave_no_trace above.
65
+ outbox_rows_match_reactions: [],
66
+ # Reads command.mutations for :corrects ops — the same list
67
+ # mutations_match_recompute reads, for a different question.
68
+ corrections_reference_an_emitted_event: %w[Command#mutations],
69
+ # No feature string exists for what this reads: an argument's own
70
+ # `relationship` (Argument is a value object, outside the meta-domain
71
+ # walk). Its declaration side, `Query#options`, is already claimed by
72
+ # authorize_scopes_or_refuses.
73
+ commands_respect_tenant_scope: []
74
+ }.freeze
75
+
76
+ # Features a replay could never observe violated, because construction
77
+ # makes the violation impossible to produce — not untested, but
78
+ # unfalsifiable. Each entry names the runtime path that guarantees it;
79
+ # add one only after reading and confirming that path (mirrors the
80
+ # discipline spec/fuzzing/meta_domain_coverage_spec.rb's KNOWN_GAPS demands
81
+ # in the other direction).
82
+ GUARANTEED_BY_CONSTRUCTION = {
83
+ "Aggregate#attributes" => "every field's pattern/closed-set/type passes through Value.build's one coercion " \
84
+ "door (value/coercion.rb#check_patterns, value/admission.rb) before it can exist " \
85
+ "— a stored value that violated its own declared shape was never producible to " \
86
+ "begin with",
87
+ "Aggregate#value_objects" => "the shape being coerced above — same door, same guarantee",
88
+ # S17, ADR 0026 — saga_advances_follow_declared_handlers already walks
89
+ # this list to find event_type/from_state/to_state; same door, same guarantee.
90
+ "ProcessManager#handlers" => "saga_advances_follow_declared_handlers already walks this list to find " \
91
+ "event_type/from_state/to_state — same door, same guarantee",
92
+ "Aggregate#identified_by" => "CommandInterpreter's data-driven dispatch order refuses AlreadyExists " \
93
+ "(command_interpreter.rb, command.creates?) for every creating command uniformly, " \
94
+ "before a duplicate id can ever be stored — collision is refused at the door, not " \
95
+ "produced and later caught",
96
+ "Entity#identified_by" => "EntityElement.check_entity_collision (runtime/entity_element.rb, moved there " \
97
+ "BUG#145 so both call sites share it) checks Array(current) against every part " \
98
+ "of the entity's own identity before an append can land — MutationApplier#" \
99
+ "entity_element's aggregate-owned call (Workspace.boards, on both branches " \
100
+ "identity arrives by: caller-supplied, or composite) AND EntityElement#" \
101
+ "appended_to_element's entity-owned, nested-one-hop-further call (Board.cards — " \
102
+ "unconditional, no auto-mint branch exists at that depth) — the same " \
103
+ "AlreadyExists refusal Aggregate#identified_by gets above, one or two levels " \
104
+ "down. Auto-minted (aggregate-owned) entities never reach the check " \
105
+ "(current.size + 1 can't repeat unless something remove:s from the list between " \
106
+ "mints, which no real domain does today — see the comment on #entity_element " \
107
+ "itself)",
108
+ "Command#attributes" => "command arguments are coerced through the SAME Value.build door as any other " \
109
+ "attribute — an accepted dispatch's own args already passed pattern/admits/invariant checks",
110
+ "Command#emits" => "CommandRules::Emission#emit iterates command.emits ITSELF to construct every " \
111
+ "announced Event (command_rules/emission.rb) — there is no other path to emit, so " \
112
+ "a command can never announce a name its own declaration doesn't list",
113
+ "Query#attributes" => "query arguments are coerced through the same Value.build door — same guarantee as " \
114
+ "Command#attributes",
115
+ "Query#returns" => "every row a port answers is built as the returned value object by Value.build " \
116
+ "(QueryInterpreter#shaped) before it enters the domain — an answer of any other " \
117
+ "shape is refused, so none can be accepted",
118
+ "Entity#attributes" => "same coercion door, one level in — an entity's own attributes are Value-typed exactly " \
119
+ "the way an aggregate's are",
120
+ "ValueObject#attributes" => "the shape Value.build enforces IS this declaration — the guarantee and the " \
121
+ "feature are the same fact seen from two sides",
122
+ "ValueObject#invariants" => "run inside the SAME coercion call (coercion.rb, before construction returns) " \
123
+ "that pattern-checks a VO's fields — a VO whose invariant did not hold could not " \
124
+ "finish being built",
125
+ "ValueObject#rows" => "closed-set membership is checked in value/admission.rb, the second half of the same " \
126
+ "one construction door",
127
+ # S17, ADR 0026 — Member is a genuine entity, so this reads Member#pairs,
128
+ # not Member#shape. ValueObject#members is the same fact ValueObject#rows
129
+ # counts, seen from the other side.
130
+ "ValueObject#members" => "the members list IS what ValueObject#rows counts — same door, same guarantee",
131
+ "Member#pairs" => "one level into ValueObject#rows — same door"
132
+ }.freeze
133
+
134
+ # Runs the standard property battery over one replayed history — all but
135
+ # determinism, which replays twice itself and so is checked separately.
136
+ #
137
+ # @param history [Hash] a replayed history, as returned by `Fuzzing::Replay.call`
138
+ # @return [Hash{Symbol => true, String}] each property name mapped to `true`
139
+ # (passed) or a message string naming what broke
140
+ def check(history)
141
+ { lifecycle_values_are_declared: lifecycle_values_are_declared(history),
142
+ saga_advances_follow_declared_handlers: saga_advances_follow_declared_handlers(history),
143
+ query_answers_match_reference: query_answers_match_reference(history),
144
+ guard_refusals_are_declared: guard_refusals_are_declared(history),
145
+ sagas_rehydrate_cleanly: sagas_rehydrate_cleanly(history),
146
+ fanout_dispatches_once_per_matching_row: fanout_dispatches_once_per_matching_row(history),
147
+ aggregation_matches_recompute: aggregation_matches_recompute(history),
148
+ stored_records_satisfy_declared_invariants: stored_records_satisfy_declared_invariants(history),
149
+ group_by_matches_recompute: group_by_matches_recompute(history),
150
+ paging_offset_partitions_correctly: paging_offset_partitions_correctly(history),
151
+ lifecycle_guard_and_given_violations_are_refused: lifecycle_guard_and_given_violations_are_refused(history),
152
+ authorize_scopes_or_refuses: authorize_scopes_or_refuses(history),
153
+ commands_respect_tenant_scope: commands_respect_tenant_scope(history),
154
+ dispatch_binding_fidelity: dispatch_binding_fidelity(history),
155
+ mutations_match_recompute: mutations_match_recompute(history),
156
+ dry_runs_leave_no_trace: dry_runs_leave_no_trace(history),
157
+ corrections_reference_an_emitted_event: corrections_reference_an_emitted_event(history),
158
+ outbox_rows_match_reactions: outbox_rows_match_reactions(history) }
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,123 @@
1
+ require "yaml"
2
+
3
+ module Hecks
4
+ module Fuzzing
5
+ # Loads and validates `qa/settings.yml`, the hecks_qa dials.
6
+ #
7
+ # Fails loud: a missing key, unknown key or wrong-typed value raises at load time.
8
+ # The meaning of each dial is documented on `QualityControlDials` in the bluebook.
9
+ class QaSettings
10
+ # Accessor name to the class (or classes) a valid value must be an instance of.
11
+ # Booleans list both TrueClass and FalseClass; Numeric admits `0` as well as `0.0`.
12
+ EXPECTED_TYPES = {
13
+ cadence_seconds: Integer,
14
+ pr_cap_per_day: Integer,
15
+ widening_tiers: Array,
16
+ sweep_max_parallel: Integer,
17
+ liveness_fallback_seconds: Integer,
18
+ draft_only: [TrueClass, FalseClass],
19
+ auto_merge: [TrueClass, FalseClass],
20
+ branch_prefix: String,
21
+ adversarial_fraction: Numeric,
22
+ guided_generation: [TrueClass, FalseClass],
23
+ corpus_splice_probability: Numeric,
24
+ favor_rare_verbs: Integer,
25
+ self_consistency_checks: [TrueClass, FalseClass],
26
+ shrink_budget: Integer,
27
+ yield_weight_seconds: Integer,
28
+ yield_decay_percent: Integer,
29
+ rotation_stale_floor_seconds: Integer,
30
+ persistence_parity_seed_cap: Integer,
31
+ concurrency_seed_cap: Integer,
32
+ adapter_parity_pairs: Hash,
33
+ modes: Hash,
34
+ role_draw_probability: Numeric,
35
+ dry_run_fraction: Numeric,
36
+ generated_domains_per_tick: Integer,
37
+ generated_domains_rust: [TrueClass, FalseClass],
38
+ generated_domain_seeds: Integer,
39
+ structural_refusal_boundary: Array
40
+ }.freeze
41
+
42
+ attr_reader(*EXPECTED_TYPES.keys)
43
+
44
+ # The one real file, resolved from this file's `__dir__` rather than the bluebook's: the
45
+ # chapter ships in `lib/hecks/quality_control/`, the settings stay in `qa/`, and IsolatedBoot
46
+ # copies only `qa/bluebook`, so a bluebook-relative path would miss it.
47
+ DEFAULT_PATH = File.expand_path("../../../qa/settings.yml", __dir__)
48
+
49
+ class << self
50
+ # Loads and validates `qa/settings.yml` (or `path`), returning a frozen instance.
51
+ #
52
+ # @param path [String] path to the YAML settings file; defaults to `DEFAULT_PATH`
53
+ # @return [Hecks::Fuzzing::QaSettings] the validated, frozen settings
54
+ # @raise [ArgumentError] if `path` does not exist, is not valid YAML, is not a
55
+ # YAML mapping at the top level, is missing a required key, declares an
56
+ # unknown key, or gives a value the wrong type for its dial
57
+ def load(path = DEFAULT_PATH)
58
+ raise ArgumentError, "qa settings file not found: #{path}" unless File.file?(path)
59
+
60
+ raw = begin
61
+ YAML.safe_load_file(path, symbolize_names: true)
62
+ rescue Psych::SyntaxError => e
63
+ raise ArgumentError, "#{path} is not valid YAML: #{e.message}"
64
+ end
65
+ raise ArgumentError, "#{path} must be a YAML mapping at the top level, got #{raw.class}" unless raw.is_a?(Hash)
66
+
67
+ new(raw, path)
68
+ end
69
+ end
70
+
71
+ # @param raw [Hash] parsed YAML settings keyed by symbol, one entry per dial in
72
+ # `EXPECTED_TYPES`
73
+ # @param path [String] path to the settings file, used only in error messages
74
+ # @raise [ArgumentError] if `raw` is missing a required key, declares an unknown
75
+ # key, or gives a value the wrong type for its dial
76
+ def initialize(raw, path)
77
+ missing = EXPECTED_TYPES.keys - raw.keys
78
+ raise ArgumentError, "#{path} is missing #{missing.sort.join(', ')}" if missing.any?
79
+
80
+ extra = raw.keys - EXPECTED_TYPES.keys
81
+ if extra.any?
82
+ raise ArgumentError,
83
+ "#{path} declares unknown key(s) #{extra.sort.join(', ')} — " \
84
+ "Hecks::Fuzzing::QaSettings::EXPECTED_TYPES doesn't recognise them"
85
+ end
86
+
87
+ EXPECTED_TYPES.each do |key, expected|
88
+ value = raw.fetch(key)
89
+ expected_classes = Array(expected)
90
+ unless expected_classes.any? { |klass| value.is_a?(klass) }
91
+ raise ArgumentError,
92
+ "#{path}: #{key} must be a #{expected_classes.map(&:name).join(' or ')}, " \
93
+ "got #{value.class} (#{value.inspect})"
94
+ end
95
+
96
+ instance_variable_set(:"@#{key}", value)
97
+ end
98
+
99
+ symbolize_adapter_parity_pairs!(path)
100
+ freeze_values!
101
+ end
102
+
103
+ private
104
+
105
+ # YAML cannot spell a Symbol value, and IsolatedBoot case-matches adapters by symbol.
106
+ def symbolize_adapter_parity_pairs!(path)
107
+ @adapter_parity_pairs = @adapter_parity_pairs.to_h do |mode, pair|
108
+ unless pair.is_a?(Hash) && pair.key?(:left) && pair.key?(:right)
109
+ raise ArgumentError,
110
+ "#{path}: adapter_parity_pairs.#{mode} must have both left and right, got #{pair.inspect}"
111
+ end
112
+
113
+ [mode, { left: pair[:left].to_sym, right: pair[:right].to_sym }]
114
+ end
115
+ end
116
+
117
+ def freeze_values!
118
+ EXPECTED_TYPES.each_key { |key| instance_variable_get(:"@#{key}").freeze }
119
+ freeze
120
+ end
121
+ end
122
+ end
123
+ end