hecks 0.2.0 → 1.0.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 (419) hide show
  1. checksums.yaml +5 -5
  2. data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +127 -0
  4. data/lib/hecks/adapters/driven/d1.adapter +12 -0
  5. data/lib/hecks/adapters/driven/d1.rb +427 -0
  6. data/lib/hecks/adapters/driven/folder.adapter +3 -0
  7. data/lib/hecks/adapters/driven/folder.rb +199 -0
  8. data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
  9. data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
  10. data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
  11. data/lib/hecks/adapters/driven/governance_authorization.rb +101 -0
  12. data/lib/hecks/adapters/driven/heki/journal.rb +56 -0
  13. data/lib/hecks/adapters/driven/heki/saga_store.rb +99 -0
  14. data/lib/hecks/adapters/driven/heki/snapshot.rb +65 -0
  15. data/lib/hecks/adapters/driven/heki.adapter +4 -0
  16. data/lib/hecks/adapters/driven/heki.rb +179 -0
  17. data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
  18. data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
  19. data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
  20. data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
  21. data/lib/hecks/adapters/driven/lambda.adapter +4 -0
  22. data/lib/hecks/adapters/driven/lambda.rb +145 -0
  23. data/lib/hecks/adapters/driven/memory.adapter +3 -0
  24. data/lib/hecks/adapters/driven/memory.rb +103 -0
  25. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
  26. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
  27. data/lib/hecks/adapters/driven/postgres/codec.rb +88 -0
  28. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +207 -0
  29. data/lib/hecks/adapters/driven/postgres.adapter +5 -0
  30. data/lib/hecks/adapters/driven/postgres.rb +438 -0
  31. data/lib/hecks/adapters/driven/postgres_era.adapter +17 -0
  32. data/lib/hecks/adapters/driven/prism.adapter +3 -0
  33. data/lib/hecks/adapters/driven/prism.rb +80 -0
  34. data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
  35. data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
  36. data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
  37. data/lib/hecks/adapters/driven/sqlite/codec.rb +80 -0
  38. data/lib/hecks/adapters/driven/sqlite/projection.rb +172 -0
  39. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +192 -0
  40. data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
  41. data/lib/hecks/adapters/driven/sqlite.rb +306 -0
  42. data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
  43. data/lib/hecks/adapters/driven/system_clock.rb +14 -0
  44. data/lib/hecks/adapters/driven.rb +54 -0
  45. data/lib/hecks/adapters.rb +6 -0
  46. data/lib/hecks/behaviors/dsl.rb +125 -0
  47. data/lib/hecks/behaviors/expectations.rb +340 -0
  48. data/lib/hecks/behaviors/ir.rb +31 -0
  49. data/lib/hecks/behaviors/rspec.rb +42 -0
  50. data/lib/hecks/behaviors/runner.rb +96 -0
  51. data/lib/hecks/behaviors.rb +25 -0
  52. data/lib/hecks/bluebook/aggregate.rb +108 -0
  53. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
  54. data/lib/hecks/bluebook/assembly/build.rb +48 -0
  55. data/lib/hecks/bluebook/assembly/contract.rb +112 -0
  56. data/lib/hecks/bluebook/assembly/contracts.rb +438 -0
  57. data/lib/hecks/bluebook/assembly/marks.rb +228 -0
  58. data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
  59. data/lib/hecks/bluebook/assembly.rb +91 -0
  60. data/lib/hecks/bluebook/attribute.rb +96 -0
  61. data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
  62. data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
  63. data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
  64. data/lib/hecks/bluebook/behaviour/command.rb +116 -0
  65. data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
  66. data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
  67. data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
  68. data/lib/hecks/bluebook/behaviour/lifecycle.rb +68 -0
  69. data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
  70. data/lib/hecks/bluebook/behaviour/process_manager.rb +51 -0
  71. data/lib/hecks/bluebook/behaviour/query.rb +10 -0
  72. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
  73. data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
  74. data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
  75. data/lib/hecks/bluebook/chapter.rb +78 -0
  76. data/lib/hecks/bluebook/command.rb +124 -0
  77. data/lib/hecks/bluebook/domain_port.rb +102 -0
  78. data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
  79. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +1018 -0
  80. data/lib/hecks/bluebook/dsl/attribute_collector.rb +348 -0
  81. data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
  82. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1087 -0
  83. data/lib/hecks/bluebook/dsl/command_builder.rb +714 -0
  84. data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
  85. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
  86. data/lib/hecks/bluebook/dsl/entity_builder.rb +430 -0
  87. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
  88. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
  89. data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
  90. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
  91. data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
  92. data/lib/hecks/bluebook/dsl/policy_builder.rb +135 -0
  93. data/lib/hecks/bluebook/dsl/port_builder.rb +39 -0
  94. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
  95. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +306 -0
  96. data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
  97. data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
  98. data/lib/hecks/bluebook/dsl/rule_reference.rb +173 -0
  99. data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
  100. data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
  101. data/lib/hecks/bluebook/dsl/word_gate.rb +228 -0
  102. data/lib/hecks/bluebook/dsl/world_builder.rb +117 -0
  103. data/lib/hecks/bluebook/dsl.rb +45 -0
  104. data/lib/hecks/bluebook/entity.rb +103 -0
  105. data/lib/hecks/bluebook/expression/canonical_form.rb +123 -0
  106. data/lib/hecks/bluebook/expression/evaluator.rb +304 -0
  107. data/lib/hecks/bluebook/expression/projection.json +218 -0
  108. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
  109. data/lib/hecks/bluebook/expression/resolver.rb +781 -0
  110. data/lib/hecks/bluebook/expression.rb +13 -0
  111. data/lib/hecks/bluebook/hexagon.rb +60 -0
  112. data/lib/hecks/bluebook/lifecycle.rb +42 -0
  113. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
  114. data/lib/hecks/bluebook/meta_validator/judge.rb +621 -0
  115. data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
  116. data/lib/hecks/bluebook/meta_validator/port_judge.rb +51 -0
  117. data/lib/hecks/bluebook/meta_validator/readings.rb +360 -0
  118. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +351 -0
  119. data/lib/hecks/bluebook/meta_validator/shapes.rb +266 -0
  120. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
  121. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
  122. data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
  123. data/lib/hecks/bluebook/meta_validator.rb +542 -0
  124. data/lib/hecks/bluebook/model_check.rb +445 -0
  125. data/lib/hecks/bluebook/pattern_subset.rb +184 -0
  126. data/lib/hecks/bluebook/policy.rb +41 -0
  127. data/lib/hecks/bluebook/process_manager.rb +128 -0
  128. data/lib/hecks/bluebook/project_discovery.rb +30 -0
  129. data/lib/hecks/bluebook/project_loader.rb +40 -0
  130. data/lib/hecks/bluebook/project_register.rb +82 -0
  131. data/lib/hecks/bluebook/query.rb +61 -0
  132. data/lib/hecks/bluebook/read_model.rb +109 -0
  133. data/lib/hecks/bluebook/reference.rb +74 -0
  134. data/lib/hecks/bluebook/smoke_test.rb +166 -0
  135. data/lib/hecks/bluebook/synthesizer.rb +95 -0
  136. data/lib/hecks/bluebook/translation.rb +92 -0
  137. data/lib/hecks/bluebook/value_object.rb +66 -0
  138. data/lib/hecks/bluebook.rb +74 -0
  139. data/lib/hecks/codemod.rb +342 -0
  140. data/lib/hecks/construct.rb +71 -0
  141. data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
  142. data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
  143. data/lib/hecks/deploy/oidc.json +18 -0
  144. data/lib/hecks/doc/reference.rb +410 -0
  145. data/lib/hecks/embryonaut_bluebook.rb +75 -0
  146. data/lib/hecks/facade/cli_door.rb +119 -0
  147. data/lib/hecks/facade/cli_runner.rb +190 -0
  148. data/lib/hecks/facade/command_request.rb +105 -0
  149. data/lib/hecks/facade/handle.rb +173 -0
  150. data/lib/hecks/facade/json_door.rb +166 -0
  151. data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
  152. data/lib/hecks/facade/surface/chapter.rb +107 -0
  153. data/lib/hecks/facade/surface.rb +48 -0
  154. data/lib/hecks/facade.rb +44 -0
  155. data/lib/hecks/forms/app.rb +341 -0
  156. data/lib/hecks/forms/command_form_renderer.rb +113 -0
  157. data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
  158. data/lib/hecks/forms/field_renderer.rb +177 -0
  159. data/lib/hecks/forms/field_shape.rb +232 -0
  160. data/lib/hecks/forms/html.rb +84 -0
  161. data/lib/hecks/forms/index_renderer.rb +35 -0
  162. data/lib/hecks/forms/page.rb +157 -0
  163. data/lib/hecks/forms/params.rb +161 -0
  164. data/lib/hecks/forms/port_argument.rb +46 -0
  165. data/lib/hecks/forms/query_form_renderer.rb +114 -0
  166. data/lib/hecks/forms/record_renderer.rb +119 -0
  167. data/lib/hecks/forms/record_table.rb +68 -0
  168. data/lib/hecks/forms/reference_options.rb +30 -0
  169. data/lib/hecks/forms/value_object_shape.rb +46 -0
  170. data/lib/hecks/forms.rb +54 -0
  171. data/lib/hecks/fqn.rb +94 -0
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
  175. data/lib/hecks/framework/bluebook/governance.bluebook +145 -0
  176. data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
  177. data/lib/hecks/framework/oidc.json +39 -0
  178. data/lib/hecks/framework.rb +90 -0
  179. data/lib/hecks/freezer.rb +67 -0
  180. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +527 -0
  181. data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
  182. data/lib/hecks/fuzzing/isolated_boot.rb +286 -0
  183. data/lib/hecks/fuzzing/properties.rb +1192 -0
  184. data/lib/hecks/fuzzing/replay.rb +668 -0
  185. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
  186. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +80 -0
  187. data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
  188. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +250 -0
  189. data/lib/hecks/fuzzing/sequence_generator.rb +131 -0
  190. data/lib/hecks/fuzzing/value_generator.rb +200 -0
  191. data/lib/hecks/fuzzing.rb +12 -0
  192. data/lib/hecks/grammar/evolve.rb +324 -0
  193. data/lib/hecks/grammar/expression.bluebook +420 -0
  194. data/lib/hecks/grammar/expression_operators.json +1648 -0
  195. data/lib/hecks/grammar/grammar.hecksagon +20 -0
  196. data/lib/hecks/grammar/oidc.json +69 -0
  197. data/lib/hecks/grammar/translation.bluebook +203 -0
  198. data/lib/hecks/grammar.rb +178 -0
  199. data/lib/hecks/ir.rb +126 -0
  200. data/lib/hecks/language/adapter.bluebook +116 -0
  201. data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
  202. data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
  203. data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
  204. data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
  205. data/lib/hecks/language/bluebook/command.bluebook +471 -0
  206. data/lib/hecks/language/bluebook/entity.bluebook +392 -0
  207. data/lib/hecks/language/bluebook/policy.bluebook +189 -0
  208. data/lib/hecks/language/bluebook/process_manager.bluebook +381 -0
  209. data/lib/hecks/language/bluebook/projection.bluebook +267 -0
  210. data/lib/hecks/language/bluebook/query.bluebook +245 -0
  211. data/lib/hecks/language/bluebook/shape.bluebook +292 -0
  212. data/lib/hecks/language/bluebook/syntax.bluebook +448 -0
  213. data/lib/hecks/language/bluebook/vocabulary.bluebook +379 -0
  214. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
  215. data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
  216. data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
  217. data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
  218. data/lib/hecks/language/oidc.json +333 -0
  219. data/lib/hecks/language/port.bluebook +120 -0
  220. data/lib/hecks/language/translation/translation.bluebook +110 -0
  221. data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
  222. data/lib/hecks/language/world/wiring.bluebook +62 -0
  223. data/lib/hecks/language/world/world.bluebook +84 -0
  224. data/lib/hecks/literal.rb +125 -0
  225. data/lib/hecks/naming.rb +174 -0
  226. data/lib/hecks/ports/access_control.port +9 -0
  227. data/lib/hecks/ports/access_control.rb +62 -0
  228. data/lib/hecks/ports/agent/answers.rb +104 -0
  229. data/lib/hecks/ports/agent.port +8 -0
  230. data/lib/hecks/ports/agent.rb +167 -0
  231. data/lib/hecks/ports/authentication.port +6 -0
  232. data/lib/hecks/ports/authentication.rb +50 -0
  233. data/lib/hecks/ports/authorization.port +7 -0
  234. data/lib/hecks/ports/authorization.rb +62 -0
  235. data/lib/hecks/ports/clock.port +5 -0
  236. data/lib/hecks/ports/clock.rb +62 -0
  237. data/lib/hecks/ports/extraction.port +5 -0
  238. data/lib/hecks/ports/extraction.rb +37 -0
  239. data/lib/hecks/ports/identity_assignment.port +5 -0
  240. data/lib/hecks/ports/identity_assignment.rb +45 -0
  241. data/lib/hecks/ports/identity_generation.port +5 -0
  242. data/lib/hecks/ports/identity_generation.rb +49 -0
  243. data/lib/hecks/ports/identity_resolution.port +5 -0
  244. data/lib/hecks/ports/identity_resolution.rb +40 -0
  245. data/lib/hecks/ports/loading.port +4 -0
  246. data/lib/hecks/ports/loading.rb +13 -0
  247. data/lib/hecks/ports/persistence/append_only.rb +138 -0
  248. data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
  249. data/lib/hecks/ports/persistence/execution.rb +23 -0
  250. data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
  251. data/lib/hecks/ports/persistence/plugin.rb +54 -0
  252. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +169 -0
  253. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +128 -0
  254. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +161 -0
  255. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +61 -0
  256. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +304 -0
  257. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +171 -0
  258. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +190 -0
  259. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +478 -0
  260. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +166 -0
  261. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +314 -0
  262. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +168 -0
  263. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +170 -0
  264. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +134 -0
  265. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +137 -0
  266. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +89 -0
  267. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +85 -0
  268. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
  269. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +132 -0
  270. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +74 -0
  271. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +758 -0
  272. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +120 -0
  273. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +31 -0
  274. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +46 -0
  275. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +102 -0
  276. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +46 -0
  277. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +70 -0
  278. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +72 -0
  279. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +120 -0
  280. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +183 -0
  281. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +41 -0
  282. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +28 -0
  283. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +30 -0
  284. data/lib/hecks/ports/persistence/plugins/era/translation.rb +11 -0
  285. data/lib/hecks/ports/persistence/plugins/era.rb +48 -0
  286. data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
  287. data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
  288. data/lib/hecks/ports/persistence.port +4 -0
  289. data/lib/hecks/ports/persistence.rb +39 -0
  290. data/lib/hecks/ports/projection.port +4 -0
  291. data/lib/hecks/ports/projection.rb +96 -0
  292. data/lib/hecks/ports/query/in_memory.rb +60 -0
  293. data/lib/hecks/ports/query/ordering.rb +41 -0
  294. data/lib/hecks/ports/query.rb +36 -0
  295. data/lib/hecks/ports.rb +27 -0
  296. data/lib/hecks/projections/diagrams.rb +642 -0
  297. data/lib/hecks/projections/ir.rb +18 -0
  298. data/lib/hecks/projections/model/deviations.rb +98 -0
  299. data/lib/hecks/projections/model.rb +145 -0
  300. data/lib/hecks/projections/oidc.rb +110 -0
  301. data/lib/hecks/projections/parser_table.rb +159 -0
  302. data/lib/hecks/projections/reference.rb +38 -0
  303. data/lib/hecks/projections/shape.rb +44 -0
  304. data/lib/hecks/projections/statements.rb +110 -0
  305. data/lib/hecks/projections/vocabulary.rb +100 -0
  306. data/lib/hecks/projections.rb +32 -0
  307. data/lib/hecks/projector/cli_projector.rb +446 -0
  308. data/lib/hecks/projector/docs_projector.rb +321 -0
  309. data/lib/hecks/projector/exporter.rb +158 -0
  310. data/lib/hecks/projector/ir_projector.rb +18 -0
  311. data/lib/hecks/projector/narrate_projector.rb +243 -0
  312. data/lib/hecks/projector/target.rb +97 -0
  313. data/lib/hecks/projector.rb +186 -0
  314. data/lib/hecks/query_ir.rb +411 -0
  315. data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
  316. data/lib/hecks/query_specification/common/comparators.rb +25 -0
  317. data/lib/hecks/query_specification/common/comparison.rb +174 -0
  318. data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
  319. data/lib/hecks/query_specification/common/dsl.rb +58 -0
  320. data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
  321. data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
  322. data/lib/hecks/query_specification/common/null_policy.rb +92 -0
  323. data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
  324. data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
  325. data/lib/hecks/query_specification/common/options.rb +32 -0
  326. data/lib/hecks/query_specification/common/order_by.rb +9 -0
  327. data/lib/hecks/query_specification/common/specification.rb +10 -0
  328. data/lib/hecks/query_specification/common/where_clause.rb +9 -0
  329. data/lib/hecks/query_specification/field_path.rb +107 -0
  330. data/lib/hecks/query_specification/hop_path.rb +132 -0
  331. data/lib/hecks/query_specification/read_model/specification.rb +18 -0
  332. data/lib/hecks/query_specification.rb +14 -0
  333. data/lib/hecks/rendering.rb +48 -0
  334. data/lib/hecks/router/namespace_installer.rb +157 -0
  335. data/lib/hecks/router.rb +70 -0
  336. data/lib/hecks/runtime/aggregate_lock.rb +45 -0
  337. data/lib/hecks/runtime/boot_gates.rb +41 -0
  338. data/lib/hecks/runtime/caller.rb +66 -0
  339. data/lib/hecks/runtime/capability_graph.rb +44 -0
  340. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +135 -0
  341. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +260 -0
  342. data/lib/hecks/runtime/command_interpreter.rb +497 -0
  343. data/lib/hecks/runtime/command_rules/admissibility.rb +357 -0
  344. data/lib/hecks/runtime/command_rules/arithmetic.rb +260 -0
  345. data/lib/hecks/runtime/command_rules/authorization.rb +65 -0
  346. data/lib/hecks/runtime/command_rules/emission.rb +34 -0
  347. data/lib/hecks/runtime/command_rules/references.rb +189 -0
  348. data/lib/hecks/runtime/command_rules.rb +28 -0
  349. data/lib/hecks/runtime/dependency_planning.rb +245 -0
  350. data/lib/hecks/runtime/dispatcher.rb +301 -0
  351. data/lib/hecks/runtime/entity_element.rb +253 -0
  352. data/lib/hecks/runtime/entity_interpreter.rb +299 -0
  353. data/lib/hecks/runtime/errors.rb +122 -0
  354. data/lib/hecks/runtime/event.rb +51 -0
  355. data/lib/hecks/runtime/identity.rb +156 -0
  356. data/lib/hecks/runtime/instance.rb +175 -0
  357. data/lib/hecks/runtime/interpreting.rb +90 -0
  358. data/lib/hecks/runtime/loader.rb +184 -0
  359. data/lib/hecks/runtime/policy_interpreter.rb +366 -0
  360. data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
  361. data/lib/hecks/runtime/query_interpreter.rb +286 -0
  362. data/lib/hecks/runtime/reaction_invocation.rb +253 -0
  363. data/lib/hecks/runtime/read_model_interpreter.rb +341 -0
  364. data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
  365. data/lib/hecks/runtime/reference_hop.rb +99 -0
  366. data/lib/hecks/runtime/refusal_wording.rb +126 -0
  367. data/lib/hecks/runtime/registry/saga_persistence.rb +142 -0
  368. data/lib/hecks/runtime/registry/verification.rb +246 -0
  369. data/lib/hecks/runtime/registry.rb +279 -0
  370. data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
  371. data/lib/hecks/runtime/routing.rb +96 -0
  372. data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
  373. data/lib/hecks/runtime/saga_interpreter.rb +503 -0
  374. data/lib/hecks/runtime/saga_pending_dispatch.rb +45 -0
  375. data/lib/hecks/runtime/tenant_check.rb +84 -0
  376. data/lib/hecks/runtime/tenant_scope.rb +55 -0
  377. data/lib/hecks/runtime/value/admission.rb +130 -0
  378. data/lib/hecks/runtime/value/coercion.rb +565 -0
  379. data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
  380. data/lib/hecks/runtime/value.rb +125 -0
  381. data/lib/hecks/runtime.rb +107 -0
  382. data/lib/hecks/storehouse.rb +632 -0
  383. data/lib/hecks/version.rb +16 -0
  384. data/lib/hecks/vocabulary.rb +218 -0
  385. data/lib/hecks.rb +123 -6
  386. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +90 -0
  387. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +128 -0
  388. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +160 -0
  389. metadata +412 -222
  390. data/bin/hecks +0 -7
  391. data/bin/hecks-package +0 -65
  392. data/bin/hecks_console +0 -12
  393. data/bin/hecks_serverless +0 -6
  394. data/lib/cli/build.rb +0 -14
  395. data/lib/cli/command_runner.rb +0 -28
  396. data/lib/cli/console.rb +0 -10
  397. data/lib/cli/generate.rb +0 -37
  398. data/lib/cli/hecks-cli.rb +0 -27
  399. data/lib/cli/test.rb +0 -57
  400. data/lib/console/commands.rb +0 -8
  401. data/lib/console/hecks-console.rb +0 -1
  402. data/lib/packager/README.md +0 -0
  403. data/lib/packager/app_runner.rb +0 -21
  404. data/lib/packager/args.rb +0 -26
  405. data/lib/packager/compatibility/fixnum.rb +0 -6
  406. data/lib/packager/hecks.rb +0 -39
  407. data/lib/packager/query_runner.rb +0 -21
  408. data/lib/packager/resources/Dockerfile +0 -11
  409. data/lib/packager/resources/app_binary +0 -7
  410. data/lib/packager/resources/bundle_config +0 -3
  411. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
  412. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
  413. data/lib/serverless/Domain +0 -32
  414. data/lib/serverless/cli.rb +0 -75
  415. data/lib/serverless/resources/command_name.js +0 -5
  416. data/lib/serverless/resources/environment.js +0 -7
  417. data/lib/serverless/resources/handler.js.tt +0 -28
  418. data/lib/serverless/resources/run_binary.js +0 -22
  419. data/lib/serverless/resources/serverless.yml +0 -20
@@ -0,0 +1,62 @@
1
+ require_relative "../runtime/registry"
2
+
3
+ module Hecks
4
+ module Ports
5
+ # WHAT TIME IT IS — the one fact a domain cannot derive and must not invent.
6
+ #
7
+ # Resolved exactly the way `Ports::IdentityGeneration` resolves its own
8
+ # adapter: one adapter registry-wide implements this, not a per-aggregate
9
+ # binding, because two aggregates in one running app have no real reason to
10
+ # disagree about the time.
11
+ #
12
+ # WHY A PORT AND NOT `Time.now`. A staleness rule — "another agent may take
13
+ # this claim after fifteen minutes" — is untestable against the real clock:
14
+ # a spec for it would either sleep for fifteen minutes or never run at all.
15
+ # Bound to a fixed adapter, the same rule is three lines. That is the whole
16
+ # argument, and it is the same one `SequentialIdentity` makes next door.
17
+ #
18
+ # A PREDICATE STILL CANNOT ASK THE TIME, and this does not change that. The
19
+ # expression sublanguage has no clock and should not: a `given` that read
20
+ # the time would evaluate differently on two runs over the same record, and
21
+ # every replay, audit and fuzz oracle in this repository assumes it does
22
+ # not. So `now` remains an ARGUMENT the predicate merely reads, and this
23
+ # port is what lets a caller stop typing it — the door fills it in, the
24
+ # value is baked into the dispatch, and the recorded step carries the
25
+ # concrete number forever after.
26
+ #
27
+ # WHICH IS WHY THE RUNTIME DOES NOT CALL THIS. `IdentityGeneration`'s own
28
+ # note works through the replay question for a minted uuid and lands on
29
+ # "the value gets baked into the caller's args at the first live dispatch".
30
+ # A clock consulted INSIDE the interpreter would not have that property: a
31
+ # recorded corpus step replayed tomorrow would silently get tomorrow's
32
+ # time, and the fuzzer's oracle and the adapter-agreement gate both compare
33
+ # runs of exactly that shape. So the filling happens at the door, where a
34
+ # human or an agent is typing, and never on the dispatch path a replay uses.
35
+ module Clock
36
+ NAME = "clock"
37
+
38
+ module_function
39
+
40
+ # SECONDS, AS AN INTEGER, because that is what the sublanguage can do
41
+ # arithmetic on. `claimed_at.value + window.value <= now.value` is
42
+ # addition and comparison — the only two things it has — and a Time
43
+ # object supports neither.
44
+ def now(registry) = adapter(registry).now
45
+
46
+ def adapter(registry)
47
+ implementations = registry.adapters.values.select { |a| a.port == NAME }
48
+
49
+ case implementations.size
50
+ when 1 then registry.adapter_class(implementations.first.name)
51
+ when 0
52
+ raise Runtime::WiringError,
53
+ "no adapter implements the #{NAME} port — nothing can say what time it is"
54
+ else
55
+ raise Runtime::WiringError,
56
+ "#{implementations.size} adapters implement the #{NAME} port " \
57
+ "(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,5 @@
1
+ Hecks.port "extraction" do
2
+ verb "extracted_by"
3
+ signal :reply
4
+ answers :canonical
5
+ end
@@ -0,0 +1,37 @@
1
+ require_relative "../runtime/registry"
2
+
3
+ module Hecks
4
+ module Ports
5
+ module Extraction
6
+ NAME = "extraction"
7
+
8
+ module_function
9
+
10
+ def canonical(block) = adapter.canonical(block)
11
+
12
+ def adapter
13
+ registry = Hecks.current_registry
14
+ unless registry
15
+ raise Runtime::WiringError,
16
+ "extraction resolved outside a boot — a predicate can only be " \
17
+ "read while a bluebook is loading"
18
+ end
19
+
20
+ implementations = registry.adapters.values.select { |a| a.port == NAME }
21
+
22
+ case implementations.size
23
+ when 1 then registry.adapter_class(implementations.first.name)
24
+ when 0
25
+ raise Runtime::WiringError,
26
+ "no adapter implements the #{NAME} port — nothing can recover a " \
27
+ "predicate's source"
28
+ else
29
+ raise Runtime::WiringError,
30
+ "#{implementations.size} adapters implement the #{NAME} port " \
31
+ "(#{implementations.map(&:name).sort.join(', ')}) — the runtime " \
32
+ "will not choose for you"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,5 @@
1
+ Hecks.port "identity_assignment" do
2
+ verb "assigned_by"
3
+ signal :reply
4
+ answers :assign
5
+ end
@@ -0,0 +1,45 @@
1
+ require_relative "../runtime/registry"
2
+
3
+ module Hecks
4
+ module Ports
5
+ # WHICH VALUE A CREATING COMMAND'S OWN IDENTITY FIELD GETS, when
6
+ # neither a slug-from-another-field nor a sequence-with-prefix
7
+ # mechanically answers it — a driving app's own console, say,
8
+ # falls back to this when a collection's identity strategy names
9
+ # "port" rather than "slug"/"sequence" (both of those stay pure
10
+ # config-driven mechanics, computed by whoever is dispatching,
11
+ # never by this port).
12
+ #
13
+ # Pure delegation, same as every sibling port (AccessControl,
14
+ # IdentityGeneration): an app's own domain declares what "port"
15
+ # actually MEANS for it — mint a real UUID via
16
+ # Ports::IdentityGeneration, derive something else entirely — this
17
+ # port just resolves the one adapter that domain wired and calls
18
+ # it, the same "one adapter registry-wide, refuse if zero or many"
19
+ # resolution every other port here already uses.
20
+ module IdentityAssignment
21
+ NAME = "identity_assignment"
22
+
23
+ module_function
24
+
25
+ def assign(registry, agg_name:, field_name:, args:)
26
+ adapter(registry).assign(registry, agg_name: agg_name, field_name: field_name, args: args)
27
+ end
28
+
29
+ def adapter(registry)
30
+ implementations = registry.adapters.values.select { |a| a.port == NAME }
31
+
32
+ case implementations.size
33
+ when 1 then registry.adapter_class(implementations.first.name)
34
+ when 0
35
+ raise Runtime::WiringError,
36
+ "no adapter implements the #{NAME} port — nothing can assign an identity"
37
+ else
38
+ raise Runtime::WiringError,
39
+ "#{implementations.size} adapters implement the #{NAME} port " \
40
+ "(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,5 @@
1
+ Hecks.port "identity_generation" do
2
+ verb "generated_by"
3
+ signal :reply
4
+ answers :uuid
5
+ end
@@ -0,0 +1,49 @@
1
+ require_relative "../runtime/registry"
2
+
3
+ module Hecks
4
+ module Ports
5
+ # A creating command with no natural key needs a value from
6
+ # SOMEWHERE to be its identity — this is that somewhere. Resolved
7
+ # the same way `Ports::Extraction` resolves its own adapter (one
8
+ # adapter registry-wide implements this port, not a per-aggregate
9
+ # binding the way persistence needs — different aggregates have no
10
+ # real reason to want different id-generation strategies within
11
+ # one running app).
12
+ #
13
+ # REPLAY NEEDS NO SUPPRESSION HERE, ON PURPOSE. A UUID minted for a
14
+ # creating command's identity gets baked into that step's own args
15
+ # — an ordinary string value — the moment it's generated, by
16
+ # whoever issues the first, live dispatch. A recorded corpus
17
+ # script or a captured fuzz-replay step already holds that
18
+ # concrete value; replaying it calls `dispatch` with the SAME
19
+ # args, and this module is never consulted again, for the same
20
+ # reason `SecureRandom.uuid` never runs twice for one
21
+ # already-recorded step today. `Event#occurred_at`
22
+ # (`command_rules/emission.rb`) is the one other "environmental
23
+ # fact, not caller-supplied" this codebase has, and it's handled
24
+ # the identical way: it fires on every dispatch, replay included,
25
+ # and comparison simply excludes it.
26
+ module IdentityGeneration
27
+ NAME = "identity_generation"
28
+
29
+ module_function
30
+
31
+ def uuid(registry) = adapter(registry).uuid
32
+
33
+ def adapter(registry)
34
+ implementations = registry.adapters.values.select { |a| a.port == NAME }
35
+
36
+ case implementations.size
37
+ when 1 then registry.adapter_class(implementations.first.name)
38
+ when 0
39
+ raise Runtime::WiringError,
40
+ "no adapter implements the #{NAME} port — nothing can mint an identity"
41
+ else
42
+ raise Runtime::WiringError,
43
+ "#{implementations.size} adapters implement the #{NAME} port " \
44
+ "(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,5 @@
1
+ Hecks.port "identity_resolution" do
2
+ verb "resolved_by"
3
+ signal :reply
4
+ answers :resolve
5
+ end
@@ -0,0 +1,40 @@
1
+ require_relative "../runtime/registry"
2
+
3
+ module Hecks
4
+ module Ports
5
+ # AN AUTHENTICATED (issuer, subject) PAIR, RESOLVED TO A STABLE
6
+ # `Identity` — the other half of the same symmetry `Authorization`
7
+ # already has for Governance: one adapter registry-wide answers this
8
+ # port, resolved the same zero/one/many way, so an application never
9
+ # has to name `Identity` directly to ask "who is this."
10
+ #
11
+ # Answers `nil` for a pair nothing has linked — the caller decides
12
+ # what that means (refuse, prompt to register, whatever), the same
13
+ # way `Authorization#holds_role?` answering `false` decides nothing
14
+ # on its own.
15
+ module IdentityResolution
16
+ NAME = "identity_resolution"
17
+
18
+ module_function
19
+
20
+ def resolve(registry, issuer:, subject:)
21
+ adapter(registry).resolve(registry, issuer: issuer, subject: subject)
22
+ end
23
+
24
+ def adapter(registry)
25
+ implementations = registry.adapters.values.select { |a| a.port == NAME }
26
+
27
+ case implementations.size
28
+ when 1 then registry.adapter_class(implementations.first.name)
29
+ when 0
30
+ raise Runtime::WiringError,
31
+ "no adapter implements the #{NAME} port — nothing can resolve an identity"
32
+ else
33
+ raise Runtime::WiringError,
34
+ "#{implementations.size} adapters implement the #{NAME} port " \
35
+ "(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,4 @@
1
+ Hecks.port "loading" do
2
+ verb "loaded_by"
3
+ signal :reply
4
+ end
@@ -0,0 +1,13 @@
1
+ require_relative "../adapters/driven/folder"
2
+
3
+ module Hecks
4
+ module Ports
5
+ module Loading
6
+ NAME = "loading"
7
+
8
+ module_function
9
+
10
+ def bootstrap = Adapters::Folder.new
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,138 @@
1
+ require_relative "../../runtime/registry"
2
+ require_relative "execution"
3
+
4
+ module Hecks
5
+ module Ports
6
+ module Persistence
7
+ # `mirrors` is durable replication intent. It is part of the same
8
+ # append as the authoritative state, never a second outbox store.
9
+ Entry = Struct.new(:operation, :id, :state, :mirrors, keyword_init: true) do
10
+ def save? = operation == "save"
11
+ def delete? = operation == "delete"
12
+ end
13
+
14
+ # Makes append-before-projection a port invariant. Adapters retain
15
+ # control of their durable format, but every one must accept the same
16
+ # entry stream and materialize current state from it.
17
+ class AppendOnly
18
+ attr_reader :adapter
19
+
20
+ def aggregate = @adapter.aggregate
21
+
22
+ def initialize(adapter)
23
+ @adapter = adapter
24
+ required = %i[append project entries]
25
+ missing = required.reject { |method| adapter.respond_to?(method) }
26
+ raise Runtime::WiringError,
27
+ "#{adapter.class} does not implement append-only persistence: #{missing.join(', ')}" unless missing.empty?
28
+ end
29
+
30
+ def find(id) = @adapter.find(id)
31
+ def all(**opts) = @adapter.all(**opts)
32
+ def count = @adapter.count
33
+ def entries = @adapter.entries
34
+
35
+ def capabilities
36
+ return [] unless @adapter.respond_to?(:persistence_capabilities)
37
+
38
+ Array(@adapter.persistence_capabilities).map(&:to_sym).freeze
39
+ end
40
+
41
+ def reset!
42
+ raise Runtime::WiringError, "append-only adapter cannot reset" unless @adapter.respond_to?(:reset!)
43
+
44
+ @adapter.reset!
45
+ end
46
+
47
+ # NOT an endless `def events = ... if ...` — that modifier binds to
48
+ # the WHOLE `def`, not just its body, so it evaluates against
49
+ # `@adapter` while `@adapter` is still nil (class-body time,
50
+ # before `initialize` ever runs) and silently skips defining the
51
+ # method at all. Found live: nothing in this codebase called
52
+ # `AppendOnly#events` before Memory got a `reset!` test that did.
53
+ def events
54
+ @adapter.events if @adapter.respond_to?(:events)
55
+ end
56
+
57
+ # An append is durable before a projection is attempted. Replaying the
58
+ # log restores a snapshot/table after a crash in that small window.
59
+ def recover!
60
+ entries.each { |entry| project(entry) }
61
+ self
62
+ end
63
+
64
+ def append(entry) = @adapter.append(entry)
65
+ def project(entry) = @adapter.project(entry)
66
+
67
+ # Returns an `Outcome`, not a bare `Instance` — every call site
68
+ # (`CommandInterpreter`/`EntityInterpreter`'s own `step_save`,
69
+ # `RebuildSweep#refresh`) reads it that way.
70
+ #
71
+ # `expected_version:` requests optimistic-concurrency CAS — commit
72
+ # only if the stored record's version still matches what THIS
73
+ # instance was read at. It is `nil` both when a caller explicitly
74
+ # doesn't want CAS (`RebuildSweep#refresh`'s own projection-field
75
+ # touch-up, which has no `given` to protect) and when the instance
76
+ # is brand new (never read from storage, so `instance.version` is
77
+ # nil) — both cases fall through to the plain, unconditional
78
+ # `project(entry)` below, byte-for-byte today's behavior. Only an
79
+ # adapter that both receives a non-nil `expected_version` AND
80
+ # declares `:optimistic_concurrency` gets CAS treatment; every
81
+ # other adapter/call site is unaffected.
82
+ def save(instance, expected_version: nil)
83
+ entry = Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
84
+ append(entry)
85
+ if expected_version && capabilities.include?(:optimistic_concurrency)
86
+ saved = @adapter.project(entry, expected_version: expected_version)
87
+ return Outcome.new(status: :stale, instance: instance) if saved.nil?
88
+
89
+ return Outcome.new(status: :saved, instance: saved)
90
+ end
91
+ saved = @adapter.project(entry)
92
+ Outcome.new(status: :saved, instance: saved || instance)
93
+ end
94
+
95
+ def atomic_put(instance, insert_only: false)
96
+ unless capabilities.include?(:atomic_put) && @adapter.respond_to?(:atomic_put)
97
+ raise Runtime::WiringError, "#{@adapter.class} advertises no atomic_put persistence capability"
98
+ end
99
+
100
+ entry = Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
101
+ status = @adapter.atomic_put(entry, insert_only: insert_only)
102
+ Outcome.new(status: status, instance: instance)
103
+ end
104
+
105
+ def delete(id)
106
+ return false unless find(id)
107
+
108
+ entry = Entry.new(operation: "delete", id: id.to_s, state: nil)
109
+ append(entry)
110
+ project(entry)
111
+ true
112
+ end
113
+
114
+ # NOT an endless `def record_event = ... if ...` — same gotcha as
115
+ # `events` above, and it bit for real here: this guard evaluated
116
+ # against `@adapter` at class-body time (nil, always false), so
117
+ # `record_event` was never defined at all. `emission.rb`'s own
118
+ # `repository.record_event(event) if repository.respond_to?(:record_event)`
119
+ # therefore never fired for any adapter, ever — every declared
120
+ # `emits` was computed and reported in `registry.event_log` (an
121
+ # in-process array, gone at exit) but never durably recorded.
122
+ # Caught because a live tail of a domain's own persisted events
123
+ # found nothing to tail. `sqlite_spec.rb`/`postgres_spec.rb`/
124
+ # `postgres_era_spec.rb` all call `adapter.record_event` directly,
125
+ # bypassing this wrapper — which is exactly why no spec noticed.
126
+ def record_event(event)
127
+ @adapter.record_event(event) if @adapter.respond_to?(:record_event)
128
+ end
129
+
130
+ def query_read_model(domain, model, args, bluebook = nil)
131
+ return unless @adapter.respond_to?(:query_read_model)
132
+
133
+ @adapter.query_read_model(domain, model, args, bluebook)
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,56 @@
1
+ require_relative "../../bluebook/hexagon"
2
+ require_relative "../../runtime/registry"
3
+
4
+ module Hecks
5
+ module Ports
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.
10
+ module BindingPolicy
11
+ module_function
12
+
13
+ def resolve(registry, domain, aggregate)
14
+ hexagon = registry.hecksagon(domain)
15
+ return default_binding(aggregate) unless hexagon
16
+
17
+ bindings = hexagon.binds_for(aggregate.hecks_name, VERB)
18
+ raise missing_binding(domain, aggregate) if bindings.empty?
19
+
20
+ authoritative = bindings.select { |bind| bind.role.nil? || bind.role.empty? }
21
+ raise ambiguous_binding(domain, aggregate, authoritative) unless authoritative.size == 1
22
+ raise unsupported_roles(domain, aggregate, bindings - authoritative) unless (bindings - authoritative).empty?
23
+
24
+ authoritative.first
25
+ end
26
+
27
+ def default_binding(aggregate)
28
+ Bluebook::Bind.new(aggregate: aggregate.hecks_name, verb: VERB, adapter: DEFAULT_ADAPTER)
29
+ end
30
+
31
+ def missing_binding(domain, aggregate)
32
+ Runtime::WiringError.new(
33
+ "#{domain}::#{aggregate.hecks_name} has no #{VERB} bind. #{domain} declares a " \
34
+ "hecksagon, so its wiring is being decided explicitly and an aggregate " \
35
+ "left out is a forgotten decision. Bind it, or say " \
36
+ "#{aggregate.hecks_name}.#{VERB}(#{DEFAULT_ADAPTER.inspect}) to keep it in memory on purpose."
37
+ )
38
+ end
39
+
40
+ def ambiguous_binding(domain, aggregate, authoritative)
41
+ Runtime::WiringError.new(
42
+ "#{domain}::#{aggregate.hecks_name} has #{authoritative.size} authoritative #{VERB} bindings. " \
43
+ "Declare exactly one adapter without a role."
44
+ )
45
+ end
46
+
47
+ def unsupported_roles(domain, aggregate, bindings)
48
+ Runtime::WiringError.new(
49
+ "#{domain}::#{aggregate.hecks_name} uses persistence role#{bindings.size == 1 ? '' : 's'} " \
50
+ "#{bindings.map(&:role).map(&:inspect).join(', ')}. Only persisted_by is supported."
51
+ )
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,23 @@
1
+ module Hecks
2
+ module Ports
3
+ module Persistence
4
+ # Adapter outcomes are runtime facts, never command lifecycle.
5
+ Outcome = Struct.new(:status, :instance, keyword_init: true) do
6
+ # `:stale` is an optimistic-concurrency conflict on a plain `save`
7
+ # (someone else committed since this instance was read) — never a
8
+ # domain refusal, distinct from `:conflicted` (a `creates?`
9
+ # command's identity already exists, `AlreadyExists`). See
10
+ # `Runtime::StaleWrite` (runtime/errors.rb) and `AppendOnly#save`.
11
+ STATUSES = %i[inserted replaced updated conflicted missing saved stale].freeze
12
+
13
+ def initialize(status:, instance: nil)
14
+ normalized = status.to_sym
15
+ raise ArgumentError, "unknown persistence outcome #{status.inspect}" unless STATUSES.include?(normalized)
16
+
17
+ super(status: normalized, instance: instance)
18
+ freeze
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ module Hecks
2
+ module Ports
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`.
16
+ class NullSagaStore
17
+ def save_saga(**) = nil
18
+ def delete_saga(**) = nil
19
+ def each_saga(*) = nil
20
+ end
21
+
22
+ NULL_SAGA_STORE = NullSagaStore.new
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,54 @@
1
+ module Hecks
2
+ module Ports
3
+ module Persistence
4
+ # A tiny, real seam — ADR 0033. Core never names a persistence plugin;
5
+ # a plugin makes itself known as a side effect of being `require`d, the
6
+ # same way an adapter's own `.rb` file already registers itself
7
+ # (`Runtime::Registry#add_adapter`) rather than core listing adapter
8
+ # names anywhere. `register_plugin` is the ENTIRE contract a plugin
9
+ # must satisfy: anything responding to `contribute_boot_gates(registry,
10
+ # gates)` (a no-op is a valid implementation).
11
+ #
12
+ # This is deliberately NOT the same registry `Hecks::Projector` uses
13
+ # (ADR 0027) or `Runtime::BootGates` (ADR 0031) — each of those is
14
+ # scoped to its own seam (IR-in/artifact-out; per-boot phased gates).
15
+ # A shared base is deferred until a third registry actually wants one.
16
+ #
17
+ # NAME-KEYED, PROCESS-WIDE, NOT PER-BOOT — unlike `BootGates` (one
18
+ # instance per `Loader.boot` call, because gate REGISTRATION is
19
+ # capability-conditional per registry), a persistence plugin is either
20
+ # `require`d into this process or it isn't; there is no "this boot's
21
+ # registry doesn't need it" case to isolate against, so a plain
22
+ # module-level hash is the right shape here, not an instantiable class.
23
+ module Plugin
24
+ @plugins = {}
25
+
26
+ class << self
27
+ def register(name, plugin)
28
+ @plugins[name.to_sym] = plugin
29
+ end
30
+
31
+ def registered?(name)
32
+ @plugins.key?(name.to_sym)
33
+ end
34
+
35
+ def each(&)
36
+ @plugins.each_value(&)
37
+ end
38
+
39
+ def any? = !@plugins.empty?
40
+ end
41
+ end
42
+
43
+ # `Ports::Persistence.plugin?(:era)` / `.each_plugin` — the ordinary
44
+ # call surface; `Plugin.register`/`.registered?`/`.each` stay reachable
45
+ # directly for a plugin's own file to call at require-time.
46
+ module_function
47
+
48
+ def register_plugin(name, plugin) = Plugin.register(name, plugin)
49
+ def plugin?(name) = Plugin.registered?(name)
50
+ def each_plugin(&) = Plugin.each(&)
51
+ def plugins_loaded? = Plugin.any?
52
+ end
53
+ end
54
+ end