hecks 1.3.0 → 1.5.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 (373) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +72 -7
  3. data/lib/hecks/adapters/driven/d1.rb +187 -23
  4. data/lib/hecks/adapters/driven/folder.rb +83 -10
  5. data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
  6. data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
  7. data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
  8. data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
  9. data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
  10. data/lib/hecks/adapters/driven/heki.rb +106 -9
  11. data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
  12. data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
  13. data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
  15. data/lib/hecks/adapters/driven/lambda/client.rb +67 -14
  16. data/lib/hecks/adapters/driven/lambda.rb +82 -35
  17. data/lib/hecks/adapters/driven/local_storage.rb +83 -10
  18. data/lib/hecks/adapters/driven/memory.rb +205 -9
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -1
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
  24. data/lib/hecks/adapters/driven/postgres.rb +175 -28
  25. data/lib/hecks/adapters/driven/prism.rb +50 -11
  26. data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
  27. data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
  28. data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
  29. data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
  30. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
  31. data/lib/hecks/adapters/driven/sqlite.rb +181 -21
  32. data/lib/hecks/adapters/driven/system_clock.rb +3 -0
  33. data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
  34. data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
  35. data/lib/hecks/adapters/driven.rb +6 -4
  36. data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
  37. data/lib/hecks/behaviors/dsl.rb +60 -2
  38. data/lib/hecks/behaviors/expectations.rb +190 -29
  39. data/lib/hecks/behaviors/ir.rb +12 -1
  40. data/lib/hecks/behaviors/rspec.rb +9 -1
  41. data/lib/hecks/behaviors/runner.rb +21 -2
  42. data/lib/hecks/behaviors.rb +9 -1
  43. data/lib/hecks/bluebook/aggregate.rb +43 -13
  44. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
  45. data/lib/hecks/bluebook/assembly/build.rb +26 -5
  46. data/lib/hecks/bluebook/assembly/contract.rb +98 -23
  47. data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
  48. data/lib/hecks/bluebook/assembly/marks.rb +159 -30
  49. data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
  50. data/lib/hecks/bluebook/assembly.rb +32 -14
  51. data/lib/hecks/bluebook/attribute.rb +26 -12
  52. data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
  53. data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
  54. data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
  55. data/lib/hecks/bluebook/behaviour/command.rb +55 -25
  56. data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
  57. data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
  58. data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
  59. data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
  60. data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
  61. data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
  62. data/lib/hecks/bluebook/behaviour/query.rb +6 -1
  63. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
  64. data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
  65. data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
  66. data/lib/hecks/bluebook/capabilities.rb +27 -0
  67. data/lib/hecks/bluebook/chapter.rb +51 -9
  68. data/lib/hecks/bluebook/command.rb +62 -17
  69. data/lib/hecks/bluebook/domain_port.rb +34 -9
  70. data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
  71. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
  72. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +282 -123
  73. data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
  74. data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
  75. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
  76. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
  77. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
  78. data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
  79. data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
  80. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
  81. data/lib/hecks/bluebook/dsl/entity_builder.rb +191 -61
  82. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
  83. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
  84. data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
  85. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
  86. data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
  87. data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
  88. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
  89. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +111 -47
  90. data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
  91. data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
  92. data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
  93. data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
  94. data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
  95. data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
  96. data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
  97. data/lib/hecks/bluebook/entity.rb +40 -11
  98. data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
  99. data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
  100. data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
  101. data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
  102. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
  103. data/lib/hecks/bluebook/expression/resolver.rb +369 -128
  104. data/lib/hecks/bluebook/hexagon.rb +35 -1
  105. data/lib/hecks/bluebook/lifecycle.rb +12 -1
  106. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
  107. data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
  108. data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
  109. data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
  110. data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
  111. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
  112. data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
  113. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
  114. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
  115. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
  116. data/lib/hecks/bluebook/meta_validator.rb +235 -139
  117. data/lib/hecks/bluebook/model_check.rb +434 -104
  118. data/lib/hecks/bluebook/pattern_subset.rb +32 -10
  119. data/lib/hecks/bluebook/policy.rb +15 -13
  120. data/lib/hecks/bluebook/process_manager.rb +27 -14
  121. data/lib/hecks/bluebook/project_discovery.rb +5 -0
  122. data/lib/hecks/bluebook/project_loader.rb +40 -0
  123. data/lib/hecks/bluebook/project_register.rb +50 -6
  124. data/lib/hecks/bluebook/query.rb +31 -4
  125. data/lib/hecks/bluebook/read_model.rb +35 -15
  126. data/lib/hecks/bluebook/reference.rb +26 -13
  127. data/lib/hecks/bluebook/smoke_test.rb +46 -23
  128. data/lib/hecks/bluebook/synthesizer.rb +46 -12
  129. data/lib/hecks/bluebook/translation.rb +34 -5
  130. data/lib/hecks/bluebook/value_object.rb +29 -11
  131. data/lib/hecks/bluebook.rb +5 -6
  132. data/lib/hecks/codemod.rb +138 -50
  133. data/lib/hecks/construct.rb +21 -7
  134. data/lib/hecks/corpus.rb +438 -0
  135. data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
  136. data/lib/hecks/doc/reference.rb +200 -31
  137. data/lib/hecks/embryonaut_bluebook.rb +38 -15
  138. data/lib/hecks/facade/cli_door.rb +69 -10
  139. data/lib/hecks/facade/cli_runner.rb +105 -24
  140. data/lib/hecks/facade/command_request.rb +23 -0
  141. data/lib/hecks/facade/handle.rb +155 -35
  142. data/lib/hecks/facade/json_door.rb +106 -25
  143. data/lib/hecks/facade/surface/aggregate_door.rb +50 -27
  144. data/lib/hecks/facade/surface/chapter.rb +26 -17
  145. data/lib/hecks/facade/surface.rb +16 -3
  146. data/lib/hecks/facade.rb +15 -4
  147. data/lib/hecks/forms/app.rb +46 -30
  148. data/lib/hecks/forms/command_form_renderer.rb +70 -9
  149. data/lib/hecks/forms/field_renderer.rb +142 -6
  150. data/lib/hecks/forms/field_shape.rb +183 -19
  151. data/lib/hecks/forms/html.rb +51 -7
  152. data/lib/hecks/forms/index_renderer.rb +14 -2
  153. data/lib/hecks/forms/page.rb +14 -0
  154. data/lib/hecks/forms/params.rb +120 -23
  155. data/lib/hecks/forms/port_argument.rb +14 -2
  156. data/lib/hecks/forms/query_form_renderer.rb +65 -2
  157. data/lib/hecks/forms/record_renderer.rb +60 -2
  158. data/lib/hecks/forms/record_table.rb +28 -1
  159. data/lib/hecks/forms/reference_options.rb +24 -0
  160. data/lib/hecks/forms/value_object_shape.rb +13 -3
  161. data/lib/hecks/forms.rb +24 -4
  162. data/lib/hecks/fqn.rb +59 -1
  163. data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
  164. data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
  165. data/lib/hecks/framework/oidc.json +15 -0
  166. data/lib/hecks/framework.rb +79 -25
  167. data/lib/hecks/freezer.rb +27 -11
  168. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
  169. data/lib/hecks/fuzzing/combination_miner.rb +178 -0
  170. data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
  171. data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
  172. data/lib/hecks/fuzzing/differential.rb +192 -0
  173. data/lib/hecks/fuzzing/domain_generator.rb +871 -0
  174. data/lib/hecks/fuzzing/era_boundary.rb +68 -19
  175. data/lib/hecks/fuzzing/form_census.rb +121 -20
  176. data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
  177. data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
  178. data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
  179. data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
  180. data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
  181. data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
  182. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
  183. data/lib/hecks/fuzzing/properties/guards.rb +86 -42
  184. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
  185. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
  186. data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
  187. data/lib/hecks/fuzzing/properties/querying.rb +82 -28
  188. data/lib/hecks/fuzzing/properties.rb +84 -51
  189. data/lib/hecks/fuzzing/qa_settings.rb +164 -0
  190. data/lib/hecks/fuzzing/replay.rb +241 -133
  191. data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
  192. data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
  193. data/lib/hecks/fuzzing/self_consistency.rb +315 -137
  194. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
  195. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
  196. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
  197. data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
  198. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +59 -32
  199. data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
  200. data/lib/hecks/fuzzing/shrinker.rb +309 -0
  201. data/lib/hecks/fuzzing/structural_skips.rb +37 -130
  202. data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
  203. data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
  204. data/lib/hecks/fuzzing/value_generator.rb +110 -22
  205. data/lib/hecks/fuzzing.rb +5 -0
  206. data/lib/hecks/grammar/evolve.rb +188 -12
  207. data/lib/hecks/grammar.rb +53 -7
  208. data/lib/hecks/ir.rb +51 -20
  209. data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
  210. data/lib/hecks/language/bluebook/policy.bluebook +11 -1
  211. data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
  212. data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
  213. data/lib/hecks/language/oidc.json +5 -0
  214. data/lib/hecks/literal.rb +41 -9
  215. data/lib/hecks/naming.rb +112 -31
  216. data/lib/hecks/ports/access_control.rb +53 -2
  217. data/lib/hecks/ports/agent/answers.rb +83 -6
  218. data/lib/hecks/ports/agent.rb +119 -35
  219. data/lib/hecks/ports/authentication.rb +44 -4
  220. data/lib/hecks/ports/authorization.rb +50 -11
  221. data/lib/hecks/ports/clock.rb +42 -23
  222. data/lib/hecks/ports/extraction.rb +16 -0
  223. data/lib/hecks/ports/identity_assignment.rb +23 -2
  224. data/lib/hecks/ports/identity_generation.rb +17 -3
  225. data/lib/hecks/ports/identity_resolution.rb +17 -1
  226. data/lib/hecks/ports/key_vault.port +6 -0
  227. data/lib/hecks/ports/key_vault.rb +58 -0
  228. data/lib/hecks/ports/loading.rb +4 -0
  229. data/lib/hecks/ports/persistence/append_only.rb +172 -8
  230. data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
  231. data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
  232. data/lib/hecks/ports/persistence/execution.rb +4 -0
  233. data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
  234. data/lib/hecks/ports/persistence/plugin.rb +42 -4
  235. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
  236. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
  237. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
  238. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
  239. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
  242. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
  243. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
  244. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
  245. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
  246. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +34 -5
  247. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
  248. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
  249. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
  250. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
  251. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
  252. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +95 -6
  253. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
  254. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
  255. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
  256. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
  257. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
  258. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +48 -8
  259. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
  260. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
  261. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
  262. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +58 -19
  263. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
  264. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
  265. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -1
  266. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
  267. data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
  268. data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
  269. data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
  270. data/lib/hecks/ports/persistence/state_codec.rb +319 -0
  271. data/lib/hecks/ports/persistence.rb +36 -1
  272. data/lib/hecks/ports/projection.rb +61 -7
  273. data/lib/hecks/ports/query/in_memory.rb +41 -8
  274. data/lib/hecks/ports/query/ordering.rb +21 -6
  275. data/lib/hecks/ports/query.rb +35 -0
  276. data/lib/hecks/ports.rb +1 -0
  277. data/lib/hecks/projections/bootstrap_table.rb +147 -0
  278. data/lib/hecks/projections/diagrams.rb +315 -79
  279. data/lib/hecks/projections/glossary/html.rb +91 -3
  280. data/lib/hecks/projections/glossary/markdown.rb +50 -3
  281. data/lib/hecks/projections/glossary/mermaid.rb +49 -1
  282. data/lib/hecks/projections/glossary/sections.rb +1 -1
  283. data/lib/hecks/projections/glossary/sentences.rb +110 -5
  284. data/lib/hecks/projections/glossary.rb +173 -31
  285. data/lib/hecks/projections/ir.rb +1 -1
  286. data/lib/hecks/projections/model/deviations.rb +62 -17
  287. data/lib/hecks/projections/model.rb +75 -21
  288. data/lib/hecks/projections/oidc.rb +45 -8
  289. data/lib/hecks/projections/parser_table.rb +53 -12
  290. data/lib/hecks/projections/reference.rb +15 -3
  291. data/lib/hecks/projections/rust_vocabulary.rb +646 -0
  292. data/lib/hecks/projections/shape.rb +10 -2
  293. data/lib/hecks/projections/statements.rb +69 -22
  294. data/lib/hecks/projections/vocabulary.rb +26 -9
  295. data/lib/hecks/projections.rb +5 -3
  296. data/lib/hecks/projector/cli_projector.rb +245 -37
  297. data/lib/hecks/projector/docs_projector.rb +154 -28
  298. data/lib/hecks/projector/exporter.rb +104 -29
  299. data/lib/hecks/projector/ir_projector.rb +7 -1
  300. data/lib/hecks/projector/narrate_projector.rb +144 -23
  301. data/lib/hecks/projector/target.rb +42 -18
  302. data/lib/hecks/projector.rb +86 -18
  303. data/lib/hecks/query_ir.rb +94 -47
  304. data/lib/hecks/query_specification/common/comparators.rb +19 -3
  305. data/lib/hecks/query_specification/common/comparison.rb +109 -27
  306. data/lib/hecks/query_specification/common/dsl.rb +65 -9
  307. data/lib/hecks/query_specification/common/null_policy.rb +61 -15
  308. data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
  309. data/lib/hecks/query_specification/common/options.rb +25 -0
  310. data/lib/hecks/query_specification/field_path.rb +69 -15
  311. data/lib/hecks/query_specification/hop_path.rb +57 -20
  312. data/lib/hecks/query_specification/read_model/specification.rb +4 -0
  313. data/lib/hecks/rendering.rb +9 -3
  314. data/lib/hecks/router/namespace_installer.rb +16 -3
  315. data/lib/hecks/router.rb +56 -1
  316. data/lib/hecks/runtime/aggregate_lock.rb +20 -11
  317. data/lib/hecks/runtime/boot_gates.rb +21 -3
  318. data/lib/hecks/runtime/caller.rb +40 -8
  319. data/lib/hecks/runtime/capability_graph.rb +13 -2
  320. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
  321. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
  322. data/lib/hecks/runtime/command_interpreter.rb +195 -113
  323. data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
  324. data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
  325. data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
  326. data/lib/hecks/runtime/command_rules/emission.rb +18 -1
  327. data/lib/hecks/runtime/command_rules/references.rb +145 -55
  328. data/lib/hecks/runtime/command_rules.rb +3 -0
  329. data/lib/hecks/runtime/dependency_planning.rb +56 -11
  330. data/lib/hecks/runtime/dispatcher.rb +263 -92
  331. data/lib/hecks/runtime/entity_element.rb +304 -67
  332. data/lib/hecks/runtime/entity_interpreter.rb +149 -88
  333. data/lib/hecks/runtime/errors.rb +37 -23
  334. data/lib/hecks/runtime/event.rb +15 -10
  335. data/lib/hecks/runtime/identity.rb +91 -23
  336. data/lib/hecks/runtime/instance.rb +103 -18
  337. data/lib/hecks/runtime/interpreting.rb +24 -16
  338. data/lib/hecks/runtime/invocation.rb +358 -0
  339. data/lib/hecks/runtime/loader.rb +105 -19
  340. data/lib/hecks/runtime/outbox.rb +164 -26
  341. data/lib/hecks/runtime/policy_interpreter.rb +73 -60
  342. data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
  343. data/lib/hecks/runtime/query_interpreter.rb +93 -74
  344. data/lib/hecks/runtime/reaction_invocation.rb +73 -28
  345. data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
  346. data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
  347. data/lib/hecks/runtime/reference_hop.rb +48 -6
  348. data/lib/hecks/runtime/refusal_wording.rb +142 -115
  349. data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
  350. data/lib/hecks/runtime/registry/verification.rb +153 -28
  351. data/lib/hecks/runtime/registry.rb +202 -32
  352. data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
  353. data/lib/hecks/runtime/routing.rb +36 -154
  354. data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
  355. data/lib/hecks/runtime/saga_interpreter.rb +90 -76
  356. data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
  357. data/lib/hecks/runtime/tenant_check.rb +33 -13
  358. data/lib/hecks/runtime/tenant_scope.rb +23 -5
  359. data/lib/hecks/runtime/value/admission.rb +75 -30
  360. data/lib/hecks/runtime/value/coercion.rb +555 -142
  361. data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
  362. data/lib/hecks/runtime/value.rb +71 -21
  363. data/lib/hecks/runtime.rb +39 -7
  364. data/lib/hecks/storehouse.rb +368 -72
  365. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
  366. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
  367. data/lib/hecks/version.rb +3 -3
  368. data/lib/hecks/vocabulary.rb +205 -4
  369. data/lib/hecks.rb +91 -11
  370. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
  371. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
  372. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
  373. metadata +28 -2
@@ -0,0 +1,161 @@
1
+ module Hecks
2
+ module Fuzzing
3
+ # What one sweep has already reached, and where the next seed starts.
4
+ #
5
+ # `SequenceGenerator` steers within one sequence (an unexercised verb is
6
+ # weighted up — picker.rb's `steer`), but without this, every seed of a
7
+ # sweep would start from nothing and forget everything the seeds before
8
+ # it reached. Roughly half of the QA ledger's bugs are refusal-kind splits (BUG#2–4,
9
+ # 7, 8, 14–16, 19–21, 23, 27, 28, 36–38, 41, 54, 56): a particular verb,
10
+ # in a particular state, with a particular malformed argument, refused
11
+ # one way on Ruby and another on Rust. BUG#11 was found by hand because
12
+ # a 25-step random walk never reached `Annotate` at all. Uniform seeds
13
+ # reach those corners by luck; this makes the sweep remember.
14
+ #
15
+ # ## The coverage unit
16
+ #
17
+ # A tuple, not a verb — `verb | step kind | lifecycle state before |
18
+ # adversarial mutation | outcome` (see `SequenceGenerator#coverage_tuple`).
19
+ # "Renew refused LifecycleRefused from free" and "Renew ok from held" are
20
+ # different places a runtime can be wrong, where a verb-level count
21
+ # calls them the same.
22
+ #
23
+ # ## Two levers
24
+ #
25
+ # Both expressed as plain generator arguments so a seed stays
26
+ # reproducible from one call:
27
+ #
28
+ # 1. Splicing (`prefix:`). A sequence that reached a new tuple joins
29
+ # the corpus, cut at its last new tuple. A later seed may start by
30
+ # re-generating a random-length prefix of a corpus entry — the same
31
+ # seed, favor and nested prefix, so the same steps, dispatched for
32
+ # real, reaching the same state — and then carries on with its own
33
+ # seed's randomness from there. Deep state gets reached once and
34
+ # explored many times, instead of re-rolled per seed.
35
+ # 2. Favor (`favor:`). The verbs this sweep has hit least often —
36
+ # including declared verbs it has never hit at all — are weighted
37
+ # up for the next seed.
38
+ #
39
+ # ## Reproducibility
40
+ #
41
+ # Nothing here is random at the campaign's own level beyond
42
+ # `Random.new(seed)` per plan, and a plan is printed with every finding
43
+ # (`bin/qa_sweep`'s `reproduce:` line), so no finding depends on
44
+ # re-running the whole campaign to get back to it.
45
+ class CoverageCampaign
46
+ # One seed's generation instructions, printable and reproducible.
47
+ Plan = Struct.new(:prefix, :favor, keyword_init: true) do
48
+ # Renders this plan as the keyword arguments a generator call accepts.
49
+ #
50
+ # @return [Hash{Symbol => Object}] `{ prefix:, favor: }`, the keyword arguments
51
+ # `SequenceGenerator.generate` and `.trace` accept for this plan
52
+ def generator_options = { prefix: prefix, favor: favor }
53
+
54
+ # Answers whether this plan starts from a spliced corpus prefix.
55
+ #
56
+ # @return [Boolean] whether this plan starts from a spliced corpus prefix
57
+ def spliced? = !prefix.nil?
58
+ end
59
+
60
+ attr_reader :corpus
61
+
62
+ # @param splice_probability [Float, Integer] chance, per seed with a non-empty
63
+ # corpus, of splicing a corpus entry's prefix instead of generating fresh
64
+ # @param favor_count [Integer, Float] how many rare verbs to favor per plan; 0
65
+ # or non-positive disables favoring entirely
66
+ # @param corpus_limit [Integer] maximum corpus entries kept; the oldest is
67
+ # dropped once this is exceeded
68
+ # @param max_prefix_depth [Integer] maximum nested-prefix depth a new corpus
69
+ # entry may reach before splicing stops extending it further
70
+ def initialize(splice_probability:, favor_count:, corpus_limit: 64, max_prefix_depth: 6)
71
+ @splice_probability = splice_probability.to_f
72
+ @favor_count = favor_count.to_i
73
+ @corpus_limit = corpus_limit
74
+ @max_prefix_depth = max_prefix_depth
75
+ @corpus = []
76
+ @seen = Set.new
77
+ @verb_hits = Hash.new(0)
78
+ @declared_verbs = Set.new
79
+ @seeds = 0
80
+ @spliced = 0
81
+ @seeds_with_new = 0
82
+ end
83
+
84
+ # Builds this seed's generation plan — a possible splice, plus which
85
+ # verbs to favor.
86
+ #
87
+ # @param seed [Integer] the seed this plan is for; drives the splice-or-not
88
+ # coin flip and, when splicing, which corpus entry and prefix length are drawn
89
+ # @return [Fuzzing::CoverageCampaign::Plan] the plan `SequenceGenerator.generate`
90
+ # or `.trace` should be run with for this seed
91
+ def plan(seed)
92
+ random = Random.new(seed)
93
+ prefix = nil
94
+ if !@corpus.empty? && random.rand < @splice_probability
95
+ entry = @corpus[random.rand(@corpus.size)]
96
+ prefix = entry[:spec].merge("steps" => random.rand(1..entry[:attempts]))
97
+ end
98
+ Plan.new(prefix: prefix, favor: rare_verbs)
99
+ end
100
+
101
+ # Folds one seed's generation results into the campaign's running
102
+ # coverage, and admits its corpus entry if it reached anything new.
103
+ #
104
+ # @param seed [Integer] the seed this trace was generated from
105
+ # @param plan [Fuzzing::CoverageCampaign::Plan] the plan that seed was generated with
106
+ # @param trace [Fuzzing::SequenceGenerator::Trace] `SequenceGenerator.trace`'s answer:
107
+ # `coverage` is `[[attempt_index, tuple], ...]`, `verbs` every verb the booted
108
+ # catalog offered
109
+ # @return [void]
110
+ def record(seed, plan, trace)
111
+ @seeds += 1
112
+ @spliced += 1 if plan.spliced?
113
+ @declared_verbs.merge(trace.verbs)
114
+
115
+ new_at = trace.coverage.filter_map do |index, tuple|
116
+ @verb_hits[tuple.split(" | ").first] += 1
117
+ index if @seen.add?(tuple)
118
+ end
119
+ return if new_at.empty?
120
+
121
+ @seeds_with_new += 1
122
+ admit(seed, plan, new_at.max + 1)
123
+ end
124
+
125
+ # Counts the distinct coverage tuples this campaign has reached so far.
126
+ #
127
+ # @return [Integer] the number of distinct coverage tuples seen
128
+ def tuples_seen = @seen.size
129
+
130
+ # Renders the campaign's running totals as one human-readable line.
131
+ #
132
+ # @return [String] a one-line summary of tuples seen, seeds run, seeds that
133
+ # reached something new, and how many were spliced from the corpus
134
+ def summary
135
+ "coverage: #{@seen.size} distinct (verb, kind, state, mutation, outcome) tuple(s) over #{@seeds} seed(s); " \
136
+ "#{@seeds_with_new} seed(s) reached something new; #{@spliced} spliced from a corpus of #{@corpus.size}"
137
+ end
138
+
139
+ private
140
+
141
+ def admit(seed, plan, attempts)
142
+ return if depth(plan.prefix) + 1 > @max_prefix_depth
143
+
144
+ spec = { "seed" => seed, "favor" => plan.favor }
145
+ spec["prefix"] = plan.prefix if plan.prefix
146
+ @corpus << { spec: spec, attempts: attempts }
147
+ @corpus.shift while @corpus.size > @corpus_limit
148
+ end
149
+
150
+ def depth(prefix) = prefix.nil? ? 0 : 1 + depth(prefix["prefix"])
151
+
152
+ # Never-hit declared verbs first (hits 0), then the least-hit, ties
153
+ # broken by name so a plan is a pure function of what was recorded.
154
+ def rare_verbs
155
+ return [] unless @favor_count.positive?
156
+
157
+ (@declared_verbs | @verb_hits.keys).min_by(@favor_count) { |verb| [@verb_hits[verb], verb] }
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,192 @@
1
+ require "json"
2
+ require "open3"
3
+ require_relative "replay"
4
+ require_relative "properties"
5
+ require_relative "self_consistency"
6
+ require_relative "rust_gap_manifest"
7
+ require_relative "nondeterministic"
8
+
9
+ module Hecks
10
+ module Fuzzing
11
+ # The Ruby-vs-Rust comparison of one generated sequence — moved here,
12
+ # unchanged, out of `bin/qa_sweep`'s own `diff_ruby_vs_rust` so a second
13
+ # caller (`bin/qa_generated_domains --rust`, which compares domains the
14
+ # generator wrote rather than ones on the rotation) runs the same
15
+ # comparison rather than a re-derived one. `bin/qa_sweep` delegates to
16
+ # it; its own comments on the known-gap filtering, the wire-precision
17
+ # reduction and the deep copy taken before `strip_emitted_flags!` apply
18
+ # here word for word.
19
+ #
20
+ # `differ` is anything answering `RustConformanceHelpers`' comparison
21
+ # helpers (spec/support/rust_conformance_helpers.rb) plus a
22
+ # `structural_skips` set — duck-typed, so lib never requires spec/.
23
+ # Which query verbs may diverge is not the differ's call: it is read
24
+ # off the binary's own manifest.json (`manifest_partition`, below).
25
+ #
26
+ # Returns one divergence list per active mode — `{ differential: [...],
27
+ # self_consistency: [...], properties_in_differential: [...],
28
+ # adapter_parity_sqlite: [...] }`, keys present only for the modes in
29
+ # `modes:` (`differential` always). `adapter_parity_sqlite:` is the
30
+ # caller's own callable, because which two adapters that mode pairs is
31
+ # the caller's dial, not this module's.
32
+ module Differential
33
+ module_function
34
+
35
+ # The one place a Ruby/Rust query divergence may be tolerated — and
36
+ # only for a verb `gaps` (a `RustGapManifest`) declares not generated.
37
+ # Rust refuses such a verb outright while Ruby answers it for real
38
+ # (or refuses it for its own business reason), so both sides' rows for
39
+ # that verb leave the comparison. Everything else stays in: a refusal
40
+ # the manifest doesn't account for — however its message is worded —
41
+ # is a real divergence.
42
+ #
43
+ # `skipped` is every tolerated verb this history actually reached (for
44
+ # the sweep's structural_skip_report). `stale` is a divergence per
45
+ # tolerated verb Rust nonetheless answered with a query row: the
46
+ # manifest says "not generated" but the binary disagrees, so the
47
+ # tolerance itself is wrong and must not silently hold.
48
+ # @param gaps [Hecks::Fuzzing::RustGapManifest] the compiled binary's manifest
49
+ # @param ruby_refusals [Array<Hash>] Ruby's own refusal rows for this history
50
+ # @param rust_refusals [Array<Hash>] the Rust binary's own refusal rows
51
+ # @param ruby_queries [Array<Hash>] Ruby's own query rows for this history
52
+ # @param rust_queries [Array<Hash>] the Rust binary's own query rows
53
+ # @return [Hash] `ruby_refusals:`/`rust_refusals:`/`ruby_queries:`/
54
+ # `rust_queries:` (each with tolerated verbs removed), `skipped:` (a
55
+ # `Set<String>` of every tolerated verb this history actually reached),
56
+ # and `stale:` (an `Array<Hash>` divergence per tolerated verb the Rust
57
+ # binary nonetheless answered)
58
+ def manifest_partition(gaps, ruby_refusals:, rust_refusals:, ruby_queries:, rust_queries:)
59
+ verb_of = ->(row) { row.key?("verb") ? row["verb"] : row["query"] }
60
+ declared = ->(row) { gaps.not_generated?(verb_of.call(row)) }
61
+ stale = rust_queries.select(&declared).map do |row|
62
+ { field: "manifest", verb: row["query"],
63
+ detail: "#{row['query']} is declared generated: false in manifest.json " \
64
+ "(#{gaps.not_generated(row['query']).values_at('gap_class', 'construct').join('/')}), " \
65
+ "but the Rust binary answered it — regenerate with bin/project_rust" }
66
+ end
67
+ reached = (ruby_refusals + rust_refusals + ruby_queries).select(&declared)
68
+ { ruby_refusals: ruby_refusals.reject(&declared), rust_refusals: rust_refusals.reject(&declared),
69
+ ruby_queries: ruby_queries.reject(&declared), rust_queries: rust_queries.reject(&declared),
70
+ skipped: reached.to_set(&verb_of), stale: stale }
71
+ end
72
+
73
+ # Runs the standard property battery and reports every one that failed.
74
+ #
75
+ # @param history [Hash] a replayed history as returned by `Replay.call`
76
+ # @return [Array<Hash>] one `{field:, detail:}` entry per failed property
77
+ def property_divergences(history)
78
+ Properties.check(history).reject { |_, result| result == true }
79
+ .map { |name, message| { field: name.to_s, detail: message } }
80
+ end
81
+
82
+ # Flattens `history[:self_consistency]`'s own per-check divergence lists.
83
+ #
84
+ # @param history [Hash] a replayed history as returned by `Replay.call`,
85
+ # optionally carrying `:self_consistency` (`Replay.call`'s own
86
+ # `self_consistency: true`)
87
+ # @return [Array<Hash>] every self-consistency divergence recorded; empty if
88
+ # `history` carries no `:self_consistency`
89
+ def self_consistency_divergences(history)
90
+ return [] unless history[:self_consistency]
91
+
92
+ history[:self_consistency].values.flatten(1)
93
+ end
94
+
95
+ # rubocop:disable-next Metrics/AbcSize
96
+ # rubocop:disable-next Metrics/CyclomaticComplexity
97
+ # rubocop:disable-next Metrics/PerceivedComplexity
98
+ # rubocop:disable-next Metrics/MethodLength
99
+ # @param differ [Object] duck-typed comparison helper answering
100
+ # `RustConformanceHelpers`' interface (adapter-defined)
101
+ # @param domain_path [String] path to the domain directory to replay
102
+ # @param steps [Array<Hash>] the step sequence to replay on both engines
103
+ # @param binary [String] path to the compiled Rust conformance binary to run
104
+ # @param modes [Array<Symbol>] which comparisons to run — any of
105
+ # `:differential` (always run), `:self_consistency`,
106
+ # `:properties_in_differential`, `:adapter_parity_sqlite`
107
+ # @param adapter_parity_sqlite [Proc, nil] callable answering the
108
+ # `:adapter_parity_sqlite` mode's own comparison, or `nil` to skip it
109
+ # even when named in `modes`
110
+ # @return [Hash{Symbol => Array<Hash>}] one divergence list per active mode,
111
+ # keyed by the mode name; `:differential` is always present
112
+ def diff(differ, domain_path, steps, binary, modes:, adapter_parity_sqlite: nil)
113
+ self_consistency = modes.include?(:self_consistency)
114
+ ruby_result = Replay.call(domain_path, steps, self_consistency: self_consistency)
115
+ ruby_instances = JSON.parse(JSON.generate(ruby_result[:instances]))
116
+ ruby_events = JSON.parse(JSON.generate(ruby_result[:events]))
117
+ ruby_refusals = ruby_result[:refusals].map do |r|
118
+ { "verb" => r[:verb].to_s, "kind" => r[:kind].to_s.split("::").last, "error" => r[:error] }
119
+ end
120
+ ruby_queries = JSON.parse(JSON.generate(ruby_result[:queries].map { |row| Nondeterministic.strip(row, :query_row) }))
121
+ ruby_sagas = JSON.parse(JSON.generate(ruby_result[:sagas]))
122
+ ruby_dry_runs = ruby_result[:dry_runs].map { |d| { "verb" => d[:verb].to_s, "ok" => d[:ok] } }
123
+
124
+ outcomes = {}
125
+ outcomes[:properties_in_differential] = property_divergences(ruby_result) if modes.include?(:properties_in_differential)
126
+ if modes.include?(:adapter_parity_sqlite) && adapter_parity_sqlite
127
+ outcomes[:adapter_parity_sqlite] = adapter_parity_sqlite.call
128
+ end
129
+
130
+ stdout, status = Open3.capture2(binary, stdin_data: JSON.generate({ "steps" => steps }))
131
+ unless status.success?
132
+ return outcomes.merge(differential: [{ field: "process",
133
+ detail: "rust binary exited #{status.exitstatus}: #{stdout}" }])
134
+ end
135
+
136
+ rust_output = JSON.parse(stdout)
137
+ rust_live_instances = JSON.parse(JSON.generate(rust_output["instances"])) if self_consistency
138
+ differ.strip_emitted_flags!(rust_output["instances"])
139
+ differ.strip_emitted_flags!(rust_output["queries"])
140
+ differ.strip_occurred_at!(rust_output["events"])
141
+
142
+ divergences = []
143
+ divergences << { field: "instances", ruby: ruby_instances, rust: rust_output["instances"] } \
144
+ unless rust_output["instances"] == ruby_instances
145
+ divergences << { field: "events", ruby: ruby_events, rust: rust_output["events"] } \
146
+ unless rust_output["events"] == ruby_events
147
+
148
+ kept = manifest_partition(RustGapManifest.for_binary(binary),
149
+ ruby_refusals: ruby_refusals, rust_refusals: rust_output["refusals"],
150
+ ruby_queries: ruby_queries, rust_queries: rust_output["queries"])
151
+ differ.structural_skips.merge(kept[:skipped])
152
+ divergences.concat(kept[:stale])
153
+
154
+ by_kind = ->(r) { r.slice("verb", "kind") }
155
+ rust_refusals = kept[:rust_refusals].map(&by_kind)
156
+ kept_ruby_refusals = kept[:ruby_refusals].map(&by_kind)
157
+ divergences << { field: "refusals", ruby: kept_ruby_refusals, rust: rust_refusals } \
158
+ unless rust_refusals == kept_ruby_refusals
159
+
160
+ wordless = ->(row) { differ.reduce_to_wire_precision(row.except("error", "reference_error")) }
161
+ rust_queries = kept[:rust_queries].map(&wordless)
162
+ kept_ruby_queries = kept[:ruby_queries].map(&wordless)
163
+ divergences << { field: "queries", ruby: kept_ruby_queries, rust: rust_queries } \
164
+ unless rust_queries == kept_ruby_queries
165
+
166
+ divergences << { field: "sagas", ruby: ruby_sagas, rust: rust_output["sagas"] } \
167
+ unless rust_output["sagas"] == ruby_sagas
168
+
169
+ cross_domain = differ.cross_domain_policy_names(rust_output)
170
+ kept_ruby_reactions = JSON.parse(JSON.generate(ruby_result[:reactions]))
171
+ .reject { |r| cross_domain.include?(r["policy"]) }
172
+ rust_reactions = rust_output.fetch("reactions")
173
+ divergences << { field: "reactions", ruby: kept_ruby_reactions, rust: rust_reactions } \
174
+ unless rust_reactions == kept_ruby_reactions
175
+
176
+ rust_dry_runs = Array(rust_output["dry_runs"]).map { |d| d.slice("verb", "ok") }
177
+ divergences << { field: "dry_runs", ruby: ruby_dry_runs, rust: rust_dry_runs } \
178
+ unless rust_dry_runs == ruby_dry_runs
179
+ outcomes[:differential] = divergences
180
+
181
+ if self_consistency
182
+ outcomes[:self_consistency] =
183
+ self_consistency_divergences(ruby_result) +
184
+ SelfConsistency.check_rust_rehydration(binary, differ, rust_live_instances) +
185
+ SelfConsistency.check_rust_idempotency(binary, differ, rust_live_instances)
186
+ end
187
+
188
+ outcomes
189
+ end
190
+ end
191
+ end
192
+ end