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
@@ -6,25 +6,27 @@ require_relative "../naming"
6
6
 
7
7
  module Hecks
8
8
  module Runtime
9
- # THE TRANSACTIONAL OUTBOX — the durable hand-off between "a command
9
+ # **The transactional outbox** — the durable hand-off between "a command
10
10
  # committed" and "everything that was owed because it committed":
11
11
  # the policies that react to its events, the process managers that
12
12
  # advance on them, and (through a policy whose trigger is an
13
13
  # outbound port operation) the external effects those reactions
14
14
  # cause. `future-features.md` item 8, built.
15
15
  #
16
- # THE SHAPE. One row per (event, consumer). A consumer is a named
16
+ # ## The shape
17
+ #
18
+ # One row per (event, consumer). A consumer is a named
17
19
  # policy or process manager that would react to the event — resolved
18
- # at ENQUEUE time from the registry (`Fanout`), so the outbox records
19
- # WHO was owed what, not just that an event happened. Rows move
20
+ # at enqueue time from the registry (`Fanout`), so the outbox records
21
+ # who was owed what, not just that an event happened. Rows move
20
22
  # `pending → claimed → delivered | failed`:
21
23
  #
22
- # pending written in the SAME adapter transaction as the
24
+ # pending written in the same adapter transaction as the
23
25
  # aggregate save (`Interpreting#run_dispatch_order`
24
26
  # wraps `save` + `emit` + enqueue in `repository.
25
27
  # transaction`), so a row exists iff the state change
26
28
  # it reacts to committed — never one without the other.
27
- # claimed the relay is about to run this consumer. Set BEFORE
29
+ # claimed the relay is about to run this consumer. Set before
28
30
  # the reaction (a policy's `reenter`, a saga leg's
29
31
  # dispatch, an adapter call) runs.
30
32
  # delivered the consumer ran to completion — including the
@@ -33,31 +35,37 @@ module Hecks
33
35
  # tracks delivery, not the domain's answer.
34
36
  # failed the consumer raised a defect (non-refusal error).
35
37
  #
36
- # DELIVERY IS INLINE BY DEFAULT — the dispatcher drains the rows it
38
+ # ## Delivery is inline by default
39
+ #
40
+ # The dispatcher drains the rows it
37
41
  # just wrote, in the same call, in the order C10.2 fixes (per event
38
42
  # in `emits` order: that event's policy rows, then its saga rows —
39
43
  # the emitting domain's own policies before other domains'). Nothing about the
40
44
  # happy path is deferred or asynchronous; a caller still sees every
41
45
  # reaction settled when `dispatch` returns. What changes is the
42
- # crash window: a process that dies between commit and reaction
43
- # used to lose the reaction silently. Now the row survives, and
44
- # `Relay#redrive!` — run at boot by `Loader.run_boot_gates!` —
46
+ # crash window: without a durable row, a process that dies between
47
+ # commit and reaction loses the reaction silently. The row survives,
48
+ # and `Relay#redrive!` — run at boot by `Loader.run_boot_gates!` —
45
49
  # finds it.
46
50
  #
47
- # WHAT REDRIVE DOES, AND DELIBERATELY DOESN'T. A `pending` row is
48
- # REDRIVEN: its consumer provably never started (claiming is the
51
+ # ## What redrive does, and deliberately doesn't
52
+ #
53
+ # A `pending` row is
54
+ # redriven: its consumer provably never started (claiming is the
49
55
  # first thing delivery does), so running it now is exactly-once by
50
- # construction. A `claimed` row is NOT auto-redriven: the consumer
56
+ # construction. A `claimed` row is not auto-redriven: the consumer
51
57
  # started and the crash hid its outcome — the same reasoning
52
58
  # `saga_pending_dispatch.rb` gives (a stalled transfer is a better
53
59
  # defect than a double-credited one). It is surfaced loudly
54
60
  # (`warn`, and a `stalled: true` entry in `Relay#log`) and left for
55
61
  # `Relay#redrive!(claimed: true)` — an explicit operator decision,
56
62
  # never a boot-time default. `delivery_id` (event uid + consumer) is
57
- # UNIQUE per store, so a re-enqueue of the same fact to the same
63
+ # unique per store, so a re-enqueue of the same fact to the same
58
64
  # consumer is a no-op rather than a second row.
59
65
  #
60
- # WHICH ADAPTERS. Memory (in-process rows — visible to specs,
66
+ # ## Which adapters
67
+ #
68
+ # Memory (in-process rows — visible to specs,
61
69
  # gone with the process, exactly like everything else Memory holds),
62
70
  # Sqlite and Postgres (a `hecks_outbox` table in the aggregate's own
63
71
  # database — the only way the enqueue can share the save's
@@ -71,13 +79,30 @@ module Hecks
71
79
 
72
80
  Row = Struct.new(:id, :delivery_id, :event_uid, :aggregate, :domain, :kind, :consumer, :event,
73
81
  :status, :attempts, :error, keyword_init: true) do
82
+ # Reports whether this row is still waiting to be claimed.
83
+ #
84
+ # @return [Boolean] true if this row's status is `"pending"`
74
85
  def pending? = status == "pending"
86
+
87
+ # Reports whether this row's consumer is currently running.
88
+ #
89
+ # @return [Boolean] true if this row's status is `"claimed"`
75
90
  def claimed? = status == "claimed"
91
+
92
+ # Reports whether this row's consumer ran to completion.
93
+ #
94
+ # @return [Boolean] true if this row's status is `"delivered"`
76
95
  def delivered? = status == "delivered"
96
+
97
+ # Reports whether this row's consumer raised a defect.
98
+ #
99
+ # @return [Boolean] true if this row's status is `"failed"`
77
100
  def failed? = status == "failed"
78
101
 
79
102
  # Wire-shaped — what an adapter persists. `event` is the event's
80
103
  # own `to_h` plus correlation; `Row.event_from` reverses it.
104
+ #
105
+ # @return [Hash{Symbol => Object}] this row's own fields, keyed by name
81
106
  def to_h
82
107
  { id: id, delivery_id: delivery_id, event_uid: event_uid, aggregate: aggregate, domain: domain,
83
108
  kind: kind, consumer: consumer, event: event, status: status, attempts: attempts, error: error }
@@ -89,18 +114,34 @@ module Hecks
89
114
 
90
115
  module_function
91
116
 
117
+ # Renders an event as the wire-shaped Hash an outbox row's own `event`
118
+ # field stores.
119
+ #
120
+ # @param event [Runtime::Event] the event to serialize
121
+ # @return [Hash{Symbol => Object}] `event`'s own `to_h`, with `correlation`
122
+ # merged in
92
123
  def serialize_event(event)
93
124
  event.to_h.merge(correlation: event.correlation)
94
125
  end
95
126
 
96
- # THE EMITTING DOMAIN'S OWN BLUEBOOK FIRST, then the rest in load
127
+ # The emitting domain's own bluebook first, then the rest in load
97
128
  # order (C10.2) — the one policy ordering both `PolicyInterpreter#
98
129
  # policies_for` and `Fanout.policies` read.
130
+ #
131
+ # @param registry [Runtime::Registry] the booted registry whose loaded
132
+ # bluebooks are ordered
133
+ # @param domain [String, Symbol] the emitting domain, sorted first
134
+ # @return [Array<Bluebook::Chapter>] every loaded chapter, `domain`'s own first
99
135
  def bluebooks_home_first(registry, domain)
100
136
  home, others = registry.bluebooks.each_value.partition { |bluebook| bluebook.name == domain }
101
137
  home + others
102
138
  end
103
139
 
140
+ # Rebuilds a frozen event from an outbox row's own stored `event` field.
141
+ #
142
+ # @param hash [Hash{String, Symbol => Object}] the wire-shaped event Hash,
143
+ # as `serialize_event` built it (String or Symbol keys either way)
144
+ # @return [Runtime::Event] the rebuilt, frozen event
104
145
  def event_from(hash)
105
146
  hash = hash.transform_keys(&:to_sym)
106
147
  Event.new(
@@ -113,6 +154,12 @@ module Hecks
113
154
  ).emit!
114
155
  end
115
156
 
157
+ # Recursively symbolizes every Hash key reachable from `value`.
158
+ #
159
+ # @param value [Object] the value to symbolize; typically a Hash or Array,
160
+ # possibly nested
161
+ # @return [Object] `value` with every Hash key (at any depth) turned into a
162
+ # Symbol; a non-Hash, non-Array value passes through unchanged
116
163
  def deep_symbolize(value)
117
164
  case value
118
165
  when Hash then value.to_h { |k, v| [k.to_sym, deep_symbolize(v)] }
@@ -121,7 +168,7 @@ module Hecks
121
168
  end
122
169
  end
123
170
 
124
- # WHO IS OWED WHAT — the same selection `PolicyInterpreter#policies_for`
171
+ # **Who is owed what** — the same selection `PolicyInterpreter#policies_for`
125
172
  # and `SagaInterpreter#advance` make at delivery time, made once at
126
173
  # enqueue time so the row names its consumer. Policy rows first,
127
174
  # then saga rows, event order preserved within each: exactly the
@@ -129,14 +176,20 @@ module Hecks
129
176
  module Fanout
130
177
  module_function
131
178
 
132
- # ONE UID PER EVENT for this enqueue — the Event struct is
179
+ # One UID per event for this enqueue — the Event struct is
133
180
  # frozen after `emit!`, so the uid lives on the rows rather than
134
181
  # on it (and stays off `Event#to_h`, whose shape the golden and
135
182
  # parity specs pin). Policy and saga rows for the same event
136
183
  # share it, which is what makes `delivery_id` mean "this fact,
137
184
  # this consumer".
138
- # ROW ORDER IS DELIVERY ORDER (C10.2): per event, in `emits`
185
+ # Row order is delivery order (C10.2): per event, in `emits`
139
186
  # order — that event's policy rows, then its saga rows.
187
+ #
188
+ # @param registry [Runtime::Registry] the booted registry every candidate
189
+ # consumer is resolved against
190
+ # @param events [Array<Runtime::Event>] the just-emitted events to build rows for
191
+ # @param domain [String, Symbol] the emitting domain
192
+ # @return [Array<Runtime::Outbox::Row>] one pending row per (event, consumer)
140
193
  def rows_for(registry, events, domain)
141
194
  uids = events.to_h { |event| [event, SecureRandom.uuid] }
142
195
  events.flat_map do |event|
@@ -144,6 +197,16 @@ module Hecks
144
197
  end
145
198
  end
146
199
 
200
+ # Builds one pending row per policy `event` triggers.
201
+ #
202
+ # @param registry [Runtime::Registry] the booted registry policies are
203
+ # resolved against
204
+ # @param event [Runtime::Event] the just-emitted event
205
+ # @param domain [String, Symbol] the emitting domain
206
+ # @param uid [String] this event's own enqueue-time UID, shared by every row
207
+ # built for it
208
+ # @return [Array<Runtime::Outbox::Row>] one pending row per matching policy,
209
+ # emitting domain's own bluebook first
147
210
  def policies(registry, event, domain, uid)
148
211
  emitting = Naming.demodulise(event.aggregate)
149
212
  Outbox.bluebooks_home_first(registry, domain).flat_map do |bluebook|
@@ -159,6 +222,18 @@ module Hecks
159
222
  end
160
223
  end
161
224
 
225
+ # Builds one pending row per process manager `event` advances, in `domain`
226
+ # only (a saga never reacts across domains).
227
+ #
228
+ # @param registry [Runtime::Registry] the booted registry process managers
229
+ # are resolved against
230
+ # @param event [Runtime::Event] the just-emitted event
231
+ # @param domain [String, Symbol] the domain whose declared process managers
232
+ # are checked
233
+ # @param uid [String] this event's own enqueue-time UID, shared by every row
234
+ # built for it
235
+ # @return [Array<Runtime::Outbox::Row>] one pending row per process manager
236
+ # `event` starts, ends, or advances
162
237
  def sagas(registry, event, domain, uid)
163
238
  bluebook = registry.bluebook(domain)
164
239
  return [] unless bluebook
@@ -170,6 +245,13 @@ module Hecks
170
245
  end
171
246
  end
172
247
 
248
+ # Reports whether `event` starts, ends, or advances `process_manager`.
249
+ #
250
+ # @param process_manager [Bluebook::ProcessManager] the declared process
251
+ # manager to check
252
+ # @param event [Runtime::Event] the just-emitted event
253
+ # @return [Boolean] true if `event` names `process_manager`'s own
254
+ # `starts_on`, `ends_on`, or a declared handler
173
255
  def listens?(process_manager, event)
174
256
  process_manager.starts_on == event.name || process_manager.ends_on == event.name ||
175
257
  !process_manager.handler_for(event.name).nil?
@@ -179,6 +261,14 @@ module Hecks
179
261
  # operation — the row is the durable record that an external
180
262
  # call was owed, claimed right before the adapter is asked and
181
263
  # settled right after. Everything else is a plain "reaction".
264
+ #
265
+ # @param registry [Runtime::Registry] the booted registry the trigger's own
266
+ # aggregate/port are resolved against
267
+ # @param policy [Bluebook::Policy] the policy whose trigger is classified
268
+ # @param home_domain [String, Symbol] the domain `policy` is declared in,
269
+ # used when `policy` declares no `target_domain`
270
+ # @return [String] `"effect"` when the trigger resolves to an outbound port
271
+ # operation, `"reaction"` otherwise
182
272
  def kind_for(registry, policy, home_domain)
183
273
  target = "#{policy.target_domain || home_domain}::#{policy.trigger_command}"
184
274
  parsed = Naming.split_verb(target)
@@ -194,42 +284,65 @@ module Hecks
194
284
  end
195
285
  end
196
286
 
197
- # THE RELAY — one per `Dispatcher`. Enqueues into a repository's
287
+ # **The relay** — one per `Dispatcher`. Enqueues into a repository's
198
288
  # store, drains rows inline, and redrives what a previous process
199
289
  # left behind.
200
290
  class Relay
201
291
  attr_reader :registry, :log
202
292
 
203
- # NOT `saga_log`/`reaction_log` — those are ported byte-for-byte
293
+ # Not `saga_log`/`reaction_log` — those are ported byte-for-byte
204
294
  # by the Rust kernel (`spec/rust_conformance_spec.rb`); this is
205
295
  # an additive, Ruby-only log, the same rule `saga_dispatch_log`
206
296
  # and `policy_dispatch_log` already follow.
297
+ #
298
+ # @param registry [Runtime::Registry] the booted registry this relay drains
299
+ # rows for
207
300
  def initialize(registry)
208
301
  @registry = registry
209
302
  @log = []
210
303
  end
211
304
 
305
+ # Attaches the interpreters a delivered row's own consumer runs through.
306
+ #
212
307
  # A Dispatcher hands over the interpreters a consumer runs
213
308
  # through (`Dispatcher#initialize`). Until then this relay can
214
309
  # enqueue (that needs only the registry) but not deliver — and
215
310
  # nothing can dispatch without a dispatcher, so nothing asks it
216
- # to. The registry holds ONE relay for its lifetime; a second
311
+ # to. The registry holds one relay for its lifetime; a second
217
312
  # dispatcher fronting the same registry re-attaches, which is
218
313
  # fine because both dispatchers share every log and store.
314
+ #
315
+ # @param policies [Runtime::PolicyInterpreter] the interpreter a `"policy:"`
316
+ # consumer reacts through
317
+ # @param sagas [Runtime::SagaInterpreter] the interpreter a `"saga:"` consumer
318
+ # advances through
319
+ # @return [Runtime::Outbox::Relay] self
219
320
  def attach(policies:, sagas:)
220
321
  @policies = policies
221
322
  @sagas = sagas
222
323
  self
223
324
  end
224
325
 
326
+ # Reports whether a dispatcher has attached its interpreters.
327
+ #
328
+ # @return [Boolean] true once `attach` has run
225
329
  def attached? = !@policies.nil?
226
330
 
227
- # Called INSIDE the save transaction by `Interpreting` for the
331
+ # Writes one pending row per (event, consumer) `events` owes a reaction to.
332
+ #
333
+ # Called inside the save transaction by `Interpreting` for the
228
334
  # command/entity paths, and outside one by `Dispatcher` for port
229
335
  # operations (which save nothing, so there is no transaction to
230
336
  # share). Returns the rows as stored (ids assigned), or nil when
231
337
  # the repository has no outbox — the dispatcher then reacts
232
338
  # directly, exactly as before.
339
+ #
340
+ # @param repository [Persistence::AppendOnly] the repository whose store the
341
+ # rows are enqueued into; a no-op unless it has an outbox
342
+ # @param events [Array<Runtime::Event>] the events this dispatch just emitted
343
+ # @param domain [String, Symbol] the emitting domain
344
+ # @return [Array<Runtime::Outbox::Row>, nil] the stored rows, `id` assigned;
345
+ # `nil` when `repository` has no outbox; `[]` when `events` is empty
233
346
  def enqueue(repository, events, domain)
234
347
  return nil unless repository.outbox?
235
348
  return [] if events.empty?
@@ -241,9 +354,18 @@ module Hecks
241
354
 
242
355
  # Drain the rows a dispatch just committed. `rows` nil means "no
243
356
  # outbox here" — react directly, the pre-outbox path.
357
+ #
358
+ # @param rows [Array<Runtime::Outbox::Row>, nil] the just-enqueued rows to
359
+ # deliver; nil to react directly instead (no outbox on this repository)
360
+ # @param events [Array<Runtime::Event>] the events this dispatch just emitted,
361
+ # read when `rows` is nil
362
+ # @param domain [String, Symbol] the emitting domain
363
+ # @param repository [Persistence::AppendOnly] the repository `rows` were
364
+ # enqueued into
365
+ # @return [void]
244
366
  def deliver(rows, events, domain, repository)
245
367
  if rows.nil?
246
- # PER EVENT, in `emits` order — its policies, then its sagas
368
+ # Per event, in `emits` order — its policies, then its sagas
247
369
  # (C10.2, docs/semantics/bluebook-semantics.md); the same
248
370
  # order `Fanout.rows_for` lays the outbox rows in.
249
371
  events.each do |event|
@@ -258,6 +380,11 @@ module Hecks
258
380
 
259
381
  # One row: claim, run its consumer, settle. A claim that fails
260
382
  # means another relay (or this one, re-entrantly) already has it.
383
+ #
384
+ # @param row [Runtime::Outbox::Row] the row to claim and deliver
385
+ # @param repository [Persistence::AppendOnly] the repository `row` is stored in
386
+ # @return [Boolean] true when this call claimed and delivered `row`; false
387
+ # when the claim failed, or the consumer raised a defect (recorded on `log`)
261
388
  def deliver_row(row, repository)
262
389
  return false unless repository.outbox_claim(row.id)
263
390
 
@@ -283,13 +410,24 @@ module Hecks
283
410
  end
284
411
 
285
412
  # Every row in every bound store, newest last. `status:` narrows.
413
+ #
414
+ # @param status [String, Symbol, nil] keep only rows with this status; nil
415
+ # for every row
416
+ # @return [Array<Runtime::Outbox::Row>] copies of the matching rows, across
417
+ # every store with an outbox
286
418
  def rows(status: nil)
287
419
  stores.flat_map { |repository| repository.outbox_rows(status: status) }
288
420
  end
289
421
 
290
- # BOOT-TIME RECONCILIATION. Redrives `pending` rows (never
422
+ # Runs the boot-time reconciliation over every bound store.
423
+ #
424
+ # **Boot-time reconciliation**. Redrives `pending` rows (never
291
425
  # claimed — safe by construction); surfaces `claimed` rows and
292
- # redrives them ONLY when told to (`claimed: true`).
426
+ # redrives them only when told to (`claimed: true`).
427
+ #
428
+ # @param claimed [Boolean] whether to also redrive `claimed` rows (an
429
+ # explicit operator decision); false leaves them surfaced and untouched
430
+ # @return [Array<Runtime::Outbox::Row>] every row this call actually delivered
293
431
  def redrive!(claimed: false)
294
432
  redriven = []
295
433
  stores.each do |repository|