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,175 @@
1
+ require_relative "value"
2
+ require_relative "identity"
3
+
4
+ module Hecks
5
+ module Runtime
6
+ class Instance
7
+ attr_reader :aggregate, :id
8
+ attr_accessor :state
9
+ # OUT-OF-BAND ADAPTER BOOKKEEPING, NOT DOMAIN STATE — the optimistic-
10
+ # concurrency version a CAS-capable adapter (Postgres today) stamps
11
+ # on a record it reads/writes, so a later `save` can assert "commit
12
+ # only if nobody has written since". Deliberately absent from
13
+ # `to_h`/`[]`/`[]=`/`method_missing` : a domain author never declares
14
+ # this, a `given`/`ensures`/`invariant` can never read it, and no
15
+ # adapter that doesn't understand it (Memory, Heki) ever sets it —
16
+ # `nil` there just means "no CAS attempted", which is exactly what a
17
+ # plain `save` already does. See docs/decisions/ (concurrency-control
18
+ # ADR) for the full mechanism.
19
+ attr_accessor :version
20
+
21
+ # `args:` — THE ORIGINAL COMMAND PAYLOAD, offered only by a fresh
22
+ # creation (`CommandInterpreter#hydrate_legacy_creation`/
23
+ # `#hydrate_complete_state`/`#hydrate_prior_or_initial`, each already
24
+ # holding it when they mint a brand-new record). See
25
+ # `materialize_identity!` for why a composite identity needs it.
26
+ def initialize(aggregate:, id:, state: nil, args: nil)
27
+ @aggregate = aggregate
28
+ @id = id
29
+ @state = state ? self.class.hydrate_with_defaults(aggregate, state) : self.class.defaults(aggregate)
30
+ @version = nil
31
+ materialize_identity!(args)
32
+ end
33
+
34
+ # Loading existing state runs the same default-fill a fresh instance
35
+ # gets: an attribute the record predates — a newly-required field
36
+ # with a declared default:, a list added since the record was
37
+ # written — arrives filled instead of nil. Only declared defaults
38
+ # fill in; an attribute with no default stays absent, exactly as
39
+ # stored.
40
+ def self.hydrate_with_defaults(aggregate, state)
41
+ hydrated = Value.hydrate(aggregate, state)
42
+ defaults(aggregate).each do |name, value|
43
+ hydrated[name] = value unless value.nil? || hydrated.key?(name)
44
+ end
45
+ hydrated
46
+ end
47
+
48
+ def self.defaults(aggregate)
49
+ state = aggregate.attributes.each_with_object({}) do |attr, acc|
50
+ # FROZEN, like a list that has had something appended to it.
51
+ # An untouched list is the easiest one to miss and the easiest
52
+ # to mutate: nothing has replaced it yet, so a caller pushing
53
+ # into it writes straight into the aggregate's own state.
54
+ acc[attr.name] = attr.list? ? Freezer.deep([]) : default_for(aggregate, attr)
55
+ end
56
+ state[aggregate.lifecycle.field.to_sym] = aggregate.lifecycle.default if aggregate.lifecycle
57
+ state
58
+ end
59
+
60
+ def self.default_for(aggregate, attribute)
61
+ return Value.for_attribute(aggregate, attribute, attribute.default) unless attribute.default.nil?
62
+ # An entity's members hydrate through the same path but an entity
63
+ # declares no value objects of its own — nothing to default-build.
64
+ return nil unless aggregate.respond_to?(:value_object)
65
+
66
+ value_object = aggregate.value_object(attribute.type)
67
+ return nil unless value_object && value_object.attributes.all? { |field| !field.default.nil? }
68
+
69
+ Value.build(value_object, {}, aggregate)
70
+ end
71
+
72
+ def [](name) = @state[name.to_sym]
73
+
74
+ def key?(name) = @state.key?(name.to_sym)
75
+
76
+ def []=(name, value)
77
+ @state[name.to_sym] = value
78
+ end
79
+
80
+ def method_missing(name, *args)
81
+ return @state[name] if @state.key?(name)
82
+
83
+ super
84
+ end
85
+
86
+ def respond_to_missing?(name, include_private = false)
87
+ @state.key?(name) || super
88
+ end
89
+
90
+ # `id: @id` LAST, not first — see Facade::Handle#to_h's own comment
91
+ # for the full story (the same fix, landed there first): an
92
+ # aggregate free to declare its own attribute literally named `id`
93
+ # (BurningManPrep's `Item`, `attribute :id, ItemId`) has that
94
+ # attribute's own wrapped value object sitting in `@state[:id]` —
95
+ # merging `@state` on top of `{ id: @id }` let it silently clobber
96
+ # the correct bare identity. `@id` merged last always wins.
97
+ def to_h = @state.merge(id: @id)
98
+
99
+ # A COPY A MUTATION MAY TOUCH. Every adapter but Memory hands `find`
100
+ # a freshly-decoded Instance already; Memory's holds the record it
101
+ # eventually saves — the SAME state Hash, aliased. Before `ensures`
102
+ # existed, nothing could refuse between apply_mutations and save, so
103
+ # that aliasing was invisible: a dispatch either ran to completion or
104
+ # raised before touching state at all. `ensures` is the first refusal
105
+ # to sit AFTER mutation, and it found the bug the moment it did — an
106
+ # in-memory record left half-mutated by a dispatch that then refused.
107
+ # `command_interpreter`/`entity_interpreter` hydrate an EXISTING
108
+ # record through this, never through the adapter's own return value
109
+ # directly, so a refused ensures leaves the stored record untouched
110
+ # regardless of which adapter is holding it.
111
+ def dup
112
+ copy = super
113
+ copy.state = @state.dup
114
+ copy
115
+ end
116
+
117
+ def inspect
118
+ fields = @state.map { |k, v| "#{k}=#{v.inspect}" }.join(" ")
119
+ "#<#{@aggregate.hecks_name} #{@id} #{fields}>"
120
+ end
121
+
122
+ private
123
+
124
+ # M17 — a COMPOSITE identity (`identity_heads.size > 1`, e.g.
125
+ # `identified_by :branch_code, :box_number`) has no single
126
+ # `identified_by` to fall back to `:id` for — `@aggregate.identified_by`
127
+ # is nil the moment there is more than one head (`Behaviour::Identified
128
+ # #derive_identity`), so the single-head branch below never runs for
129
+ # it at all. A creating command that declares those heads as ordinary
130
+ # attributes but doesn't ALSO `sets` them (redundant with the identity
131
+ # the command's own args already named) used to persist every head as
132
+ # nil — the id correctly named the record, but the record's own
133
+ # attributes forgot what named it.
134
+ #
135
+ # Filled from `args`, never from splitting `@id` back apart — the
136
+ # same reason the single-head branch below won't guess a multi-path
137
+ # identifier from its joined string: `@id` is a display key, not a
138
+ # reversible serialization, and a composite's own separator can
139
+ # collide with a part's own text. `args` is only offered by a FRESH
140
+ # creation (`Instance.new`'s own `args:` comment); an existing record
141
+ # read back from storage has no args to lean on, and doesn't need
142
+ # one since a correctly-persisted record already carries its own
143
+ # heads.
144
+ def materialize_identity!(args = nil)
145
+ return materialize_composite_identity!(args) if @aggregate.identity_heads.size > 1
146
+
147
+ identity = @aggregate.identified_by || :id
148
+ attribute = @aggregate.attribute(identity)
149
+ return unless attribute && @state[identity].nil?
150
+ # Several paths under one head mean the identifier is a display key,
151
+ # not a reversible serialization of the structured value object. A
152
+ # creating command supplies that object explicitly; persisted state
153
+ # hydrates it from storage. Never guess by splitting the joined id.
154
+ return if @aggregate.identity_heads.one? && @aggregate.identity_paths.size > 1
155
+
156
+ @state[identity] = Value.from_identifier(@aggregate, attribute, @id)
157
+ end
158
+
159
+ def materialize_composite_identity!(args)
160
+ return unless args
161
+
162
+ @aggregate.identity_paths.each do |path|
163
+ head = path.to_s.split(".").first.to_sym
164
+ attribute = @aggregate.attribute(head)
165
+ next unless attribute && @state[head].nil?
166
+
167
+ raw = Identity.from(@aggregate, args, path, value_owner: @aggregate)
168
+ next if raw.nil?
169
+
170
+ @state[head] = Value.from_identifier(@aggregate, attribute, raw)
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,90 @@
1
+ require_relative "value"
2
+ require_relative "aggregate_lock"
3
+
4
+ module Hecks
5
+ module Runtime
6
+ # What every stepwise interpreter shares: the traced step, and the
7
+ # coercion of a command's declared arguments. Two copies of each lived
8
+ # in CommandInterpreter and EntityInterpreter, where they could only
9
+ # ever drift.
10
+ module Interpreting
11
+ # Each including interpreter gets its own `trace` — set by a spec to
12
+ # observe dispatch order (Vocabulary::AggregateDispatchOrder and
13
+ # Vocabulary::EntityDispatchOrder in language/bluebook/vocabulary.bluebook);
14
+ # nil in production, always — one array push and a nil check per step
15
+ # is the entire cost of leaving this in.
16
+ def self.included(interpreter)
17
+ interpreter.singleton_class.attr_accessor :trace
18
+ end
19
+
20
+ private
21
+
22
+ # Logged AFTER the step's own work, so a step that wraps sub-steps logs
23
+ # itself once everything inside it has already logged — trace order is
24
+ # completion order, which is dispatch order.
25
+ def step(name)
26
+ result = yield
27
+ self.class.trace << name if self.class.trace
28
+ result
29
+ end
30
+
31
+ # DRIVES `DISPATCH_ORDER` (CommandInterpreter/EntityInterpreter, each
32
+ # mirroring its own Vocabulary::*DispatchOrder — vocabulary.bluebook,
33
+ # held equal to it by spec/vocabulary_conformance_spec.rb) by `send`ing
34
+ # each declared step name against the including interpreter's own
35
+ # `step_<name>` handler, in declared order. What used to be `call`'s own
36
+ # literal sequence of method calls is now DATA driving that sequence —
37
+ # tracing a real dispatch and comparing it to the declaration is
38
+ # tautological once `call` mechanically follows the declaration; a
39
+ # conditional step (assign_creation_attributes, advance_lifecycle) still
40
+ # has to guard ITSELF at the top of its own handler and skip tracing
41
+ # when it does not fire, rather than the caller branching around it —
42
+ # see CommandInterpreter#step_assign_creation_attributes.
43
+ def run_dispatch_order(order, ctx)
44
+ order.each { |name| send(:"step_#{name}", ctx) }
45
+ end
46
+
47
+ # THE CONCURRENCY-CONTROL SPLIT — see docs/decisions/ (concurrency
48
+ # control ADR) for the full mechanism. A repository that declares
49
+ # `:optimistic_concurrency` (Postgres today) already closes the
50
+ # lost-update gap itself, via `step_save`'s CAS + `#call`'s own
51
+ # `StaleWrite` retry loop — an extra in-process lock here would be
52
+ # pointless overhead, not incorrect, so it's skipped for clarity.
53
+ # Every other repository (Heki, Memory — confirmed process-local
54
+ # data, never a second process writing the same store) gets a
55
+ # striped `Mutex` held for the WHOLE dispatch-order run, so a second
56
+ # thread's own hydrate can't start until the first thread's save has
57
+ # landed. `lock_key_id` is best-effort (`Identity.best_effort`) —
58
+ # `nil` still locks correctly, just coarser (by aggregate type).
59
+ def run_dispatch_order_with_isolation(order, ctx, lock_key_id:)
60
+ if ctx.repository.capabilities.include?(:optimistic_concurrency)
61
+ run_dispatch_order(order, ctx)
62
+ else
63
+ AggregateLock.for(ctx.domain, ctx.aggregate, lock_key_id).synchronize { run_dispatch_order(order, ctx) }
64
+ end
65
+ end
66
+
67
+ # Every declared attribute present in the payload passes the reference
68
+ # gate, then coercion — the same walk whether the command acts on an
69
+ # aggregate or on one of its entity's elements.
70
+ def coerce_declared_arguments(aggregate, command, args)
71
+ command.attributes.each_with_object(args.dup) do |attribute, normalized|
72
+ next unless normalized.key?(attribute.name)
73
+
74
+ Value.refuse_object_reference(command, attribute, normalized[attribute.name])
75
+ normalized[attribute.name] = Value.for_attribute(aggregate, attribute, normalized[attribute.name])
76
+ end
77
+ end
78
+
79
+ # Coercion only — CommandInterpreter's own refuse_unknown_arguments/
80
+ # refuse_absent_arguments are separate DISPATCH_ORDER steps now (the
81
+ # declared vocabulary lists all three as flat, sequential members, not
82
+ # one nesting the other two), and EntityInterpreter never had them here
83
+ # at all (an entity inherits its aggregate's own gate). One copy,
84
+ # shared, rather than the two identical ones that used to drift.
85
+ def normalize_args(aggregate, command, args)
86
+ coerce_declared_arguments(aggregate, command, args)
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,184 @@
1
+ require_relative "../facade/surface"
2
+ require_relative "../ports/loading"
3
+ require_relative "../ports/persistence"
4
+ require_relative "dispatcher"
5
+ require_relative "remote_dispatcher"
6
+ require_relative "boot_gates"
7
+ require_relative "registry"
8
+
9
+ module Hecks
10
+ module Runtime
11
+ class Loader
12
+ # `install_facade:` defaults on — every ordinary caller wants
13
+ # `Widget::Item.Add(...)` sugar. A caller that only ever dispatches
14
+ # by FQN string (`SmokeTest`, the one caller so far) can pass
15
+ # `false` to skip it: `Facade::Surface.install` puts a bare global
16
+ # Ruby constant on `Object` per domain AND per aggregate name, with
17
+ # no scoping and no cleanup hook, so a tool booting arbitrary
18
+ # throwaway domains under generic names ("Widget", "Item", "Tag")
19
+ # would otherwise leak those names into the rest of the process —
20
+ # measured, not hypothetical: this exact leak once made an
21
+ # unrelated `dsl_spec.rb` example resolve a bare `Widget` constant
22
+ # to a stale smoke-test facade from a deleted temp directory instead
23
+ # of raising, corrupting that spec's own unrelated build. Skipping
24
+ # the install is safe because nothing downstream of a raw
25
+ # `Dispatcher` needs the sugar — `Dispatcher#dispatch`/`#query` work
26
+ # identically either way.
27
+ # `environment:` — see Adapters::Folder#load_domain's own comment
28
+ # for the mechanism. hecks never reads ENV itself (every other
29
+ # env-var lookup in this codebase lives in app-owned .world/
30
+ # .hecksagon files, never library internals) — a caller resolves
31
+ # its own env var name and passes the resulting string straight
32
+ # through, e.g. `Hecks.boot(path, environment:
33
+ # ENV.fetch("MYAPP_ENV", "development"))`.
34
+ def self.boot(path, shared: nil, install_facade: true, environment: nil)
35
+ loading = Ports::Loading.bootstrap
36
+ directory = loading.bluebook_directory(path)
37
+ root = loading.shared_root(shared, directory)
38
+ registry = Registry.new(root: File.dirname(directory))
39
+
40
+ Hecks.with_registry(registry) do
41
+ loading.load_library
42
+ loading.load_project(root)
43
+ loading.load_domain(directory, environment: environment)
44
+ end
45
+
46
+ run_boot_gates!(registry, directory)
47
+ dispatcher = dispatcher_for(registry)
48
+ install_facade ? bind_runtime(dispatcher) : dispatcher
49
+ end
50
+
51
+ # THE EXPLICIT-FILE FORM — `paths` names the exact bluebook/hecksagon/
52
+ # world files to boot, in place, wherever they actually live. `boot`
53
+ # above only ever takes a directory and globs it; that is the right
54
+ # shape for a real deployment (`examples/banking`, a domain someone
55
+ # `cd`s into), and the wrong one for a caller that wants to declare a
56
+ # narrow, explicit scope and have it booted exactly as declared — a
57
+ # `.behaviors` file's own `loads` line is the motivating caller
58
+ # (`Hecks::Behaviors`), but this carries no behaviors-specific
59
+ # logic and is not gated behind requiring that module.
60
+ #
61
+ # NO COPYING, NO TEMP DIRECTORY. A prior port of this same idea
62
+ # (vendored into a downstream consumer, read before writing this)
63
+ # scoped a per-test boot by copying files into `Dir.mktmpdir` — which
64
+ # destroys real relative paths, and worse, makes a `persisted_by`
65
+ # path resolve against the TEMP copy's root instead of the project's
66
+ # own (confirmed there: a file adapter kept reading and writing the
67
+ # same deterministic tmp copy across an entire session, because
68
+ # `Hecks.boot`'s own `root` is always `File.dirname` of whatever
69
+ # directory it was handed). `directory` here is `File.dirname` of the
70
+ # FIRST real path in `paths` — genuinely on disk, not a copy — so
71
+ # every downstream path (`EraCheck`, `persisted_by`, `shared_root`)
72
+ # resolves exactly as an ordinary directory boot's would.
73
+ def self.boot_files(paths, shared: nil, install_facade: true, environment: nil)
74
+ loading = Ports::Loading.bootstrap
75
+ files = Array(paths).map { |path| File.expand_path(path) }
76
+ directory = File.dirname(files.first)
77
+ root = loading.shared_root(shared, directory)
78
+ registry = Registry.new(root: File.dirname(directory))
79
+
80
+ Hecks.with_registry(registry) do
81
+ loading.load_library
82
+ loading.load_project(root)
83
+ loading.load_selected(files, environment: environment)
84
+ end
85
+
86
+ run_boot_gates!(registry, directory)
87
+ dispatcher = dispatcher_for(registry)
88
+ install_facade ? bind_runtime(dispatcher) : dispatcher
89
+ end
90
+
91
+ # ADR 0031 — replaces two previously-hardcoded, unconditional calls
92
+ # with a per-boot `BootGates` instance holding exactly the gates THIS
93
+ # registry's own bound adapters have a capability for. Ordering is
94
+ # preserved: era-checking (when a persistence plugin contributes one)
95
+ # still runs before `verify!`, saga rehydration still runs after
96
+ # (conservative — see `SagaPersistence#rehydrate_sagas!`'s own
97
+ # comment).
98
+ #
99
+ # ADR 0033 — this loader no longer names `EraCheck`, or any other
100
+ # era-specific class, at all. Every LOADED persistence plugin
101
+ # (`Ports::Persistence.each_plugin` — nothing here if nothing was
102
+ # ever `require`d) is asked to contribute its own `:pre_verify`/
103
+ # `:post_verify` gates generically; `:saga_rehydration` is the one
104
+ # gate core still registers directly, because ADR 0031 already
105
+ # proved it's not era-specific.
106
+ def self.run_boot_gates!(registry, directory)
107
+ gates = BootGates.new
108
+ Ports::Persistence.each_plugin { |plugin| plugin.contribute_boot_gates(registry, gates) }
109
+ check_compute_rules_backstop!(registry)
110
+
111
+ gates.run!(:pre_verify, registry, directory)
112
+ registry.verify!
113
+
114
+ gates.register(:saga_rehydration, ->(reg, _dir) { reg.rehydrate_sagas! }, phase: :post_verify) if
115
+ registry.hecksagons.each_key.any? { |domain| registry.saga_persistence(domain) != Ports::Persistence::NULL_SAGA_STORE }
116
+ gates.run!(:post_verify, registry, directory)
117
+ gates
118
+ end
119
+
120
+ # The one piece of the old, era-owned `check_compute_rules!` core
121
+ # still carries — deliberately thinner. `registry.translations` is
122
+ # plain `Bluebook::Translation`/`TranslationAggregate`/
123
+ # `TranslationCompute`/`TranslationRekey` data (`bluebook/
124
+ # translation.rb`, core, no era-specific class involved), so this
125
+ # needs nothing plugin-specific to ask "does anything declare a
126
+ # compute/rekey rule at all." A LOADED persistence plugin (e.g. the
127
+ # era plugin's own `:era_compute_rules` gate, registered above) runs
128
+ # the real, adapter-aware version of this check and refuses by name
129
+ # ("...is bound to Memory") long before this ever would; this only
130
+ # fires when nothing did, because nothing was loaded to.
131
+ def self.check_compute_rules_backstop!(registry)
132
+ return if Ports::Persistence.plugins_loaded?
133
+
134
+ registry.translations.each do |translation|
135
+ translation.aggregates.each do |aggregate|
136
+ next if aggregate.computes.empty? && aggregate.rekeys.empty?
137
+
138
+ raise WiringError,
139
+ "cannot boot #{translation.domain}::#{aggregate.name}: a compute/rekey rule is declared, but no " \
140
+ "persistence plugin that can interpret it is loaded (e.g. require " \
141
+ "\"hecks/ports/persistence/plugins/era\")"
142
+ end
143
+ end
144
+ end
145
+
146
+ # `RemoteDispatcher` for a domain routed through Lambda,
147
+ # `Dispatcher` otherwise — the ONE place this decision gets
148
+ # made, so everything built on top (`Handle`, `AggregateDoor`,
149
+ # `Facade::Surface`) never has to know which class it's holding.
150
+ # `registry.bluebooks.keys.first` is the just-booted domain's own
151
+ # name (insertion order — the target's own bluebook loads before
152
+ # any `uses_framework` chapter, `bin/project_rust`'s own header
153
+ # draws the identical distinction), not a directory basename.
154
+ #
155
+ # `dispatched_by("Lambda")` is its OWN, EXPLICIT verb — NOT
156
+ # inferred from `deployed_to("AwsLambda")`'s mere presence. Both
157
+ # Banking's and Embryonaut's `.world` files already declare
158
+ # `deployed_to("AwsLambda")` (it only means "a deploy target
159
+ # exists"), so treating that alone as "boot this domain against
160
+ # Lambda" would have silently rerouted Banking's every local
161
+ # boot — every spec, every `bin/console` session — the moment
162
+ # this landed. A domain opts in explicitly, the same way
163
+ # `persisted_by("PostgresEra")` is never inferred from anything
164
+ # else either.
165
+ def self.dispatcher_for(registry)
166
+ domain = registry.bluebooks.keys.first
167
+ settings = registry.world(domain)&.for_verb("dispatched_by") || {}
168
+ return Dispatcher.new(registry) unless settings[:adapter] == "Lambda"
169
+
170
+ RemoteDispatcher.new(registry, region: settings.fetch(:region, "us-east-1"))
171
+ end
172
+
173
+ # THE DOOR IS INSTALLED HERE, NOT STAMPED. This used to write the
174
+ # dispatcher onto every aggregate's class (`ruby_class.runtime =`) — the
175
+ # class-level global that made two boots in one process share one
176
+ # name. The facade's modules close over THIS dispatcher instead, so the
177
+ # binding lives in the surface a boot installs, not on anything shared.
178
+ def self.bind_runtime(dispatcher)
179
+ Facade::Surface.install(dispatcher)
180
+ dispatcher
181
+ end
182
+ end
183
+ end
184
+ end