hecks 1.3.0 → 1.4.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 (355) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +7 -7
  3. data/lib/hecks/adapters/driven/d1.rb +187 -23
  4. data/lib/hecks/adapters/driven/folder.rb +10 -10
  5. data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
  6. data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
  7. data/lib/hecks/adapters/driven/heki/journal.rb +5 -4
  8. data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
  9. data/lib/hecks/adapters/driven/heki.rb +13 -7
  10. data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
  11. data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
  12. data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
  13. data/lib/hecks/adapters/driven/lambda.rb +39 -33
  14. data/lib/hecks/adapters/driven/local_storage.rb +17 -10
  15. data/lib/hecks/adapters/driven/memory.rb +205 -9
  16. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -1
  17. data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
  18. data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
  19. data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
  20. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
  21. data/lib/hecks/adapters/driven/postgres.rb +175 -28
  22. data/lib/hecks/adapters/driven/prism.rb +4 -4
  23. data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
  24. data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
  25. data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
  26. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
  27. data/lib/hecks/adapters/driven/sqlite.rb +181 -21
  28. data/lib/hecks/adapters/driven.rb +4 -4
  29. data/lib/hecks/adapters/driving/github_webhook.rb +18 -18
  30. data/lib/hecks/behaviors/dsl.rb +2 -2
  31. data/lib/hecks/behaviors/expectations.rb +23 -23
  32. data/lib/hecks/behaviors/ir.rb +1 -1
  33. data/lib/hecks/behaviors/rspec.rb +1 -1
  34. data/lib/hecks/behaviors/runner.rb +2 -2
  35. data/lib/hecks/behaviors.rb +1 -1
  36. data/lib/hecks/bluebook/aggregate.rb +13 -13
  37. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
  38. data/lib/hecks/bluebook/assembly/build.rb +1 -1
  39. data/lib/hecks/bluebook/assembly/contract.rb +39 -16
  40. data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
  41. data/lib/hecks/bluebook/assembly/marks.rb +18 -18
  42. data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
  43. data/lib/hecks/bluebook/assembly.rb +9 -9
  44. data/lib/hecks/bluebook/attribute.rb +9 -9
  45. data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
  46. data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
  47. data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
  48. data/lib/hecks/bluebook/behaviour/command.rb +23 -23
  49. data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -7
  50. data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
  51. data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
  52. data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
  53. data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
  54. data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
  55. data/lib/hecks/bluebook/behaviour/query.rb +1 -1
  56. data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
  57. data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
  58. data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
  59. data/lib/hecks/bluebook/capabilities.rb +27 -0
  60. data/lib/hecks/bluebook/chapter.rb +28 -9
  61. data/lib/hecks/bluebook/command.rb +12 -12
  62. data/lib/hecks/bluebook/domain_port.rb +9 -9
  63. data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
  64. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
  65. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +96 -96
  66. data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
  67. data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
  68. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
  69. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
  70. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
  71. data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
  72. data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
  73. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
  74. data/lib/hecks/bluebook/dsl/entity_builder.rb +56 -56
  75. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
  76. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
  77. data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
  78. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
  79. data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
  80. data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
  81. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
  82. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +35 -35
  83. data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
  84. data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
  85. data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
  86. data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
  87. data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
  88. data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
  89. data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
  90. data/lib/hecks/bluebook/entity.rb +11 -11
  91. data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
  92. data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
  93. data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
  94. data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
  95. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
  96. data/lib/hecks/bluebook/expression/resolver.rb +60 -62
  97. data/lib/hecks/bluebook/hexagon.rb +1 -1
  98. data/lib/hecks/bluebook/lifecycle.rb +1 -1
  99. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
  100. data/lib/hecks/bluebook/meta_validator/judge.rb +101 -87
  101. data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
  102. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
  103. data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
  104. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
  105. data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
  106. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
  107. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
  108. data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
  109. data/lib/hecks/bluebook/meta_validator.rb +70 -70
  110. data/lib/hecks/bluebook/model_check.rb +170 -84
  111. data/lib/hecks/bluebook/pattern_subset.rb +9 -9
  112. data/lib/hecks/bluebook/policy.rb +15 -13
  113. data/lib/hecks/bluebook/process_manager.rb +14 -14
  114. data/lib/hecks/bluebook/project_register.rb +6 -6
  115. data/lib/hecks/bluebook/query.rb +4 -4
  116. data/lib/hecks/bluebook/read_model.rb +14 -14
  117. data/lib/hecks/bluebook/reference.rb +8 -8
  118. data/lib/hecks/bluebook/smoke_test.rb +19 -19
  119. data/lib/hecks/bluebook/synthesizer.rb +12 -12
  120. data/lib/hecks/bluebook/translation.rb +4 -4
  121. data/lib/hecks/bluebook/value_object.rb +6 -6
  122. data/lib/hecks/bluebook.rb +2 -2
  123. data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
  124. data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
  125. data/lib/hecks/codemod.rb +36 -35
  126. data/lib/hecks/construct.rb +6 -6
  127. data/lib/hecks/corpus.rb +317 -0
  128. data/lib/hecks/deprecation.rb +95 -0
  129. data/lib/hecks/doc/reference.rb +19 -19
  130. data/lib/hecks/embryonaut_bluebook.rb +11 -11
  131. data/lib/hecks/facade/cli_door.rb +69 -10
  132. data/lib/hecks/facade/cli_runner.rb +105 -24
  133. data/lib/hecks/facade/command_request.rb +23 -0
  134. data/lib/hecks/facade/handle.rb +79 -32
  135. data/lib/hecks/facade/json_door.rb +106 -25
  136. data/lib/hecks/facade/surface/aggregate_door.rb +42 -27
  137. data/lib/hecks/facade/surface/chapter.rb +26 -17
  138. data/lib/hecks/facade/surface.rb +16 -3
  139. data/lib/hecks/facade.rb +15 -4
  140. data/lib/hecks/forms/app.rb +46 -30
  141. data/lib/hecks/forms/command_form_renderer.rb +70 -9
  142. data/lib/hecks/forms/field_renderer.rb +142 -6
  143. data/lib/hecks/forms/field_shape.rb +180 -19
  144. data/lib/hecks/forms/html.rb +51 -7
  145. data/lib/hecks/forms/index_renderer.rb +14 -2
  146. data/lib/hecks/forms/params.rb +120 -23
  147. data/lib/hecks/forms/port_argument.rb +2 -2
  148. data/lib/hecks/forms/query_form_renderer.rb +2 -2
  149. data/lib/hecks/forms/record_renderer.rb +2 -2
  150. data/lib/hecks/forms/record_table.rb +1 -1
  151. data/lib/hecks/forms/value_object_shape.rb +3 -3
  152. data/lib/hecks/forms.rb +24 -4
  153. data/lib/hecks/fqn.rb +1 -1
  154. data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
  155. data/lib/hecks/framework.rb +48 -17
  156. data/lib/hecks/freezer.rb +11 -11
  157. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
  158. data/lib/hecks/fuzzing/combination_miner.rb +119 -0
  159. data/lib/hecks/fuzzing/concurrent_dispatch.rb +140 -45
  160. data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
  161. data/lib/hecks/fuzzing/differential.rb +158 -0
  162. data/lib/hecks/fuzzing/domain_generator.rb +694 -0
  163. data/lib/hecks/fuzzing/era_boundary.rb +37 -18
  164. data/lib/hecks/fuzzing/form_census.rb +35 -20
  165. data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
  166. data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
  167. data/lib/hecks/fuzzing/isolated_boot.rb +62 -62
  168. data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
  169. data/lib/hecks/fuzzing/persistence_parity.rb +19 -21
  170. data/lib/hecks/fuzzing/properties/corrections.rb +9 -9
  171. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +154 -49
  172. data/lib/hecks/fuzzing/properties/guards.rb +42 -42
  173. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
  174. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +22 -35
  175. data/lib/hecks/fuzzing/properties/outbox.rb +24 -24
  176. data/lib/hecks/fuzzing/properties/querying.rb +17 -17
  177. data/lib/hecks/fuzzing/properties.rb +64 -40
  178. data/lib/hecks/fuzzing/qa_settings.rb +152 -0
  179. data/lib/hecks/fuzzing/replay.rb +114 -114
  180. data/lib/hecks/fuzzing/rotation_priority.rb +10 -10
  181. data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
  182. data/lib/hecks/fuzzing/self_consistency.rb +141 -112
  183. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +35 -35
  184. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
  185. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
  186. data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
  187. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +58 -31
  188. data/lib/hecks/fuzzing/sequence_generator.rb +111 -23
  189. data/lib/hecks/fuzzing/shrinker.rb +197 -0
  190. data/lib/hecks/fuzzing/structural_skips.rb +23 -130
  191. data/lib/hecks/fuzzing/sweep_depth.rb +4 -4
  192. data/lib/hecks/fuzzing/target_capabilities.rb +49 -18
  193. data/lib/hecks/fuzzing/value_generator.rb +14 -14
  194. data/lib/hecks/fuzzing.rb +5 -0
  195. data/lib/hecks/grammar/evolve.rb +10 -10
  196. data/lib/hecks/grammar.rb +7 -7
  197. data/lib/hecks/ir.rb +13 -13
  198. data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
  199. data/lib/hecks/language/bluebook/policy.bluebook +11 -1
  200. data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
  201. data/lib/hecks/language/oidc.json +5 -0
  202. data/lib/hecks/literal.rb +9 -9
  203. data/lib/hecks/naming.rb +25 -25
  204. data/lib/hecks/ports/access_control.rb +58 -2
  205. data/lib/hecks/ports/agent/answers.rb +83 -6
  206. data/lib/hecks/ports/agent.rb +119 -35
  207. data/lib/hecks/ports/authentication.rb +44 -4
  208. data/lib/hecks/ports/authorization.rb +53 -11
  209. data/lib/hecks/ports/clock.rb +42 -23
  210. data/lib/hecks/ports/extraction.rb +16 -0
  211. data/lib/hecks/ports/identity_assignment.rb +24 -2
  212. data/lib/hecks/ports/identity_generation.rb +17 -3
  213. data/lib/hecks/ports/identity_resolution.rb +18 -1
  214. data/lib/hecks/ports/loading.rb +4 -0
  215. data/lib/hecks/ports/persistence/append_only.rb +172 -8
  216. data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
  217. data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
  218. data/lib/hecks/ports/persistence/execution.rb +4 -0
  219. data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
  220. data/lib/hecks/ports/persistence/plugin.rb +42 -4
  221. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
  222. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
  223. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
  224. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
  225. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
  226. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
  227. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
  228. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
  229. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
  230. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
  231. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +28 -5
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +57 -4
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
  241. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
  242. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
  243. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
  244. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +42 -8
  245. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
  246. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
  247. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
  248. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -19
  249. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
  250. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -1
  251. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
  252. data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
  253. data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
  254. data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
  255. data/lib/hecks/ports/persistence/state_codec.rb +319 -0
  256. data/lib/hecks/ports/persistence.rb +36 -1
  257. data/lib/hecks/ports/projection.rb +61 -7
  258. data/lib/hecks/ports/query/in_memory.rb +3 -3
  259. data/lib/hecks/ports/query/ordering.rb +6 -6
  260. data/lib/hecks/ports/query.rb +35 -0
  261. data/lib/hecks/projections/bootstrap_table.rb +112 -0
  262. data/lib/hecks/projections/diagrams.rb +75 -75
  263. data/lib/hecks/projections/glossary/html.rb +3 -3
  264. data/lib/hecks/projections/glossary/markdown.rb +3 -3
  265. data/lib/hecks/projections/glossary/mermaid.rb +1 -1
  266. data/lib/hecks/projections/glossary/sections.rb +1 -1
  267. data/lib/hecks/projections/glossary/sentences.rb +5 -5
  268. data/lib/hecks/projections/glossary.rb +15 -15
  269. data/lib/hecks/projections/ir.rb +1 -1
  270. data/lib/hecks/projections/model/deviations.rb +18 -17
  271. data/lib/hecks/projections/model.rb +25 -21
  272. data/lib/hecks/projections/oidc.rb +7 -7
  273. data/lib/hecks/projections/parser_table.rb +5 -5
  274. data/lib/hecks/projections/reference.rb +3 -3
  275. data/lib/hecks/projections/rust_vocabulary.rb +443 -0
  276. data/lib/hecks/projections/shape.rb +2 -2
  277. data/lib/hecks/projections/statements.rb +11 -11
  278. data/lib/hecks/projections/vocabulary.rb +9 -9
  279. data/lib/hecks/projections.rb +5 -3
  280. data/lib/hecks/projector/cli_projector.rb +29 -29
  281. data/lib/hecks/projector/docs_projector.rb +13 -13
  282. data/lib/hecks/projector/exporter.rb +42 -21
  283. data/lib/hecks/projector/ir_projector.rb +1 -1
  284. data/lib/hecks/projector/narrate_projector.rb +11 -11
  285. data/lib/hecks/projector/target.rb +13 -13
  286. data/lib/hecks/projector.rb +15 -15
  287. data/lib/hecks/query_ir.rb +47 -47
  288. data/lib/hecks/query_specification/common/comparators.rb +19 -3
  289. data/lib/hecks/query_specification/common/comparison.rb +109 -27
  290. data/lib/hecks/query_specification/common/dsl.rb +65 -9
  291. data/lib/hecks/query_specification/common/null_policy.rb +57 -13
  292. data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
  293. data/lib/hecks/query_specification/common/options.rb +25 -0
  294. data/lib/hecks/query_specification/field_path.rb +69 -15
  295. data/lib/hecks/query_specification/hop_path.rb +57 -20
  296. data/lib/hecks/query_specification/read_model/specification.rb +4 -0
  297. data/lib/hecks/rendering.rb +3 -3
  298. data/lib/hecks/router/namespace_installer.rb +3 -3
  299. data/lib/hecks/router.rb +1 -1
  300. data/lib/hecks/runtime/aggregate_lock.rb +11 -11
  301. data/lib/hecks/runtime/boot_gates.rb +3 -3
  302. data/lib/hecks/runtime/caller.rb +8 -8
  303. data/lib/hecks/runtime/capability_graph.rb +2 -2
  304. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
  305. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +42 -63
  306. data/lib/hecks/runtime/command_interpreter.rb +159 -102
  307. data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
  308. data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
  309. data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
  310. data/lib/hecks/runtime/command_rules/emission.rb +18 -1
  311. data/lib/hecks/runtime/command_rules/references.rb +35 -35
  312. data/lib/hecks/runtime/command_rules.rb +3 -0
  313. data/lib/hecks/runtime/dependency_planning.rb +11 -11
  314. data/lib/hecks/runtime/dispatcher.rb +283 -90
  315. data/lib/hecks/runtime/entity_element.rb +151 -67
  316. data/lib/hecks/runtime/entity_interpreter.rb +108 -82
  317. data/lib/hecks/runtime/errors.rb +19 -19
  318. data/lib/hecks/runtime/event.rb +6 -6
  319. data/lib/hecks/runtime/identity.rb +22 -22
  320. data/lib/hecks/runtime/instance.rb +39 -14
  321. data/lib/hecks/runtime/interpreting.rb +12 -12
  322. data/lib/hecks/runtime/invocation.rb +276 -0
  323. data/lib/hecks/runtime/loader.rb +14 -14
  324. data/lib/hecks/runtime/outbox.rb +23 -23
  325. data/lib/hecks/runtime/policy_interpreter.rb +54 -54
  326. data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
  327. data/lib/hecks/runtime/query_interpreter.rb +55 -64
  328. data/lib/hecks/runtime/reaction_invocation.rb +20 -20
  329. data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
  330. data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
  331. data/lib/hecks/runtime/reference_hop.rb +6 -6
  332. data/lib/hecks/runtime/refusal_wording.rb +92 -115
  333. data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
  334. data/lib/hecks/runtime/registry/verification.rb +36 -26
  335. data/lib/hecks/runtime/registry.rb +45 -28
  336. data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
  337. data/lib/hecks/runtime/routing.rb +10 -153
  338. data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
  339. data/lib/hecks/runtime/saga_interpreter.rb +64 -64
  340. data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
  341. data/lib/hecks/runtime/tenant_check.rb +9 -9
  342. data/lib/hecks/runtime/tenant_scope.rb +5 -5
  343. data/lib/hecks/runtime/value/admission.rb +75 -30
  344. data/lib/hecks/runtime/value/coercion.rb +318 -127
  345. data/lib/hecks/runtime/value/entity_list_coercion.rb +34 -34
  346. data/lib/hecks/runtime/value.rb +21 -21
  347. data/lib/hecks/runtime.rb +7 -7
  348. data/lib/hecks/storehouse.rb +64 -64
  349. data/lib/hecks/version.rb +3 -3
  350. data/lib/hecks/vocabulary.rb +205 -4
  351. data/lib/hecks.rb +13 -11
  352. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
  353. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
  354. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
  355. metadata +22 -2
@@ -2,20 +2,20 @@ require "hecks/ports/persistence/plugins/era"
2
2
 
3
3
  module Hecks
4
4
  module Fuzzing
5
- # THE FORK-LOSS CLASS, AS A CHECK — not a fuzz. Every other mode in
6
- # this file generates a sequence and compares two answers to the SAME
5
+ # **The fork-loss class, as a check** — not a fuzz. Every other mode in
6
+ # this file generates a sequence and compares two answers to the same
7
7
  # question; this one asks a single, unconditional question of a
8
- # target's own REAL, already-configured `PostgresEra` ledger:
8
+ # target's own real, already-configured `PostgresEra` ledger:
9
9
  # does any ancestor era still hold writes nobody has merged forward?
10
10
  #
11
- # THE BUG THIS TARGETS, NAMED EXACTLY. Minting a new era (an attribute
11
+ # **The bug this targets, named exactly**. Minting a new era (an attribute
12
12
  # or aggregate addition — `StorageShape.project`, lib/hecks/ports/
13
13
  # persistence/plugins/era/storage_shape.rb) advances the readable head
14
- # to a NEW partition; an OLD checkout, or a process that boots slower
14
+ # to a new partition; an old checkout, or a process that boots slower
15
15
  # than the mint, can keep writing into the era it still believes is
16
16
  # current. Those writes are not lost — `Lineage#diverged_count`
17
17
  # (postgres_era/lineage/tail_merge.rb) can always find them — but
18
- # nothing EVER asked it automatically. The practice's own ledger found
18
+ # nothing ever asked it automatically. The practice's own ledger found
19
19
  # exactly this live, twice: once as an operational gap this session's
20
20
  # Step 0 recovered by hand (`bin/merge_tail`, three conflicting
21
21
  # records, three eras deep), and once as BUG#24 (a superuser
@@ -26,15 +26,15 @@ module Hecks
26
26
  # line already answers by hand, run as an ordinary sweep Check instead
27
27
  # of only when a human remembers to ask.
28
28
  #
29
- # THE TARGET'S REAL DATABASE, READ-ONLY, NEVER A DISPOSABLE ONE — every
29
+ # The target's real database, read-only, never a disposable one — every
30
30
  # other Postgres-touching mode here (`persistence_parity`,
31
31
  # `adapter_parity_sqlite`) owns a throwaway schema for the exact
32
32
  # reason it must never look at what a real deployment actually holds;
33
- # this check exists FOR what a real deployment actually holds, so it
33
+ # this check exists for what a real deployment actually holds, so it
34
34
  # connects the same way `bin/merge_tail` itself does — the target's
35
35
  # own `.world` binding — and never writes anything: `eras`/
36
36
  # `diverged_count` are both plain `SELECT`s, and `ensure_base!` only
37
- # ever provisions (`CREATE TABLE IF NOT EXISTS`) when THIS connection
37
+ # ever provisions (`CREATE TABLE IF NOT EXISTS`) when this connection
38
38
  # is the database's own provisioning owner, the identical idempotent
39
39
  # call every ordinary boot already makes. A target with no real,
40
40
  # reachable database configured (nothing provisioned locally, say) is
@@ -45,20 +45,30 @@ module Hecks
45
45
  module_function
46
46
 
47
47
  # `{ checked: true, diverged_total:, breakdown: [{ordinal:, diverged:}] }`
48
- # or `{ checked: false, reason: "..." }` the caller decides what a
49
- # `false` means (an operational note, not a finding); a `true` with
48
+ # or `{ checked: false, kind:, reason: "..." }`. A `true` with
50
49
  # `diverged_total.positive?` is the finding this module exists to
51
50
  # surface: real post-cut writes an ancestor era is still holding.
51
+ #
52
+ # `kind:` splits the two things `checked: false` used to mean. Both
53
+ # answered the same shape, and `bin/qa_sweep` logged that shape as a
54
+ # held Check either way — so a refused connection or a `Lineage`
55
+ # defect read, in the ledger, exactly like an audit that ran and
56
+ # found nothing: counted toward `sweep.made` and the target's clean
57
+ # streak. `:not_applicable` is a target with no lineage to audit at
58
+ # all; `:error` is the audit failing to run, which the caller
59
+ # surfaces as a finding rather than holding.
52
60
  def diverged_ancestor_writes(domain_path)
53
61
  registry, directory = load_registry(domain_path)
54
62
  bluebook = registry.bluebooks.values.first
55
- return unchecked("no bluebook in #{directory}") unless bluebook
63
+ return failed("no bluebook in #{directory}") unless bluebook
56
64
 
57
65
  first = bluebook.aggregates.first
58
- return unchecked("#{bluebook.name} declares no aggregates") unless first
66
+ return not_applicable("#{bluebook.name} declares no aggregates") unless first
59
67
 
60
68
  adapter_name = Hecks::Ports::Persistence::BindingPolicy.resolve(registry, bluebook.name, first).adapter
61
- return unchecked("#{bluebook.name} is bound to #{adapter_name}, not PostgresEra") unless adapter_name == "PostgresEra"
69
+ unless adapter_name == "PostgresEra"
70
+ return not_applicable("#{bluebook.name} is bound to #{adapter_name}, not PostgresEra")
71
+ end
62
72
 
63
73
  settings = registry.world(bluebook.name)&.for_binding(Hecks::Ports::Persistence::VERB, adapter_name) || {}
64
74
  db = Hecks::Adapters::PostgresEra.connect_for(bluebook.name, settings)
@@ -81,14 +91,23 @@ module Hecks
81
91
  db.close
82
92
  end
83
93
  rescue StandardError => e
84
- unchecked("#{e.class}: #{e.message}")
94
+ # Could not audit is not the same as nothing to audit — a refused
95
+ # connection, a `Lineage` defect, malformed `.world` settings. Every
96
+ # one of these used to answer the benign shape and be held.
97
+ failed("#{e.class}: #{e.message}")
85
98
  end
86
99
 
87
- def unchecked(reason) = { checked: false, reason: reason }
100
+ # Nothing to audit this target holds no PostgresEra lineage, so no
101
+ # ancestor era can carry post-cut writes. Not a finding, and not
102
+ # evidence of anything either: the caller logs no Check at all.
103
+ def not_applicable(reason) = { checked: false, kind: :not_applicable, reason: reason }
104
+
105
+ # The audit itself could not run — reported as a finding, never held.
106
+ def failed(reason) = { checked: false, kind: :error, reason: reason }
88
107
 
89
- # THE SAME LOAD `bin/merge_tail` ITSELF PERFORMS (that script's own
108
+ # The same load `bin/merge_tail` itself performs (that script's own
90
109
  # top half) — a fresh `Registry`, never the ledger's own (this asks
91
- # about the SWEPT TARGET's lineage, not QualityControl's own).
110
+ # about the swept target's lineage, not QualityControl's own).
92
111
  def load_registry(domain_path)
93
112
  loading = Hecks::Ports::Loading.bootstrap
94
113
  directory = loading.bluebook_directory(domain_path)
@@ -1,27 +1,28 @@
1
1
  require "json"
2
+ require_relative "../corpus"
2
3
 
3
4
  module Hecks
4
5
  module Fuzzing
5
- # WHAT AN AGGREGATE CAN EXHIBIT, AND WHICH PAIRS IT PUTS TOGETHER.
6
+ # What an aggregate can exhibit, and which pairs it puts together.
6
7
  #
7
8
  # Extracted from `spec/combination_coverage_spec.rb`'s own pairwise
8
9
  # table so it has exactly two consumers that can never drift: that
9
10
  # spec (the golden corpus, held to every pair) and `bin/qa_domain_
10
- # novelty` (a CANDIDATE stress domain, measured against every path
11
+ # novelty` (a candidate stress domain, measured against every path
11
12
  # the QA ledger already sweeps — see that script's own header for why
12
13
  # a new domain has to name the pair no existing target meets before
13
14
  # it earns a place in the rotation).
14
15
  #
15
- # THE UNIT IS ONE AGGREGATE. Two forms in the same chapter but
16
+ # **The unit is one aggregate**. Two forms in the same chapter but
16
17
  # different heads never meet at dispatch; two forms on one head do
17
18
  # — that spec's own header has the four defects that argument came
18
19
  # from. Each entry below is a form the language declares and a
19
20
  # runtime has to handle, chosen because it has produced a defect or
20
21
  # sits one step from one; adding one here is how a new form joins
21
- # BOTH gates at once, and it will name its own uncovered pairs on the
22
+ # both gates at once, and it will name its own uncovered pairs on the
22
23
  # first run of each.
23
24
  #
24
- # ONE FLAT TABLE, ON PURPOSE — each entry is an independent boolean
25
+ # **One flat table, on purpose** — each entry is an independent boolean
25
26
  # check against the same string-keyed aggregate IR hash (the shape
26
27
  # `spec/golden/ir/*.json` carries and `Projector::Exporter.call`
27
28
  # round-trips to through JSON), laid out so every declared form can
@@ -48,7 +49,7 @@ module Hecks
48
49
  "closed_set" => ->(a) { (a["value_objects"] || []).any? { |shape| shape["closed_set"] } },
49
50
  "has_default" => ->(a) { attributes(a).any? { |held| !held["default"].nil? } },
50
51
  "has_optional" => ->(a) { commands(a).any? { |verb| (verb["attributes"] || []).any? { |held| held["optional"] } } },
51
- # THE REFERENCE-HOP FAMILY (ANGLE-2, qa/bluebook ledger) — the
52
+ # The reference-hop family (angle-2, qa/bluebook ledger) — the
52
53
  # forms `qa/stress_domains/referral_chain` exists for, absent
53
54
  # from the census until that domain named them. Each is one
54
55
  # step from a catalogued gap: `two_hop_given` is `dereference`'s
@@ -59,6 +60,19 @@ module Hecks
59
60
  # command redeclaring the aggregate's own reference field under
60
61
  # a non-reference type, so only `resolve_state_references` (never
61
62
  # ported) can catch a dangling id.
63
+ # A retroactive correction, and a role-gated command — both are
64
+ # declared forms this census could not see, on aggregates it was
65
+ # already measuring: `examples/banking` has carried `corrects`
66
+ # mutations and `role`-bearing commands the whole time, and
67
+ # `qa/stress_domains/corrections` exists for the first. Three
68
+ # stress domains' own NOTES.md record `bin/qa_domain_novelty`
69
+ # answering "no new pair" for a domain whose whole point was a
70
+ # shape this table did not name (case_escalation's is the
71
+ # bluntest: "read that as a gap in the census, not in this
72
+ # domain"). Entity commands count for both — BUG#30-33 and
73
+ # BUG#31 were all entity-level `corrects`.
74
+ "corrects" => ->(a) { every_command(a).any? { |verb| corrects?(verb) } },
75
+ "role_gated" => ->(a) { every_command(a).any? { |verb| !verb["role"].to_s.empty? } },
62
76
  "two_hop_given" => ->(a) { two_hop_given?(a) },
63
77
  "multi_hop_where" => ->(a) { multi_hop_where?(a) },
64
78
  "revalued_reference" => ->(a) { revalued_reference?(a) }
@@ -68,6 +82,13 @@ module Hecks
68
82
 
69
83
  def entities(aggregate) = aggregate["entities"] || []
70
84
  def commands(aggregate) = aggregate["commands"] || []
85
+
86
+ # One aggregate's commands, its pieces' included — a form carried by
87
+ # an entity command is carried by the aggregate that owns it, the
88
+ # same way `composite_piece`/`piece_lifecycle` already read pieces.
89
+ def every_command(aggregate) = commands(aggregate) + entities(aggregate).flat_map { |piece| commands(piece) }
90
+
91
+ def corrects?(verb) = (verb["mutations"] || []).any? { |change| change["op"].to_s == "corrects" }
71
92
  def attributes(aggregate) = aggregate["attributes"] || []
72
93
  def queries(aggregate) = aggregate["queries"] || []
73
94
  def reference?(attribute) = attribute["type"].to_s.start_with?("Reference<")
@@ -82,7 +103,7 @@ module Hecks
82
103
  end
83
104
 
84
105
  # A command attribute sharing a name with one of the aggregate's
85
- # own reference-typed attributes while carrying a DIFFERENT, non-
106
+ # own reference-typed attributes while carrying a different, non-
86
107
  # reference type — `attribute :member, Handle` against
87
108
  # `reference_to Member`.
88
109
  def revalued_reference?(aggregate)
@@ -121,7 +142,7 @@ module Hecks
121
142
  def pair_key(left, right) = [left, right].sort.join(" + ")
122
143
 
123
144
  # `held` is `[[aggregate_name, properties], ...]`. Answers which
124
- # pairs are met on ONE aggregate, and by which — a Hash from pair
145
+ # pairs are met on one aggregate, and by which — a Hash from pair
125
146
  # key to the names carrying it, so a caller can say who.
126
147
  def covered_pairs(held)
127
148
  held.each_with_object(Hash.new { |h, k| h[k] = [] }) do |(name, shows), covered|
@@ -140,23 +161,17 @@ module Hecks
140
161
  end
141
162
  end
142
163
 
143
- # WHERE A DOMAIN PATH KEEPS ITS BLUEBOOKS`<domain>/bluebook/*.
144
- # bluebook` (every example and stress domain), or the directory
145
- # itself (`qa/bluebook`, the ledger's own `Target.path`): the same
146
- # two shapes `bin/model_check`'s `bluebook_in` reads. `nil` when
147
- # neither holds a bluebook.
164
+ # Where a domain path keeps its bluebookssee
165
+ # `Hecks::Corpus.bluebook_files`, the one definition every corpus
166
+ # walk shares. `nil` when neither shape holds a bluebook.
148
167
  def bluebook_files(domain_path)
149
- [File.join(domain_path, "bluebook"), domain_path].each do |dir|
150
- files = Dir[File.join(dir, "*.bluebook")]
151
- return files unless files.empty?
152
- end
153
- nil
168
+ Hecks::Corpus.bluebook_files(domain_path)
154
169
  end
155
170
 
156
- # THE SAME CENSUS OVER A DOMAIN ON DISK, booted the lightweight
171
+ # The same census over a domain on disk, booted the lightweight
157
172
  # way `bin/model_check` and `Hecks::Codemod.load_bluebook` already
158
173
  # do (ports and the two in-process adapters, no `Hecks.boot`, no
159
- # live database, no `.hecksagon`: the census reads declared SHAPE,
174
+ # live database, no `.hecksagon`: the census reads declared shape,
160
175
  # and a framework chapter a `.hecksagon` would attach is not this
161
176
  # domain's own). Only the domain's own chapter is measured — the
162
177
  # first bluebook loaded, the same "target chapter is always first"
@@ -0,0 +1,95 @@
1
+ require "json"
2
+ require_relative "isolated_boot"
3
+ require_relative "sequence_generator"
4
+ require_relative "replay"
5
+ require_relative "shrinker"
6
+ require_relative "differential"
7
+
8
+ module Hecks
9
+ module Fuzzing
10
+ # **One generated domain, checked** — the child-process half of
11
+ # `bin/qa_generated_domains` (one domain per process: every generated
12
+ # domain is named `QaGenerated`, see DomainGenerator's header).
13
+ #
14
+ # The same comparisons `bin/qa_sweep` runs on a rotation target, over
15
+ # sequences generated against a domain nobody wrote: with a compiled
16
+ # binary, `Differential.diff` (Ruby vs Rust, self-consistency, declared
17
+ # properties); without one, a Ruby-only replay (properties,
18
+ # self-consistency, an interpreter crash). A domain that does not even
19
+ # boot is `invalid` — a generator defect, not a finding, reported apart.
20
+ #
21
+ # `match:` is how domain-level shrinking asks "does this smaller domain
22
+ # still show the same finding?": only a divergence in the same mode
23
+ # whose `Shrinker.signature` contains the original's counts. Verb names
24
+ # survive a removal that did not touch them, so the signature does too.
25
+ module GeneratedDomainCheck
26
+ DIFFERENTIAL_MODES = %i[differential self_consistency properties_in_differential].freeze
27
+
28
+ module_function
29
+
30
+ def run(domain_path, seeds:, steps:, adversarial:, binary: nil, differ: nil, match: nil, shrink_budget: 0)
31
+ error = boot_error(domain_path)
32
+ return { "status" => "invalid", "error" => error } if error
33
+
34
+ (1..seeds).each do |seed|
35
+ finding = check_seed(domain_path, seed, steps, adversarial, binary, differ, match)
36
+ next unless finding
37
+
38
+ finding["shrunk_steps"] = shrink(domain_path, finding, binary, differ, shrink_budget) if shrink_budget.positive?
39
+ return finding.merge("status" => "found", "seeds_run" => seed)
40
+ end
41
+ { "status" => "clean", "seeds_run" => seeds }
42
+ end
43
+
44
+ def boot_error(domain_path)
45
+ IsolatedBoot.call(domain_path) { |copy| Hecks.boot(copy) }
46
+ nil
47
+ rescue StandardError, ScriptError => e
48
+ "#{e.class}: #{e.message.lines.first&.strip}"
49
+ end
50
+
51
+ def check_seed(domain_path, seed, steps, adversarial, binary, differ, match)
52
+ sequence = SequenceGenerator.generate(domain_path, seed: seed, steps: steps, adversarial: adversarial)
53
+ rescue StandardError => e
54
+ finding(seed, :generator, [{ field: "generator_crash", detail: "#{e.class}: #{e.message}" }], [], match)
55
+ else
56
+ outcomes(domain_path, sequence, binary, differ).each do |mode, divergences|
57
+ found = finding(seed, mode, divergences, sequence, match)
58
+ return found if found
59
+ end
60
+ nil
61
+ end
62
+
63
+ def outcomes(domain_path, sequence, binary, differ)
64
+ return Differential.diff(differ, domain_path, sequence, binary, modes: DIFFERENTIAL_MODES) if binary
65
+
66
+ history = Replay.call(domain_path, sequence, self_consistency: true)
67
+ { ruby_only: Differential.property_divergences(history),
68
+ self_consistency: Differential.self_consistency_divergences(history) }
69
+ rescue StandardError => e
70
+ { ruby_only: [{ field: "crash", detail: "#{e.class}: #{e.message}" }] }
71
+ end
72
+
73
+ def finding(seed, mode, divergences, sequence, match)
74
+ return nil if divergences.empty?
75
+
76
+ signature = Shrinker.signature(divergences)
77
+ return nil if match && !(match["mode"] == mode.to_s && Set.new(match["signature"]).subset?(signature))
78
+
79
+ { "seed" => seed, "mode" => mode.to_s, "signature" => signature.to_a.sort, "steps" => sequence,
80
+ "divergences" => JSON.parse(JSON.generate(divergences)) }
81
+ end
82
+
83
+ # The step-level half of shrinking, in this same process: the domain
84
+ # is already as small as the parent could make it.
85
+ def shrink(domain_path, found, binary, differ, budget)
86
+ original = Set.new(found["signature"])
87
+ mode = found["mode"].to_sym
88
+ Shrinker.call(found["steps"], budget: budget) do |candidate|
89
+ divergences = outcomes(domain_path, candidate, binary, differ)[mode] || []
90
+ Shrinker.reproduces?(original, divergences)
91
+ end.steps
92
+ end
93
+ end
94
+ end
95
+ end
@@ -1,6 +1,6 @@
1
1
  module Hecks
2
2
  module Fuzzing
3
- # A value that is DELIBERATELY THE WRONG SHAPE for the attribute it is handed
3
+ # A value that is deliberately the wrong shape for the attribute it is handed
4
4
  # to — the sibling of ValueGenerator, which only ever builds values that satisfy
5
5
  # a declared type.
6
6
  #
@@ -14,7 +14,7 @@ module Hecks
14
14
  # Rendering's whole story), and no generated value
15
15
  # could ever have produced it.
16
16
  #
17
- # These are expected to be REFUSED, and that is the point : a refusal is an
17
+ # These are expected to be refused, and that is the point : a refusal is an
18
18
  # answer, and its wording is pinned byte-for-byte. The bugs live in
19
19
  # the sentence, not in the happy path.
20
20
  module InvalidValueGenerator
@@ -37,7 +37,7 @@ module Hecks
37
37
  build(kind, attribute, aggregate, random: random)
38
38
  end
39
39
 
40
- # Only the confusions that MEAN anything for this attribute. Offering
40
+ # Only the confusions that mean anything for this attribute. Offering
41
41
  # `scalar_for_object` for a plain Integer would just be a second spelling of
42
42
  # `numeral_string`, and a kind that cannot be wrong for the attribute it is
43
43
  # handed teaches the corpus nothing.
@@ -60,16 +60,16 @@ module Hecks
60
60
  end
61
61
  end
62
62
 
63
- # A bare scalar where a value object is declared. A SINGLE-FIELD value object
63
+ # A bare scalar where a value object is declared. A single-field value object
64
64
  # legitimately accepts one (that is the standing-in rule every domain relies
65
- # on), so the interesting case is a value object with SEVERAL fields, where a
65
+ # on), so the interesting case is a value object with several fields, where a
66
66
  # scalar cannot stand for anything and the refusal has to say so.
67
67
  def scalar_for(attribute, aggregate, random:)
68
68
  value_object = aggregate.value_object(attribute.type.to_s)
69
69
  sole = value_object&.sole_attribute
70
70
  return "a bare scalar" unless sole
71
71
 
72
- # One field, so a scalar is legal — corrupt the FIELD's own type instead,
72
+ # One field, so a scalar is legal — corrupt the field's own type instead,
73
73
  # which is still a shape the attribute cannot accept.
74
74
  { sole.name.to_s => ["nested", "array"] }
75
75
  end