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
@@ -4,39 +4,28 @@ module Hecks
4
4
  module Ports
5
5
  module Persistence
6
6
  class PostgresDump
7
- # A Postgres connection URL, split so a password reaches the tools run
8
- # against it through their environment and never on a command line, where
9
- # any user of the machine can read it from the process list.
7
+ # A Postgres connection URL, split so a password reaches child-process tools
8
+ # through their environment, never on a command line the process list exposes.
10
9
  class Connection
11
- # @param url [String] a `postgres://` or `postgresql://` URL
12
- # @raise [PostgresDump::Error] if `url` is not a Postgres URL
13
10
  def initialize(url)
14
11
  @uri = URI.parse(url)
15
12
  raise Error, "not a postgres URL" unless %w[postgres postgresql].include?(@uri.scheme)
16
13
  end
17
14
 
18
- # Names the database the URL points at.
19
- #
20
- # @return [String] the database name, empty when the URL names none
15
+ # Names the database the URL points at, empty when the URL names none.
21
16
  def database
22
17
  @uri.path.to_s.delete_prefix("/")
23
18
  end
24
19
 
25
- # Points the same server and credentials at another database.
26
- #
27
- # @param name [String] the database to connect to instead
28
- # @return [Connection] a new connection; this one is unchanged
20
+ # Points the same server and credentials at another database; this one is unchanged.
29
21
  def with_database(name)
30
22
  copy = @uri.dup
31
23
  copy.path = "/#{name}"
32
24
  self.class.new(copy.to_s)
33
25
  end
34
26
 
35
- # Lists the connection settings the URL carries, in `PG.connect` keyword form.
36
- #
37
- # @return [Hash{Symbol => String, Integer}] `host`, `port`, `user`, `password`,
38
- # `dbname` and `sslmode`, each only when the URL gives one; the user and password
39
- # are percent-decoded
27
+ # Lists the connection settings the URL carries, in `PG.connect` keyword form; the
28
+ # user and password come back percent-decoded.
40
29
  def settings
41
30
  {
42
31
  host: (@uri.host unless @uri.host.to_s.empty?), port: @uri.port,
@@ -46,16 +35,11 @@ module Hecks
46
35
  end
47
36
 
48
37
  # Builds the libpq environment variables for a child process.
49
- #
50
- # @return [Hash{String => String}] `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`,
51
- # `PGDATABASE` and `PGSSLMODE`, each only when `settings` has the matching key
52
38
  def env
53
39
  settings.to_h { |key, value| ["PG#{key.to_s.sub('dbname', 'database').upcase}", value.to_s] }
54
40
  end
55
41
 
56
- # Opens a connection with the `pg` gem.
57
- #
58
- # @return [PG::Connection] an open connection; the caller closes it
42
+ # Opens a connection with the `pg` gem; the caller closes it.
59
43
  def connect
60
44
  require "pg"
61
45
  PG.connect(**settings)
@@ -5,42 +5,16 @@ require_relative "postgres_dump/connection"
5
5
  module Hecks
6
6
  module Ports
7
7
  module Persistence
8
- # Dumps one schema of a Postgres database and proves the dump restores.
9
- #
10
- # ## What the proof is
11
- #
12
- # A dump nobody has restored is a hope. After `pg_dump` writes the file,
13
- # the dump is restored into a scratch database (on the source server, or on a
14
- # separate `verify_url` server such as the one the dump will be loaded
15
- # into) and every table's row count is compared with the source. That shows
16
- # the dump is complete and loadable. It does not re-derive current state
17
- # from an event journal, so a caller who needs that proves it separately.
18
- #
19
- # ## Credentials
20
- #
21
- # URLs are split by `Connection`: the password travels in the child
22
- # process's environment, never in `argv`.
23
- #
24
- # ## Requirements
25
- #
26
- # The `pg` gem, and `pg_dump` and `pg_restore` on `PATH` at a version no older
27
- # than the server's. Neither the gem nor the tools are a dependency of
28
- # hecks; nothing here loads until a dump is asked for.
8
+ # Dumps one schema of a Postgres database and proves the dump restores, by
9
+ # restoring it into a scratch database and comparing every table's row count.
29
10
  class PostgresDump
30
11
  # Raised for a refusal or a failed tool.
31
12
  class Error < StandardError; end
32
13
 
33
- # What a verified dump holds.
34
- #
35
- # @!attribute [r] tables
36
- # @return [Hash{String => Integer}] row count of every table in the schema, by name
14
+ # What a verified dump holds: `tables` is each table's row count, by name.
37
15
  Result = Struct.new(:tables, keyword_init: true)
38
16
 
39
- # @param url [String] the source database's `postgres://` URL
40
- # @param schema [String] the schema to dump, a plain identifier
41
- # @param verify_url [String, nil] the server to restore into for the proof; the source
42
- # server when nil
43
- # @raise [Error] if `schema` is not a plain identifier or a URL is not a Postgres URL
17
+ # Splits `url` and `verify_url` into connections and validates `schema`.
44
18
  def initialize(url:, schema:, verify_url: nil)
45
19
  raise Error, "schema #{schema.inspect} is not a plain identifier" unless schema.to_s.match?(/\A[a-z_][a-z0-9_]*\z/i)
46
20
 
@@ -50,11 +24,6 @@ module Hecks
50
24
  end
51
25
 
52
26
  # Writes a custom-format dump of the schema and proves it restores.
53
- #
54
- # @param dest [String] the file to write
55
- # @return [Result] the verified row counts
56
- # @raise [Error] if the schema has no tables, a tool fails, the database refuses, or
57
- # the restored counts differ from the source's
58
27
  def call(dest)
59
28
  require "pg"
60
29
  raise Error, "schema #{@schema} has no tables in #{@source.database}" if counting(@source) { |db| counts(db) }.empty?
@@ -3,30 +3,8 @@ require_relative "../../runtime/registry"
3
3
  module Hecks
4
4
  module Ports
5
5
  module Persistence
6
- # The other shape an adapter can be. `AppendOnly` names one shape
7
- # already — something that stores bytes, locally (Postgres, SQLite,
8
- # Heki) or over the network (D1), doesn't matter, the point is it
9
- # does real local interpretation and has real entries to replay.
10
- # This names the second, different shape: not "storage reached
11
- # remotely," but the real interpreter itself living behind a call
12
- # boundary — `append`/`project` don't do partial local work and
13
- # then fail, they raise unconditionally, because there is no local
14
- # write-ahead log to have; `entries` is always `[]` for the same
15
- # reason (`AppendOnly#recover!` replaying zero entries here is a
16
- # correct no-op, not a lie).
17
- #
18
- # `Adapters::Lambda` is the first adapter to `include` this, not
19
- # the only one meant to — any future adapter for some other
20
- # remote-runtime target (a different compute platform, a gRPC
21
- # stub, whatever) gets this shape for free by including one module
22
- # instead of re-deriving the same raise-on-write/always-empty-
23
- # entries boilerplate from scratch. And because it's a real,
24
- # checkable module rather than an adapter's own ad hoc
25
- # implementation, a caller can ask "is this adapter a remote-
26
- # runtime delegate?" as a genuine capability check
27
- # (`registry.adapter_class(name) <= RemoteRuntime`,
28
- # `Runtime::RemoteDispatcher`'s own use) instead of comparing
29
- # adapter names by string.
6
+ # Adapter shape for an interpreter behind a call boundary: no local log, so writes raise.
7
+ # Detect it with `registry.adapter_class(name) <= RemoteRuntime`, not by adapter name.
30
8
  module RemoteRuntime
31
9
  # Refuses every local write; `project` is an alias and refuses the same way.
32
10
  #
@@ -4,48 +4,29 @@ require_relative "codec_boundary"
4
4
  module Hecks
5
5
  module Ports
6
6
  module Persistence
7
- # Turns a declared adapter binding plus its world configuration into a
8
- # concrete repository. Selection policy stays out of adapter creation.
9
- #
10
- # Every adapter it builds is guarded (`CodecBoundary.guard!`) before
11
- # anything else touches it — `recover!` included — so no adapter a
12
- # runtime reaches can build an `Instance` from undecoded state.
7
+ # Turns a declared adapter binding into a concrete repository, guarding every
8
+ # adapter it builds (`CodecBoundary.guard!`) before `recover!` or anything else touches it.
13
9
  module RepositoryFactory
14
10
  module_function
15
11
 
16
12
  # Instantiates the adapter a bind names, guards it, and wraps it as a repository.
17
- #
18
- # @param registry [Runtime::Registry] the registry supplying the adapter class, the
19
- # domain's world settings, its resolved eras and the root path
20
- # @param domain [String, Symbol] name of the domain the aggregate belongs to
21
- # @param aggregate [Bluebook::Aggregate] the aggregate to persist
22
- # @param bind [Bluebook::Bind] the bind naming the adapter, as `BindingPolicy.resolve`
23
- # or the projection port chose it
24
- # @param recover [Boolean] true to replay the journal through `project` before returning
25
- # @param settings_verb [String] the verb whose world settings configure the adapter;
26
- # `"persisted_by"` by default, `Ports::Projection::VERB` for a projection
27
- # @return [Persistence::AppendOnly] the repository over the guarded adapter
28
- # @raise [Runtime::WiringError] if the bind's verb or settings fail the registry's
29
- # checks, no `Hecks::Adapters` constant matches the adapter name, or the adapter
30
- # lacks `append`, `project` or `entries`
31
13
  def build(registry, domain, aggregate, bind, recover: true, settings_verb: VERB)
32
14
  registry.check_verb(bind)
33
15
  settings = registry.binding_settings(domain, settings_verb, bind.adapter)
34
16
  .reject { |key, _| key.to_sym == :role }
35
17
  registry.check_settings(bind, settings)
36
- # The domain, the resolved era, and (for an old checkout) the era
37
- # that superseded it ride along after the declared-settings
38
- # check: a lineage adapter journals per domain, writes into its
39
- # own era's partition, and refuses to write at all once that era
40
- # is superseded (`PostgresEra#append`, BUG#24) — none of which a
41
- # world's settings carry.
18
+ # domain, resolved era and superseding era ride along outside the declared
19
+ # settings: a lineage adapter needs them to journal per domain, per-era.
42
20
  adapter = registry.adapter_class(bind.adapter)
43
21
  .new(aggregate: aggregate,
44
22
  settings: settings.merge(domain: domain.to_s, era: registry.resolved_eras[domain.to_s],
45
23
  superseded_by: registry.superseded_eras[domain.to_s]),
46
24
  root: registry.root)
47
25
  repository = AppendOnly.new(CodecBoundary.guard!(adapter))
48
- recover ? repository.recover! : repository
26
+ # `:atomic_append` (PostgresEra) means `append` already committed the
27
+ # projected state in the same transaction as the journal row, so a
28
+ # boot-time replay through `project` has nothing left to fix up.
29
+ recover && !repository.capabilities.include?(:atomic_append) ? repository.recover! : repository
49
30
  end
50
31
  end
51
32
  end
@@ -4,58 +4,11 @@ require_relative "../../runtime/value"
4
4
  module Hecks
5
5
  module Ports
6
6
  module Persistence
7
- # One spelling of an aggregate's state across the store boundary
8
- # (Phase 2, Track A, PR A2). This codec is the single, IR-driven
9
- # answer every adapter converges on, because adapters left to decode
10
- # their own way disagree: symbolizing the top level only (a Heki head,
11
- # a journal reader), symbolizing deep (a SQL or Lambda head), or never
12
- # serializing at all (Memory's shallow `state.dup`).
13
- # spec/ports/persistence_legacy_decode_spec.rb pins that bytes written
14
- # without the codec still decode to the one canonical shape. Every
15
- # adapter writes through `encode` and reads through `decode`
16
- # (Memory through `copy`; PR A3), and `CodecBoundary` — installed on every
17
- # adapter `RepositoryFactory.build` makes — refuses an `Instance`
18
- # built inside an adapter call from state `decoded?` rejects.
7
+ # Canonical spelling of an aggregate's state across the store boundary;
8
+ # every adapter's `encode`, `decode` and `copy` route through here.
19
9
  #
20
- # ## The three operations
21
- #
22
- # - `encode` — canonical and JSON-ready: string keys at every depth,
23
- # `Runtime::Value`s materialized, only JSON scalars at the leaves.
24
- # Needs no IR (JSON has one spelling), but takes it for symmetry.
25
- # - `decode` — walks the aggregate's IR: attributes, value objects
26
- # (their fields, recursively), `list_of` value objects and entities
27
- # (entity fields, nested entities, the entity's own lifecycle),
28
- # references, the lifecycle field, and projected fields. A declared
29
- # key becomes a symbol at every depth, whichever spelling arrived.
30
- # - `copy` — `decode(encode(state))`: what a durable adapter would hand
31
- # back, for Memory, with no JSON text in between.
32
- #
33
- # ## What decode never does
34
- #
35
- # It never invents a key. A declared field absent from the stored
36
- # state stays absent — not a present nil — because the runtime reads
37
- # absence as "this record predates the field": `Instance.
38
- # hydrate_with_defaults` fills a declared `default:` only when the key
39
- # is missing (spec/runtime/hydrate_defaults_spec.rb), Era translation
40
- # backfills only `unless state.key?` (era/lineage.rb#translate), and a
41
- # required declared-but-absent field reads as a named refusal rather
42
- # than nil (spec/runtime/attribute_absence_spec.rb). A present nil
43
- # would silently suppress all three. For the same reason it never
44
- # drops a key, nil or not: a stored nil stays a stored nil.
45
- #
46
- # It never touches an undeclared key's value — a retired field, or a
47
- # member a value object no longer declares, is exactly what an Era
48
- # translation (rename/move/drop) still has to read. Its key keeps its
49
- # spelling below the top level; at the top level every key is a
50
- # symbol, declared or not, because every adapter has always
51
- # symbolized the top level and `Lineage#translate` reads retired
52
- # top-level names as symbols.
53
- #
54
- # ## When both spellings arrive
55
- #
56
- # When a hash carries both spellings of one declared key, the symbol
57
- # spelling wins: it can only have been written by Ruby after the
58
- # string one was read.
10
+ # `decode` never invents or drops a declared key: absence must stay absence, since
11
+ # defaults, era translation and required-field checks all key off it.
59
12
  module StateCodec
60
13
  JSON_SCALARS = [String, Integer, Float, TrueClass, FalseClass, NilClass].freeze
61
14
 
@@ -95,14 +48,8 @@ module Hecks
95
48
 
96
49
  # Maps every declared top-level field name to its attribute.
97
50
  #
98
- # The field walk Sqlite::Codec#persisted_fields does for columns,
99
- # as name => Attribute (nil for the lifecycle field and projected
100
- # fields: bare scalars with no attribute of their own). The same
101
- # three sources, the same precedence — an attribute that happens to
102
- # share the lifecycle's or a projected field's name keeps its type.
103
- # An entity (no `projected_fields` of its own) gets its entity
104
- # field set — `decoded?` is asked about any `Instance`, and an
105
- # entity is "structurally interchangeable with an aggregate".
51
+ # An attribute that shares a name with the lifecycle field or a projected field keeps
52
+ # its declared type; an entity (no `projected_fields` of its own) gets its entity fields.
106
53
  #
107
54
  # @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the construct to walk
108
55
  # @return [Hash{Symbol => Bluebook::Attribute, nil}] a new Hash of field name to
@@ -117,12 +64,8 @@ module Hecks
117
64
 
118
65
  # Checks whether state already has the shape `decode` produces.
119
66
  #
120
- # Whether `decode` would hand `state` back unchanged — every
121
- # top-level key a Symbol, every declared key below it a Symbol, no
122
- # hash carrying both spellings of a declared key. Allocates
123
- # nothing; `CodecBoundary` asks it of every `Instance` an adapter
124
- # builds. A `Runtime::Value` (hydrated state, a save's own entry)
125
- # already is the declared shape, so it answers true.
67
+ # `CodecBoundary` asks this of every `Instance` an adapter builds; a `Runtime::Value`
68
+ # already is the declared shape, so it answers true without allocating.
126
69
  #
127
70
  # @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the construct the state
128
71
  # belongs to
@@ -134,8 +77,6 @@ module Hecks
134
77
  hash_decoded?(aggregate, declared_top_level(aggregate), state, top: true)
135
78
  end
136
79
 
137
- # ── encode ──────────────────────────────────────────────────────
138
-
139
80
  # Encodes one value, recursing through Hashes, Arrays and `Runtime::Value`s.
140
81
  #
141
82
  # @param value [Object] any state value; a `Runtime::Value` is encoded as its `to_h`
@@ -155,21 +96,15 @@ module Hecks
155
96
  end
156
97
  end
157
98
 
158
- # ── decode ──────────────────────────────────────────────────────
159
-
160
99
  # Decodes one Hash level, symbolizing its declared keys and recursing into their values.
100
+ # Key order is kept; a string key is skipped when the symbol spelling is also present.
161
101
  #
162
- # `fields` is name => Attribute-or-nil for this level. Key order is
163
- # kept; a string key is skipped when the same hash also holds its
164
- # symbol spelling, so the symbol wins wherever either one sits.
165
- #
166
- # @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the root construct, through
167
- # which nested value objects and entities resolve
168
- # @param fields [Hash{Symbol => Bluebook::Attribute, nil}] the fields declared at
169
- # this level
102
+ # @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the root construct nested
103
+ # value objects and entities resolve through
104
+ # @param fields [Hash{Symbol => Bluebook::Attribute, nil}] the fields declared at this level
170
105
  # @param raw [Hash] the stored Hash for this level
171
- # @param symbolize_undeclared [Boolean] true to symbolize undeclared keys too, as the
172
- # top level does; false to leave their spelling alone
106
+ # @param symbolize_undeclared [Boolean] true to symbolize undeclared keys too (the top
107
+ # level does); false to leave their spelling alone
173
108
  # @return [Hash] a new Hash in `raw`'s key order
174
109
  def decode_hash(aggregate, fields, raw, symbolize_undeclared: false)
175
110
  raw.each_with_object({}) do |(key, value), out|
@@ -186,12 +121,8 @@ module Hecks
186
121
 
187
122
  # Decodes the stored value of one declared field.
188
123
  #
189
- # One declared field's value, by the four shapes `Value::Coercion`
190
- # names (scalar / list / optional / composite). nil, a reference (a
191
- # bare id or a list of them), and a scalar pass through untouched; a
192
- # value object or entity recurses only when the stored value really
193
- # is the Hash/Array its declaration says — anything else (a legacy
194
- # bare scalar for a one-field value object) is left for hydration.
124
+ # A value object or entity recurses only when the stored value matches its declared
125
+ # Hash/Array shape; anything else (a legacy bare scalar) is left for hydration.
195
126
  #
196
127
  # @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the root construct, through
197
128
  # which the field's type resolves
@@ -235,11 +166,8 @@ module Hecks
235
166
  # Maps an entity's (or aggregate's) attribute names, plus its lifecycle field, to
236
167
  # their attributes.
237
168
  #
238
- # An entity is "structurally interchangeable with an aggregate"
239
- # (behaviour/entity.rb): its attributes plus its own lifecycle field.
240
- # Its value objects and nested entities resolve through the root
241
- # aggregate, the same way `EntityListCoercion#hydrate_entity_list`
242
- # resolves them.
169
+ # Nested value objects and entities resolve through the root aggregate, the same way
170
+ # `EntityListCoercion#hydrate_entity_list` resolves them.
243
171
  #
244
172
  # @param entity [Bluebook::Entity, Bluebook::Aggregate] the construct to walk
245
173
  # @return [Hash{Symbol => Bluebook::Attribute, nil}] a new Hash of field name to
@@ -251,23 +179,15 @@ module Hecks
251
179
  fields
252
180
  end
253
181
 
254
- # ── decoded? ────────────────────────────────────────────────────
255
-
256
182
  # Checks one Hash level for keys `decode` would respell.
183
+ # The mirror of `decode_hash`: an undeclared nested key keeps its own spelling.
257
184
  #
258
- # The mirror of `decode_hash`: a key `decode` would respell (any
259
- # non-Symbol at the top, a non-Symbol declared key below it) means
260
- # "not decoded"; an undeclared nested key keeps whatever spelling
261
- # it has, exactly as `decode` keeps it.
262
- #
263
- # @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the root construct, through
264
- # which nested types resolve
265
- # @param fields [Hash{Symbol => Bluebook::Attribute, nil}] the fields declared at
266
- # this level
185
+ # @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the root construct nested
186
+ # types resolve through
187
+ # @param fields [Hash{Symbol => Bluebook::Attribute, nil}] the fields declared at this level
267
188
  # @param hash [Hash] the Hash to inspect
268
189
  # @param top [Boolean] true for the top level, where every key must be a Symbol
269
- # @return [Boolean] true when this level and every declared composite below it is
270
- # decoded
190
+ # @return [Boolean] true when this level and every declared composite below it is decoded
271
191
  def hash_decoded?(aggregate, fields, hash, top: false)
272
192
  hash.all? do |key, value|
273
193
  if key.is_a?(Symbol)
@@ -23,29 +23,22 @@ Hecks::Ports::Persistence.autoload(:PostgresDump, File.join(__dir__, "persistenc
23
23
 
24
24
  module Hecks
25
25
  module Ports
26
- # Reopened once `BindingPolicy`/`RepositoryFactory` (required above) are
27
- # loaded, to add the persistence port's actual call surface: resolving
28
- # which adapter authoritatively binds an aggregate and building the
29
- # repository that reads/writes it.
26
+ # Reopened once BindingPolicy/RepositoryFactory are loaded, adding the port's
27
+ # call surface for resolving binds and building repositories.
30
28
  module Persistence
31
29
  module_function
32
30
 
33
- # Builds the repository that reads and writes one aggregate's authoritative store.
34
- #
35
- # The public persistence port owns only authoritative aggregate heads. The repository
36
- # comes back already recovered: every journal entry has been re-projected.
31
+ # Builds the repository that reads and writes one aggregate's authoritative store,
32
+ # already recovered from its journal.
37
33
  #
38
34
  # @param registry [Runtime::Registry] the booted registry holding the domain's
39
35
  # hecksagon, world and adapters
40
36
  # @param domain [String, Symbol] name of the domain the aggregate belongs to
41
37
  # @param aggregate [Bluebook::Aggregate] the aggregate to persist
42
38
  # @return [Persistence::AppendOnly] repository over the aggregate's authoritative
43
- # adapter, or over a `Memory` adapter when the domain declares no hecksagon
44
- # @raise [Runtime::WiringError] if the aggregate has no authoritative bind, more than
45
- # one, or a bind with a role this port does not support; or if the bound adapter is
46
- # unknown, answers a different verb, is given a setting it does not declare, has no
47
- # Ruby implementation, or lacks a method its port's `answers` list or the
48
- # append-only contract (`append`, `project`, `entries`) requires
39
+ # adapter, or a `Memory` adapter when the domain declares no hecksagon
40
+ # @raise [Runtime::WiringError] if the aggregate's authoritative bind is missing,
41
+ # ambiguous, or the bound adapter cannot satisfy the append-only contract
49
42
  def repository(registry, domain, aggregate)
50
43
  authoritative = BindingPolicy.resolve(registry, domain, aggregate)
51
44
  RepositoryFactory.build(registry, domain, aggregate, authoritative)
@@ -4,40 +4,18 @@ require_relative "../runtime/registry"
4
4
 
5
5
  module Hecks
6
6
  module Ports
7
- # Projection is a read-side port. Its stores are rebuildable consumers of
8
- # authoritative entries and never participate in command decisions.
7
+ # Read-side port: rebuildable stores fed from authoritative entries, never read by commands.
9
8
  module Projection
10
9
  NAME = "projection".freeze
11
10
  VERB = "projected_by".freeze
12
11
 
13
12
  module_function
14
13
 
15
- # Lists the `projected_by` binds a domain's hecksagon declares for one aggregate.
16
- #
17
- # @param registry [Runtime::Registry] the booted registry to search
18
- # @param domain [String, Symbol] name of the domain the aggregate belongs to
19
- # @param aggregate [Bluebook::Aggregate] the aggregate to find projection binds for
20
- # @return [Array<Bluebook::Bind>] the binds naming the aggregate, or failing that the
21
- # hecksagon's aggregate-less `projected_by` binds; `[]` if there are none or the
22
- # domain declares no hecksagon
23
14
  def binds_for(registry, domain, aggregate)
24
15
  registry.hecksagon(domain)&.binds_for(aggregate.hecks_name, VERB) || []
25
16
  end
26
17
 
27
- # Builds a worker that catches one projection's store up to its authoritative journal.
28
- #
29
- # @param registry [Runtime::Registry] the booted registry to resolve binds against
30
- # @param domain [String, Symbol] name of the domain the aggregate belongs to
31
- # @param aggregate [Bluebook::Aggregate] the aggregate being projected
32
- # @param policy [Symbol, String] `:refresh` (reset the store and rebuild it) or
33
- # `:strict` (refuse on divergent history) — see `Worker::VALID_POLICIES`
34
- # @return [Ports::Projection::Worker, nil] a worker for the aggregate's first declared
35
- # bind, or nil if it has no projection bind
36
- # @raise [Runtime::WiringError] if the aggregate's authoritative persistence bind does
37
- # not resolve (see `Persistence.repository`), or the projection's adapter is unknown,
38
- # answers a different verb, is given a setting it does not declare, has no Ruby
39
- # implementation, or lacks a method the append-only contract requires
40
- # @raise [ArgumentError] if `policy` is not one of `Worker::VALID_POLICIES`
18
+ # Builds a worker for the aggregate's first `projected_by` bind; nil when it has none.
41
19
  def worker(registry, domain, aggregate, policy: :refresh)
42
20
  bind = binds_for(registry, domain, aggregate).first
43
21
  return unless bind
@@ -48,38 +26,15 @@ module Hecks
48
26
  Worker.new(authoritative, target, policy: policy)
49
27
  end
50
28
 
51
- # Catches one projection's store up to its authoritative journal:
52
- # replays whatever entries the store hasn't yet appended/projected.
53
- # Constructed via Projection.worker; #catch_up! runs from a
54
- # separate process or scheduler, never from the command-side write
55
- # path.
29
+ # Catches one projection store up to its authoritative journal; run from a separate
30
+ # process or scheduler, never from the command-side write path.
56
31
  class Worker
57
- # @return [Persistence::AppendOnly] the projection store this worker catches up
58
32
  attr_reader :projection
59
33
 
60
- # The only two policies anything in this codebase ever passes
61
- # (`bin/project`, every spec) — there is no third, legitimate
62
- # "lenient append" policy on record anywhere. This list is the
63
- # contract for what a valid policy is: `catch_up!` enforces
64
- # history agreement only for the exact symbol `:strict`, so any
65
- # other unrecognised value — a typo like `:strikt` — would fall
66
- # through the `consistent?` check below and append onto
67
- # divergent history without a word. A caller-supplied String
68
- # `"strict"` is accepted on purpose, normalised by `policy.to_sym`
69
- # in `initialize` before it is checked against this list, not by
70
- # the accident of duck typing.
71
- # Refusing loudly at construction, once, for anything outside
72
- # this list turns a silent no-op into an immediate, named error
73
- # — the "refuse rather than silently skip" reading of L1, since
74
- # `:strict` really is meant to be the only enforcing contract.
34
+ # Only `:strict` enforces history agreement, so an unknown policy (a typo) would append
35
+ # onto divergent history silently; construction refuses anything else.
75
36
  VALID_POLICIES = %i[refresh strict].freeze
76
37
 
77
- # @param authoritative [Persistence::AppendOnly] the authoritative repository whose
78
- # journal the projection is caught up to
79
- # @param projection [Persistence::AppendOnly] the projection store being caught up
80
- # @param policy [Symbol, String] one of `VALID_POLICIES`, as a Symbol or its String
81
- # spelling
82
- # @raise [ArgumentError] if `policy` is not one of `VALID_POLICIES`
83
38
  def initialize(authoritative, projection, policy: :refresh)
84
39
  @authoritative = authoritative
85
40
  @projection = projection
@@ -91,45 +46,55 @@ module Hecks
91
46
  end
92
47
  end
93
48
 
94
- # Appends and projects every authoritative entry the projection store lacks.
49
+ # Appends and projects every authoritative entry the store lacks.
95
50
  #
96
- # Invoke from a separate process or scheduler. The command-side write
97
- # path never calls this method.
98
- #
99
- # Under `:refresh` the store is reset first and rebuilt from the whole journal. Under
100
- # `:strict` the store's own entries must be a prefix of the authoritative journal
101
- # (same operation, id and state, in order); only the entries after that prefix are
102
- # replayed.
103
- #
104
- # @return [Persistence::AppendOnly] the projection store, now caught up
105
- # @raise [Runtime::WiringError] under `:strict`, if the projection's history is not a
106
- # prefix of the authoritative journal; under `:refresh`, if the projection's
107
- # adapter cannot `reset!`
51
+ # `:refresh` never reads journal history — it rebuilds from the aggregate's own current
52
+ # records, which the authoritative table already holds directly, so it stays correct
53
+ # past a compacted journal. `:strict` verifies its own entries are a real prefix of the
54
+ # authoritative journal, which genuinely needs that history; it refuses rather than
55
+ # silently misreading a compacted journal's surviving rows as if they started at
56
+ # position zero.
108
57
  def catch_up!
109
- entries = Queue.new(@authoritative).entries
58
+ return refresh! if @policy == :refresh
59
+
60
+ compacted_through = @authoritative.respond_to?(:compacted_through) ? @authoritative.compacted_through : 0
110
61
  present = @projection.entries
111
- if @policy == :refresh
112
- @projection.reset!
113
- present = []
62
+ if present.length < compacted_through
63
+ raise Runtime::WiringError,
64
+ ":strict catch-up needs journal history the authoritative store has already " \
65
+ "compacted (through sequence #{compacted_through}, this projection has only " \
66
+ "consumed #{present.length}) — use :refresh instead"
114
67
  end
115
- unless consistent?(entries, present)
116
- raise Runtime::WiringError, "projection history does not match its authoritative history" if @policy == :strict
68
+
69
+ entries = Queue.new(@authoritative).entries
70
+ present_tail = present.drop(compacted_through)
71
+ unless consistent?(entries, present_tail)
72
+ raise Runtime::WiringError, "projection history does not match its authoritative history"
117
73
  end
118
- entries.drop(present.length).each do |entry|
74
+
75
+ entries.drop(present_tail.length).each do |entry|
119
76
  @projection.append(entry)
120
77
  @projection.project(entry)
121
78
  end
122
79
  @projection
123
80
  end
124
81
 
125
- # Counts the entries the projection store holds, which is how far it has caught up.
126
- #
127
- # @return [Integer] number of journal entries already appended to the projection
128
- # store; 0 for a store that has never caught up
129
82
  def checkpoint = @projection.entries.length
130
83
 
131
84
  private
132
85
 
86
+ # Resets the store and rebuilds it from the aggregate's own current records — one
87
+ # synthetic save per live record, never a replay of deleted or superseded history.
88
+ def refresh!
89
+ @projection.reset!
90
+ @authoritative.all.each do |instance|
91
+ entry = Persistence::Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
92
+ @projection.append(entry)
93
+ @projection.project(entry)
94
+ end
95
+ @projection
96
+ end
97
+
133
98
  def same_entry?(left, right) = left.operation == right.operation && left.id == right.id && left.state == right.state
134
99
 
135
100
  def consistent?(entries, present)
@@ -139,18 +104,11 @@ module Hecks
139
104
  end
140
105
  end
141
106
 
142
- # The authoritative append-only journal is the durable projection queue.
143
- # It is committed before a worker sees it; projection entries are the
144
- # worker's durable checkpoint, so delivery is at-least-once and safe to replay.
107
+ # The authoritative journal doubles as the queue: entries are committed before a worker
108
+ # sees them, so delivery is at-least-once and safe to replay.
145
109
  class Queue
146
- # @param authoritative [Persistence::AppendOnly] the authoritative repository to read
147
- # entries from
148
110
  def initialize(authoritative) = @authoritative = authoritative
149
111
 
150
- # Reads the whole authoritative journal, the work a projection worker replays from.
151
- #
152
- # @return [Array<Persistence::Entry>] every committed entry, oldest first; `[]` for
153
- # an empty journal
154
112
  def entries = @authoritative.entries
155
113
  end
156
114
  end