hecks 2.7.0 → 2.9.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 (463) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +11 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +4 -104
  4. data/lib/hecks/adapters/driven/d1.rb +44 -237
  5. data/lib/hecks/adapters/driven/folder.rb +17 -76
  6. data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
  7. data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
  8. data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
  9. data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
  10. data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
  11. data/lib/hecks/adapters/driven/heki.rb +13 -42
  12. data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
  15. data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
  16. data/lib/hecks/adapters/driven/lambda.rb +10 -84
  17. data/lib/hecks/adapters/driven/local_storage.rb +5 -40
  18. data/lib/hecks/adapters/driven/memory.rb +18 -99
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
  24. data/lib/hecks/adapters/driven/postgres.rb +145 -134
  25. data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
  26. data/lib/hecks/adapters/driven/prism.rb +7 -30
  27. data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
  28. data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
  29. data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
  30. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
  31. data/lib/hecks/adapters/driven/sqlite.rb +128 -65
  32. data/lib/hecks/adapters/driven/tenant_provisioner.rb +15 -37
  33. data/lib/hecks/adapters/driven.rb +5 -36
  34. data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
  35. data/lib/hecks/behaviors/dsl.rb +7 -25
  36. data/lib/hecks/behaviors/expectations.rb +32 -142
  37. data/lib/hecks/behaviors/ir.rb +7 -20
  38. data/lib/hecks/behaviors/rspec.rb +6 -17
  39. data/lib/hecks/behaviors/runner.rb +10 -34
  40. data/lib/hecks/behaviors.rb +4 -8
  41. data/lib/hecks/bluebook/aggregate.rb +9 -75
  42. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
  43. data/lib/hecks/bluebook/assembly/build.rb +4 -15
  44. data/lib/hecks/bluebook/assembly/contract.rb +15 -136
  45. data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
  46. data/lib/hecks/bluebook/assembly/marks.rb +31 -223
  47. data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
  48. data/lib/hecks/bluebook/assembly.rb +6 -45
  49. data/lib/hecks/bluebook/attribute.rb +12 -61
  50. data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
  51. data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
  52. data/lib/hecks/bluebook/behaviour/chapter.rb +17 -42
  53. data/lib/hecks/bluebook/behaviour/command.rb +14 -66
  54. data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
  55. data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
  56. data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
  57. data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
  58. data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
  59. data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
  60. data/lib/hecks/bluebook/behaviour/read_model.rb +38 -26
  61. data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
  62. data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
  63. data/lib/hecks/bluebook/capabilities.rb +17 -49
  64. data/lib/hecks/bluebook/chapter.rb +10 -48
  65. data/lib/hecks/bluebook/command.rb +23 -81
  66. data/lib/hecks/bluebook/domain_port.rb +14 -63
  67. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
  68. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
  69. data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
  70. data/lib/hecks/bluebook/dsl/binding_proxy.rb +17 -65
  71. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
  72. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
  73. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
  74. data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
  75. data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
  76. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
  77. data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
  78. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
  79. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -189
  80. data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
  81. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
  82. data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
  83. data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
  84. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
  85. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
  86. data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
  87. data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
  88. data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
  89. data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
  90. data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
  91. data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
  92. data/lib/hecks/bluebook/dsl/world_builder.rb +19 -106
  93. data/lib/hecks/bluebook/entity.rb +13 -71
  94. data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
  95. data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
  96. data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
  97. data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
  98. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
  99. data/lib/hecks/bluebook/expression/resolver.rb +37 -660
  100. data/lib/hecks/bluebook/hexagon.rb +5 -28
  101. data/lib/hecks/bluebook/lifecycle.rb +5 -16
  102. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
  103. data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
  104. data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
  105. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
  106. data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
  107. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
  108. data/lib/hecks/bluebook/meta_validator/shapes.rb +54 -268
  109. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -308
  110. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
  111. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
  112. data/lib/hecks/bluebook/meta_validator.rb +79 -349
  113. data/lib/hecks/bluebook/model_check/client_profile.rb +10 -91
  114. data/lib/hecks/bluebook/model_check.rb +102 -600
  115. data/lib/hecks/bluebook/pattern_subset.rb +11 -69
  116. data/lib/hecks/bluebook/process_manager.rb +13 -83
  117. data/lib/hecks/bluebook/project_loader.rb +3 -39
  118. data/lib/hecks/bluebook/project_register.rb +12 -60
  119. data/lib/hecks/bluebook/query.rb +9 -48
  120. data/lib/hecks/bluebook/read_model.rb +57 -81
  121. data/lib/hecks/bluebook/reference.rb +7 -41
  122. data/lib/hecks/bluebook/smoke_test.rb +8 -98
  123. data/lib/hecks/bluebook/synthesizer.rb +13 -62
  124. data/lib/hecks/bluebook/translation.rb +26 -62
  125. data/lib/hecks/bluebook/value_object.rb +11 -36
  126. data/lib/hecks/bluebook.rb +5 -22
  127. data/lib/hecks/cache_dir.rb +84 -0
  128. data/lib/hecks/cli/document.rb +86 -0
  129. data/lib/hecks/cli/ir.rb +45 -0
  130. data/lib/hecks/cli/mcp.rb +31 -0
  131. data/lib/hecks/cli/mcp_door.rb +366 -0
  132. data/lib/hecks/cli/model_check.rb +203 -0
  133. data/lib/hecks/cli/project_cli.rb +90 -0
  134. data/lib/hecks/cli/project_diagrams.rb +36 -0
  135. data/lib/hecks/cli/run.rb +173 -0
  136. data/lib/hecks/cli/smoke_test.rb +45 -0
  137. data/lib/hecks/cli/stores.rb +53 -0
  138. data/lib/hecks/cli.rb +160 -0
  139. data/lib/hecks/construct.rb +8 -34
  140. data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
  141. data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
  142. data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
  143. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
  144. data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
  145. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
  146. data/lib/hecks/embryonaut_bluebook.rb +4 -71
  147. data/lib/hecks/facade/cli_door.rb +24 -106
  148. data/lib/hecks/facade/cli_runner.rb +28 -172
  149. data/lib/hecks/facade/command_request.rb +13 -33
  150. data/lib/hecks/facade/handle.rb +34 -121
  151. data/lib/hecks/facade/json_door.rb +31 -146
  152. data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
  153. data/lib/hecks/facade/surface/chapter.rb +10 -54
  154. data/lib/hecks/facade/surface.rb +8 -23
  155. data/lib/hecks/facade.rb +10 -25
  156. data/lib/hecks/forms/app.rb +15 -70
  157. data/lib/hecks/forms/command_form_renderer.rb +10 -29
  158. data/lib/hecks/forms/field_renderer.rb +17 -156
  159. data/lib/hecks/forms/field_shape.rb +30 -222
  160. data/lib/hecks/forms/html.rb +12 -47
  161. data/lib/hecks/forms/index_renderer.rb +1 -4
  162. data/lib/hecks/forms/page.rb +3 -9
  163. data/lib/hecks/forms/params.rb +14 -60
  164. data/lib/hecks/forms/port_argument.rb +2 -19
  165. data/lib/hecks/forms/query_form_renderer.rb +4 -79
  166. data/lib/hecks/forms/record_renderer.rb +8 -75
  167. data/lib/hecks/forms/record_table.rb +7 -50
  168. data/lib/hecks/forms/reference_options.rb +3 -30
  169. data/lib/hecks/forms/value_object_shape.rb +8 -35
  170. data/lib/hecks/forms.rb +5 -33
  171. data/lib/hecks/fqn.rb +5 -12
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
  175. data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
  176. data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
  177. data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
  178. data/lib/hecks/framework.rb +11 -80
  179. data/lib/hecks/freezer.rb +5 -39
  180. data/lib/hecks/grammar/expression.bluebook +15 -40
  181. data/lib/hecks/grammar/translation.bluebook +3 -6
  182. data/lib/hecks/grammar.rb +16 -41
  183. data/lib/hecks/ir.rb +14 -83
  184. data/lib/hecks/language/adapter.bluebook +2 -7
  185. data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
  186. data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
  187. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
  188. data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
  189. data/lib/hecks/language/bluebook/command.bluebook +27 -144
  190. data/lib/hecks/language/bluebook/entity.bluebook +24 -97
  191. data/lib/hecks/language/bluebook/policy.bluebook +16 -67
  192. data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
  193. data/lib/hecks/language/bluebook/projection.bluebook +126 -60
  194. data/lib/hecks/language/bluebook/query.bluebook +12 -43
  195. data/lib/hecks/language/bluebook/shape.bluebook +23 -73
  196. data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
  197. data/lib/hecks/language/bluebook/vocabulary.bluebook +98 -197
  198. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
  199. data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
  200. data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
  201. data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
  202. data/lib/hecks/language/oidc.json +35 -0
  203. data/lib/hecks/language/port.bluebook +4 -17
  204. data/lib/hecks/language/translation/translation.bluebook +3 -9
  205. data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
  206. data/lib/hecks/language/world/wiring.bluebook +1 -3
  207. data/lib/hecks/language/world/world.bluebook +5 -7
  208. data/lib/hecks/literal.rb +8 -50
  209. data/lib/hecks/mcp_door_scope.rb +95 -0
  210. data/lib/hecks/mcp_stdio_guard.rb +4 -41
  211. data/lib/hecks/naming.rb +29 -137
  212. data/lib/hecks/ports/access_control.rb +7 -23
  213. data/lib/hecks/ports/agent/answers.rb +5 -23
  214. data/lib/hecks/ports/agent.rb +47 -172
  215. data/lib/hecks/ports/authentication.rb +12 -39
  216. data/lib/hecks/ports/authorization.rb +16 -43
  217. data/lib/hecks/ports/clock.rb +5 -45
  218. data/lib/hecks/ports/extraction.rb +7 -12
  219. data/lib/hecks/ports/identity_assignment.rb +8 -29
  220. data/lib/hecks/ports/identity_generation.rb +6 -29
  221. data/lib/hecks/ports/identity_resolution.rb +2 -10
  222. data/lib/hecks/ports/key_vault.rb +9 -45
  223. data/lib/hecks/ports/persistence/append_only.rb +62 -65
  224. data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
  225. data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
  226. data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
  227. data/lib/hecks/ports/persistence/plugin.rb +13 -42
  228. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
  229. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
  231. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +7 -26
  232. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
  233. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
  242. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
  243. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
  244. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
  245. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
  246. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
  247. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
  248. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
  249. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
  250. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
  251. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
  252. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
  253. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
  254. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
  255. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
  256. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
  257. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +22 -122
  258. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
  259. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
  260. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
  261. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
  262. data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
  263. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
  264. data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
  266. data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
  267. data/lib/hecks/ports/persistence/state_codec.rb +23 -103
  268. data/lib/hecks/ports/persistence.rb +7 -14
  269. data/lib/hecks/ports/projection.rb +42 -84
  270. data/lib/hecks/ports/query/in_memory.rb +10 -58
  271. data/lib/hecks/ports/query/ordering.rb +11 -32
  272. data/lib/hecks/ports/query.rb +14 -37
  273. data/lib/hecks/ports.rb +2 -8
  274. data/lib/hecks/projections/bootstrap_table.rb +11 -43
  275. data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
  276. data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
  277. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
  278. data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
  279. data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
  280. data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
  281. data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
  282. data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
  283. data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
  284. data/lib/hecks/projections/deploy/fargate.rb +35 -202
  285. data/lib/hecks/projections/deploy/lambda.rb +151 -679
  286. data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
  287. data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
  288. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
  289. data/lib/hecks/projections/deploy/preview/script.rb +2 -14
  290. data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
  291. data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
  292. data/lib/hecks/projections/deploy/preview/template.rb +2 -58
  293. data/lib/hecks/projections/deploy/preview.rb +2 -67
  294. data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
  295. data/lib/hecks/projections/deploy/scripts.rb +4 -57
  296. data/lib/hecks/projections/deploy/shared.rb +47 -146
  297. data/lib/hecks/projections/deploy/smoke.rb +4 -61
  298. data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
  299. data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
  300. data/lib/hecks/projections/deploy/template_diff.rb +5 -57
  301. data/lib/hecks/projections/diagrams.rb +20 -582
  302. data/lib/hecks/projections/glossary/html.rb +14 -130
  303. data/lib/hecks/projections/glossary/markdown.rb +8 -69
  304. data/lib/hecks/projections/glossary/mermaid.rb +9 -66
  305. data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
  306. data/lib/hecks/projections/glossary/sentences.rb +14 -159
  307. data/lib/hecks/projections/glossary.rb +30 -222
  308. data/lib/hecks/projections/ir.rb +3 -10
  309. data/lib/hecks/projections/model/deviations.rb +15 -42
  310. data/lib/hecks/projections/model.rb +12 -51
  311. data/lib/hecks/projections/oidc.rb +12 -56
  312. data/lib/hecks/projections/parser_table.rb +9 -28
  313. data/lib/hecks/projections/reference.rb +9 -16
  314. data/lib/hecks/projections/rust_vocabulary.rb +12 -250
  315. data/lib/hecks/projections/shape.rb +3 -29
  316. data/lib/hecks/projections/statements.rb +7 -86
  317. data/lib/hecks/projections/vocabulary.rb +6 -22
  318. data/lib/hecks/projections.rb +2 -16
  319. data/lib/hecks/projector/cli_projector.rb +60 -375
  320. data/lib/hecks/projector/docs_projector.rb +10 -201
  321. data/lib/hecks/projector/exporter.rb +46 -138
  322. data/lib/hecks/projector/ir_projector.rb +2 -9
  323. data/lib/hecks/projector/narrate_projector.rb +15 -189
  324. data/lib/hecks/projector/target.rb +13 -76
  325. data/lib/hecks/projector.rb +30 -121
  326. data/lib/hecks/query_specification/common/comparators.rb +5 -26
  327. data/lib/hecks/query_specification/common/comparison.rb +35 -143
  328. data/lib/hecks/query_specification/common/dsl.rb +8 -24
  329. data/lib/hecks/query_specification/common/null_policy.rb +20 -70
  330. data/lib/hecks/query_specification/common/options.rb +2 -6
  331. data/lib/hecks/query_specification/common/where_clause.rb +2 -10
  332. data/lib/hecks/query_specification/field_path.rb +37 -77
  333. data/lib/hecks/query_specification/hop_path.rb +12 -76
  334. data/lib/hecks/release/gem_pin.rb +2 -26
  335. data/lib/hecks/release/runner/ci_publisher.rb +93 -0
  336. data/lib/hecks/release/runner/commands.rb +66 -0
  337. data/lib/hecks/release/runner/console.rb +52 -0
  338. data/lib/hecks/release/runner/gem_publisher.rb +43 -0
  339. data/lib/hecks/release/runner/git.rb +48 -0
  340. data/lib/hecks/release/runner/npm_publisher.rb +75 -0
  341. data/lib/hecks/release/runner/preflight.rb +104 -0
  342. data/lib/hecks/release/runner/published.rb +55 -0
  343. data/lib/hecks/release/runner/tagger.rb +80 -0
  344. data/lib/hecks/release/runner.rb +215 -0
  345. data/lib/hecks/rendering.rb +4 -26
  346. data/lib/hecks/router/namespace_installer.rb +12 -36
  347. data/lib/hecks/router.rb +4 -8
  348. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  349. data/lib/hecks/runtime/boot_gates.rb +3 -18
  350. data/lib/hecks/runtime/caller.rb +19 -59
  351. data/lib/hecks/runtime/capability_graph.rb +5 -21
  352. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  353. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
  354. data/lib/hecks/runtime/command_interpreter.rb +125 -307
  355. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  356. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  357. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  358. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  359. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  360. data/lib/hecks/runtime/dependency_planning.rb +30 -102
  361. data/lib/hecks/runtime/dispatcher.rb +82 -241
  362. data/lib/hecks/runtime/entity_element.rb +40 -462
  363. data/lib/hecks/runtime/entity_interpreter.rb +65 -220
  364. data/lib/hecks/runtime/errors.rb +32 -115
  365. data/lib/hecks/runtime/event.rb +3 -25
  366. data/lib/hecks/runtime/identity.rb +18 -154
  367. data/lib/hecks/runtime/instance.rb +18 -150
  368. data/lib/hecks/runtime/interpreting.rb +11 -70
  369. data/lib/hecks/runtime/invocation.rb +28 -164
  370. data/lib/hecks/runtime/loader.rb +48 -186
  371. data/lib/hecks/runtime/outbox.rb +38 -261
  372. data/lib/hecks/runtime/policy_interpreter.rb +38 -200
  373. data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
  374. data/lib/hecks/runtime/query_interpreter.rb +45 -187
  375. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  376. data/lib/hecks/runtime/read_model_interpreter.rb +139 -208
  377. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  378. data/lib/hecks/runtime/reference_hop.rb +8 -81
  379. data/lib/hecks/runtime/refusal_wording.rb +8 -89
  380. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  381. data/lib/hecks/runtime/registry/verification.rb +52 -225
  382. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  383. data/lib/hecks/runtime/registry.rb +103 -244
  384. data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
  385. data/lib/hecks/runtime/routing.rb +4 -15
  386. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  387. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  388. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  389. data/lib/hecks/runtime/tenant_check.rb +6 -68
  390. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  391. data/lib/hecks/runtime/value/admission.rb +16 -67
  392. data/lib/hecks/runtime/value/coercion.rb +155 -641
  393. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  394. data/lib/hecks/runtime/value.rb +12 -84
  395. data/lib/hecks/runtime.rb +17 -65
  396. data/lib/hecks/storehouse.rb +146 -567
  397. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
  398. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  399. data/lib/hecks/vendoring/git_environment.rb +2 -8
  400. data/lib/hecks/vendoring/git_source.rb +4 -10
  401. data/lib/hecks/vendoring.rb +7 -46
  402. data/lib/hecks/version.rb +3 -14
  403. data/lib/hecks/vocabulary.rb +5 -0
  404. data/lib/hecks.rb +32 -82
  405. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  406. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  407. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  408. metadata +30 -59
  409. data/lib/hecks/bench/cli.rb +0 -74
  410. data/lib/hecks/bench/environment.rb +0 -100
  411. data/lib/hecks/bench/postgres_probe.rb +0 -41
  412. data/lib/hecks/bench/report.rb +0 -116
  413. data/lib/hecks/bench/ruby_runner.rb +0 -151
  414. data/lib/hecks/bench/run.rb +0 -55
  415. data/lib/hecks/bench/rust_runner.rb +0 -165
  416. data/lib/hecks/bench/stats.rb +0 -75
  417. data/lib/hecks/bench/suite.rb +0 -172
  418. data/lib/hecks/bench/workload.rb +0 -137
  419. data/lib/hecks/bench.rb +0 -28
  420. data/lib/hecks/codemod.rb +0 -443
  421. data/lib/hecks/corpus.rb +0 -479
  422. data/lib/hecks/doc/reference.rb +0 -585
  423. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
  424. data/lib/hecks/fuzzing/combination_miner.rb +0 -178
  425. data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
  426. data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
  427. data/lib/hecks/fuzzing/differential.rb +0 -192
  428. data/lib/hecks/fuzzing/domain_generator.rb +0 -871
  429. data/lib/hecks/fuzzing/era_boundary.rb +0 -154
  430. data/lib/hecks/fuzzing/form_census.rb +0 -285
  431. data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
  432. data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
  433. data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
  434. data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
  435. data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
  436. data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
  437. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
  438. data/lib/hecks/fuzzing/properties/guards.rb +0 -398
  439. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
  440. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
  441. data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
  442. data/lib/hecks/fuzzing/properties/querying.rb +0 -260
  443. data/lib/hecks/fuzzing/properties.rb +0 -267
  444. data/lib/hecks/fuzzing/qa_settings.rb +0 -164
  445. data/lib/hecks/fuzzing/replay.rb +0 -930
  446. data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
  447. data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
  448. data/lib/hecks/fuzzing/self_consistency.rb +0 -825
  449. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
  450. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
  451. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
  452. data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
  453. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
  454. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
  455. data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
  456. data/lib/hecks/fuzzing/shrinker.rb +0 -309
  457. data/lib/hecks/fuzzing/structural_skips.rb +0 -53
  458. data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
  459. data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
  460. data/lib/hecks/fuzzing/value_generator.rb +0 -340
  461. data/lib/hecks/fuzzing.rb +0 -23
  462. data/lib/hecks/grammar/evolve.rb +0 -524
  463. data/lib/hecks/query_ir.rb +0 -482
@@ -6,7 +6,12 @@ module Hecks
6
6
  module Persistence
7
7
  # `mirrors` is durable replication intent. It is part of the same
8
8
  # append as the authoritative state, never a second outbox store.
9
- Entry = Struct.new(:operation, :id, :state, :mirrors, keyword_init: true) do
9
+ #
10
+ # `sequence` is the journal position an adapter assigned this entry
11
+ # (set by `append` on a live write, or by `entries`/`entries_since` on
12
+ # a replayed one); nil for an adapter that assigns none. It rides
13
+ # along so `project` can advance a checkpoint without a second lookup.
14
+ Entry = Struct.new(:operation, :id, :state, :mirrors, :sequence, keyword_init: true) do
10
15
  # Tells a projecting adapter that this entry writes a record.
11
16
  #
12
17
  # @return [Boolean] true when `operation` is the String `"save"`
@@ -18,9 +23,8 @@ module Hecks
18
23
  def delete? = operation == "delete"
19
24
  end
20
25
 
21
- # Makes append-before-projection a port invariant. Adapters retain
22
- # control of their durable format, but every one must accept the same
23
- # entry stream and materialize current state from it.
26
+ # Makes append-before-projection a port invariant: adapters keep their own durable
27
+ # format, but must accept the same entry stream and materialize state from it.
24
28
  class AppendOnly
25
29
  attr_reader :adapter
26
30
 
@@ -71,6 +75,17 @@ module Hecks
71
75
  # undecoded (`CodecBoundary.check_entries!`)
72
76
  def entries = @adapter.entries
73
77
 
78
+ # Reads how far this repository's journal has been compacted, when the adapter tracks
79
+ # that (Postgres, Sqlite).
80
+ #
81
+ # @return [Integer] the highest journal `sequence` already deleted by compaction; 0 for
82
+ # an adapter that tracks no such thing, or nothing compacted yet
83
+ def compacted_through
84
+ return 0 unless @adapter.respond_to?(:compacted_through)
85
+
86
+ @adapter.compacted_through
87
+ end
88
+
74
89
  # Lists the optional persistence behaviours the adapter advertises.
75
90
  #
76
91
  # @return [Array<Symbol>] frozen capability names such as `:atomic_put`,
@@ -96,12 +111,8 @@ module Hecks
96
111
 
97
112
  # Reads the events the adapter has durably recorded.
98
113
  #
99
- # Not an endless `def events = ... if ...` — that modifier binds to
100
- # the whole `def`, not just its body, so it evaluates against
101
- # `@adapter` while `@adapter` is still nil (class-body time,
102
- # before `initialize` ever runs) and silently skips defining the
103
- # method at all. Found live: nothing in this codebase called
104
- # `AppendOnly#events` before Memory got a `reset!` test that did.
114
+ # Written as an `if` guard, not a trailing modifier, which would evaluate against
115
+ # `@adapter` before `initialize` runs and silently skip defining the method.
105
116
  #
106
117
  # @return [Array<Runtime::Event>, nil] recorded events, oldest first; nil when the
107
118
  # adapter keeps no event log
@@ -109,15 +120,39 @@ module Hecks
109
120
  @adapter.events if @adapter.respond_to?(:events)
110
121
  end
111
122
 
112
- # Replays the whole journal through `project` to rebuild the projected records.
123
+ # Reads back one record's durably recorded events, oldest first, scoped by the
124
+ # adapter itself when it can (a SQL adapter pushes this down as a `WHERE` clause
125
+ # instead of `#events`'s whole-table read); falls back to filtering `#events` for
126
+ # an adapter with no scoped lookup of its own.
113
127
  #
114
- # An append is durable before a projection is attempted. Replaying the
115
- # log restores a snapshot/table after a crash in that small window.
128
+ # @param aggregate [String] the `"domain::AggregateName"` key events are stored under
129
+ # @param id [String, Object] the record's identity, matched as `id.to_s`
130
+ # @return [Array<Runtime::Event>, nil] the record's events; nil when the adapter
131
+ # keeps no event log at all
132
+ def events_for(aggregate:, id:)
133
+ return @adapter.events_for(aggregate: aggregate, id: id) if @adapter.respond_to?(:events_for)
134
+
135
+ events&.select { |event| event.aggregate == aggregate && event.id.to_s == id.to_s }
136
+ end
137
+
138
+ # Replays unprojected journal entries through `project` to rebuild the projected records.
139
+ #
140
+ # An append is durable before a projection is attempted; replaying restores a
141
+ # snapshot/table after a crash in that small window. Postgres and Sqlite keep their
142
+ # projected table in the same transaction as the journal append, so they never fall
143
+ # behind it — each tracks how far it has replayed (`checkpoint`) and only re-walks
144
+ # entries past that point (`entries_since`), bounded by activity since the last boot,
145
+ # not total history. An adapter without that pair (Memory, Heki, D1, PostgresEra,
146
+ # RemoteRuntime) is unaffected: it still replays every entry.
116
147
  #
117
148
  # @return [Persistence::AppendOnly] self, so a factory can build and recover in one
118
149
  # expression
119
150
  def recover!
120
- entries.each { |entry| project(entry) }
151
+ if @adapter.respond_to?(:checkpoint) && @adapter.respond_to?(:entries_since)
152
+ @adapter.entries_since(@adapter.checkpoint).each { |entry| project(entry) }
153
+ else
154
+ entries.each { |entry| project(entry) }
155
+ end
121
156
  self
122
157
  end
123
158
 
@@ -142,29 +177,15 @@ module Hecks
142
177
 
143
178
  # Journals an instance's state, projects it, and reports how the write landed.
144
179
  #
145
- # Returns an `Outcome`, not a bare `Instance` — every call site
146
- # (`CommandInterpreter`/`EntityInterpreter`'s own `step_save`,
147
- # `RebuildSweep#refresh`) reads it that way.
148
- #
149
- # `expected_version:` requests optimistic-concurrency CAS — commit
150
- # only if the stored record's version still matches what this
151
- # instance was read at. It is `nil` both when a caller explicitly
152
- # doesn't want CAS (`RebuildSweep#refresh`'s own projection-field
153
- # touch-up, which has no `given` to protect) and when the instance
154
- # is brand new (never read from storage, so `instance.version` is
155
- # nil) — both cases fall through to the plain, unconditional
156
- # `project(entry)` below, byte-for-byte today's behavior. Only an
157
- # adapter that both receives a non-nil `expected_version` and
158
- # declares `:optimistic_concurrency` gets CAS treatment; every
159
- # other adapter/call site is unaffected.
180
+ # Returns an Outcome, not a bare Instance, so callers can check `:saved` vs `:stale`;
181
+ # `expected_version:` only triggers CAS on an adapter declaring `:optimistic_concurrency`.
160
182
  #
161
183
  # @param instance [Runtime::Instance] the record to persist; its `state` is shallow
162
184
  # copied into the entry
163
185
  # @param expected_version [Integer, nil] the version the instance was read at, or nil
164
186
  # for an unconditional write
165
- # @return [Persistence::Outcome] status `:saved` or `:stale`; on `:saved` its `instance`
166
- # is what the adapter's `project` answered, else the instance passed in; on `:stale`
167
- # it is the instance passed in. The entry is journaled even when the result is `:stale`
187
+ # @return [Persistence::Outcome] status `:saved` or `:stale`; `instance` is the
188
+ # adapter's projected result on `:saved`, else the instance passed in
168
189
  # @raise [Runtime::WiringError] when the adapter's `append` or `project` refuses
169
190
  def save(instance, expected_version: nil)
170
191
  entry = Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
@@ -218,18 +239,7 @@ module Hecks
218
239
 
219
240
  # Records one emitted event durably, when the adapter keeps an event log.
220
241
  #
221
- # Not an endless `def record_event = ... if ...` — same gotcha as
222
- # `events` above, and it bit for real here: this guard evaluated
223
- # against `@adapter` at class-body time (nil, always false), so
224
- # `record_event` was never defined at all. `emission.rb`'s own
225
- # `repository.record_event(event) if repository.respond_to?(:record_event)`
226
- # therefore never fired for any adapter, ever — every declared
227
- # `emits` was computed and reported in `registry.event_log` (an
228
- # in-process array, gone at exit) but never durably recorded.
229
- # Caught because a live tail of a domain's own persisted events
230
- # found nothing to tail. `sqlite_spec.rb`/`postgres_spec.rb`/
231
- # `postgres_era_spec.rb` all call `adapter.record_event` directly,
232
- # bypassing this wrapper — which is exactly why no spec noticed.
242
+ # Written as an `if` guard, not a trailing modifier — same reason as `events` above.
233
243
  #
234
244
  # @param event [Runtime::Event] the event to record
235
245
  # @return [Object, nil] adapter-defined write result (an Array for Memory, a driver
@@ -240,17 +250,9 @@ module Hecks
240
250
 
241
251
  # Runs the block inside the adapter's transaction, or plainly when it has none.
242
252
  #
243
- # One COMMIT boundary for save + emit + outbox — `Interpreting#
244
- # run_dispatch_order` runs the `save` and `emit` steps inside
245
- # this block, so an adapter that owns a real transaction
246
- # (Sqlite, Postgres) commits the aggregate row, its journal
247
- # entry, and the outbox rows together or not at all. An adapter
248
- # without one just yields: Memory has nothing to roll back, and
249
- # a file adapter's own `with_lock` already serialises its pair
250
- # of writes. Nested calls are the adapter's problem to make
251
- # re-entrant (both SQL adapters check for an open transaction
252
- # first) — a reaction dispatched from inside a drain never nests
253
- # here anyway, because draining happens after this block returns.
253
+ # One commit boundary for save + emit + outbox: an adapter with a real transaction
254
+ # (Sqlite, Postgres) commits the row, journal entry, and outbox rows together or
255
+ # not at all; Memory just yields, having nothing to roll back.
254
256
  #
255
257
  # @yield the writes to commit together; an exception raised inside rolls back an
256
258
  # adapter-owned transaction and propagates
@@ -264,11 +266,9 @@ module Hecks
264
266
 
265
267
  # Runs the block while holding the adapter's cross-process write lock.
266
268
  #
267
- # Only an adapter advertising `:cross_process_lock` (PostgresEra —
268
- # see ADR 0036) implements this; `run_dispatch_order_with_isolation`
269
- # (runtime/interpreting.rb) checks `capabilities` before ever
270
- # calling it, so the plain `yield` fallback here only guards
271
- # against a stray direct call, not the real dispatch path.
269
+ # Only PostgresEra advertises `:cross_process_lock` (ADR 0036); the real dispatch
270
+ # path checks `capabilities` first, so this `yield` fallback only guards a stray
271
+ # direct call.
272
272
  #
273
273
  # @yield the dispatch to run with writers serialised
274
274
  # @return [Object] adapter-defined; the block's own value when the adapter has no
@@ -279,10 +279,7 @@ module Hecks
279
279
  yield
280
280
  end
281
281
 
282
- # **The outbox contract** — four optional adapter methods, probed
283
- # together the way `save_saga`/`delete_saga`/`each_saga` are
284
- # (`Registry::SagaPersistence`): an adapter either has an outbox
285
- # or it doesn't, never half of one. See `Runtime::Outbox`.
282
+ # An adapter implements the whole outbox contract or none of it (see Runtime::Outbox).
286
283
  OUTBOX_METHODS = %i[outbox_enqueue outbox_claim outbox_settle outbox_rows].freeze
287
284
 
288
285
  # Reports whether the adapter implements the whole outbox contract; the answer is
@@ -308,7 +305,7 @@ module Hecks
308
305
  #
309
306
  # @param id [Integer] the row id assigned by `outbox_enqueue`
310
307
  # @return [Boolean] true when this call claimed the row; false when it is missing or
311
- # no longer pending
308
+ # not pending
312
309
  def outbox_claim(id) = @adapter.outbox_claim(id)
313
310
 
314
311
  # Records the final status of a claimed outbox row.
@@ -4,23 +4,13 @@ require_relative "../../runtime/registry"
4
4
  module Hecks
5
5
  module Ports
6
6
  module Persistence
7
- # Resolves one aggregate's persistence topology. An unmarked binding is
8
- # authoritative; roles are deliberately opt-in so a second adapter can
9
- # never silently become a read source.
7
+ # Resolves one aggregate's persistence topology; an unmarked binding is
8
+ # authoritative, and roles are opt-in so no adapter silently becomes a read source.
10
9
  module BindingPolicy
11
10
  module_function
12
11
 
13
- # Picks the one bind that names an aggregate's authoritative store.
14
- #
15
- # A domain with no hecksagon gets the default in-memory bind. A domain that has one
16
- # must bind the aggregate exactly once without a role.
17
- #
18
- # A `default_adapter` a world declares (`Registry#default_adapter_for` — the
19
- # chapter's own world, else the project's) stands in wherever the hecksagon binds
20
- # nothing for the aggregate, and replaces the in-memory fallback for a domain
21
- # with no hecksagon. It is never consulted once the hecksagon binds the aggregate
22
- # or declares a domain-level default of its own, so an explicit chapter decision
23
- # always wins.
12
+ # Picks the one bind that names an aggregate's authoritative store. A world's
13
+ # `default_adapter` only fills a hecksagon's gap; an explicit bind always wins.
24
14
  #
25
15
  # @param registry [Runtime::Registry] the registry holding the domain's hecksagon
26
16
  # @param domain [String, Symbol] name of the domain the aggregate belongs to
@@ -4,47 +4,15 @@ require_relative "../../runtime/errors"
4
4
  module Hecks
5
5
  module Ports
6
6
  module Persistence
7
- # No adapter can build an `Instance` from undecoded state (Phase 2,
8
- # Track A, PR A3). Routing every adapter through `StateCodec` is a
9
- # convention until something refuses the bypass; this is that
10
- # something, and it is installed by `RepositoryFactory.build` — the
11
- # one place every runtime repository is made — so no adapter can opt
12
- # out of it.
13
- #
14
- # ## The mechanism, two halves
15
- #
16
- # 1. `guard!(adapter)` extends the adapter object (not its class,
17
- # not a proxy — `is_a?`, `class`, and `===` stay the adapter's
18
- # own) with a module that wraps every public method the adapter's
19
- # class defines. Each call runs inside the boundary: a
20
- # thread-local flag, re-entrant, restored on the way out. A block
21
- # the caller passes (`transaction`, `with_write_lock`,
22
- # `each_saga`) runs outside it — that block is the dispatch
23
- # itself (hydrate, entity views, mutation), not adapter code.
24
- # Reaching the adapter through `repository.adapter` (the query
25
- # port, saga persistence, `bin/heki_compact`) is guarded all the
26
- # same: the wrapper is on the object.
27
- #
28
- # 2. `Runtime::Instance#initialize` asks `check_state!` whenever it
29
- # is handed `state:`. Outside the boundary that is a no-op; inside
30
- # it, state `StateCodec.decoded?` rejects refuses by name. An
31
- # `entries` answer is checked the same way on its way out, since a
32
- # journal `Entry` is not an `Instance`.
33
- #
34
- # ## Checked, not silently decoded
35
- #
36
- # An adapter that forgets the codec is a bug in that adapter, and
37
- # decoding for it here would hide the forgetting. Hydration does not
38
- # respell keys either (A4) — `Value.hydrate` refuses a non-Symbol
39
- # top-level key everywhere — so this boundary's deep check and
40
- # hydration's shallow one agree.
7
+ # Refuses to let a guarded adapter build an `Instance` from undecoded state.
8
+ # `RepositoryFactory.build` installs it, so no adapter can opt out.
41
9
  module CodecBoundary
42
10
  KEY = :hecks_persistence_codec_boundary
43
11
 
44
12
  module_function
45
13
 
46
- # Extends an adapter object so every public method its class defines runs inside the
47
- # boundary; guarding an adapter twice wraps it once.
14
+ # Extends the adapter object so every public method its class defines runs inside the
15
+ # boundary; guarding twice wraps once.
48
16
  #
49
17
  # @param adapter [Object] a driven persistence adapter instance
50
18
  # @return [Object] the same adapter object, now including `Guarded`
@@ -53,33 +21,22 @@ module Hecks
53
21
  adapter
54
22
  end
55
23
 
56
- # Reports whether the current thread is executing inside a guarded adapter call.
24
+ # Whether this thread is currently inside a guarded adapter call.
57
25
  #
58
- # @return [Boolean] true between entering a guarded adapter method and yielding to the
59
- # caller's block or returning
26
+ # @return [Boolean]
60
27
  def active? = Thread.current[KEY] == true
61
28
 
62
- # Runs the block with the boundary switched on for this thread, restoring the earlier
63
- # setting afterwards.
64
- #
65
- # @yield adapter code whose `Instance` construction is to be checked
66
- # @return [Object] the block's value
29
+ # Runs the block with the boundary on for this thread; re-entrant.
67
30
  def within(&) = with_flag(true, &)
68
31
 
69
- # Runs the block with the boundary switched off for this thread, restoring the earlier
70
- # setting afterwards.
71
- #
72
- # @yield caller code, such as a dispatch running inside the adapter's transaction
73
- # @return [Object] the block's value
32
+ # Runs the block with the boundary off for this thread; re-entrant.
74
33
  def outside(&) = with_flag(false, &)
75
34
 
76
- # Refuses state an adapter is about to build an `Instance` from unless it is decoded;
77
- # outside the boundary it checks nothing.
35
+ # Refuses undecoded state headed for `Runtime::Instance.new`; a no-op outside the boundary.
78
36
  #
79
37
  # @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the construct the state
80
38
  # belongs to
81
39
  # @param state [Hash, Runtime::Value] the state handed to `Runtime::Instance.new`
82
- # @return [nil] when the boundary is inactive or the state is decoded
83
40
  # @raise [Runtime::WiringError] if the boundary is active and
84
41
  # `StateCodec.decoded?` rejects the state
85
42
  def check_state!(aggregate, state)
@@ -93,13 +50,12 @@ module Hecks
93
50
  end
94
51
 
95
52
  # Refuses a guarded adapter's `entries` answer if any entry's state is undecoded.
53
+ # A journal `Entry` is not an `Instance`, so `check_state!` never sees it.
96
54
  #
97
- # @param adapter [Object] the guarded adapter, asked for its `aggregate` and class name
98
- # @param entries [Array<Persistence::Entry>, Object] what the adapter's `entries`
99
- # returned; anything that is not an Array passes through unchecked
55
+ # @param adapter [Object] the guarded adapter
56
+ # @param entries [Array<Persistence::Entry>, Object] anything but an Array passes unchecked
100
57
  # @return [Array<Persistence::Entry>, Object] `entries` itself
101
- # @raise [Runtime::WiringError] if an `Entry` in the Array carries state
102
- # `StateCodec.decoded?` rejects
58
+ # @raise [Runtime::WiringError] if an `Entry` carries state `StateCodec.decoded?` rejects
103
59
  def check_entries!(adapter, entries)
104
60
  return entries unless entries.is_a?(Array)
105
61
 
@@ -113,13 +69,7 @@ module Hecks
113
69
  entries
114
70
  end
115
71
 
116
- # Sets the thread-local boundary flag for the length of the block, which is what makes
117
- # the boundary re-entrant.
118
- #
119
- # @param value [Boolean] true to switch the boundary on, false to switch it off
120
- # @yield the code to run under that setting
121
- # @return [Object] the block's value; the earlier flag is restored even when the
122
- # block raises
72
+ # Sets the thread-local flag for the block and restores it after, even on a raise.
123
73
  def with_flag(value)
124
74
  previous = Thread.current[KEY]
125
75
  Thread.current[KEY] = value
@@ -128,24 +78,18 @@ module Hecks
128
78
  Thread.current[KEY] = previous
129
79
  end
130
80
 
131
- # Marks a guarded adapter so a second `guard!` (a projection and an
132
- # authoritative repository built over one object) never wraps twice.
81
+ # Marks a guarded adapter so a second `guard!` never wraps it twice.
133
82
  module Guarded; end
134
83
 
135
- # Filled lazily under `WRAPPERS_LOCK`, one entry per adapter class —
136
- # a cache, deliberately mutable.
84
+ # A per-adapter-class cache, filled lazily under `WRAPPERS_LOCK`; deliberately mutable.
137
85
  WRAPPERS = {} # rubocop:disable Style/MutableConstant
138
86
  WRAPPERS_LOCK = Mutex.new
139
87
 
140
88
  # Builds, or fetches from the cache, the module that guards one adapter class.
141
- #
142
- # One wrapper module per adapter class, built once: every public
143
- # instance method the class (and its ancestors below Object)
144
- # defines, each forwarding to `super` inside the boundary.
89
+ # Each public method forwards to `super` inside the boundary.
145
90
  #
146
91
  # @param klass [Class] the adapter's class
147
- # @return [Module] an anonymous module including `Guarded`, meant to be `extend`ed onto
148
- # instances of `klass`
92
+ # @return [Module] an anonymous module including `Guarded`, to `extend` onto instances
149
93
  def wrapper_for(klass)
150
94
  WRAPPERS_LOCK.synchronize do
151
95
  WRAPPERS[klass] ||= Module.new do
@@ -161,12 +105,8 @@ module Hecks
161
105
  end
162
106
  end
163
107
 
164
- # Wraps the caller's own block so it runs outside the boundary
165
- # whenever the adapter yields to it.
166
- #
167
- # @param block [Proc, nil] the block the caller passed to the adapter method
168
- # @return [Proc, nil] a proc forwarding its arguments to `block` under `outside`; nil
169
- # when no block was given
108
+ # Wraps the caller's block so it runs outside the boundary when the adapter yields to it;
109
+ # that block is the dispatch itself, not adapter code.
170
110
  def outside_block(block)
171
111
  return nil unless block
172
112
 
@@ -1,32 +1,16 @@
1
1
  module Hecks
2
2
  module Ports
3
3
  module Persistence
4
- # **The no-op saga store** — what `Registry#saga_persistence` hands
5
- # back for a domain whose resolved adapter doesn't implement the
6
- # (optional) saga-persistence capability: Memory, deliberately
7
- # (sagas stay in-memory-only, exactly as they always have), and
8
- # any `RemoteRuntime`-shaped adapter (Lambda; the real durability
9
- # for those lives on the other side of the call — Phase 1, not
10
- # here). `SagaInterpreter`'s hook points call through
11
- # `saga_persistence(domain)` unconditionally; this is what makes
12
- # that safe without a `respond_to?` check at every call site,
13
- # mirroring the same optional-capability shape
14
- # `Ports::Persistence::AppendOnly` already gives adapters that
15
- # don't implement `reset!`/`events`/`record_event`.
4
+ # A saga store that keeps nothing, for adapters without the optional saga-persistence
5
+ # capability; callers call through it unconditionally instead of checking `respond_to?`.
16
6
  class NullSagaStore
17
- # Accepts and discards a saga checkpoint, whatever keywords it carries.
18
- #
19
- # @return [nil] always; nothing is stored
7
+ # Discards the checkpoint.
20
8
  def save_saga(**) = nil
21
9
 
22
- # Accepts and ignores a request to forget a saga.
23
- #
24
- # @return [nil] always; there is nothing to delete
10
+ # Ignores the request; there is nothing to delete.
25
11
  def delete_saga(**) = nil
26
12
 
27
13
  # Yields nothing, because no saga is ever stored here.
28
- #
29
- # @return [nil] always; a block, if given, is never called
30
14
  def each_saga(*) = nil
31
15
  end
32
16
 
@@ -1,28 +1,12 @@
1
1
  module Hecks
2
2
  module Ports
3
- # Reopened to hold the persistence-plugin registry (`Plugin`, below) and
4
- # its call surface (`register_plugin`/`plugin?`/`each_plugin`) — see ADR
5
- # 0033 and `Plugin`'s own header for the seam this fills.
3
+ # Holds the persistence-plugin registry and its `register_plugin`/`plugin?`/`each_plugin`
4
+ # surface.
6
5
  module Persistence
7
- # A tiny, real seam — ADR 0033. Core never names a persistence plugin;
8
- # a plugin makes itself known as a side effect of being `require`d, the
9
- # same way an adapter's own `.rb` file already registers itself
10
- # (`Runtime::Registry#add_adapter`) rather than core listing adapter
11
- # names anywhere. `register_plugin` is the entire contract a plugin
12
- # must satisfy: anything responding to `contribute_boot_gates(registry,
13
- # gates)` (a no-op is a valid implementation).
6
+ # The persistence-plugin registry (ADR 0033). A plugin registers itself when required, and
7
+ # need only respond to `contribute_boot_gates(registry, gates)`; a no-op is valid.
14
8
  #
15
- # This is deliberately not the same registry `Hecks::Projector` uses
16
- # (ADR 0027) or `Runtime::BootGates` (ADR 0031) — each of those is
17
- # scoped to its own seam (IR-in/artifact-out; per-boot phased gates).
18
- # A shared base is deferred until a third registry actually wants one.
19
- #
20
- # **Name-keyed, process-wide, not per-boot** — unlike `BootGates` (one
21
- # instance per `Loader.boot` call, because gate registration is
22
- # capability-conditional per registry), a persistence plugin is either
23
- # `require`d into this process or it isn't; there is no "this boot's
24
- # registry doesn't need it" case to isolate against, so a plain
25
- # module-level hash is the right shape here, not an instantiable class.
9
+ # Process-wide and name-keyed: a plugin is either required into this process or it is not.
26
10
  module Plugin
27
11
  @plugins = {}
28
12
 
@@ -37,33 +21,25 @@ module Hecks
37
21
  @plugins[name.to_sym] = plugin
38
22
  end
39
23
 
40
- # Reports whether a plugin of that name has been required into this process.
24
+ # Whether a plugin is registered under `name`.
41
25
  #
42
- # @param name [Symbol, String] the plugin's name
43
- # @return [Boolean] true when a plugin is registered under `name`
26
+ # @return [Boolean]
44
27
  def registered?(name)
45
28
  @plugins.key?(name.to_sym)
46
29
  end
47
30
 
48
31
  # Yields every registered plugin, in registration order.
49
- #
50
- # @yieldparam plugin [Object] a registered plugin
51
- # @return [Hash{Symbol => Object}, Enumerator] the plugin table when a block is
52
- # given, otherwise an Enumerator over the plugins
53
32
  def each(&)
54
33
  @plugins.each_value(&)
55
34
  end
56
35
 
57
- # Reports whether any persistence plugin is loaded at all.
36
+ # Whether at least one plugin is registered.
58
37
  #
59
- # @return [Boolean] true when at least one plugin is registered
38
+ # @return [Boolean]
60
39
  def any? = !@plugins.empty?
61
40
  end
62
41
  end
63
42
 
64
- # `Ports::Persistence.plugin?(:era)` / `.each_plugin` — the ordinary
65
- # call surface; `Plugin.register`/`.registered?`/`.each` stay reachable
66
- # directly for a plugin's own file to call at require-time.
67
43
  module_function
68
44
 
69
45
  # Registers a persistence plugin; a plugin's own file calls this when it is required.
@@ -73,22 +49,17 @@ module Hecks
73
49
  # @return [Object] the plugin just registered
74
50
  def register_plugin(name, plugin) = Plugin.register(name, plugin)
75
51
 
76
- # Reports whether the named persistence plugin is loaded in this process.
52
+ # Whether the named persistence plugin is loaded in this process.
77
53
  #
78
- # @param name [Symbol, String] the plugin's name
79
- # @return [Boolean] true when a plugin is registered under `name`
54
+ # @return [Boolean]
80
55
  def plugin?(name) = Plugin.registered?(name)
81
56
 
82
57
  # Yields every loaded persistence plugin, in registration order.
83
- #
84
- # @yieldparam plugin [Object] a registered plugin
85
- # @return [Hash{Symbol => Object}, Enumerator] the plugin table when a block is given,
86
- # otherwise an Enumerator over the plugins
87
58
  def each_plugin(&) = Plugin.each(&)
88
59
 
89
- # Reports whether any persistence plugin is loaded in this process.
60
+ # Whether any persistence plugin is loaded in this process.
90
61
  #
91
- # @return [Boolean] true when at least one plugin is registered
62
+ # @return [Boolean]
92
63
  def plugins_loaded? = Plugin.any?
93
64
  end
94
65
  end