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,385 @@
1
+ require_relative "../invalid_value_generator"
2
+ require_relative "../value_generator"
3
+ require_relative "../../runtime/value"
4
+
5
+ module Hecks
6
+ module Fuzzing
7
+ class SequenceGenerator
8
+ # Adversarial argument mutations, the shapes Ruby/Rust divergences were found through.
9
+ # Off at `adversarial: 0.0`, which returns before any RNG draw.
10
+ module Adversary
11
+ KINDS = %i[
12
+ routing_key
13
+ blank_identity
14
+ null_value_object
15
+ duplicate_entity_identity
16
+ omit_mapped_argument
17
+ refusal_precedence
18
+ ].freeze
19
+
20
+ # `to`/`with` are the dispatcher's routing keywords and `id` the untyped identity fallback;
21
+ # each is offered as bare null, a scalar, or a routing-shaped object.
22
+ ROUTING_KEYS = %w[to with id].freeze
23
+ ROUTING_SHAPES = %w[null scalar route].freeze
24
+
25
+ # A creating command's own identity part, blank three ways.
26
+ BLANK_SHAPES = %w[empty whitespace null].freeze
27
+
28
+ # A single-field value object as bare `null` and as `{}`.
29
+ VALUE_OBJECT_SHAPES = %w[null empty_object].freeze
30
+
31
+ # An unknown key, a type-mismatched key and an absent required key in one step, so both
32
+ # engines must pick which refusal wins. The second row reaches later `DISPATCH_ORDER`
33
+ # stages (lib/hecks/vocabulary.rb): `nonexistent` addresses an id nothing holds,
34
+ # `lifecycle` aims a transition-guarded command at a real record, `role` binds a caller
35
+ # the command does not name.
36
+ PRECEDENCE_SHAPES = %w[
37
+ unknown+mismatch+absent unknown+absent unknown+mismatch mismatch+absent
38
+ nonexistent+mismatch nonexistent+unknown lifecycle+mismatch role+absent role+nonexistent
39
+ ].freeze
40
+
41
+ # A drawn caller on a role-gated command, separate from `KINDS` (`role_draw:`) so it
42
+ # composes with any argument mutation and draws nothing when off.
43
+ # matching the command's own role
44
+ # mismatched another declared role, or none the domain knows
45
+ # absent_on_gated no caller at all: the unchecked default, recorded as a control
46
+ # actor_known the role plus an actor this sequence already granted it
47
+ # actor_unknown the role plus an actor nothing granted
48
+ CALLER_SHAPES = %w[matching mismatched absent_on_gated actor_known actor_unknown].freeze
49
+ UNKNOWN_ROLE = "Nobody the domain names".freeze
50
+
51
+ # An entity command two or more hops deep, addressed by flat one-head-per-hop args or the
52
+ # routed `to: { aggregate:, entities: [...] }` envelope. picker.rb weights these up.
53
+ DEEP_ENTITY_DEPTH = 2
54
+ DEEP_ENTITY_WEIGHT = 4
55
+
56
+ # Needs an element this sequence already appended, a rare moment, so it is weighted up
57
+ # when on offer.
58
+ DUPLICATE_IDENTITY_WEIGHT = 3
59
+
60
+ private
61
+
62
+ def adversarial? = @adversarial.positive?
63
+
64
+ # `[]` with no RNG draw when adversarial mode is off; otherwise the deep-entity note, then
65
+ # with probability `@adversarial` one mutation applicable to this step.
66
+ def adversarial_mutations!(args, entry, catalog)
67
+ return [] unless adversarial?
68
+
69
+ mutations = []
70
+ mutations << deep_entity_addressing!(args, entry) if (entry[:chain] || []).size >= DEEP_ENTITY_DEPTH
71
+ return mutations if @random.rand >= @adversarial
72
+
73
+ applicable = KINDS.select { |kind| send(:"#{kind}_applicable?", args, entry, catalog) }
74
+ return mutations if applicable.empty?
75
+
76
+ weighted = applicable.flat_map { |kind| [kind] * (kind == :duplicate_entity_identity ? DUPLICATE_IDENTITY_WEIGHT : 1) }
77
+ mutations << send(:"apply_#{weighted.sample(random: @random)}!", args, entry, catalog)
78
+ end
79
+
80
+ def deep_entity_addressing!(args, entry)
81
+ depth = entry[:chain].size
82
+ routed = @random.rand(2).zero?
83
+ note = { "mutation" => "deep_entity", "bug" => "BUG#11", "depth" => depth,
84
+ "addressing" => routed ? "routed" : "flat" }
85
+ return note unless routed
86
+
87
+ heads = [entry[:aggregate], *entry[:chain]].map { |construct| (construct.identified_by || :id).to_s }
88
+ scalars = heads.map { |head| ValueGenerator.scalar_of(args[head]) }
89
+ # The heads leave the flat args unless the command declares an attribute of that name
90
+ # (chess's `Piece.Move` declares `id`); dropping that would be a different mutation.
91
+ heads.each { |head| args.delete(head) unless entry[:command].attribute(head) }
92
+ args["to"] = { "aggregate" => scalars.first, "entities" => scalars.drop(1) }
93
+ note
94
+ end
95
+
96
+ # Not applied over a routed `to:` from `deep_entity_addressing!`; overwriting it would
97
+ # contradict that step's note.
98
+ def routing_key_applicable?(args, _entry, _catalog) = !args.key?("to")
99
+
100
+ def apply_routing_key!(args, entry, _catalog)
101
+ key = ROUTING_KEYS.sample(random: @random)
102
+ shape = ROUTING_SHAPES.sample(random: @random)
103
+ args[key] =
104
+ case shape
105
+ when "null" then nil
106
+ when "scalar" then routing_scalar(args, entry)
107
+ else routing_object(args, entry)
108
+ end
109
+ { "mutation" => "routing_key", "bug" => "BUG#7/#16/#8", "key" => key, "shape" => shape,
110
+ "declared" => !entry[:command].attribute(key).nil? }
111
+ end
112
+
113
+ # This step's own parent id, an out-of-range Integer, or a minted id nothing holds.
114
+ def routing_scalar(args, entry)
115
+ case @random.rand(3)
116
+ when 0 then parent_scalar_of(args, entry)
117
+ when 1 then ValueGenerator::INTEGER_EDGE_CASES.sample(random: @random)
118
+ else ValueGenerator.random_id(@random)
119
+ end
120
+ end
121
+
122
+ def routing_object(args, entry)
123
+ entities = (entry[:chain] || []).map { |piece| ValueGenerator.scalar_of(args[(piece.identified_by || :id).to_s]) }
124
+ # Half the time one identity too many, a depth the verb lacks, which both engines must
125
+ # refuse alike.
126
+ entities << ValueGenerator.random_id(@random) if @random.rand(2).zero?
127
+ { "aggregate" => parent_scalar_of(args, entry), "entities" => entities }
128
+ end
129
+
130
+ def parent_scalar_of(args, entry)
131
+ key = (entry[:aggregate].identified_by || :id).to_s
132
+ args.key?(key) ? ValueGenerator.scalar_of(args[key]) : identity_scalar_of(entry[:aggregate], args)
133
+ end
134
+
135
+ def blank_identity_applicable?(args, entry, catalog) = blank_identity_targets(args, entry, catalog).any?
136
+
137
+ # Identity heads this step supplies: a creating command's own (every composite part) and
138
+ # an append's entity identity arguments.
139
+ def blank_identity_targets(args, entry, catalog)
140
+ targets = []
141
+ if entry[:entity].nil? && entry[:command].creates?
142
+ aggregate = entry[:aggregate]
143
+ heads = composite_identity?(aggregate) ? aggregate.identity_heads : [aggregate.identified_by || :id]
144
+ targets.concat(heads.map(&:to_s))
145
+ end
146
+ populator = populator_for_entry(catalog, entry)
147
+ targets.concat(populator[:identity_arguments].map(&:to_s)) if populator
148
+ targets.uniq.select { |head| args.key?(head) }
149
+ end
150
+
151
+ def apply_blank_identity!(args, entry, catalog)
152
+ head = blank_identity_targets(args, entry, catalog).sample(random: @random)
153
+ shape = BLANK_SHAPES.sample(random: @random)
154
+ blank = shape == "empty" ? "" : " "
155
+ args[head] =
156
+ if shape == "null" then nil
157
+ elsif args[head].is_a?(Hash) then args[head].transform_values { blank }
158
+ else blank
159
+ end
160
+ { "mutation" => "blank_identity", "bug" => "BUG#15", "argument" => head, "shape" => shape }
161
+ end
162
+
163
+ def null_value_object_applicable?(args, entry, _catalog) = value_object_targets(args, entry).any?
164
+
165
+ def value_object_targets(args, entry)
166
+ aggregate = entry[:aggregate]
167
+ entry[:command].attributes.reject { |attribute| attribute.list? || attribute.reference? }
168
+ .select { |attribute| args.key?(attribute.name.to_s) }
169
+ .filter_map do |attribute|
170
+ value_object = Runtime::Value.value_object_for(aggregate, attribute.type.to_s)
171
+ [attribute, value_object] if value_object&.sole_attribute
172
+ end
173
+ end
174
+
175
+ def apply_null_value_object!(args, entry, _catalog)
176
+ targets = value_object_targets(args, entry)
177
+ closed = targets.select { |_, value_object| value_object.closed_set? }
178
+ attribute, value_object = (closed.empty? ? targets : closed).sample(random: @random)
179
+ shape = VALUE_OBJECT_SHAPES.sample(random: @random)
180
+ args[attribute.name.to_s] = shape == "null" ? nil : {}
181
+ { "mutation" => "null_value_object", "bug" => "BUG#14", "argument" => attribute.name.to_s,
182
+ "value_object" => value_object.hecks_name, "closed_set" => value_object.closed_set? == true,
183
+ "shape" => shape }
184
+ end
185
+
186
+ def duplicate_entity_identity_applicable?(args, entry, catalog) = duplicate_identity_pool(args, entry, catalog).any?
187
+
188
+ def duplicate_identity_pool(args, entry, catalog)
189
+ populator = populator_for_entry(catalog, entry)
190
+ return [] unless populator && populator[:identity_arguments].any?
191
+
192
+ @appended_identities[append_pool_key(populator, args)]
193
+ end
194
+
195
+ def apply_duplicate_entity_identity!(args, entry, catalog)
196
+ populator = populator_for_entry(catalog, entry)
197
+ tuple = duplicate_identity_pool(args, entry, catalog).sample(random: @random)
198
+ args.merge!(tuple)
199
+ { "mutation" => "duplicate_entity_identity", "bug" => "BUG#13", "entity" => populator[:entity].hecks_name,
200
+ "composite" => populator[:identity_arguments].size > 1, "identity" => tuple }
201
+ end
202
+
203
+ def omit_mapped_argument_applicable?(args, entry, _catalog) = mapped_argument_targets(args, entry).any?
204
+
205
+ # An append's mapped source arguments, or a plain creating command's non-identity
206
+ # attributes; never an identity head.
207
+ def mapped_argument_targets(args, entry)
208
+ command = entry[:command]
209
+ heads = identity_heads_of(entry)
210
+ mapped = command.mutations.select { |mutation| mutation.op == :append }
211
+ .flat_map { |mutation| mutation.source.values.grep(Symbol).map(&:to_s) }
212
+ if mapped.empty? && !entry.key?(:entity) && command.creates?
213
+ mapped = command.attributes.map { |attribute| attribute.name.to_s }
214
+ end
215
+ command.attributes.select do |attribute|
216
+ name = attribute.name.to_s
217
+ mapped.include?(name) && args.key?(name) && !heads.include?(name)
218
+ end
219
+ end
220
+
221
+ def apply_omit_mapped_argument!(args, entry, _catalog)
222
+ attribute = mapped_argument_targets(args, entry).sample(random: @random)
223
+ args.delete(attribute.name.to_s)
224
+ { "mutation" => "omit_mapped_argument", "bug" => "BUG#12", "argument" => attribute.name.to_s,
225
+ "optional" => attribute.optional? == true }
226
+ end
227
+
228
+ def refusal_precedence_applicable?(args, entry, _catalog)
229
+ precedence_shapes_for(args, entry).any?
230
+ end
231
+
232
+ # Every shape whose parts this step can carry. `nonexistent` and `lifecycle` need a
233
+ # record-addressing command with flat addressing; `lifecycle` also a guarded command;
234
+ # `role` a declared role.
235
+ def precedence_shapes_for(args, entry)
236
+ can = {
237
+ "mismatch" => corruptible_attributes(args, entry).any?,
238
+ "absent" => droppable_required_attributes(args, entry).any?,
239
+ "unknown" => true,
240
+ "nonexistent" => acts_on_record?(args, entry),
241
+ "lifecycle" => acts_on_record?(args, entry) && transition_guarded?(entry),
242
+ "role" => !entry[:command].role.to_s.empty?
243
+ }
244
+ PRECEDENCE_SHAPES.select { |shape| shape.split("+").all? { |part| can.fetch(part) } }
245
+ end
246
+
247
+ def acts_on_record?(args, entry) = !entry[:command].creates? && !args.key?("to")
248
+
249
+ def transition_guarded?(entry)
250
+ command = entry[:command]
251
+ owner = entry.key?(:entity) ? entry[:entity] : entry[:aggregate]
252
+ return true if command.from
253
+ return false unless owner.respond_to?(:lifecycle)
254
+
255
+ owner.lifecycle&.transitions_for(command.hecks_name)&.any? || false
256
+ end
257
+
258
+ def apply_refusal_precedence!(args, entry, catalog)
259
+ corruptible = corruptible_attributes(args, entry)
260
+ droppable = droppable_required_attributes(args, entry)
261
+ wanted = precedence_shapes_for(args, entry).sample(random: @random).split("+")
262
+ detail = { "mutation" => "refusal_precedence", "bug" => "BUG#7/#8/#14" }
263
+ applied = []
264
+
265
+ if wanted.include?("absent")
266
+ dropped = droppable.sample(random: @random)
267
+ args.delete(dropped.name.to_s)
268
+ detail["absent"] = dropped.name.to_s
269
+ applied << "absent"
270
+ corruptible -= [dropped]
271
+ end
272
+ if wanted.include?("mismatch") && corruptible.any?
273
+ attribute = corruptible.sample(random: @random)
274
+ args[attribute.name.to_s] = InvalidValueGenerator.corrupt(attribute, entry[:aggregate], random: @random)
275
+ detail["mismatched"] = attribute.name.to_s
276
+ applied << "mismatch"
277
+ end
278
+ if wanted.include?("unknown")
279
+ name, value = InvalidValueGenerator.undeclared_argument(random: @random)
280
+ args[name] = value
281
+ detail["unknown"] = name
282
+ applied << "unknown"
283
+ end
284
+ apply_late_stage_parts!(wanted, args, entry, detail, applied, catalog)
285
+ # Reported as what was done: a single-attribute command cannot carry both a drop and a
286
+ # corruption.
287
+ detail.merge("shape" => applied.sort.join("+"))
288
+ end
289
+
290
+ # The parts past the argument gate. `nonexistent` re-addresses the last hop to an id
291
+ # nothing holds; `lifecycle` mutates nothing and is recorded so the pairing is visible;
292
+ # `role` parks a mismatched caller for StepBuilder to bind around the dispatch.
293
+ def apply_late_stage_parts!(wanted, args, entry, detail, applied, catalog)
294
+ if wanted.include?("nonexistent")
295
+ piece = (entry[:chain] || []).last || entry[:aggregate]
296
+ head = (piece.identified_by || :id).to_s
297
+ args[head] = identity_shaped(piece, piece.identified_by, ValueGenerator.random_id(@random), entry[:aggregate])
298
+ detail["nonexistent"] = head
299
+ applied << "nonexistent"
300
+ end
301
+ if wanted.include?("lifecycle")
302
+ detail["lifecycle"] = entry[:command].from || "transition-guarded"
303
+ applied << "lifecycle"
304
+ end
305
+ return unless wanted.include?("role")
306
+
307
+ @precedence_caller = { "role" => other_role(entry[:command].role.to_s, catalog) }
308
+ detail["role"] = @precedence_caller["role"]
309
+ applied << "role"
310
+ end
311
+
312
+ def role_draw? = @role_draw.positive?
313
+
314
+ # `[caller, note]`: the caller StepBuilder binds around the dispatch (nil for the unchecked
315
+ # control) and the note for the step's `"adversarial"` metadata. A caller parked by
316
+ # `refusal_precedence` wins; an ungated command draws nothing.
317
+ def caller_draw!(entry, catalog)
318
+ if @precedence_caller
319
+ caller = @precedence_caller
320
+ @precedence_caller = nil
321
+ return [caller, nil]
322
+ end
323
+
324
+ role = entry[:command].role.to_s
325
+ return [nil, nil] if !role_draw? || role.empty? || @random.rand >= @role_draw
326
+
327
+ shapes = CALLER_SHAPES.dup
328
+ shapes.delete("actor_known") if @granted[role].empty?
329
+ shape = shapes.sample(random: @random)
330
+ caller = caller_for_shape(shape, role, catalog)
331
+ note = { "mutation" => "caller_role", "angle" => "ANGLE-5", "shape" => shape, "gated_role" => role }
332
+ [caller, caller ? note.merge(caller) : note]
333
+ end
334
+
335
+ def caller_for_shape(shape, role, catalog)
336
+ case shape
337
+ when "matching" then { "role" => role }
338
+ when "mismatched" then { "role" => other_role(role, catalog) }
339
+ when "actor_known" then { "role" => role, "actor_id" => @granted[role].sample(random: @random) }
340
+ when "actor_unknown" then { "role" => role, "actor_id" => ValueGenerator.random_id(@random) }
341
+ end
342
+ end
343
+
344
+ # Another declared role when there is one (a real wrong hat), else a role none names.
345
+ def other_role(role, catalog)
346
+ others = catalog[:roles] - [role]
347
+ others.empty? ? UNKNOWN_ROLE : others.sample(random: @random)
348
+ end
349
+
350
+ def corruptible_attributes(args, entry)
351
+ entry[:command].attributes.reject(&:list?).select { |attribute| args.key?(attribute.name.to_s) }
352
+ end
353
+
354
+ def droppable_required_attributes(args, entry)
355
+ heads = identity_heads_of(entry)
356
+ entry[:command].attributes.reject(&:optional?).select do |attribute|
357
+ args.key?(attribute.name.to_s) && !heads.include?(attribute.name.to_s)
358
+ end
359
+ end
360
+
361
+ # Aims a grant at a role some command declares; random role text would make `actor_known`
362
+ # unreachable. Does nothing without the role draw.
363
+ def steer_grant!(args, entry, catalog)
364
+ return unless role_draw? && catalog[:grant_verbs].include?(entry[:verb]) && catalog[:roles].any?
365
+ return unless args.key?("role_name")
366
+
367
+ args["role_name"] = { "value" => catalog[:roles].sample(random: @random) }
368
+ end
369
+
370
+ def populator_for_entry(catalog, entry)
371
+ owner = entry.key?(:entity) ? entry[:entity] : entry[:aggregate]
372
+ catalog[:populators].find { |p| p[:command].equal?(entry[:command]) && p[:owner].equal?(owner) }
373
+ end
374
+
375
+ # Every identity head this step addresses by: the aggregate's own, the untyped `id`, and
376
+ # one per entity hop.
377
+ def identity_heads_of(entry)
378
+ heads = entry[:aggregate].identity_heads.map(&:to_s) + ["id"]
379
+ (entry[:chain] || []).each { |piece| heads.concat(piece.identity_heads.map(&:to_s)) }
380
+ heads.uniq
381
+ end
382
+ end
383
+ end
384
+ end
385
+ end
@@ -0,0 +1,132 @@
1
+ module Hecks
2
+ module Fuzzing
3
+ class SequenceGenerator
4
+ # Everything the booted domain offers, indexed for the picker:
5
+ # creating vs instance commands, entity commands, queries, and the
6
+ # populators that predict where an appended element lands.
7
+ module Catalog
8
+ private
9
+
10
+ def build_catalog(runtime)
11
+ creating = []
12
+ instance = []
13
+ entity_commands = []
14
+ queries = []
15
+ entity_queries = []
16
+ read_models = []
17
+
18
+ runtime.registry.bluebooks.each do |domain_name, bluebook|
19
+ bluebook.aggregates.each do |aggregate|
20
+ aggregate.commands.each do |command|
21
+ entry = { verb: "#{domain_name}::#{aggregate.hecks_name}.#{command.hecks_name}",
22
+ command: command, aggregate: aggregate }
23
+ (command.creates? ? creating : instance) << entry
24
+ end
25
+ aggregate.queries.each do |query|
26
+ queries << { verb: "#{domain_name}::#{aggregate.hecks_name}.#{query.name}",
27
+ query: query, aggregate: aggregate }
28
+ end
29
+ catalog_entities(domain_name, aggregate, entity_commands, entity_queries)
30
+ end
31
+
32
+ # Reports use the bare "Domain.report_name" form, which `Dispatcher#query`
33
+ # routes to the read model. Keyed by `model:`, not `aggregate:`: a
34
+ # rootless report has no aggregate to be eligible against.
35
+ bluebook.read_models.each do |model|
36
+ read_models << { verb: "#{domain_name}.#{model.query_name}", model: model }
37
+ end
38
+ end
39
+
40
+ { creating: creating, instance: instance, entity_commands: entity_commands,
41
+ queries: queries, entity_queries: entity_queries, read_models: read_models,
42
+ populators: populators(runtime),
43
+ # The aggregates a query filters on, and the fields it compares.
44
+ query_bindings: build_query_bindings(runtime),
45
+ # Every `role "..."` a command declares; the `mismatched` caller shape draws from it.
46
+ roles: (creating + instance + entity_commands).filter_map { |e| e[:command].role }
47
+ .map(&:to_s).reject(&:empty?).uniq.sort,
48
+ # The grant verb each authorization provider declares, read off the
49
+ # declaration rather than a hard-coded Governance name.
50
+ grant_verbs: runtime.registry.authorization_providers
51
+ .filter_map { |chapter| chapter.provided_verb(Bluebook::Capabilities::AUTHORIZATION, :grant) }
52
+ .sort,
53
+ # Aggregates some creating command can make; `satisfiable?` only waits on these.
54
+ creatable: creating.to_set { |entry| entry[:aggregate].hecks_name } }
55
+ end
56
+
57
+ # Indexes every entity at every depth; `chain:` is the whole hop list
58
+ # (`[Board, Card]`) and `entity:` its last hop.
59
+ #
60
+ # Entity queries stay one hop deep: a nested entity's query has no
61
+ # established wire spelling.
62
+ def catalog_entities(domain_name, aggregate, entity_commands, entity_queries)
63
+ each_entity_chain(aggregate) do |chain|
64
+ entity = chain.last
65
+ path = chain.map(&:hecks_name).join(".")
66
+ entity.commands.each do |command|
67
+ entity_commands << { verb: "#{domain_name}::#{aggregate.hecks_name}.#{path}.#{command.hecks_name}",
68
+ command: command, aggregate: aggregate, entity: entity, chain: chain }
69
+ end
70
+ next unless chain.size == 1
71
+
72
+ entity.queries.each do |query|
73
+ entity_queries << { verb: "#{domain_name}::#{aggregate.hecks_name}.#{path}.#{query.name}",
74
+ query: query, aggregate: aggregate, entity: entity }
75
+ end
76
+ end
77
+ end
78
+
79
+ # Depth-first, parents before children, in declaration order, which
80
+ # keeps a pinned seed's picker pool stable.
81
+ def each_entity_chain(owner, chain = [], &block)
82
+ owner.entities.each do |entity|
83
+ path = chain + [entity]
84
+ yield path
85
+ each_entity_chain(entity, path, &block)
86
+ end
87
+ end
88
+
89
+ # Which command appends to which entity list, so a successful dispatch
90
+ # can predict the identity of the element it added.
91
+ #
92
+ # `owner_chain:` is `[]` for an aggregate-level append and the entity
93
+ # path for an entity-level one. `identity_arguments:` lists every
94
+ # identity head sourced from a command argument; `identity_argument:`
95
+ # is the single-head reading the auto-mint prediction keys on.
96
+ def populators(runtime)
97
+ runtime.registry.bluebooks.each_value.flat_map do |bluebook|
98
+ bluebook.aggregates.flat_map do |aggregate|
99
+ owners = [[aggregate, []]]
100
+ each_entity_chain(aggregate) { |chain| owners << [chain.last, chain] }
101
+
102
+ owners.flat_map do |owner, chain|
103
+ owner.commands.filter_map { |command| populator_for(aggregate, owner, chain, command) }
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ def populator_for(aggregate, owner, chain, command)
110
+ append = command.mutations.find { |mutation| mutation.op == :append }
111
+ return unless append
112
+
113
+ list_attribute = owner.attribute(append.target)
114
+ return unless list_attribute&.list?
115
+
116
+ entity = owner.entities.find { |candidate| candidate.hecks_name == list_attribute.type.to_s }
117
+ return unless entity
118
+
119
+ identity_field = entity.identified_by
120
+ mapped = identity_field && append.source[identity_field]
121
+ identity_arguments = entity.identity_heads.filter_map do |head|
122
+ source = append.source[head]
123
+ source if source.is_a?(Symbol)
124
+ end
125
+ { command: command, aggregate: aggregate, owner: owner, owner_chain: chain, entity: entity,
126
+ identity_field: identity_field, identity_argument: mapped.is_a?(Symbol) ? mapped : nil,
127
+ identity_arguments: identity_arguments }
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,95 @@
1
+ require_relative "../value_generator"
2
+ require_relative "../../naming"
3
+
4
+ module Hecks
5
+ module Fuzzing
6
+ class SequenceGenerator
7
+ # What a successful step created, and how a later step draws on it —
8
+ # the state tracking that makes multi-step sequences reach further
9
+ # than single calls ever did.
10
+ module OutcomeTracker
11
+ private
12
+
13
+ def record_outcome(catalog, entry, args)
14
+ aggregate = entry[:aggregate]
15
+ parent_scalar = identity_scalar_of(aggregate, args)
16
+
17
+ @known_ids[aggregate.hecks_name] << parent_scalar if entry[:entity].nil? && entry[:command].creates?
18
+ record_grant(args) if catalog[:grant_verbs].include?(entry[:verb])
19
+
20
+ populator = populator_for_entry(catalog, entry)
21
+ return unless populator
22
+
23
+ key = append_pool_key(populator, args)
24
+
25
+ # Auto-minted identities land at count-so-far + 1; an explicit one is
26
+ # read back from the args this generator supplied.
27
+ new_id =
28
+ if populator[:identity_argument]
29
+ ValueGenerator.scalar_of(args[populator[:identity_argument].to_s])
30
+ else
31
+ (@entity_known_ids[key].size + 1).to_s
32
+ end
33
+ @entity_known_ids[key] << new_id
34
+
35
+ # Keep the caller-supplied identity tuple whole so the duplicate-identity
36
+ # mutation can offer it again under the same parent.
37
+ return if populator[:identity_arguments].empty?
38
+
39
+ @appended_identities[key] << populator[:identity_arguments].to_h { |name| [name.to_s, args[name.to_s]] }
40
+ end
41
+
42
+ # Records a grant that took effect: `actor_id` now holds `role_name`, which
43
+ # lets the `actor_known` caller shape reach the authorized branch.
44
+ def record_grant(args)
45
+ role = ValueGenerator.scalar_of(args["role_name"]).to_s
46
+ actor = ValueGenerator.scalar_of(args["actor_id"]).to_s
47
+ @granted[role] << actor unless role.empty? || actor.empty?
48
+ end
49
+
50
+ # The pool an appended element lands in: the aggregate's identity, then
51
+ # one scalar per owning hop (none for an aggregate-level append).
52
+ def append_pool_key(populator, args)
53
+ parent_scalar = identity_scalar_of(populator[:aggregate], args)
54
+ owner_scalars = populator[:owner_chain].map do |piece|
55
+ ValueGenerator.scalar_of(args[(piece.identified_by || :id).to_s])
56
+ end
57
+ entity_pool_key(populator[:aggregate].hecks_name,
58
+ populator[:owner_chain].map(&:hecks_name) + [populator[:entity].hecks_name],
59
+ [parent_scalar] + owner_scalars)
60
+ end
61
+
62
+ # `"Agg.Board#w1"` for a depth-1 pool, `"Agg.Board.Card#w1/1"` one hop
63
+ # deeper. Depth-1 keys must stay stable for pinned seeds.
64
+ def entity_pool_key(aggregate_name, chain_names, scalars)
65
+ "#{aggregate_name}.#{chain_names.join('.')}##{scalars.join('/')}"
66
+ end
67
+
68
+ # The scalar the step's aggregate identity resolves to. A composite identity
69
+ # has no top-level arg, so its parts are joined in the order
70
+ # `Runtime::Identity.of` joins them, yielding a usable bare `id:` later.
71
+ def identity_scalar_of(aggregate, args)
72
+ parent_key = (aggregate.identified_by || :id).to_s
73
+ return ValueGenerator.scalar_of(args[parent_key]) unless composite_identity?(aggregate)
74
+
75
+ parts = aggregate.identity_paths.map { |path| ValueGenerator.scalar_of(args[path.to_s.split(".").first]) }
76
+ Naming.identity(parts)
77
+ end
78
+
79
+ def pick_known(name)
80
+ pool = @known_ids[name]
81
+ return ValueGenerator.random_id(@random) if pool.empty? || @random.rand < ValueGenerator::INVALID_REFERENCE_PROBABILITY
82
+
83
+ pool.sample(random: @random)
84
+ end
85
+
86
+ def pick_entity_known(key)
87
+ pool = @entity_known_ids[key]
88
+ return ValueGenerator.random_id(@random) if pool.empty? || @random.rand < ValueGenerator::INVALID_REFERENCE_PROBABILITY
89
+
90
+ pool.sample(random: @random)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end