hecks 2.8.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 (886) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +50 -7
  3. data/lib/hecks/adapters/driven/claude_code.rb +4 -104
  4. data/lib/hecks/adapters/driven/d1.rb +44 -237
  5. data/lib/hecks/adapters/driven/folder.rb +17 -76
  6. data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
  7. data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
  8. data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
  9. data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
  10. data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
  11. data/lib/hecks/adapters/driven/heki.rb +13 -42
  12. data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
  15. data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
  16. data/lib/hecks/adapters/driven/lambda.rb +10 -84
  17. data/lib/hecks/adapters/driven/local_storage.rb +5 -40
  18. data/lib/hecks/adapters/driven/memory.rb +32 -100
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +10 -32
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
  24. data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
  25. data/lib/hecks/adapters/driven/postgres.rb +149 -136
  26. data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
  27. data/lib/hecks/adapters/driven/prism.rb +48 -38
  28. data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
  29. data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
  30. data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
  31. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
  32. data/lib/hecks/adapters/driven/sqlite.rb +128 -65
  33. data/lib/hecks/adapters/driven/tenant_provisioner.rb +76 -62
  34. data/lib/hecks/adapters/driven.rb +5 -36
  35. data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
  36. data/lib/hecks/behaviors/dsl.rb +7 -25
  37. data/lib/hecks/behaviors/expectations.rb +32 -142
  38. data/lib/hecks/behaviors/ir.rb +7 -20
  39. data/lib/hecks/behaviors/rspec.rb +6 -17
  40. data/lib/hecks/behaviors/runner.rb +10 -34
  41. data/lib/hecks/behaviors.rb +4 -8
  42. data/lib/hecks/bench/cli.rb +62 -0
  43. data/lib/hecks/bench/environment.rb +71 -0
  44. data/lib/hecks/bench/postgres_probe.rb +30 -0
  45. data/lib/hecks/bench/report.rb +76 -0
  46. data/lib/hecks/bench/ruby_runner.rb +93 -0
  47. data/lib/hecks/bench/run.rb +55 -0
  48. data/lib/hecks/bench/rust_runner.rb +101 -0
  49. data/lib/hecks/bench/stats.rb +46 -0
  50. data/lib/hecks/bench/suite.rb +98 -0
  51. data/lib/hecks/bench/workload.rb +89 -0
  52. data/lib/hecks/bench.rb +17 -0
  53. data/lib/hecks/bluebook/aggregate.rb +9 -75
  54. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
  55. data/lib/hecks/bluebook/assembly/build.rb +4 -15
  56. data/lib/hecks/bluebook/assembly/contract.rb +15 -136
  57. data/lib/hecks/bluebook/assembly/contracts.rb +56 -174
  58. data/lib/hecks/bluebook/assembly/marks.rb +31 -223
  59. data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
  60. data/lib/hecks/bluebook/assembly.rb +6 -45
  61. data/lib/hecks/bluebook/attribute.rb +12 -61
  62. data/lib/hecks/bluebook/behaviour/aggregate.rb +39 -39
  63. data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
  64. data/lib/hecks/bluebook/behaviour/chapter.rb +14 -42
  65. data/lib/hecks/bluebook/behaviour/command.rb +14 -66
  66. data/lib/hecks/bluebook/behaviour/domain_port.rb +11 -20
  67. data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
  68. data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
  69. data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
  70. data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
  71. data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
  72. data/lib/hecks/bluebook/behaviour/read_model.rb +25 -31
  73. data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
  74. data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
  75. data/lib/hecks/bluebook/capabilities.rb +17 -49
  76. data/lib/hecks/bluebook/chapter.rb +16 -50
  77. data/lib/hecks/bluebook/command.rb +23 -81
  78. data/lib/hecks/bluebook/domain_port.rb +49 -67
  79. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
  80. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
  81. data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
  82. data/lib/hecks/bluebook/dsl/binding_proxy.rb +16 -69
  83. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +96 -708
  84. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +63 -169
  85. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +3 -1
  86. data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
  87. data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
  88. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +42 -89
  89. data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
  90. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
  91. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +148 -197
  92. data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
  93. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
  94. data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
  95. data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
  96. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
  97. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
  98. data/lib/hecks/bluebook/dsl/query_builder.rb +38 -43
  99. data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
  100. data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
  101. data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
  102. data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
  103. data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
  104. data/lib/hecks/bluebook/dsl/world_builder.rb +46 -111
  105. data/lib/hecks/bluebook/entity.rb +13 -71
  106. data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
  107. data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
  108. data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
  109. data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
  110. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
  111. data/lib/hecks/bluebook/expression/resolver.rb +39 -661
  112. data/lib/hecks/bluebook/hexagon.rb +18 -33
  113. data/lib/hecks/bluebook/lifecycle.rb +5 -16
  114. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
  115. data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
  116. data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
  117. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
  118. data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
  119. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +25 -176
  120. data/lib/hecks/bluebook/meta_validator/shapes.rb +53 -267
  121. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -306
  122. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
  123. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
  124. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
  125. data/lib/hecks/bluebook/meta_validator.rb +87 -352
  126. data/lib/hecks/bluebook/model_check/client_profile.rb +6 -21
  127. data/lib/hecks/bluebook/model_check.rb +122 -596
  128. data/lib/hecks/bluebook/pattern_subset.rb +49 -69
  129. data/lib/hecks/bluebook/process_manager.rb +13 -83
  130. data/lib/hecks/bluebook/project_loader.rb +3 -39
  131. data/lib/hecks/bluebook/project_register.rb +12 -60
  132. data/lib/hecks/bluebook/query.rb +27 -51
  133. data/lib/hecks/bluebook/read_model.rb +57 -81
  134. data/lib/hecks/bluebook/reference.rb +7 -41
  135. data/lib/hecks/bluebook/smoke_test.rb +9 -99
  136. data/lib/hecks/bluebook/synthesizer.rb +13 -62
  137. data/lib/hecks/bluebook/translation.rb +26 -62
  138. data/lib/hecks/bluebook/value_object.rb +11 -36
  139. data/lib/hecks/bluebook.rb +5 -22
  140. data/lib/hecks/cache_dir.rb +4 -23
  141. data/lib/hecks/canonical_json.rb +30 -0
  142. data/lib/hecks/chapters.rb +102 -0
  143. data/lib/hecks/cli/behaviors.rb +61 -0
  144. data/lib/hecks/cli/canonicalise.rb +25 -0
  145. data/lib/hecks/cli/check_engine_agreement.rb +32 -0
  146. data/lib/hecks/cli/check_era.rb +88 -0
  147. data/lib/hecks/cli/console.rb +66 -0
  148. data/lib/hecks/cli/deploy_template_diff.rb +40 -0
  149. data/lib/hecks/cli/doc_coverage.rb +32 -0
  150. data/lib/hecks/cli/document.rb +5 -23
  151. data/lib/hecks/cli/expression_projection.rb +41 -0
  152. data/lib/hecks/cli/follow.rb +88 -0
  153. data/lib/hecks/cli/history.rb +44 -0
  154. data/lib/hecks/cli/ir.rb +4 -6
  155. data/lib/hecks/cli/mcp.rb +5 -6
  156. data/lib/hecks/cli/model_check.rb +24 -88
  157. data/lib/hecks/cli/pattern_cases.rb +47 -0
  158. data/lib/hecks/cli/present.rb +39 -0
  159. data/lib/hecks/cli/project_cli.rb +223 -64
  160. data/lib/hecks/cli/project_diagrams.rb +4 -10
  161. data/lib/hecks/cli/project_glossary.rb +44 -0
  162. data/lib/hecks/cli/reference.rb +46 -0
  163. data/lib/hecks/cli/refresh_projections.rb +44 -0
  164. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
  165. data/lib/hecks/cli/release.rb +70 -0
  166. data/lib/hecks/cli/release_gem.rb +59 -0
  167. data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
  168. data/lib/hecks/cli/rspec_shard_files.rb +66 -0
  169. data/lib/hecks/cli/run.rb +15 -61
  170. data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
  171. data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
  172. data/lib/hecks/cli/shape.rb +70 -0
  173. data/lib/hecks/cli/smoke_http.rb +230 -0
  174. data/lib/hecks/cli/smoke_test.rb +7 -19
  175. data/lib/hecks/cli/spec_example.rb +27 -0
  176. data/lib/hecks/cli/statements.rb +32 -0
  177. data/lib/hecks/cli/stores.rb +4 -6
  178. data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
  179. data/lib/hecks/cli.rb +216 -73
  180. data/lib/hecks/codemod.rb +324 -0
  181. data/lib/hecks/construct.rb +8 -34
  182. data/lib/hecks/corpus.rb +423 -0
  183. data/lib/hecks/deploy/bluebook/deploy.bluebook +617 -158
  184. data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -11
  185. data/lib/hecks/deploy/oidc.json +82 -2
  186. data/lib/hecks/doc/reference.rb +351 -0
  187. data/lib/hecks/doors/cli_door.rb +196 -0
  188. data/lib/hecks/doors/cli_runner.rb +297 -0
  189. data/lib/hecks/{facade → doors}/command_request.rb +14 -34
  190. data/lib/hecks/doors/handle.rb +206 -0
  191. data/lib/hecks/doors/json_door.rb +132 -0
  192. data/lib/hecks/doors/launcher_options.rb +129 -0
  193. data/lib/hecks/{cli → doors}/mcp_door.rb +25 -120
  194. data/lib/hecks/doors/mcp_door_scope.rb +97 -0
  195. data/lib/hecks/doors/ruby_door/aggregate_door.rb +162 -0
  196. data/lib/hecks/doors/ruby_door/chapter.rb +72 -0
  197. data/lib/hecks/doors/ruby_door.rb +77 -0
  198. data/lib/hecks/doors.rb +61 -0
  199. data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
  200. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
  201. data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
  202. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
  203. data/lib/hecks/embryonaut_bluebook.rb +5 -72
  204. data/lib/hecks/engine_agreement.rb +156 -0
  205. data/lib/hecks/facade.rb +23 -51
  206. data/lib/hecks/forms/app.rb +21 -76
  207. data/lib/hecks/forms/banking_presentation.rb +50 -0
  208. data/lib/hecks/forms/command_form_renderer.rb +10 -29
  209. data/lib/hecks/forms/field_renderer.rb +17 -156
  210. data/lib/hecks/forms/field_shape.rb +31 -222
  211. data/lib/hecks/forms/html.rb +12 -47
  212. data/lib/hecks/forms/index_renderer.rb +1 -4
  213. data/lib/hecks/forms/page.rb +3 -9
  214. data/lib/hecks/forms/params.rb +14 -60
  215. data/lib/hecks/forms/port_argument.rb +2 -19
  216. data/lib/hecks/forms/query_form_renderer.rb +4 -79
  217. data/lib/hecks/forms/record_renderer.rb +8 -75
  218. data/lib/hecks/forms/record_table.rb +7 -50
  219. data/lib/hecks/forms/reference_options.rb +3 -30
  220. data/lib/hecks/forms/value_object_shape.rb +8 -35
  221. data/lib/hecks/forms.rb +5 -33
  222. data/lib/hecks/fqn.rb +5 -12
  223. data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
  224. data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
  225. data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
  226. data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
  227. data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
  228. data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
  229. data/lib/hecks/framework.rb +11 -80
  230. data/lib/hecks/freezer.rb +5 -39
  231. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
  232. data/lib/hecks/fuzzing/combination_miner.rb +86 -0
  233. data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
  234. data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
  235. data/lib/hecks/fuzzing/differential.rb +156 -0
  236. data/lib/hecks/fuzzing/domain_generator.rb +615 -0
  237. data/lib/hecks/fuzzing/era_boundary.rb +71 -0
  238. data/lib/hecks/fuzzing/form_census.rb +136 -0
  239. data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
  240. data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
  241. data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
  242. data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
  243. data/lib/hecks/fuzzing/outside_world.rb +45 -0
  244. data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
  245. data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
  246. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
  247. data/lib/hecks/fuzzing/properties/guards.rb +190 -0
  248. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
  249. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
  250. data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
  251. data/lib/hecks/fuzzing/properties/querying.rb +175 -0
  252. data/lib/hecks/fuzzing/properties.rb +162 -0
  253. data/lib/hecks/fuzzing/qa_settings.rb +123 -0
  254. data/lib/hecks/fuzzing/replay.rb +505 -0
  255. data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
  256. data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
  257. data/lib/hecks/fuzzing/self_consistency.rb +339 -0
  258. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
  259. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
  260. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
  261. data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
  262. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
  263. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
  264. data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
  265. data/lib/hecks/fuzzing/shrinker.rb +166 -0
  266. data/lib/hecks/fuzzing/structural_skips.rb +33 -0
  267. data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
  268. data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
  269. data/lib/hecks/fuzzing/value_generator.rb +216 -0
  270. data/lib/hecks/fuzzing.rb +23 -0
  271. data/lib/hecks/grammar/evolve.rb +336 -0
  272. data/lib/hecks/grammar/expression.bluebook +15 -40
  273. data/lib/hecks/grammar/translation.bluebook +3 -6
  274. data/lib/hecks/grammar.rb +16 -41
  275. data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
  276. data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
  277. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
  278. data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
  279. data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
  280. data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
  281. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
  282. data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
  283. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
  284. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
  285. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
  286. data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
  287. data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
  288. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
  289. data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
  290. data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
  291. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
  292. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
  293. data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
  294. data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
  295. data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
  296. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
  297. data/lib/hecks/hecks/adapters/git.adapter +7 -0
  298. data/lib/hecks/hecks/adapters/git.rb +102 -0
  299. data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
  300. data/lib/hecks/hecks/adapters/host_http.rb +65 -0
  301. data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
  302. data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
  303. data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
  304. data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
  305. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
  306. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
  307. data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
  308. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
  309. data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
  310. data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
  311. data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
  312. data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
  313. data/lib/hecks/hecks/adapters/local_files.rb +46 -0
  314. data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
  315. data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
  316. data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
  317. data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
  318. data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
  319. data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
  320. data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
  321. data/lib/hecks/hecks/adapters/shell.adapter +7 -0
  322. data/lib/hecks/hecks/adapters/shell.rb +49 -0
  323. data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
  324. data/lib/hecks/hecks/adapters/terminal.rb +74 -0
  325. data/lib/hecks/hecks/codebase.behaviors +172 -0
  326. data/lib/hecks/hecks/codebase.bluebook +2453 -0
  327. data/lib/hecks/hecks/context_map.hecksagon +13 -0
  328. data/lib/hecks/hecks/custodian.behaviors +179 -0
  329. data/lib/hecks/hecks/custodian.bluebook +1847 -0
  330. data/lib/hecks/hecks/deploy.behaviors +74 -0
  331. data/lib/hecks/hecks/deploy.world +12 -0
  332. data/lib/hecks/hecks/environments/memory.world +10 -0
  333. data/lib/hecks/hecks/governance.world +7 -0
  334. data/lib/hecks/hecks/hecks.behaviors +62 -0
  335. data/lib/hecks/hecks/hecks.bluebook +124 -0
  336. data/lib/hecks/hecks/hecks.hecksagon +390 -0
  337. data/lib/hecks/hecks/hecks.world +42 -0
  338. data/lib/hecks/ir.rb +14 -83
  339. data/lib/hecks/kernel_capabilities.rb +194 -0
  340. data/lib/hecks/language/adapter.bluebook +2 -7
  341. data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
  342. data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
  343. data/lib/hecks/language/bluebook/bluebook.bluebook +26 -83
  344. data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
  345. data/lib/hecks/language/bluebook/command.bluebook +27 -144
  346. data/lib/hecks/language/bluebook/entity.bluebook +24 -97
  347. data/lib/hecks/language/bluebook/policy.bluebook +16 -67
  348. data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
  349. data/lib/hecks/language/bluebook/projection.bluebook +126 -60
  350. data/lib/hecks/language/bluebook/query.bluebook +19 -43
  351. data/lib/hecks/language/bluebook/shape.bluebook +23 -73
  352. data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
  353. data/lib/hecks/language/bluebook/vocabulary.bluebook +83 -198
  354. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
  355. data/lib/hecks/language/hecksagon/domain_port.bluebook +14 -19
  356. data/lib/hecks/language/hecksagon/hecksagon.bluebook +26 -25
  357. data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
  358. data/lib/hecks/language/oidc.json +35 -0
  359. data/lib/hecks/language/port.bluebook +4 -17
  360. data/lib/hecks/language/translation/translation.bluebook +3 -9
  361. data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
  362. data/lib/hecks/language/world/wiring.bluebook +1 -3
  363. data/lib/hecks/language/world/world.bluebook +5 -7
  364. data/lib/hecks/literal.rb +8 -50
  365. data/lib/hecks/mcp_stdio_guard.rb +4 -41
  366. data/lib/hecks/naming.rb +29 -137
  367. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
  368. data/lib/hecks/persistence_legacy_fixture.rb +113 -0
  369. data/lib/hecks/ports/access_control.rb +7 -23
  370. data/lib/hecks/ports/agent/answers.rb +5 -23
  371. data/lib/hecks/ports/agent.rb +47 -172
  372. data/lib/hecks/ports/authentication.rb +12 -39
  373. data/lib/hecks/ports/authorization.rb +16 -43
  374. data/lib/hecks/ports/clock.rb +5 -45
  375. data/lib/hecks/ports/extraction.rb +7 -12
  376. data/lib/hecks/ports/identity_assignment.rb +8 -29
  377. data/lib/hecks/ports/identity_generation.rb +6 -29
  378. data/lib/hecks/ports/identity_resolution.rb +2 -10
  379. data/lib/hecks/ports/key_vault.rb +9 -45
  380. data/lib/hecks/ports/persistence/append_only.rb +61 -64
  381. data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
  382. data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
  383. data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
  384. data/lib/hecks/ports/persistence/plugin.rb +13 -42
  385. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +44 -144
  386. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
  387. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +20 -85
  388. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +6 -25
  389. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +18 -24
  390. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
  391. data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
  392. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
  393. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
  394. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
  395. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
  396. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
  397. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
  398. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +19 -76
  399. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
  400. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
  401. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
  402. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
  403. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
  404. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +43 -127
  405. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
  406. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +131 -555
  407. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
  408. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
  409. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
  410. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
  411. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
  412. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
  413. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
  414. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +50 -47
  415. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -124
  416. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
  417. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
  418. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
  419. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
  420. data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
  421. data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
  422. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
  423. data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
  424. data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
  425. data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
  426. data/lib/hecks/ports/persistence/state_codec.rb +47 -108
  427. data/lib/hecks/ports/persistence.rb +7 -14
  428. data/lib/hecks/ports/projection.rb +42 -84
  429. data/lib/hecks/ports/query/in_memory.rb +10 -58
  430. data/lib/hecks/ports/query/ordering.rb +11 -32
  431. data/lib/hecks/ports/query.rb +14 -37
  432. data/lib/hecks/ports.rb +2 -8
  433. data/lib/hecks/projection_files.rb +285 -0
  434. data/lib/hecks/projections/bootstrap_table.rb +12 -44
  435. data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
  436. data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
  437. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
  438. data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
  439. data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
  440. data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
  441. data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
  442. data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
  443. data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
  444. data/lib/hecks/projections/deploy/fargate.rb +40 -207
  445. data/lib/hecks/projections/deploy/lambda.rb +191 -708
  446. data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
  447. data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
  448. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  449. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
  450. data/lib/hecks/projections/deploy/preview/script.rb +2 -14
  451. data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
  452. data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
  453. data/lib/hecks/projections/deploy/preview/template.rb +3 -59
  454. data/lib/hecks/projections/deploy/preview.rb +2 -67
  455. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
  456. data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
  457. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
  458. data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
  459. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
  460. data/lib/hecks/projections/deploy/scripts.rb +4 -57
  461. data/lib/hecks/projections/deploy/shared.rb +48 -147
  462. data/lib/hecks/projections/deploy/smoke.rb +4 -61
  463. data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
  464. data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
  465. data/lib/hecks/projections/deploy/template_diff.rb +5 -57
  466. data/lib/hecks/projections/diagrams.rb +22 -584
  467. data/lib/hecks/projections/glossary/html.rb +14 -130
  468. data/lib/hecks/projections/glossary/markdown.rb +8 -69
  469. data/lib/hecks/projections/glossary/mermaid.rb +9 -66
  470. data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
  471. data/lib/hecks/projections/glossary/sentences.rb +14 -159
  472. data/lib/hecks/projections/glossary.rb +30 -222
  473. data/lib/hecks/projections/ir.rb +3 -10
  474. data/lib/hecks/projections/model/deviations.rb +16 -43
  475. data/lib/hecks/projections/model.rb +12 -51
  476. data/lib/hecks/projections/oidc.rb +12 -56
  477. data/lib/hecks/projections/parser_table.rb +13 -32
  478. data/lib/hecks/projections/reference.rb +4 -18
  479. data/lib/hecks/projections/rust_vocabulary.rb +13 -251
  480. data/lib/hecks/projections/shape.rb +3 -29
  481. data/lib/hecks/projections/statements.rb +7 -86
  482. data/lib/hecks/projections/vocabulary.rb +6 -22
  483. data/lib/hecks/projections.rb +2 -16
  484. data/lib/hecks/projector/cli_projector.rb +154 -400
  485. data/lib/hecks/projector/docs_projector.rb +10 -201
  486. data/lib/hecks/projector/exporter.rb +42 -134
  487. data/lib/hecks/projector/ir_projector.rb +2 -9
  488. data/lib/hecks/projector/narrate_projector.rb +15 -189
  489. data/lib/hecks/projector/target.rb +13 -76
  490. data/lib/hecks/projector.rb +30 -121
  491. data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
  492. data/lib/hecks/quality_control/adapters/agent.rb +58 -0
  493. data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
  494. data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
  495. data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
  496. data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
  497. data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
  498. data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
  499. data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
  500. data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
  501. data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
  502. data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
  503. data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
  504. data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
  505. data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
  506. data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
  507. data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
  508. data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
  509. data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
  510. data/lib/hecks/quality_control/cli/child.rb +45 -0
  511. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
  512. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
  513. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
  514. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
  515. data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
  516. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
  517. data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
  518. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
  519. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
  520. data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
  521. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
  522. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
  523. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
  524. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
  525. data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
  526. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
  527. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
  528. data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
  529. data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
  530. data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
  531. data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
  532. data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
  533. data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
  534. data/lib/hecks/query_ir.rb +346 -0
  535. data/lib/hecks/query_ir_mcp.rb +198 -0
  536. data/lib/hecks/query_specification/common/comparators.rb +5 -26
  537. data/lib/hecks/query_specification/common/comparison.rb +35 -143
  538. data/lib/hecks/query_specification/common/dsl.rb +8 -24
  539. data/lib/hecks/query_specification/common/null_policy.rb +20 -70
  540. data/lib/hecks/query_specification/common/options.rb +2 -6
  541. data/lib/hecks/query_specification/common/where_clause.rb +2 -10
  542. data/lib/hecks/query_specification/field_path.rb +37 -77
  543. data/lib/hecks/query_specification/hop_path.rb +12 -76
  544. data/lib/hecks/release/gem_pin.rb +2 -26
  545. data/lib/hecks/release/runner/ci_publisher.rb +5 -8
  546. data/lib/hecks/release/runner/clean_tree.rb +42 -0
  547. data/lib/hecks/release/runner/commands.rb +2 -4
  548. data/lib/hecks/release/runner/console.rb +6 -0
  549. data/lib/hecks/release/runner/gem_publisher.rb +15 -9
  550. data/lib/hecks/release/runner/npm_publisher.rb +9 -53
  551. data/lib/hecks/release/runner/preflight.rb +29 -20
  552. data/lib/hecks/release/runner/published.rb +8 -26
  553. data/lib/hecks/release/runner/tagger.rb +31 -23
  554. data/lib/hecks/release/runner.rb +60 -38
  555. data/lib/hecks/rendering.rb +4 -26
  556. data/lib/hecks/router/namespace_installer.rb +15 -39
  557. data/lib/hecks/router.rb +4 -8
  558. data/lib/hecks/runtime/adapter_lookup.rb +124 -0
  559. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  560. data/lib/hecks/runtime/boot_gates.rb +3 -18
  561. data/lib/hecks/runtime/caller.rb +19 -59
  562. data/lib/hecks/runtime/capability_graph.rb +5 -21
  563. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  564. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +27 -160
  565. data/lib/hecks/runtime/command_interpreter.rb +124 -306
  566. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  567. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  568. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  569. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  570. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  571. data/lib/hecks/runtime/dependency_planning.rb +50 -104
  572. data/lib/hecks/runtime/dispatcher.rb +134 -243
  573. data/lib/hecks/runtime/entity_element.rb +40 -462
  574. data/lib/hecks/runtime/entity_interpreter.rb +64 -219
  575. data/lib/hecks/runtime/errors.rb +32 -115
  576. data/lib/hecks/runtime/event.rb +3 -25
  577. data/lib/hecks/runtime/identity.rb +18 -154
  578. data/lib/hecks/runtime/instance.rb +18 -150
  579. data/lib/hecks/runtime/interpreting.rb +14 -70
  580. data/lib/hecks/runtime/invocation.rb +28 -164
  581. data/lib/hecks/runtime/loader.rb +109 -194
  582. data/lib/hecks/runtime/outbox.rb +39 -262
  583. data/lib/hecks/runtime/policy_interpreter.rb +43 -202
  584. data/lib/hecks/runtime/port_operation_interpreter.rb +28 -97
  585. data/lib/hecks/runtime/query_interpreter.rb +122 -187
  586. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  587. data/lib/hecks/runtime/reaction_outcome.rb +89 -0
  588. data/lib/hecks/runtime/read_model_interpreter.rb +117 -208
  589. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  590. data/lib/hecks/runtime/reference_hop.rb +8 -81
  591. data/lib/hecks/runtime/refusal_wording.rb +28 -87
  592. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  593. data/lib/hecks/runtime/registry/verification.rb +213 -227
  594. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  595. data/lib/hecks/runtime/registry.rb +156 -247
  596. data/lib/hecks/runtime/remote_dispatcher.rb +68 -154
  597. data/lib/hecks/runtime/routing.rb +4 -15
  598. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  599. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  600. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  601. data/lib/hecks/runtime/tenant_check.rb +6 -68
  602. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  603. data/lib/hecks/runtime/value/admission.rb +16 -67
  604. data/lib/hecks/runtime/value/coercion.rb +162 -760
  605. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  606. data/lib/hecks/runtime/value/field_checks.rb +172 -0
  607. data/lib/hecks/runtime/value.rb +15 -84
  608. data/lib/hecks/runtime.rb +35 -69
  609. data/lib/hecks/rust_build/conformance.rb +158 -0
  610. data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
  611. data/lib/hecks/rust_build/coverage.rb +218 -0
  612. data/lib/hecks/rust_build/native_build.rb +124 -0
  613. data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
  614. data/lib/hecks/rust_build/project_rust.rb +332 -0
  615. data/lib/hecks/rust_build/wasm.rb +105 -0
  616. data/lib/hecks/rust_build/wasm_browser.rb +113 -0
  617. data/lib/hecks/rust_build.rb +144 -0
  618. data/lib/hecks/storehouse.rb +155 -582
  619. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +10 -34
  620. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  621. data/lib/hecks/three_zero/forms.yml +127 -0
  622. data/lib/hecks/three_zero.rb +12 -0
  623. data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
  624. data/lib/hecks/tools/comment_style.rb +1070 -0
  625. data/lib/hecks/tools/corpus_report.rb +87 -0
  626. data/lib/hecks/tools/deploy_recipe.rb +255 -0
  627. data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
  628. data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
  629. data/lib/hecks/tools/era_projection_backfill.rb +94 -0
  630. data/lib/hecks/tools/era_reattest.rb +146 -0
  631. data/lib/hecks/tools/evolve_run.rb +189 -0
  632. data/lib/hecks/tools/fuzz_sweep.rb +373 -0
  633. data/lib/hecks/tools/heki_compaction.rb +100 -0
  634. data/lib/hecks/tools/hoist_local_givens.rb +246 -0
  635. data/lib/hecks/tools/journal_compaction.rb +89 -0
  636. data/lib/hecks/tools/oidc_manifests.rb +70 -0
  637. data/lib/hecks/tools/query_ir_run.rb +81 -0
  638. data/lib/hecks/tools/regeneration_run.rb +166 -0
  639. data/lib/hecks/tools/rust_comment_style.rb +642 -0
  640. data/lib/hecks/tools/sequence_script.rb +62 -0
  641. data/lib/hecks/tools/tail_merge.rb +117 -0
  642. data/lib/hecks/tools/tenant_provisioning.rb +142 -0
  643. data/lib/hecks/tools/translation_audit.rb +208 -0
  644. data/lib/hecks/tools/translation_scaffold.rb +131 -0
  645. data/lib/hecks/tools.rb +86 -0
  646. data/lib/hecks/vendoring/git_environment.rb +2 -8
  647. data/lib/hecks/vendoring/git_source.rb +4 -10
  648. data/lib/hecks/vendoring.rb +7 -46
  649. data/lib/hecks/version.rb +3 -14
  650. data/lib/hecks.rb +52 -90
  651. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  652. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  653. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  654. data/qa/settings.yml +66 -0
  655. data/rust/.cargo/config.toml +6 -0
  656. data/rust/.gitignore +2 -0
  657. data/rust/Cargo.lock +54 -0
  658. data/rust/Cargo.toml +61 -0
  659. data/rust/build/.cargo/config.toml +3 -0
  660. data/rust/build/.gitignore +1 -0
  661. data/rust/build/Cargo.lock +7 -0
  662. data/rust/build/Cargo.toml +19 -0
  663. data/rust/build/src/build_artifact.rs +47 -0
  664. data/rust/build/src/cargo_sync.rs +248 -0
  665. data/rust/build/src/json.rs +421 -0
  666. data/rust/build/src/lineage_pass.rs +285 -0
  667. data/rust/build/src/main.rs +51 -0
  668. data/rust/build/src/optional_pass.rs +150 -0
  669. data/rust/build/src/pipeline.rs +264 -0
  670. data/rust/build/src/reserved_names.rs +76 -0
  671. data/rust/build/src/resolve.rs +124 -0
  672. data/rust/build/src/root.rs +25 -0
  673. data/rust/build/src/sidecars.rs +73 -0
  674. data/rust/build/src/subprocess.rs +51 -0
  675. data/rust/build/src/tmp.rs +31 -0
  676. data/rust/codegen/.cargo/config.toml +3 -0
  677. data/rust/codegen/.gitignore +1 -0
  678. data/rust/codegen/Cargo.lock +7 -0
  679. data/rust/codegen/Cargo.toml +20 -0
  680. data/rust/codegen/src/attr.rs +32 -0
  681. data/rust/codegen/src/bridging.rs +481 -0
  682. data/rust/codegen/src/commands.rs +1226 -0
  683. data/rust/codegen/src/constraints.rs +109 -0
  684. data/rust/codegen/src/dependency_planning.rs +209 -0
  685. data/rust/codegen/src/domain_generator.rs +1288 -0
  686. data/rust/codegen/src/exemplar.rs +230 -0
  687. data/rust/codegen/src/expr_emitter.rs +87 -0
  688. data/rust/codegen/src/fielded.rs +192 -0
  689. data/rust/codegen/src/hecks_naming.rs +33 -0
  690. data/rust/codegen/src/json.rs +335 -0
  691. data/rust/codegen/src/json_codec.rs +697 -0
  692. data/rust/codegen/src/literal.rs +207 -0
  693. data/rust/codegen/src/main.rs +290 -0
  694. data/rust/codegen/src/manifest.rs +227 -0
  695. data/rust/codegen/src/mutations.rs +784 -0
  696. data/rust/codegen/src/naming.rs +286 -0
  697. data/rust/codegen/src/ports.rs +208 -0
  698. data/rust/codegen/src/prelude.rs +116 -0
  699. data/rust/codegen/src/queries.rs +786 -0
  700. data/rust/codegen/src/reactions.rs +532 -0
  701. data/rust/codegen/src/read_models.rs +697 -0
  702. data/rust/codegen/src/reference_specs.rs +40 -0
  703. data/rust/codegen/src/registry.rs +966 -0
  704. data/rust/codegen/src/reserved_names.rs +76 -0
  705. data/rust/codegen/src/shared.rs +93 -0
  706. data/rust/codegen/src/skip_reason.rs +25 -0
  707. data/rust/codegen/src/types.rs +362 -0
  708. data/rust/host/.cargo/config.toml +3 -0
  709. data/rust/host/.gitignore +1 -0
  710. data/rust/host/Cargo.lock +4297 -0
  711. data/rust/host/Cargo.toml +61 -0
  712. data/rust/host/HECKS_RELEASE +1 -0
  713. data/rust/host/rds-ca-bundle.pem +2736 -0
  714. data/rust/host/src/api.rs +1596 -0
  715. data/rust/host/src/approval.rs +652 -0
  716. data/rust/host/src/auth.rs +1287 -0
  717. data/rust/host/src/bin/expr_harness.rs +46 -0
  718. data/rust/host/src/bin/lineage_harness.rs +119 -0
  719. data/rust/host/src/bin/mint_harness.rs +135 -0
  720. data/rust/host/src/checkout.rs +265 -0
  721. data/rust/host/src/dispatch.rs +1443 -0
  722. data/rust/host/src/expr_json.rs +1117 -0
  723. data/rust/host/src/field_hints.rs +52 -0
  724. data/rust/host/src/ir.rs +733 -0
  725. data/rust/host/src/journal.rs +874 -0
  726. data/rust/host/src/lambda_client.rs +378 -0
  727. data/rust/host/src/log.rs +147 -0
  728. data/rust/host/src/main.rs +474 -0
  729. data/rust/host/src/mint.rs +1865 -0
  730. data/rust/host/src/payments/keystore.rs +330 -0
  731. data/rust/host/src/payments/tests.rs +1400 -0
  732. data/rust/host/src/payments.rs +767 -0
  733. data/rust/host/src/presentation.rs +698 -0
  734. data/rust/host/src/presentation_write.rs +1431 -0
  735. data/rust/host/src/query_step.rs +149 -0
  736. data/rust/host/src/rate_limit.rs +904 -0
  737. data/rust/host/src/reference_transform.rs +241 -0
  738. data/rust/host/src/reference_validate.rs +338 -0
  739. data/rust/host/src/resend.rs +296 -0
  740. data/rust/host/src/secrets.rs +106 -0
  741. data/rust/host/src/server.rs +663 -0
  742. data/rust/host/src/storage_shape.rs +175 -0
  743. data/rust/host/src/test_pg.rs +40 -0
  744. data/rust/host/src/ui_schema.rs +1351 -0
  745. data/rust/host/src/wasm_runner.rs +191 -0
  746. data/rust/host/src/web/newsletter/cooldown.rs +126 -0
  747. data/rust/host/src/web/newsletter/tests.rs +104 -0
  748. data/rust/host/src/web/newsletter.rs +310 -0
  749. data/rust/host/src/web/newsletter_send.rs +357 -0
  750. data/rust/host/src/web/registrations.rs +1505 -0
  751. data/rust/host/src/web.rs +2937 -0
  752. data/rust/lsp/.gitignore +1 -0
  753. data/rust/lsp/Cargo.lock +7 -0
  754. data/rust/lsp/Cargo.toml +16 -0
  755. data/rust/lsp/README.md +133 -0
  756. data/rust/lsp/src/diagnostics.rs +165 -0
  757. data/rust/lsp/src/json.rs +385 -0
  758. data/rust/lsp/src/main.rs +399 -0
  759. data/rust/lsp/src/outline.rs +177 -0
  760. data/rust/lsp/src/rpc.rs +67 -0
  761. data/rust/parser/.cargo/config.toml +5 -0
  762. data/rust/parser/.gitignore +1 -0
  763. data/rust/parser/Cargo.lock +7 -0
  764. data/rust/parser/Cargo.toml +21 -0
  765. data/rust/parser/src/build/closed_sets.rs +27 -0
  766. data/rust/parser/src/build/identity.rs +276 -0
  767. data/rust/parser/src/build/mod.rs +12 -0
  768. data/rust/parser/src/build/naming.rs +141 -0
  769. data/rust/parser/src/build/pattern_subset.rs +161 -0
  770. data/rust/parser/src/build/query_derive.rs +72 -0
  771. data/rust/parser/src/build/query_inference.rs +269 -0
  772. data/rust/parser/src/build/query_options.rs +100 -0
  773. data/rust/parser/src/build/read_model.rs +94 -0
  774. data/rust/parser/src/build/references.rs +32 -0
  775. data/rust/parser/src/canonical.rs +114 -0
  776. data/rust/parser/src/diag.rs +55 -0
  777. data/rust/parser/src/emit.rs +953 -0
  778. data/rust/parser/src/expr/ast_json.rs +147 -0
  779. data/rust/parser/src/expr/evaluator.rs +81 -0
  780. data/rust/parser/src/expr/mod.rs +84 -0
  781. data/rust/parser/src/expr/resolver.rs +497 -0
  782. data/rust/parser/src/ir.rs +387 -0
  783. data/rust/parser/src/keywords.rs +585 -0
  784. data/rust/parser/src/lex.rs +662 -0
  785. data/rust/parser/src/main.rs +252 -0
  786. data/rust/parser/src/parse/aggregate.rs +612 -0
  787. data/rust/parser/src/parse/chapter.rs +517 -0
  788. data/rust/parser/src/parse/command.rs +637 -0
  789. data/rust/parser/src/parse/domain_port.rs +127 -0
  790. data/rust/parser/src/parse/entity.rs +527 -0
  791. data/rust/parser/src/parse/file.rs +67 -0
  792. data/rust/parser/src/parse/hecksagon.rs +231 -0
  793. data/rust/parser/src/parse/lifecycle.rs +162 -0
  794. data/rust/parser/src/parse/mod.rs +1262 -0
  795. data/rust/parser/src/parse/policy.rs +87 -0
  796. data/rust/parser/src/parse/process_manager.rs +305 -0
  797. data/rust/parser/src/parse/query.rs +100 -0
  798. data/rust/parser/src/parse/read_model.rs +214 -0
  799. data/rust/parser/src/parse/value_object.rs +250 -0
  800. data/rust/parser/src/ruby_value.rs +422 -0
  801. data/rust/project/bridging.rb +257 -0
  802. data/rust/project/commands.rb +877 -0
  803. data/rust/project/constraints.rb +86 -0
  804. data/rust/project/dependency_planning.rb +178 -0
  805. data/rust/project/domain_generator.rb +980 -0
  806. data/rust/project/exemplar.rb +148 -0
  807. data/rust/project/expr_emitter.rb +72 -0
  808. data/rust/project/fielded.rb +141 -0
  809. data/rust/project/json_codec.rb +641 -0
  810. data/rust/project/mutations.rb +721 -0
  811. data/rust/project/naming.rb +205 -0
  812. data/rust/project/ports.rb +65 -0
  813. data/rust/project/queries.rb +493 -0
  814. data/rust/project/reactions.rb +317 -0
  815. data/rust/project/read_models.rb +690 -0
  816. data/rust/project/reference_specs.rb +27 -0
  817. data/rust/project/registry.rb +396 -0
  818. data/rust/project/skip_reason.rb +29 -0
  819. data/rust/project/types.rb +225 -0
  820. data/rust/project/write_if_changed.rb +83 -0
  821. data/rust/project.rb +30 -0
  822. data/rust/project_rust_pipeline.rb +383 -0
  823. data/rust/src/exemplar/commands.rs +279 -0
  824. data/rust/src/exemplar/constraints.rs +18 -0
  825. data/rust/src/exemplar/fielded.rs +280 -0
  826. data/rust/src/exemplar/json.rs +277 -0
  827. data/rust/src/exemplar/mod.rs +17 -0
  828. data/rust/src/exemplar/mutations.rs +80 -0
  829. data/rust/src/exemplar/queries.rs +33 -0
  830. data/rust/src/exemplar/reactions.rs +88 -0
  831. data/rust/src/exemplar/read_models.rs +48 -0
  832. data/rust/src/exemplar/registry.rs +166 -0
  833. data/rust/src/exemplar/types.rs +44 -0
  834. data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
  835. data/rust/src/kernel/attribute_shapes/list.rs +18 -0
  836. data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
  837. data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
  838. data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
  839. data/rust/src/kernel/cli.rs +585 -0
  840. data/rust/src/kernel/dispatch.rs +1090 -0
  841. data/rust/src/kernel/expr.rs +458 -0
  842. data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
  843. data/rust/src/kernel/expression_operators/comparison.rs +48 -0
  844. data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
  845. data/rust/src/kernel/expression_operators/logical.rs +13 -0
  846. data/rust/src/kernel/expression_operators/membership.rs +18 -0
  847. data/rust/src/kernel/expression_operators/mod.rs +68 -0
  848. data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
  849. data/rust/src/kernel/expression_operators/positional.rs +126 -0
  850. data/rust/src/kernel/expression_operators/presence.rs +101 -0
  851. data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
  852. data/rust/src/kernel/expression_operators/sized.rs +35 -0
  853. data/rust/src/kernel/expression_operators/text.rs +79 -0
  854. data/rust/src/kernel/expression_operators/to_string.rs +18 -0
  855. data/rust/src/kernel/json.rs +779 -0
  856. data/rust/src/kernel/mod.rs +133 -0
  857. data/rust/src/kernel/named_query.rs +219 -0
  858. data/rust/src/kernel/orchestrate.rs +1810 -0
  859. data/rust/src/kernel/pattern.rs +307 -0
  860. data/rust/src/kernel/query_comparators.rs +206 -0
  861. data/rust/src/kernel/query_ordering.rs +103 -0
  862. data/rust/src/kernel/read_model.rs +914 -0
  863. data/rust/src/kernel/reference_lookup.rs +149 -0
  864. data/rust/src/kernel/repository.rs +468 -0
  865. data/rust/src/kernel/routing.rs +612 -0
  866. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
  867. data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
  868. data/rust/src/kernel/vocab/field_hint.rs +70 -0
  869. data/rust/src/kernel/vocab/mod.rs +20 -0
  870. data/rust/src/kernel/vocab/query_comparator.rs +65 -0
  871. data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
  872. data/rust/src/lib.rs +8 -0
  873. data/rust/src/main.rs +16 -0
  874. data/rust/web/.gitignore +1 -0
  875. data/rust/web/Cargo.lock +119 -0
  876. data/rust/web/Cargo.toml +18 -0
  877. data/rust/web/src/lib.rs +8 -0
  878. metadata +478 -12
  879. data/lib/hecks/facade/cli_door.rb +0 -178
  880. data/lib/hecks/facade/cli_runner.rb +0 -271
  881. data/lib/hecks/facade/handle.rb +0 -293
  882. data/lib/hecks/facade/json_door.rb +0 -247
  883. data/lib/hecks/facade/surface/aggregate_door.rb +0 -222
  884. data/lib/hecks/facade/surface/chapter.rb +0 -116
  885. data/lib/hecks/facade/surface.rb +0 -61
  886. data/lib/hecks/mcp_door_scope.rb +0 -176
@@ -0,0 +1,190 @@
1
+ module Hecks
2
+ module Fuzzing
3
+ module Properties
4
+ # Guard and authorization properties over a replayed history: refusals name declared rules,
5
+ # tenant scoping holds, and guard violations are refused.
6
+ module Guards
7
+ # Checks tenant-scoped query answers and refusals against TenantScope.apply's contract.
8
+ #
9
+ # Uses hand-built fixtures: the only corpus query declaring `authorize` takes no
10
+ # attributes, so the generator cannot supply a `tenant:`.
11
+ # rubocop:disable-next Metrics/CyclomaticComplexity
12
+ # rubocop:disable-next Metrics/PerceivedComplexity
13
+ #
14
+ # @param history [Hash] a replayed history as returned by `Replay.call`
15
+ # @return [true, String] true if every answer and refusal agrees with the contract
16
+ def authorize_scopes_or_refuses(history)
17
+ bluebooks = history.fetch(:bluebooks)
18
+
19
+ offenders = history.fetch(:queries).filter_map do |asked|
20
+ next unless asked[:query].is_a?(String) && asked[:query].include?("::")
21
+
22
+ declared = query_for_verb(bluebooks, asked[:query])
23
+ authorization = declared&.authorization
24
+ tenant = authorization&.tenant&.to_sym
25
+ next unless tenant
26
+
27
+ args = asked[:args] || {}
28
+ tenant_given = args.key?(tenant)
29
+
30
+ if asked[:error]
31
+ next if tenant_given
32
+ next if asked[:error].to_s.include?("declares authorize with tenant: #{tenant}")
33
+
34
+ "#{asked[:query]} #{args.inspect} refused with no #{tenant}: given, but not with the declared " \
35
+ "tenant_required wording (#{asked[:error]})"
36
+ elsif !tenant_given
37
+ "#{asked[:query]} #{args.inspect} answered successfully with no #{tenant}: given, but #{declared.name} " \
38
+ "declares authorize with tenant: #{tenant}"
39
+ else
40
+ wanted = args[tenant].to_s
41
+ mismatched = asked[:rows].find do |row|
42
+ Ports::Query::InMemory.comparable(QuerySpecification::FieldPath.dig(row, tenant)).to_s != wanted
43
+ end
44
+ next unless mismatched
45
+
46
+ "#{asked[:query]} #{args.inspect} answered a row whose #{tenant} disagrees with the given " \
47
+ "#{wanted.inspect}: #{mismatched.inspect}"
48
+ end
49
+ end
50
+
51
+ offenders.empty? || offenders.join("; ")
52
+ end
53
+
54
+ # Raised classes that mark a guard refusal; the message alone cannot tell it from
55
+ # the other "X refused — Y" wordings.
56
+ GUARD_REFUSAL_KINDS = %w[Hecks::Runtime::GivenNotMet Hecks::Runtime::EnsuresNotMet].freeze
57
+
58
+ # Checks that every given/ensures refusal quotes a description its command declares.
59
+ def guard_refusals_are_declared(history)
60
+ bluebooks = history.fetch(:bluebooks)
61
+
62
+ offenders = history.fetch(:refusals).filter_map do |refusal|
63
+ next unless GUARD_REFUSAL_KINDS.include?(refusal[:kind])
64
+
65
+ match = refusal[:error].to_s.match(/\A(.+) refused — (.+)\z/)
66
+ next "#{refusal[:verb]} raised #{refusal[:kind]} with unparseable message #{refusal[:error].inspect}" unless match
67
+
68
+ command = command_for_verb(bluebooks, refusal[:verb])
69
+ next "#{refusal[:verb]} raised #{refusal[:kind]}, but no declared command resolves that verb" unless command
70
+
71
+ declared = effective_guard_descriptions(bluebooks, refusal[:verb], command)
72
+ next if declared.include?(match[2])
73
+
74
+ "#{refusal[:verb]} refused — #{match[2].inspect} — but #{command.hecks_name} declares no given " \
75
+ "or ensures with that description (it declares #{declared.inspect})"
76
+ end
77
+
78
+ offenders.empty? || offenders.join("; ")
79
+ end
80
+
81
+ # Checks that no stored record references a record carrying a different tenant value.
82
+ #
83
+ # Commands cannot declare `authorize`, so nothing refuses a cross-tenant write.
84
+ # Tenants compare via Query::InMemory.comparable: value objects with different
85
+ # declared names are unequal under Value#== even for the same tenant.
86
+ # rubocop:disable-next Metrics/CyclomaticComplexity
87
+ # rubocop:disable-next Metrics/PerceivedComplexity
88
+ def commands_respect_tenant_scope(history)
89
+ bluebooks = history.fetch(:bluebooks)
90
+ instances = history.fetch(:instances)
91
+
92
+ offenders = instances.flat_map do |key, state|
93
+ domain_name = key.split("::").first
94
+ aggregate_name = key.split("::").last.split("#").first
95
+ aggregate = bluebooks[domain_name]&.aggregate(aggregate_name)
96
+ next [] unless aggregate
97
+
98
+ own_tenant_field = tenant_field_for(aggregate)
99
+ next [] unless own_tenant_field && state.key?(own_tenant_field)
100
+
101
+ own_tenant = Ports::Query::InMemory.comparable(state[own_tenant_field])
102
+
103
+ aggregate.attributes.filter_map do |attribute|
104
+ next unless attribute.type.is_a?(Bluebook::Reference)
105
+
106
+ target = bluebooks[domain_name]&.aggregate(attribute.type.target_name)
107
+ target_tenant_field = target && tenant_field_for(target)
108
+ next unless target_tenant_field
109
+
110
+ target_id = state[attribute.name]
111
+ next unless target_id
112
+
113
+ target_state = instances["#{domain_name}::#{target.name}##{target_id}"]
114
+ next unless target_state&.key?(target_tenant_field)
115
+
116
+ target_tenant = Ports::Query::InMemory.comparable(target_state[target_tenant_field])
117
+ next if target_tenant == own_tenant
118
+
119
+ "#{key} (#{own_tenant_field}: #{own_tenant.inspect}) references #{attribute.name}: #{target_id.inspect}, " \
120
+ "but #{domain_name}::#{target.name}##{target_id} carries #{target_tenant_field}: " \
121
+ "#{target_tenant.inspect} — a cross-tenant write nothing refused"
122
+ end
123
+ end
124
+
125
+ offenders.empty? || offenders.join("; ")
126
+ end
127
+
128
+ # The field named by `authorize ..., tenant:` on any of the aggregate's queries, or nil.
129
+ def tenant_field_for(aggregate)
130
+ authorization = aggregate.queries.filter_map(&:authorization).find(&:tenant)
131
+ authorization&.tenant&.to_sym
132
+ end
133
+
134
+ # A command's own guard descriptions plus those of every command it delegates to.
135
+ #
136
+ # A delegating door refuses with its target's words, so both sets count. Resolved
137
+ # against the full `bluebooks` map: a verb's domain is not always `history[:bluebook]`.
138
+ def effective_guard_descriptions(bluebooks, verb, command)
139
+ own = command.guard_descriptions
140
+ delegated = command.mutations.select { |m| m.op == :delegate }.flat_map do |delegation|
141
+ domain, aggregate_name, = Naming.split_verb(verb)
142
+ target = command_for_verb(bluebooks, "#{domain}::#{aggregate_name}.#{delegation.target}")
143
+ target ? target.guard_descriptions : []
144
+ end
145
+ own + delegated
146
+ end
147
+
148
+ # Resolves a dispatched verb to the declared command it names, or nil.
149
+ def command_for_verb(bluebooks, verb)
150
+ domain, aggregate_name, command_path = Naming.split_verb(verb)
151
+ return nil unless command_path
152
+
153
+ bluebook = bluebooks[domain]
154
+ return nil unless bluebook
155
+
156
+ aggregate = bluebook.aggregate(aggregate_name)
157
+ return nil unless aggregate
158
+
159
+ if command_path.include?(".")
160
+ entity_name, sub = command_path.split(".", 2)
161
+ entity = aggregate.entities.find { |e| e.hecks_name == entity_name }
162
+ entity&.command(sub)
163
+ else
164
+ aggregate.command(command_path)
165
+ end
166
+ end
167
+
168
+ # Recomputes enforce_givens/enforce_lifecycle_guard against Replay's pre-dispatch
169
+ # snapshot and compares the result with what the real dispatch did.
170
+ #
171
+ # Catches a guard that silently stopped firing, which never appears in
172
+ # history[:refusals]. Any other refusal class, or a success, counts as "did not fire".
173
+ #
174
+ # @param history [Hash] a replayed history as returned by `Replay.call`
175
+ # @return [true, String] true if the recomputed and actual outcomes agree for every check
176
+ def lifecycle_guard_and_given_violations_are_refused(history)
177
+ offenders = history.fetch(:guard_checks).filter_map do |check|
178
+ next if check[:recomputed_refused] == check[:actual_refused]
179
+
180
+ "#{check[:verb]} — independently recomputing enforce_givens/enforce_lifecycle_guard against the " \
181
+ "pre-dispatch state says #{check[:recomputed_refused] ? "refused (#{check[:recomputed_kind]})" : 'admitted'}, " \
182
+ "but the real dispatch #{check[:actual_refused] ? "refused (#{check[:actual_kind]})" : 'admitted it'}"
183
+ end
184
+
185
+ offenders.empty? || offenders.join("; ")
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,300 @@
1
+ module Hecks
2
+ module Fuzzing
3
+ module Properties
4
+ # Stored-record, saga-rehydration, fan-out, and read-model-aggregation
5
+ # properties, extended into Properties.
6
+ module InvariantsAndAggregation
7
+ # Every stored record — and every entity nested inside it — still
8
+ # satisfies its own declared invariants, re-checked independently of
9
+ # whichever call site (Admissibility#enforce_invariants) was supposed
10
+ # to have refused a violation live.
11
+ def stored_records_satisfy_declared_invariants(history)
12
+ bluebooks = history.fetch(:bluebooks)
13
+
14
+ offenders = history.fetch(:instances).filter_map do |key, state|
15
+ domain_name = key.split("::").first
16
+ aggregate_name = key.split("::").last.split("#").first
17
+ bluebook = bluebooks[domain_name]
18
+ aggregate = bluebook&.aggregate(aggregate_name)
19
+ next unless aggregate
20
+
21
+ violated = aggregate.invariants.find do |invariant|
22
+ !Bluebook::Expression::Evaluator.call(invariant.canonical, state)
23
+ end
24
+ next "#{key} violates #{aggregate_name}'s own declared invariant #{violated.description.inspect}" if violated
25
+
26
+ check_piece_invariants(aggregate, state, key)
27
+ end
28
+
29
+ offenders.empty? || offenders.join("; ")
30
+ end
31
+
32
+ # A piece's own invariant, checked against every element its owner's
33
+ # `list_of` field holds — reapplies Admissibility#check_entity_invariants'
34
+ # own lookup against a plain Hash state rather than a live Instance.
35
+ def check_piece_invariants(owner_construct, owner_state, key)
36
+ owner_construct.entities.each do |entity|
37
+ next if entity.invariants.empty?
38
+
39
+ list_attr = owner_construct.attributes.find { |a| a.list? && a.type.to_s == entity.hecks_name }
40
+ next unless list_attr
41
+
42
+ Array(owner_state[list_attr.name]).each do |element|
43
+ violated = entity.invariants.find do |invariant|
44
+ !Bluebook::Expression::Evaluator.call(invariant.canonical, element)
45
+ end
46
+ if violated
47
+ return "#{key}'s own #{entity.hecks_name} violates its declared invariant " \
48
+ "#{violated.description.inspect}"
49
+ end
50
+
51
+ nested = check_piece_invariants(entity, element, key)
52
+ return nested if nested
53
+ end
54
+ end
55
+ nil
56
+ end
57
+
58
+ # A saga instance's own state is one the process manager declares, and
59
+ # its memory survives the same JSON round-trip `SagaInterpreter#checkpoint`'s
60
+ # own `deep_copy` performs (mirrored here, a private instance method
61
+ # with no registry to hand it).
62
+ def sagas_rehydrate_cleanly(history)
63
+ bluebook = history.fetch(:bluebook)
64
+ process_managers = bluebook.process_managers.to_h { |pm| [pm.name, pm] }
65
+
66
+ offenders = history.fetch(:saga_instances).flat_map do |pm_name, conversations|
67
+ pm = process_managers[pm_name]
68
+
69
+ conversations.filter_map do |correlation, instance|
70
+ problems = []
71
+
72
+ problems << "holds state #{instance[:state].inspect}, which #{pm_name} never declares" \
73
+ if pm && !pm.declares_state?(instance[:state])
74
+
75
+ rehydrated = JSON.parse(JSON.generate(instance[:memory]), symbolize_names: true)
76
+ if rehydrated != instance[:memory]
77
+ problems << "memory does not survive its own checkpoint round-trip " \
78
+ "(checkpointed #{instance[:memory].inspect}, rehydrated #{rehydrated.inspect})"
79
+ end
80
+
81
+ next if problems.empty?
82
+
83
+ "#{pm_name}##{correlation.inspect}: #{problems.join(' and ')}"
84
+ end
85
+ end
86
+
87
+ offenders.empty? || offenders.join("; ")
88
+ end
89
+
90
+ # A `for_each` policy dispatches exactly once per row its declared query
91
+ # answers, checked against `Replay.expected_fan_out_rows`'s independent
92
+ # computation. `expected_row_ids` is `nil`, not `[]`, when `policy.where`
93
+ # never held — no dispatch is the claim then, not "dispatched to zero rows."
94
+ def fanout_dispatches_once_per_matching_row(history)
95
+ offenders = history.fetch(:fan_outs).filter_map do |finding|
96
+ expected = finding[:expected_row_ids]
97
+ actual = finding[:actual_row_ids].sort
98
+
99
+ if expected.nil?
100
+ next if actual.empty?
101
+
102
+ next "#{finding[:policy]} on #{finding[:on]}: where did not hold, but dispatched to #{actual.inspect}"
103
+ end
104
+
105
+ next if actual == expected
106
+
107
+ "#{finding[:policy]} on #{finding[:on]}: for_each answered #{expected.inspect}, " \
108
+ "but the reaction log shows dispatches to #{actual.inspect}"
109
+ end
110
+
111
+ offenders.empty? || offenders.join("; ")
112
+ end
113
+
114
+ # A `count`/`median` report's reduced scalar matches the same reduction
115
+ # done independently over the same eligible rows, reusing FieldPath.dig
116
+ # and InMemory.comparable/.holds? — the same calls the interpreter
117
+ # itself makes, so this oracle can't drift from what a field read or a
118
+ # `where` clause means without the interpreter drifting identically.
119
+ # rubocop:disable-next Metrics/CyclomaticComplexity
120
+ # rubocop:disable-next Metrics/PerceivedComplexity
121
+ def aggregation_matches_recompute(history)
122
+ bluebook = history.fetch(:bluebook)
123
+
124
+ offenders = history.fetch(:queries).filter_map do |asked|
125
+ next if asked[:error]
126
+
127
+ domain, name = asked[:query].to_s.split(".", 2)
128
+ next unless name && domain == bluebook.name
129
+
130
+ model = bluebook.read_model(name)
131
+ next unless model&.reducing?
132
+
133
+ reduced_head = model.aggregate_heads.find { |head| head[:many] }
134
+ next unless reduced_head
135
+
136
+ rows = eligible_rows(bluebook, asked.fetch(:instances_at), domain, model, reduced_head, asked[:args] || {})
137
+ expected = recompute_reduction(model, rows)
138
+ actual = asked[:rows]&.first&.dig(reduced_head[:as])
139
+ next if actual == expected
140
+
141
+ "#{asked[:query]} #{asked[:args].inspect} answered #{actual.inspect} for #{reduced_head[:as]}, " \
142
+ "but recomputing independently from #{rows.length} eligible row(s) gives #{expected.inspect}"
143
+ end
144
+
145
+ offenders.empty? || offenders.join("; ")
146
+ end
147
+
148
+ # ADR 0078's siblings of `count`/`median`, each an independent fold over the same
149
+ # eligible rows — never reusing ReadModelInterpreter#reduce's own arithmetic, only the
150
+ # shared field-reading primitives `recompute_median` already relied on.
151
+ def recompute_reduction(model, rows)
152
+ return rows.length if model.count?
153
+ return recompute_median(rows, model.median_field) if model.median_field
154
+ return recompute_values(rows, model.sum_field).sum if model.sum_field
155
+ return recompute_avg(rows, model.avg_field) if model.avg_field
156
+ return recompute_values(rows, model.min_field).min if model.min_field
157
+ return recompute_values(rows, model.max_field).max if model.max_field
158
+ return recompute_percentile(rows, model.percentile_field, model.percentile_at) if model.percentile_field
159
+ return recompute_values(rows, model.any_field).any? if model.any_field
160
+
161
+ recompute_values(rows, model.all_field).all?
162
+ end
163
+
164
+ def recompute_values(rows, field)
165
+ rows.map { |state| Ports::Query::InMemory.comparable(QuerySpecification::FieldPath.dig(state, field)) }.compact
166
+ end
167
+
168
+ def recompute_avg(rows, field)
169
+ values = recompute_values(rows, field)
170
+ values.empty? ? nil : values.sum.to_f / values.length
171
+ end
172
+
173
+ # Same linear-interpolation formula as `ReadModelInterpreter#percentile`, written
174
+ # independently here rather than called, for the same reason `recompute_median`
175
+ # already is: an oracle that calls the interpreter's own fold agrees with a bug in it
176
+ # by construction (ADR 0061's `nest_rows` lesson).
177
+ def recompute_percentile(rows, field, at)
178
+ values = recompute_values(rows, field).sort
179
+ return nil if values.empty?
180
+
181
+ position = at * (values.length - 1)
182
+ lower = position.floor
183
+ fraction = position - lower
184
+ fraction.zero? ? values[lower] : values[lower] + (fraction * (values[lower + 1] - values[lower]))
185
+ end
186
+
187
+ # aggregation_matches_recompute's own shape, extended from reducing a
188
+ # many-side head to a scalar to nesting it (ADR 0061, decision D1): a
189
+ # group_by leaf holds one row, so two eligible rows sharing a full key
190
+ # path not covering identity means the ask must have refused.
191
+ def group_by_matches_recompute(history)
192
+ bluebook = history.fetch(:bluebook)
193
+
194
+ offenders = history.fetch(:queries).filter_map do |asked|
195
+ domain, name = asked[:query].to_s.split(".", 2)
196
+ next unless name && domain == bluebook.name
197
+
198
+ model = bluebook.read_model(name)
199
+ next unless model&.group_by&.any?
200
+
201
+ grouped_head = model.aggregate_heads.find { |head| head[:many] }
202
+ next unless grouped_head
203
+
204
+ group_by_offense(bluebook, asked, model, grouped_head)
205
+ end
206
+
207
+ offenders.empty? || offenders.join("; ")
208
+ end
209
+
210
+ # Judges one `group_by` ask against the rows it was eligible to see:
211
+ # refused exactly when two of them share a checked key path, and
212
+ # otherwise nested exactly as `nest_rows` nests them.
213
+ def group_by_offense(bluebook, asked, model, grouped_head)
214
+ rows = eligible_rows(bluebook, asked.fetch(:instances_at), bluebook.name, model, grouped_head, asked[:args] || {})
215
+ materialized = rows.map { |state| Runtime::Value.materialize_unwrapped(state) }
216
+ shared = shared_key_paths(materialized, model.group_by_fields)
217
+ checked = !model.groups_by_identity?(bluebook.aggregate(grouped_head[:aggregate]))
218
+ return collision_offense(asked, model.group_by_fields, shared.length, checked) if shared.any?
219
+ return nil if asked[:error]
220
+ return nil if asked[:rows]&.first&.dig(grouped_head[:as]) == nest_rows(materialized, model.group_by_fields)
221
+
222
+ "#{asked[:query]} #{asked[:args].inspect} answered a #{grouped_head[:as]} grouping that disagrees " \
223
+ "with independently nesting group_by #{model.group_by_fields.inspect} over #{rows.length} " \
224
+ "eligible row(s)"
225
+ end
226
+
227
+ # Judges an ask whose eligible rows share at least one full key path:
228
+ # a checked key path must have refused, and an identity-covering one
229
+ # cannot be shared by rows that hold their identity.
230
+ def collision_offense(asked, fields, shared, checked)
231
+ return nil if checked && asked[:error]
232
+
233
+ "#{asked[:query]} #{asked[:args].inspect} #{checked ? 'answered' : 'reached'}, but #{shared} key " \
234
+ "path(s) of group_by #{fields.inspect} are shared by more than one eligible row, " \
235
+ "#{checked ? 'so the ask must refuse' : 'though they cover the identity'}"
236
+ end
237
+
238
+ # Every full `group_by` key path more than one row reaches, found by
239
+ # tallying each row's tuple of grouped values, with no nesting at all.
240
+ def shared_key_paths(rows, fields)
241
+ rows.map { |row| fields.map { |field| row[field] } }.tally.select { |_, count| count > 1 }.keys
242
+ end
243
+
244
+ # One level of nesting per `group_by` field in declared order; the leaf
245
+ # is the row with every grouped field stripped. Called only when no key
246
+ # path is shared (`shared_key_paths` rules that out), so each leaf holds
247
+ # exactly one row; a second row raises rather than being picked over.
248
+ def nest_rows(rows, fields)
249
+ field, *rest = fields
250
+ rows.group_by { |row| row[field] }.transform_values do |group|
251
+ stripped = group.map { |row| row.reject { |key, _| key == field } }
252
+ next nest_rows(stripped, rest) unless rest.empty?
253
+ raise ArgumentError, "nest_rows reached a key path #{stripped.length} rows share" unless stripped.length == 1
254
+
255
+ stripped[0]
256
+ end
257
+ end
258
+
259
+ # The eligible rows a `count`/`median` head reduces: every instance of
260
+ # the reduced head's own aggregate, FK-matched against the report's root
261
+ # reference (if any), then narrowed by the report's own `where` clauses.
262
+ def eligible_rows(bluebook, instances, domain, model, reduced_head, args)
263
+ aggregate = bluebook.aggregate(reduced_head[:aggregate])
264
+ prefix = "#{domain}::#{reduced_head[:aggregate]}#"
265
+ # `id:` merged in, the same shape every live head row carries —
266
+ # count/median never read it, but group_by's own nesting does.
267
+ rows = instances.filter_map { |key, state| state.merge(id: key.split("#").last) if key.start_with?(prefix) }
268
+
269
+ if model.reference_target
270
+ reference_id = args[model.reference_name].to_s
271
+ fk_fields = aggregate.attributes.select do |attribute|
272
+ attribute.reference? && attribute.type.target_name == model.reference_target.to_s
273
+ end.map(&:name)
274
+
275
+ rows = rows.select { |state| fk_fields.any? { |field| state[field].to_s == reference_id } }
276
+ end
277
+
278
+ rows.select do |state|
279
+ model.wheres.all? do |clause|
280
+ held = Ports::Query::InMemory.comparable(QuerySpecification::FieldPath.dig(state, clause.field))
281
+ Ports::Query::InMemory.holds?(clause, held, args)
282
+ end
283
+ end
284
+ end
285
+
286
+ # ReadModelInterpreter#median's own definition, reproduced: the true
287
+ # middle for an odd count, the average of the two middles for an even
288
+ # count, `nil` for empty — never zero, so "nothing" isn't "zero."
289
+ def recompute_median(rows, field)
290
+ values = rows.map { |state| Ports::Query::InMemory.comparable(QuerySpecification::FieldPath.dig(state, field)) }
291
+ .compact.sort
292
+ return nil if values.empty?
293
+
294
+ middle = values.length / 2
295
+ values.length.odd? ? values[middle] : (values[middle - 1] + values[middle]) / 2.0
296
+ end
297
+ end
298
+ end
299
+ end
300
+ end
@@ -0,0 +1,93 @@
1
+ require_relative "../nondeterministic"
2
+
3
+ module Hecks
4
+ module Fuzzing
5
+ module Properties
6
+ # Properties over declared lifecycle states, saga edges and replay determinism.
7
+ module LifecycleAndReplay
8
+ # Every lifecycle field a replay leaves holds one of the aggregate's declared states.
9
+ #
10
+ # Uses the full state set (`ModelCheck.full_states`), not just `Lifecycle#states`.
11
+ #
12
+ # @param history [Hash] a replayed history, as returned by `Fuzzing::Replay.call`
13
+ # @return [true, String] true, or a message naming each offending instance
14
+ def lifecycle_values_are_declared(history)
15
+ bluebook = history.fetch(:bluebook)
16
+ declared = {}
17
+ bluebook.aggregates.each do |aggregate|
18
+ declared[aggregate.hecks_name] = Bluebook::ModelCheck.full_states(aggregate.lifecycle) if aggregate.lifecycle
19
+ end
20
+ return true if declared.empty?
21
+
22
+ offenders = history.fetch(:instances).filter_map do |key, state|
23
+ aggregate_name = key.split("::").last.split("#").first
24
+ states = declared[aggregate_name]
25
+ next unless states
26
+
27
+ lifecycle = bluebook.aggregate(aggregate_name).lifecycle
28
+ value = state[lifecycle.field]
29
+ next if value.nil? || states.include?(value.to_s)
30
+
31
+ "#{key} holds #{lifecycle.field}=#{value.inspect}, which #{aggregate_name} never declares as a state"
32
+ end
33
+
34
+ offenders.empty? || offenders.join("; ")
35
+ end
36
+
37
+ # Every logged saga advance follows a `(from_state, to_state)` pair some handler declares.
38
+ #
39
+ # @param history [Hash] a replayed history as returned by `Replay.call`
40
+ # @return [true, String] true, or a message naming the process manager and undeclared pair
41
+ def saga_advances_follow_declared_handlers(history)
42
+ bluebook = history.fetch(:bluebook)
43
+ edges = Hash.new { |h, k| h[k] = [] }
44
+ bluebook.process_managers.each do |pm|
45
+ pm.handlers.each { |handler| edges[pm.name] << [handler.from_state, handler.to_state] }
46
+ end
47
+ return true if edges.empty?
48
+
49
+ offenders = history.fetch(:sagas).filter_map do |entry|
50
+ next unless entry[:advanced]
51
+
52
+ pair = [entry[:from], entry[:to]]
53
+ next if edges[entry[:process_manager]].include?(pair)
54
+
55
+ "#{entry[:process_manager]} advanced #{pair.inspect}, which no declared handler names"
56
+ end
57
+
58
+ offenders.empty? || offenders.join("; ")
59
+ end
60
+
61
+ # Replaying the same steps on a fresh boot yields identical events, refusals and instances.
62
+ #
63
+ # The runtime mints no identity, so drift is nondeterminism (a clock read, hash order).
64
+ # Two independent replays are compared so a corrupted first run cannot agree with itself.
65
+ #
66
+ # @param domain_path [String] the domain directory to boot, such as `"examples/pizzas"`
67
+ # @param steps [Array<Hash>] the step list to replay twice
68
+ # @param adapter [Symbol] persistence adapter (`:memory`, `:postgres`, or `:postgres_era`)
69
+ # @return [true, String] true, or a message naming the step count that diverged
70
+ def replay_is_deterministic(domain_path, steps, adapter: :memory)
71
+ first = Replay.call(domain_path, steps, adapter: adapter)
72
+ second = Replay.call(domain_path, steps, adapter: adapter)
73
+
74
+ # What is stripped is declared in `Nondeterministic::FIELDS`. Outbox rows are stripped
75
+ # per row, not dropped, so status, consumer and event payload stay checked.
76
+ strip_outbox_nondeterminism = lambda do |history|
77
+ traces = Array(history[:outbox_traces]).map do |trace|
78
+ trace.merge(rows: trace[:rows].map do |row|
79
+ Nondeterministic.strip(row, :outbox_row).merge(event: Nondeterministic.strip(row[:event], :event))
80
+ end)
81
+ end
82
+ history.merge(outbox_traces: traces)
83
+ end
84
+
85
+ comparable = ->(history) { Nondeterministic.strip(strip_outbox_nondeterminism.call(history), :history) }
86
+ return true if comparable.call(first) == comparable.call(second)
87
+
88
+ "two replays of the same #{steps.length} steps produced different histories"
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,89 @@
1
+ require_relative "../../bluebook/expression/evaluator"
2
+
3
+ module Hecks
4
+ module Fuzzing
5
+ module Properties
6
+ # Checks a replay's `history[:outbox_traces]` against the transactional outbox contract
7
+ # (`Runtime::Outbox`): inline delivery, and no silent policy drops.
8
+ #
9
+ # A `saga:` row is exempt from the reaction_log check: `begin_saga` and `end_saga` have
10
+ # ordinary no-op paths that log nothing, so a delivered saga row without one is fine.
11
+ module Outbox
12
+ # Every outbox row drained inline and no delivered policy row lacks its reaction.
13
+ #
14
+ # @param history [Hash] a replayed history, as returned by `Fuzzing::Replay.call`
15
+ # @return [true, String] true, or a message naming each offending row
16
+ def outbox_rows_match_reactions(history)
17
+ bluebooks = history.fetch(:bluebooks, {})
18
+
19
+ offenders = Array(history[:outbox_traces]).flat_map do |trace|
20
+ trace[:rows].flat_map { |row| outbox_row_offenders(row, trace, bluebooks) }
21
+ end
22
+
23
+ offenders.empty? || offenders.join("; ")
24
+ end
25
+
26
+ # Offending messages for one row: zero or one, empty when the status names no check.
27
+ def outbox_row_offenders(row, trace, bluebooks)
28
+ on = row.dig(:event, :name)
29
+
30
+ case row[:status]
31
+ when "pending", "claimed"
32
+ ["outbox row #{row[:delivery_id]} (#{row[:consumer]} on #{on}) never drained inline — status stayed " \
33
+ "#{row[:status].inspect} though delivery is inline by contract (Runtime::Outbox's own header)"]
34
+ when "failed"
35
+ ["outbox row #{row[:delivery_id]} (#{row[:consumer]} on #{on}) failed to deliver: #{row[:error]} — " \
36
+ "a domain refusal never reaches this far; a failed row names a defect in the relay's own consumer " \
37
+ "resolution"]
38
+ when "delivered"
39
+ outbox_delivered_policy_offenders(row, on, trace, bluebooks)
40
+ else
41
+ []
42
+ end
43
+ end
44
+
45
+ # Offending messages for a delivered `policy:` row that has no `reaction_log` entry.
46
+ #
47
+ # Legitimate only when the policy's `where` does not hold; a saga row gets no such check.
48
+ def outbox_delivered_policy_offenders(row, on, trace, bluebooks)
49
+ kind, fqn = row[:consumer].to_s.split(":", 2)
50
+ return [] unless kind == "policy"
51
+
52
+ home, name = fqn.to_s.split("::", 2)
53
+ return [] if trace[:reactions].any? { |entry| entry[:policy] == name && entry[:on] == on }
54
+
55
+ policy = bluebooks[home]&.policies&.find { |candidate| candidate.name == name }
56
+ # Undeclared policy: inconclusive, not a mismatch.
57
+ return [] unless policy
58
+ # Fan-out counts belong to fanout_dispatches_once_per_matching_row.
59
+ return [] if policy.fans_out?
60
+
61
+ held = independently_re_evaluate_policy_where(policy, row[:event])
62
+ # False or inconclusive (the where raised): never a mismatch.
63
+ return [] if held != true
64
+
65
+ ["outbox row #{row[:delivery_id]} (#{row[:consumer]} on #{on}) drained as delivered, but no matching " \
66
+ "reaction_log entry exists and the policy's own where clause independently re-evaluates true — " \
67
+ "PolicyInterpreter#deliver only ever returns nil (no reaction_log entry) when where does not hold"]
68
+ end
69
+
70
+ # Re-evaluates the policy's `where` against the row's recorded payload.
71
+ #
72
+ # Reproduces `PolicyInterpreter#where_holds?` rather than calling it, which would only
73
+ # agree with itself. Rescues to nil: an unevaluable where is inconclusive, not false.
74
+ #
75
+ # @param policy [Bluebook::Policy] the policy whose `where` clause is re-evaluated
76
+ # @param event [Hash] the outbox row's own recorded event, read for `:payload`
77
+ # @return [Boolean, nil] whether `where` holds, or nil if it cannot be re-evaluated
78
+ def independently_re_evaluate_policy_where(policy, event)
79
+ return true if policy.where.to_s.empty?
80
+
81
+ payload = (event[:payload] || {}).transform_keys(&:to_sym)
82
+ Bluebook::Expression::Evaluator.call(policy.where, {}, payload)
83
+ rescue StandardError
84
+ nil
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end