hecks 0.2.0 → 0.3.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 (408) 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 +403 -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 +62 -0
  12. data/lib/hecks/adapters/driven/heki/journal.rb +51 -0
  13. data/lib/hecks/adapters/driven/heki/saga_store.rb +0 -0
  14. data/lib/hecks/adapters/driven/heki/snapshot.rb +38 -0
  15. data/lib/hecks/adapters/driven/heki.adapter +4 -0
  16. data/lib/hecks/adapters/driven/heki.rb +148 -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 +131 -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 +80 -0
  28. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +168 -0
  29. data/lib/hecks/adapters/driven/postgres.adapter +5 -0
  30. data/lib/hecks/adapters/driven/postgres.rb +391 -0
  31. data/lib/hecks/adapters/driven/postgres_era/lineage/era_store.rb +171 -0
  32. data/lib/hecks/adapters/driven/postgres_era/lineage/field_cache.rb +190 -0
  33. data/lib/hecks/adapters/driven/postgres_era/lineage/head_compiler.rb +429 -0
  34. data/lib/hecks/adapters/driven/postgres_era/lineage/mint_transaction.rb +166 -0
  35. data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +286 -0
  36. data/lib/hecks/adapters/driven/postgres_era/lineage/resumable_backfill.rb +168 -0
  37. data/lib/hecks/adapters/driven/postgres_era/lineage/tail_merge.rb +163 -0
  38. data/lib/hecks/adapters/driven/postgres_era/lineage/transform_installer.rb +114 -0
  39. data/lib/hecks/adapters/driven/postgres_era/lineage.rb +137 -0
  40. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb +89 -0
  41. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +82 -0
  42. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
  43. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/minter.rb +132 -0
  44. data/lib/hecks/adapters/driven/postgres_era/lineage_manager.rb +74 -0
  45. data/lib/hecks/adapters/driven/postgres_era.adapter +6 -0
  46. data/lib/hecks/adapters/driven/postgres_era.rb +671 -0
  47. data/lib/hecks/adapters/driven/prism.adapter +3 -0
  48. data/lib/hecks/adapters/driven/prism.rb +80 -0
  49. data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
  50. data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
  51. data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
  52. data/lib/hecks/adapters/driven/sqlite/codec.rb +63 -0
  53. data/lib/hecks/adapters/driven/sqlite/projection.rb +105 -0
  54. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +180 -0
  55. data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
  56. data/lib/hecks/adapters/driven/sqlite.rb +285 -0
  57. data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
  58. data/lib/hecks/adapters/driven/system_clock.rb +14 -0
  59. data/lib/hecks/adapters/driven.rb +36 -0
  60. data/lib/hecks/adapters.rb +6 -0
  61. data/lib/hecks/behaviors/dsl.rb +96 -0
  62. data/lib/hecks/behaviors/expectations.rb +280 -0
  63. data/lib/hecks/behaviors/ir.rb +31 -0
  64. data/lib/hecks/behaviors/rspec.rb +42 -0
  65. data/lib/hecks/behaviors/runner.rb +96 -0
  66. data/lib/hecks/behaviors.rb +25 -0
  67. data/lib/hecks/bluebook/aggregate.rb +108 -0
  68. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
  69. data/lib/hecks/bluebook/assembly/build.rb +48 -0
  70. data/lib/hecks/bluebook/assembly/contract.rb +112 -0
  71. data/lib/hecks/bluebook/assembly/contracts.rb +409 -0
  72. data/lib/hecks/bluebook/assembly/marks.rb +227 -0
  73. data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
  74. data/lib/hecks/bluebook/assembly.rb +78 -0
  75. data/lib/hecks/bluebook/attribute.rb +96 -0
  76. data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
  77. data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
  78. data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
  79. data/lib/hecks/bluebook/behaviour/command.rb +116 -0
  80. data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
  81. data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
  82. data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
  83. data/lib/hecks/bluebook/behaviour/lifecycle.rb +51 -0
  84. data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
  85. data/lib/hecks/bluebook/behaviour/process_manager.rb +38 -0
  86. data/lib/hecks/bluebook/behaviour/query.rb +10 -0
  87. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
  88. data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
  89. data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
  90. data/lib/hecks/bluebook/chapter.rb +68 -0
  91. data/lib/hecks/bluebook/command.rb +124 -0
  92. data/lib/hecks/bluebook/domain_port.rb +102 -0
  93. data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
  94. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +906 -0
  95. data/lib/hecks/bluebook/dsl/attribute_collector.rb +327 -0
  96. data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
  97. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1018 -0
  98. data/lib/hecks/bluebook/dsl/command_builder.rb +626 -0
  99. data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
  100. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
  101. data/lib/hecks/bluebook/dsl/entity_builder.rb +305 -0
  102. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
  103. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
  104. data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
  105. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
  106. data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
  107. data/lib/hecks/bluebook/dsl/policy_builder.rb +120 -0
  108. data/lib/hecks/bluebook/dsl/port_builder.rb +30 -0
  109. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
  110. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +207 -0
  111. data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
  112. data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
  113. data/lib/hecks/bluebook/dsl/rule_reference.rb +172 -0
  114. data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
  115. data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
  116. data/lib/hecks/bluebook/dsl/word_gate.rb +221 -0
  117. data/lib/hecks/bluebook/dsl/world_builder.rb +77 -0
  118. data/lib/hecks/bluebook/dsl.rb +45 -0
  119. data/lib/hecks/bluebook/entity.rb +103 -0
  120. data/lib/hecks/bluebook/expression/canonical_form.rb +55 -0
  121. data/lib/hecks/bluebook/expression/evaluator.rb +261 -0
  122. data/lib/hecks/bluebook/expression/projection.json +170 -0
  123. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
  124. data/lib/hecks/bluebook/expression/resolver.rb +630 -0
  125. data/lib/hecks/bluebook/expression.rb +13 -0
  126. data/lib/hecks/bluebook/hexagon.rb +60 -0
  127. data/lib/hecks/bluebook/lifecycle.rb +42 -0
  128. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
  129. data/lib/hecks/bluebook/meta_validator/judge.rb +559 -0
  130. data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
  131. data/lib/hecks/bluebook/meta_validator/port_judge.rb +47 -0
  132. data/lib/hecks/bluebook/meta_validator/readings.rb +350 -0
  133. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +312 -0
  134. data/lib/hecks/bluebook/meta_validator/shapes.rb +246 -0
  135. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
  136. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
  137. data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
  138. data/lib/hecks/bluebook/meta_validator.rb +452 -0
  139. data/lib/hecks/bluebook/model_check.rb +318 -0
  140. data/lib/hecks/bluebook/pattern_subset.rb +120 -0
  141. data/lib/hecks/bluebook/policy.rb +41 -0
  142. data/lib/hecks/bluebook/process_manager.rb +86 -0
  143. data/lib/hecks/bluebook/project_discovery.rb +30 -0
  144. data/lib/hecks/bluebook/project_loader.rb +40 -0
  145. data/lib/hecks/bluebook/project_register.rb +82 -0
  146. data/lib/hecks/bluebook/query.rb +61 -0
  147. data/lib/hecks/bluebook/read_model.rb +109 -0
  148. data/lib/hecks/bluebook/reference.rb +74 -0
  149. data/lib/hecks/bluebook/smoke_test.rb +166 -0
  150. data/lib/hecks/bluebook/synthesizer.rb +95 -0
  151. data/lib/hecks/bluebook/translation.rb +92 -0
  152. data/lib/hecks/bluebook/value_object.rb +58 -0
  153. data/lib/hecks/bluebook.rb +74 -0
  154. data/lib/hecks/codemod.rb +342 -0
  155. data/lib/hecks/construct.rb +71 -0
  156. data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
  157. data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
  158. data/lib/hecks/deploy/oidc.json +18 -0
  159. data/lib/hecks/doc/reference.rb +389 -0
  160. data/lib/hecks/embryonaut_bluebook.rb +75 -0
  161. data/lib/hecks/facade/cli_door.rb +116 -0
  162. data/lib/hecks/facade/cli_runner.rb +190 -0
  163. data/lib/hecks/facade/command_request.rb +105 -0
  164. data/lib/hecks/facade/handle.rb +173 -0
  165. data/lib/hecks/facade/json_door.rb +154 -0
  166. data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
  167. data/lib/hecks/facade/surface/chapter.rb +107 -0
  168. data/lib/hecks/facade/surface.rb +48 -0
  169. data/lib/hecks/facade.rb +44 -0
  170. data/lib/hecks/forms/app.rb +300 -0
  171. data/lib/hecks/forms/command_form_renderer.rb +113 -0
  172. data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
  173. data/lib/hecks/forms/field_renderer.rb +170 -0
  174. data/lib/hecks/forms/field_shape.rb +232 -0
  175. data/lib/hecks/forms/html.rb +53 -0
  176. data/lib/hecks/forms/index_renderer.rb +35 -0
  177. data/lib/hecks/forms/page.rb +157 -0
  178. data/lib/hecks/forms/params.rb +132 -0
  179. data/lib/hecks/forms/query_form_renderer.rb +114 -0
  180. data/lib/hecks/forms/record_renderer.rb +115 -0
  181. data/lib/hecks/forms/record_table.rb +63 -0
  182. data/lib/hecks/forms/reference_options.rb +30 -0
  183. data/lib/hecks/forms/value_object_shape.rb +46 -0
  184. data/lib/hecks/forms.rb +54 -0
  185. data/lib/hecks/fqn.rb +94 -0
  186. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
  187. data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
  188. data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
  189. data/lib/hecks/framework/bluebook/governance.bluebook +130 -0
  190. data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
  191. data/lib/hecks/framework/oidc.json +39 -0
  192. data/lib/hecks/framework.rb +90 -0
  193. data/lib/hecks/freezer.rb +67 -0
  194. data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
  195. data/lib/hecks/fuzzing/isolated_boot.rb +92 -0
  196. data/lib/hecks/fuzzing/properties.rb +1146 -0
  197. data/lib/hecks/fuzzing/replay.rb +635 -0
  198. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
  199. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +54 -0
  200. data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
  201. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +185 -0
  202. data/lib/hecks/fuzzing/sequence_generator.rb +130 -0
  203. data/lib/hecks/fuzzing/value_generator.rb +177 -0
  204. data/lib/hecks/fuzzing.rb +12 -0
  205. data/lib/hecks/grammar/evolve.rb +291 -0
  206. data/lib/hecks/grammar/expression.bluebook +417 -0
  207. data/lib/hecks/grammar/expression_operators.json +1152 -0
  208. data/lib/hecks/grammar/grammar.hecksagon +20 -0
  209. data/lib/hecks/grammar/oidc.json +69 -0
  210. data/lib/hecks/grammar/translation.bluebook +203 -0
  211. data/lib/hecks/grammar.rb +178 -0
  212. data/lib/hecks/ir.rb +126 -0
  213. data/lib/hecks/language/adapter.bluebook +116 -0
  214. data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
  215. data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
  216. data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
  217. data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
  218. data/lib/hecks/language/bluebook/command.bluebook +437 -0
  219. data/lib/hecks/language/bluebook/entity.bluebook +316 -0
  220. data/lib/hecks/language/bluebook/policy.bluebook +172 -0
  221. data/lib/hecks/language/bluebook/process_manager.bluebook +264 -0
  222. data/lib/hecks/language/bluebook/projection.bluebook +267 -0
  223. data/lib/hecks/language/bluebook/query.bluebook +245 -0
  224. data/lib/hecks/language/bluebook/shape.bluebook +292 -0
  225. data/lib/hecks/language/bluebook/syntax.bluebook +447 -0
  226. data/lib/hecks/language/bluebook/vocabulary.bluebook +364 -0
  227. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
  228. data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
  229. data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
  230. data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
  231. data/lib/hecks/language/oidc.json +313 -0
  232. data/lib/hecks/language/port.bluebook +92 -0
  233. data/lib/hecks/language/translation/translation.bluebook +110 -0
  234. data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
  235. data/lib/hecks/language/world/wiring.bluebook +62 -0
  236. data/lib/hecks/language/world/world.bluebook +84 -0
  237. data/lib/hecks/literal.rb +125 -0
  238. data/lib/hecks/naming.rb +121 -0
  239. data/lib/hecks/ports/access_control.port +4 -0
  240. data/lib/hecks/ports/access_control.rb +62 -0
  241. data/lib/hecks/ports/agent/answers.rb +104 -0
  242. data/lib/hecks/ports/agent.port +4 -0
  243. data/lib/hecks/ports/agent.rb +167 -0
  244. data/lib/hecks/ports/authentication.port +4 -0
  245. data/lib/hecks/ports/authentication.rb +50 -0
  246. data/lib/hecks/ports/authorization.port +4 -0
  247. data/lib/hecks/ports/authorization.rb +59 -0
  248. data/lib/hecks/ports/clock.port +4 -0
  249. data/lib/hecks/ports/clock.rb +62 -0
  250. data/lib/hecks/ports/extraction.port +4 -0
  251. data/lib/hecks/ports/extraction.rb +37 -0
  252. data/lib/hecks/ports/identity_assignment.port +4 -0
  253. data/lib/hecks/ports/identity_assignment.rb +45 -0
  254. data/lib/hecks/ports/identity_generation.port +4 -0
  255. data/lib/hecks/ports/identity_generation.rb +49 -0
  256. data/lib/hecks/ports/identity_resolution.port +4 -0
  257. data/lib/hecks/ports/identity_resolution.rb +40 -0
  258. data/lib/hecks/ports/loading.port +4 -0
  259. data/lib/hecks/ports/loading.rb +13 -0
  260. data/lib/hecks/ports/persistence/append_only.rb +102 -0
  261. data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
  262. data/lib/hecks/ports/persistence/execution.rb +18 -0
  263. data/lib/hecks/ports/persistence/lineage.rb +277 -0
  264. data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
  266. data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
  267. data/lib/hecks/ports/persistence.port +4 -0
  268. data/lib/hecks/ports/persistence.rb +39 -0
  269. data/lib/hecks/ports/projection.port +4 -0
  270. data/lib/hecks/ports/projection.rb +76 -0
  271. data/lib/hecks/ports/query/in_memory.rb +60 -0
  272. data/lib/hecks/ports/query/ordering.rb +41 -0
  273. data/lib/hecks/ports/query.rb +36 -0
  274. data/lib/hecks/ports.rb +27 -0
  275. data/lib/hecks/projections/diagrams.rb +413 -0
  276. data/lib/hecks/projections/ir.rb +18 -0
  277. data/lib/hecks/projections/model/deviations.rb +98 -0
  278. data/lib/hecks/projections/model.rb +145 -0
  279. data/lib/hecks/projections/oidc.rb +110 -0
  280. data/lib/hecks/projections/parser_table.rb +159 -0
  281. data/lib/hecks/projections/reference.rb +38 -0
  282. data/lib/hecks/projections/shape.rb +31 -0
  283. data/lib/hecks/projections/statements.rb +110 -0
  284. data/lib/hecks/projections/vocabulary.rb +100 -0
  285. data/lib/hecks/projections.rb +32 -0
  286. data/lib/hecks/projector/cli_projector.rb +437 -0
  287. data/lib/hecks/projector/docs_projector.rb +321 -0
  288. data/lib/hecks/projector/exporter.rb +141 -0
  289. data/lib/hecks/projector/ir_projector.rb +18 -0
  290. data/lib/hecks/projector/narrate_projector.rb +243 -0
  291. data/lib/hecks/projector/target.rb +97 -0
  292. data/lib/hecks/projector.rb +186 -0
  293. data/lib/hecks/query_ir.rb +392 -0
  294. data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
  295. data/lib/hecks/query_specification/common/comparators.rb +25 -0
  296. data/lib/hecks/query_specification/common/comparison.rb +174 -0
  297. data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
  298. data/lib/hecks/query_specification/common/dsl.rb +58 -0
  299. data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
  300. data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
  301. data/lib/hecks/query_specification/common/null_policy.rb +80 -0
  302. data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
  303. data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
  304. data/lib/hecks/query_specification/common/options.rb +32 -0
  305. data/lib/hecks/query_specification/common/order_by.rb +9 -0
  306. data/lib/hecks/query_specification/common/specification.rb +10 -0
  307. data/lib/hecks/query_specification/common/where_clause.rb +9 -0
  308. data/lib/hecks/query_specification/field_path.rb +89 -0
  309. data/lib/hecks/query_specification/hop_path.rb +130 -0
  310. data/lib/hecks/query_specification/read_model/specification.rb +18 -0
  311. data/lib/hecks/query_specification.rb +14 -0
  312. data/lib/hecks/rendering.rb +48 -0
  313. data/lib/hecks/router/namespace_installer.rb +157 -0
  314. data/lib/hecks/router.rb +70 -0
  315. data/lib/hecks/runtime/caller.rb +50 -0
  316. data/lib/hecks/runtime/capability_graph.rb +44 -0
  317. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +124 -0
  318. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +248 -0
  319. data/lib/hecks/runtime/command_interpreter.rb +413 -0
  320. data/lib/hecks/runtime/command_rules/admissibility.rb +307 -0
  321. data/lib/hecks/runtime/command_rules/arithmetic.rb +254 -0
  322. data/lib/hecks/runtime/command_rules/authorization.rb +64 -0
  323. data/lib/hecks/runtime/command_rules/emission.rb +34 -0
  324. data/lib/hecks/runtime/command_rules/references.rb +181 -0
  325. data/lib/hecks/runtime/command_rules.rb +28 -0
  326. data/lib/hecks/runtime/dependency_planning.rb +231 -0
  327. data/lib/hecks/runtime/dispatcher.rb +286 -0
  328. data/lib/hecks/runtime/entity_element.rb +253 -0
  329. data/lib/hecks/runtime/entity_interpreter.rb +228 -0
  330. data/lib/hecks/runtime/era_check.rb +136 -0
  331. data/lib/hecks/runtime/era_guard/shape_diff.rb +128 -0
  332. data/lib/hecks/runtime/era_guard.rb +193 -0
  333. data/lib/hecks/runtime/era_tamper.rb +61 -0
  334. data/lib/hecks/runtime/errors.rb +100 -0
  335. data/lib/hecks/runtime/event.rb +51 -0
  336. data/lib/hecks/runtime/identity.rb +128 -0
  337. data/lib/hecks/runtime/instance.rb +120 -0
  338. data/lib/hecks/runtime/interpreting.rb +69 -0
  339. data/lib/hecks/runtime/loader.rb +143 -0
  340. data/lib/hecks/runtime/policy_interpreter.rb +366 -0
  341. data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
  342. data/lib/hecks/runtime/query_interpreter.rb +254 -0
  343. data/lib/hecks/runtime/reaction_invocation.rb +245 -0
  344. data/lib/hecks/runtime/read_model_interpreter.rb +266 -0
  345. data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
  346. data/lib/hecks/runtime/reference_hop.rb +99 -0
  347. data/lib/hecks/runtime/refusal_wording.rb +124 -0
  348. data/lib/hecks/runtime/registry/saga_persistence.rb +70 -0
  349. data/lib/hecks/runtime/registry/verification.rb +158 -0
  350. data/lib/hecks/runtime/registry.rb +218 -0
  351. data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
  352. data/lib/hecks/runtime/routing.rb +96 -0
  353. data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
  354. data/lib/hecks/runtime/saga_interpreter.rb +301 -0
  355. data/lib/hecks/runtime/storage_shape.rb +120 -0
  356. data/lib/hecks/runtime/tenant_check.rb +84 -0
  357. data/lib/hecks/runtime/tenant_scope.rb +55 -0
  358. data/lib/hecks/runtime/value/admission.rb +112 -0
  359. data/lib/hecks/runtime/value/coercion.rb +500 -0
  360. data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
  361. data/lib/hecks/runtime/value.rb +125 -0
  362. data/lib/hecks/runtime.rb +95 -0
  363. data/lib/hecks/translation/audit/approval_digest.rb +31 -0
  364. data/lib/hecks/translation/audit/layer_one.rb +37 -0
  365. data/lib/hecks/translation/audit/layer_two.rb +77 -0
  366. data/lib/hecks/translation/audit/unfed_report.rb +42 -0
  367. data/lib/hecks/translation/audit.rb +70 -0
  368. data/lib/hecks/translation/reattest.rb +72 -0
  369. data/lib/hecks/translation/rule_compiler.rb +120 -0
  370. data/lib/hecks/translation/scaffold/differ.rb +183 -0
  371. data/lib/hecks/translation/scaffold/renderer.rb +41 -0
  372. data/lib/hecks/translation/scaffold/writer.rb +28 -0
  373. data/lib/hecks/translation/scaffold.rb +30 -0
  374. data/lib/hecks/translation.rb +11 -0
  375. data/lib/hecks/version.rb +16 -0
  376. data/lib/hecks/vocabulary.rb +213 -0
  377. data/lib/hecks.rb +118 -6
  378. metadata +401 -222
  379. data/bin/hecks +0 -7
  380. data/bin/hecks-package +0 -65
  381. data/bin/hecks_console +0 -12
  382. data/bin/hecks_serverless +0 -6
  383. data/lib/cli/build.rb +0 -14
  384. data/lib/cli/command_runner.rb +0 -28
  385. data/lib/cli/console.rb +0 -10
  386. data/lib/cli/generate.rb +0 -37
  387. data/lib/cli/hecks-cli.rb +0 -27
  388. data/lib/cli/test.rb +0 -57
  389. data/lib/console/commands.rb +0 -8
  390. data/lib/console/hecks-console.rb +0 -1
  391. data/lib/packager/README.md +0 -0
  392. data/lib/packager/app_runner.rb +0 -21
  393. data/lib/packager/args.rb +0 -26
  394. data/lib/packager/compatibility/fixnum.rb +0 -6
  395. data/lib/packager/hecks.rb +0 -39
  396. data/lib/packager/query_runner.rb +0 -21
  397. data/lib/packager/resources/Dockerfile +0 -11
  398. data/lib/packager/resources/app_binary +0 -7
  399. data/lib/packager/resources/bundle_config +0 -3
  400. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
  401. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
  402. data/lib/serverless/Domain +0 -32
  403. data/lib/serverless/cli.rb +0 -75
  404. data/lib/serverless/resources/command_name.js +0 -5
  405. data/lib/serverless/resources/environment.js +0 -7
  406. data/lib/serverless/resources/handler.js.tt +0 -28
  407. data/lib/serverless/resources/run_binary.js +0 -22
  408. data/lib/serverless/resources/serverless.yml +0 -20
@@ -0,0 +1,124 @@
1
+ module Hecks
2
+ module Runtime
3
+ # Every DomainRefusal wording that is not already data — `given`/
4
+ # `ensures`/a declared `invariant` already carry their own description,
5
+ # read at dispatch time off the command or value object that declared
6
+ # them. These are different in kind: LANGUAGE-LEVEL refusals, the same
7
+ # wording for every domain, not authored per-bluebook.
8
+ #
9
+ # Declared the same way in Vocabulary::RefusalTemplate
10
+ # (language/bluebook/vocabulary.bluebook) — spec/refusal_wording_
11
+ # conformance_spec holds this table equal to the language, both
12
+ # directions. Hand-typed rather than read live off the meta-domain at
13
+ # every dispatch, the same reason Expression::Evaluator::
14
+ # SIGN_TEST_OPERATORS is hand-typed beside Vocabulary::SignTest.
15
+ module RefusalWording
16
+ TEMPLATES = {
17
+ %w[NotFound creating_no_identity] =>
18
+ "{command} creates a {aggregate} — pass {identity}:",
19
+ %w[AlreadyExists creating_duplicate] =>
20
+ "{command} creates a {aggregate} that already exists — {identity} {offered}",
21
+ %w[AlreadyExists entity_duplicate] =>
22
+ "a {entity} already exists on {aggregate} — {identity} {offered}",
23
+ %w[NotFound acting_no_identity] =>
24
+ "{command} acts on an existing {aggregate} — pass {identity}:",
25
+ %w[NotFound record_missing] =>
26
+ "no {aggregate} with {identity} {offered}",
27
+ %w[NotFound entity_parent_no_identity] =>
28
+ "{command} acts on a {aggregate}'s {entity} — pass {identity}:",
29
+ %w[UnknownVerb entity_unknown] =>
30
+ "{aggregate} has no entity {entity}",
31
+ %w[NotFound entity_element_no_identity] =>
32
+ "{command} acts on one {entity} — pass {identity}:",
33
+ %w[NotFound entity_element_missing] =>
34
+ "no {entity} with {identity} {wants} on {aggregate} {parent_id}",
35
+ %w[NotFound reference_target_missing] =>
36
+ "no {target} with {heads} {key}",
37
+ %w[NotFound read_model_reference_missing] =>
38
+ "no {aggregate} with reference {offered}",
39
+ %w[TypeMismatch read_model_object_reference] =>
40
+ "{query} refused — a reference is an id, and {field} arrived as an object",
41
+ %w[UnknownVerb no_query] =>
42
+ "{aggregate} has no query {query}",
43
+ %w[UnknownVerb entity_query_missing] =>
44
+ "{entity} has no query {query}",
45
+ %w[UnknownVerb entity_holds_no_list] =>
46
+ "{aggregate} holds no list of {entity}",
47
+ %w[UnknownVerb entity_no_command] =>
48
+ "{entity} has no command {command}",
49
+ %w[UnknownVerb aggregate_no_command] =>
50
+ "{aggregate} has no command {command}",
51
+ %w[UnknownVerb port_no_operation] =>
52
+ "{port} has no operation {operation}",
53
+ %w[UnknownVerb no_domain] =>
54
+ "no domain {domain} loaded (verb {verb})",
55
+ %w[UnknownVerb no_read_model] =>
56
+ "{domain} has no read model {query}",
57
+ %w[UnknownVerb not_fully_qualified] =>
58
+ "{verb} is not a fully-qualified verb (Domain::Aggregate.Command)",
59
+ %w[UnknownVerb no_aggregate] =>
60
+ "{domain} has no aggregate {aggregate}",
61
+ %w[LifecycleRefused transition_blocked] =>
62
+ "{command} refused — {field} is {current}, and {command} moves it only from {allowed}",
63
+ %w[TypeMismatch value_object_shape] =>
64
+ "{name} is a {type} — pass its fields as an object, not {offered}",
65
+ %w[TypeMismatch reference_as_object] =>
66
+ "{command} refused — a reference is an id, and {attribute} arrived as an object{known_by}",
67
+ %w[TypeMismatch multi_field_scalar] =>
68
+ "{type} has multiple fields and cannot stand in for a scalar",
69
+ %w[TypeMismatch composite_identity] =>
70
+ "{type} is a composite identity — an identity must have exactly one field",
71
+ %w[TypeMismatch numeric_field] =>
72
+ "{type}.{field} expects {expected}, got {offered}",
73
+ %w[TypeMismatch pattern_mismatch] =>
74
+ "{type}.{field} must match {pattern}, got {offered}",
75
+ %w[TypeMismatch arithmetic_amount] =>
76
+ "{op} of {target} needs an Integer, got {offered}",
77
+ %w[TypeMismatch arithmetic_current] =>
78
+ "{op} of {target} needs an Integer {target}, got {offered}",
79
+ %w[TypeMismatch arithmetic_shared_field] =>
80
+ "{op} of {target} needs a value object with one shared Integer field",
81
+ %w[UnknownArgument unknown_args] =>
82
+ "{command} does not declare {unknown} — it takes {declared}",
83
+ %w[AbsentArgument absent_args] =>
84
+ "{command} was not given {absent} — it takes {declared}",
85
+ %w[InvariantViolation closed_set_member] =>
86
+ "{type} admits {admitted} — got {offered}",
87
+ %w[InvariantViolation value_object_invariant] =>
88
+ "{name} invariant violated — {description} (given {offered})",
89
+ %w[InvariantViolation admits_declared_set] =>
90
+ "{name} admits {admits} — {admitted} — got {offered}",
91
+ %w[InvariantViolation undeclared_set] =>
92
+ "{name} admits {admits}, which this chapter does not declare — a closed set is named " \
93
+ "Aggregate::SetName, and it must be one the bluebook actually holds",
94
+ %w[Unauthorized tenant_required] =>
95
+ "{query} declares authorize with tenant: {field} — pass {field}: to name which " \
96
+ "{field} this ask is scoped to",
97
+ %w[Unauthorized role_mismatch] =>
98
+ "{command} refused — role: {role}, and the caller stated {caller_role}",
99
+ %w[AttributeAbsent absent_read] =>
100
+ "{aggregate} {field} is absent on this record — declared, not optional, and " \
101
+ "added since it was written. Backfill it in a translation (backfill :{field}, " \
102
+ "default: ...), or declare it optional: true",
103
+ %w[ProjectionAbsent absent_read] =>
104
+ "{aggregate} {field} is not yet projected on this record — declared via " \
105
+ "projects :{field}, but no rebuild sweep has populated it. Run the sweep, " \
106
+ "or read {reference}.{remote_field} directly if this rule cannot wait"
107
+ }.freeze
108
+
109
+ module_function
110
+
111
+ # Plain text substitution, never expression syntax — a template is
112
+ # read, not evaluated. `render` computes the placeholder VALUES via
113
+ # whatever the call site already had (a joined list, a rendered
114
+ # identity reading, …) and this only replaces the markers.
115
+ def render(refusal, site, **values)
116
+ template = TEMPLATES.fetch([refusal, site]) do
117
+ raise KeyError, "no refusal template for #{refusal}/#{site} — declare it in " \
118
+ "Vocabulary::RefusalTemplate first"
119
+ end
120
+ values.reduce(template) { |text, (key, value)| text.gsub("{#{key}}", value.to_s) }
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,70 @@
1
+ module Hecks
2
+ module Runtime
3
+ class Registry
4
+ # SAGA PERSISTENCE — no new DSL verb, no new world setting.
5
+ # Whatever adapter a domain's own aggregates already use for
6
+ # durability, its sagas use the same one, automatically: three
7
+ # OPTIONAL adapter methods (`save_saga`/`delete_saga`/`each_saga`),
8
+ # `respond_to?`-checked the same way `Ports::Persistence::AppendOnly`
9
+ # already treats an adapter's own optional methods
10
+ # (`find`/`all`/`count`/`reset!`/`events`/`record_event`).
11
+ module SagaPersistence
12
+ # Resolved and memoized per domain — the FIRST real aggregate
13
+ # declared in the domain's own bluebook is the resolution
14
+ # anchor. `Ports::Persistence::BindingPolicy.resolve` for that
15
+ # aggregate already falls back to a domain-level default bind
16
+ # (§0's `Hecksagon#bind_for`) before falling back to Memory, so
17
+ # this needs no separate "was a default declared" branch of its
18
+ # own — whatever adapter that anchor resolves to already IS the
19
+ # domain's own default in the normal case (every aggregate
20
+ # shares one adapter), and is the honest, documented fallback
21
+ # for the rarer domain genuinely split across more than one
22
+ # local adapter with no default declared.
23
+ def saga_persistence(domain)
24
+ (@saga_persistence ||= {})[domain.to_s] ||= resolve_saga_persistence(domain.to_s)
25
+ end
26
+
27
+ # WALKS EVERY LOADED DOMAIN, repopulating `saga_instances` from
28
+ # whatever `saga_persistence(domain)` resolves to — a real store
29
+ # for a domain whose adapter answers the capability, `each_saga`
30
+ # yielding real rows; `NULL_SAGA_STORE`'s own `each_saga` for
31
+ # everything else, which never yields at all, so this needs no
32
+ # `respond_to?` branch of its own — the SAME reason every other
33
+ # call site in this capability never needs one. Called once at
34
+ # boot (`Loader.boot`, between `verify!` and dispatcher
35
+ # construction) — a process that's been running has no reason to
36
+ # re-walk its own already-current `saga_instances`.
37
+ def rehydrate_sagas!
38
+ @hecksagons.each_key do |domain|
39
+ saga_persistence(domain).each_saga do |process_manager, correlation, state, memory|
40
+ @saga_instances[process_manager][correlation] = { state: state, memory: memory }
41
+ end
42
+ end
43
+ self
44
+ end
45
+
46
+ private
47
+
48
+ def resolve_saga_persistence(domain)
49
+ anchor = hecksagon(domain) && bluebook(domain)&.aggregates&.first
50
+ return Ports::Persistence::NULL_SAGA_STORE unless anchor
51
+
52
+ bind = Ports::Persistence::BindingPolicy.resolve(self, domain, anchor)
53
+ return Ports::Persistence::NULL_SAGA_STORE if adapter_class(bind.adapter) <= Ports::Persistence::RemoteRuntime
54
+
55
+ adapter = repository(domain, anchor).adapter
56
+ adapter.respond_to?(:save_saga) ? adapter : Ports::Persistence::NULL_SAGA_STORE
57
+ rescue WiringError
58
+ # A domain not cleanly wired for its own anchor aggregate's
59
+ # persistence (a test fixture binding only the aggregates it
60
+ # exercises, say) degrades saga persistence to the same no-op
61
+ # default an unbound domain gets — never raises out of a
62
+ # dispatch that would otherwise have succeeded. Matches this
63
+ # capability's own framing throughout: automatic wherever it's
64
+ # cleanly supported, silently absent wherever it isn't.
65
+ Ports::Persistence::NULL_SAGA_STORE
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,158 @@
1
+ require_relative "../../bluebook/hexagon"
2
+
3
+ module Hecks
4
+ module Runtime
5
+ class Registry
6
+ # The wiring gate: every bind names a declared aggregate, every
7
+ # adapter satisfies the verb its port declares, every world setting is
8
+ # a field the adapter admits, and the default adapter is usable at
9
+ # all. Included into Registry — `verify!` is what a boot calls after
10
+ # loading, and the smaller checks are also called piecemeal by the
11
+ # repository factory.
12
+ module Verification
13
+ def verify!
14
+ verify_default_adapter!
15
+
16
+ @hecksagons.each_value do |hexagon|
17
+ refuse_ungoverned_roles!(hexagon)
18
+
19
+ hexagon.binds.each do |bind|
20
+ # A domain-level default (§0) — `persisted_by "Heki"` bare,
21
+ # applying to whichever aggregates don't override it — names
22
+ # no aggregate of its own, so there's nothing to look up in
23
+ # the bluebook for THIS row specifically. Still validate its
24
+ # own adapter/verb shape (the same reason
25
+ # `verify_default_adapter!` checks the framework-wide
26
+ # default the same way, aggregate-less). Coverage of real
27
+ # aggregates that only resolve THROUGH this default comes
28
+ # from their own dispatch-time `BindingPolicy.resolve` —
29
+ # deliberately not required to be exhaustive here, the same
30
+ # leniency this method already extended to any aggregate
31
+ # left out of an explicit bind list entirely (real test
32
+ # fixtures bind only the aggregates they exercise).
33
+ if bind.aggregate.nil?
34
+ check_verb(bind)
35
+ next
36
+ end
37
+
38
+ aggregate = bluebook(hexagon.domain)&.aggregate(bind.aggregate_name)
39
+ raise WiringError, "#{bind.aggregate} is bound but not declared in the bluebook" unless aggregate
40
+
41
+ check_verb(bind)
42
+
43
+ repository(hexagon.domain, aggregate)
44
+ end
45
+ end
46
+ self
47
+ end
48
+
49
+ def verify_default_adapter!
50
+ name = Ports::Persistence::DEFAULT_ADAPTER
51
+
52
+ check_verb(
53
+ Bluebook::Bind.new(
54
+ aggregate: "(default)",
55
+ verb: Ports::Persistence::VERB,
56
+ adapter: name
57
+ )
58
+ )
59
+ adapter_class(name)
60
+ self
61
+ rescue WiringError => error
62
+ raise WiringError,
63
+ "the default persistence adapter (#{name}) is not usable, so an " \
64
+ "aggregate with no bind could not be given one: #{error.message}"
65
+ end
66
+
67
+ def check_verb(bind)
68
+ port = port_for(bind)
69
+ return if port.verb.to_s == bind.verb.to_s
70
+
71
+ raise WiringError,
72
+ "#{bind.adapter} implements the #{port.name} port (verb #{port.verb}) " \
73
+ "and cannot satisfy #{bind.verb}"
74
+ end
75
+
76
+ def check_settings(bind, settings)
77
+ adapter = @adapters[bind.adapter]
78
+ return unless adapter
79
+
80
+ declared = settings.keys - [:adapter]
81
+ unknown = declared.reject { |field| adapter.declares?(field) }
82
+ return if unknown.empty?
83
+
84
+ raise WiringError,
85
+ "#{bind.adapter} does not declare #{unknown.map(&:inspect).join(', ')} — " \
86
+ "it declares #{adapter.all_fields.map(&:inspect).join(', ')}. " \
87
+ "Add the field to the adapter, or remove it from the world."
88
+ end
89
+
90
+ def port_for(bind)
91
+ adapter = @adapters[bind.adapter]
92
+ raise WiringError, "unknown adapter #{bind.adapter.inspect}" unless adapter
93
+
94
+ @ports[adapter.port] ||
95
+ raise(WiringError, "adapter #{bind.adapter} declares unknown port #{adapter.port.inspect}")
96
+ end
97
+
98
+ def adapter_class(name)
99
+ Adapters.const_get(name)
100
+ rescue NameError
101
+ raise WiringError, "no Ruby adapter implementation for #{name.inspect} " \
102
+ "(expected Hecks::Adapters::#{name})"
103
+ end
104
+
105
+ private
106
+
107
+ # `role` IS REAL ACCESS CONTROL ONLY WHEN GOVERNANCE CAN CHECK IT
108
+ # AGAINST SOMETHING — a command that declares a role but whose
109
+ # domain never attaches Governance would leave that role forever
110
+ # unchecked, exactly the defect ADR 0025 §9 names ("role gates
111
+ # access control by exact string equality ... Governance ...
112
+ # connected to none of it"). Checked here, at `verify!` — RECOVERED
113
+ # and MOVED, not new: this used to run per-block, at HECKSAGON
114
+ # build time (Bluebook::DSL::HecksagonBuilder#build), which broke
115
+ # the moment a domain could be split across multiple hecksagon
116
+ # blocks (base + an `environments/<name>.hecksagon` overlay,
117
+ # Runtime::Loader.boot's `environment:` — see its own comment for
118
+ # the recovery provenance): every block but the one declaring
119
+ # `uses_framework "Governance"` would be refused there, even
120
+ # though `Registry#add_hecksagon` merges every block for a domain
121
+ # into ONE Hecksagon before anything ever dispatches against it.
122
+ # Checking the MERGED result once, here, after every file for
123
+ # this domain has loaded, is both more permissive (no need to
124
+ # repeat `uses_framework` in every file) and strictly more
125
+ # correct (a check against an incomplete, not-yet-merged
126
+ # hecksagon can never see the real final shape).
127
+ #
128
+ # GOVERNANCE ITSELF IS EXEMPT — it cannot `uses_framework` its own
129
+ # aggregates, and it IS the source of truth a role check runs
130
+ # against, the same self-reference `CommandRules::Authorization
131
+ # #governance_attached?` grants it at dispatch time.
132
+ def refuse_ungoverned_roles!(hexagon)
133
+ return if hexagon.domain == "Governance"
134
+ return if hexagon.framework_members.include?("Governance")
135
+
136
+ bluebook_ir = bluebook(hexagon.domain)
137
+ return unless bluebook_ir
138
+
139
+ offender = commands_in(bluebook_ir).find { |command| !command.role.to_s.empty? }
140
+ return unless offender
141
+
142
+ raise WiringError,
143
+ "#{offender.hecks_fqn} declares role #{offender.role.inspect}, but " \
144
+ "#{hexagon.domain}'s hecksagon never uses_framework \"Governance\" — role is only " \
145
+ "real access control once Governance is attached to check it against; without that " \
146
+ "it is silent decoration, the exact defect this refusal exists to catch"
147
+ end
148
+
149
+ # Every command this domain declares, an aggregate's own AND every
150
+ # entity nested inside one — the same reach `refuse_role_mismatch`
151
+ # itself needs at dispatch time, just walked ahead of time here.
152
+ def commands_in(bluebook_ir)
153
+ bluebook_ir.aggregates.flat_map { |aggregate| aggregate.commands + aggregate.entities.flat_map(&:commands) }
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,218 @@
1
+ require_relative "registry/verification"
2
+ require_relative "registry/saga_persistence"
3
+
4
+ module Hecks
5
+ module Runtime
6
+ class WiringError < StandardError; end
7
+
8
+ # The collections a boot gathers — bluebooks, hexagons, ports, adapters,
9
+ # worlds, the logs — and how a repository is resolved from them. The
10
+ # wiring gate lives in registry/verification.rb, saga persistence
11
+ # resolution in registry/saga_persistence.rb.
12
+ class Registry
13
+ include Verification
14
+ include SagaPersistence
15
+
16
+ attr_reader :root, :bluebooks, :hecksagons, :ports, :adapters, :worlds, :event_log,
17
+ :reaction_log, :saga_log, :saga_instances, :translations, :saga_mutex,
18
+ :saga_dispatch_log, :policy_dispatch_log
19
+
20
+ def initialize(root: nil)
21
+ @root = root
22
+ @bluebooks = {}
23
+ @hecksagons = {}
24
+ @ports = {}
25
+ @adapters = {}
26
+ @worlds = {}
27
+ @translations = []
28
+ @event_log = []
29
+ @reaction_log = []
30
+ @saga_log = []
31
+ # ADDITIVE, RUBY-ONLY — never merged into saga_log/reaction_log.
32
+ # rust/src/kernel/orchestrate.rs ports THOSE two arrays' exact
33
+ # shape byte-for-byte (spec/rust_conformance_spec.rb's own
34
+ # equality check) — a landmine found by reading that spec before
35
+ # touching anything, not by hitting it. These carry the raw
36
+ # inputs a dispatch's own argument binding was resolved from
37
+ # (SagaInterpreter#deliver_saga_dispatch / PolicyInterpreter#
38
+ # trigger_args), for Properties.dispatch_binding_fidelity's own
39
+ # independent re-derivation — a fact neither existing log
40
+ # records at all, so there is nothing here for Rust to have
41
+ # matched or drifted from.
42
+ @saga_dispatch_log = []
43
+ @policy_dispatch_log = []
44
+ @saga_instances = Hash.new { |h, k| h[k] = {} }
45
+ # GUARDS `saga_instances`' OWN mutation+checkpoint sequence
46
+ # (`SagaInterpreter`'s 4 write points, §7) — the same shape of
47
+ # hazard this codebase's own prior audit already flagged for
48
+ # `@reaction_depth`, a thread-shared dispatcher ivar with no
49
+ # lock, made meaningfully easier to hit once a persistence write
50
+ # sits in the same critical section. Held across the in-memory
51
+ # mutation AND the checkpoint write together, never across a
52
+ # saga's own dispatch cascade — see `SagaInterpreter#advance_saga`'s
53
+ # own comment for why that distinction matters (non-reentrant
54
+ # Mutex, recursive re-entry is real).
55
+ @saga_mutex = Mutex.new
56
+ @repositories = {}
57
+ @projection_repositories = {}
58
+ @bluebook_builders = {}
59
+ end
60
+
61
+ # THE BUILDER STAYS OPEN FOR THE LIFE OF THIS REGISTRY, keyed by chapter
62
+ # name — see the comment on `BluebookBuilder.build`. A chapter split across
63
+ # several files (`language/bluebook/*.bluebook`, all `Hecks.bluebook "Bluebook"`)
64
+ # needs its declarations to accumulate into ONE builder rather than each
65
+ # file minting its own and silently discarding the one before.
66
+ def bluebook_builder(name)
67
+ @bluebook_builders[name.to_s] ||= yield
68
+ end
69
+
70
+ def add_bluebook(item) = @bluebooks[item.name] = item
71
+
72
+ # MERGED, NOT REPLACED — RECOVERED, not new (see Runtime::Loader
73
+ # .boot's own comment for the provenance). A domain's hecksagon can
74
+ # now load in more than one block for the same domain (base file
75
+ # plus an `environments/<name>.hecksagon` overlay), and the second
76
+ # block should ADD to what the first declared, not silently
77
+ # discard it.
78
+ def add_hecksagon(item)
79
+ existing = @hecksagons[item.domain]
80
+ @hecksagons[item.domain] = existing ? merge_hecksagons(existing, item) : item
81
+ end
82
+
83
+ def add_port(item) = @ports[item.name] = item
84
+ def add_adapter(item) = @adapters[item.name] = item
85
+
86
+ # MERGED, NOT REPLACED — the same generalization for `World` that
87
+ # `add_hecksagon` above recovers for `Hecksagon`: an
88
+ # `environments/<name>.world` overlay (or a host-owned tenancy
89
+ # overlay world, same mechanism) can now add or override settings
90
+ # for a domain a base `.world` file already declared, without
91
+ # restating everything the base file said. Settings merge shallow,
92
+ # keyed exactly the way WorldBuilder already stores them (both the
93
+ # bare verb key and the `"verb:adapter"` qualified key point at the
94
+ # same resolved hash) — an overlay's key wins over the base's same
95
+ # key; a key only the base declares survives untouched.
96
+ def add_world(item)
97
+ existing = @worlds[item.domain]
98
+ @worlds[item.domain] = existing ? merge_worlds(existing, item) : item
99
+ end
100
+
101
+ def add_translation(item) = @translations << item
102
+
103
+ # {domain name => era ordinal} as resolved by the boot-time era
104
+ # gate. A lineage adapter writes into ITS OWN era's partition —
105
+ # which, for an old checkout booting a held-but-superseded shape,
106
+ # is not the newest one.
107
+ def resolved_eras = @resolved_eras ||= {}
108
+
109
+ def bluebook(name) = @bluebooks[name.to_s]
110
+ def hecksagon(name) = @hecksagons[name.to_s]
111
+ def world(name) = @worlds[name.to_s]
112
+
113
+ def verbs = @bluebooks.values.flat_map(&:verbs).sort
114
+
115
+ def repository(domain, aggregate)
116
+ @repositories[[domain.to_s, aggregate.hecks_name]] ||= Ports::Persistence.repository(self, domain, aggregate)
117
+ end
118
+
119
+ # EVERYTHING A DISPATCH WROTE, CLEARED; NOTHING A BOOT DECLARED,
120
+ # TOUCHED. Bluebooks, hecksagons, ports, adapters, worlds and the
121
+ # resolved eras are what loading the files produced and stay as
122
+ # they are; the logs, the saga instances and the repositories are
123
+ # what running commands against them produced, and go back to
124
+ # exactly what a fresh boot of the same files hands out. Dropping
125
+ # the repositories (rather than emptying each) is deliberate: a
126
+ # fresh boot's own repositories are new adapter instances too, so
127
+ # a Memory adapter starts empty and a durable one sees whatever
128
+ # it persisted — the same reading either way. Sagas rehydrate off
129
+ # that store again, the way `Loader.boot_files` does after
130
+ # `verify!`.
131
+ #
132
+ # What this is for: a test runner that used to boot a runtime per
133
+ # test to get isolation (`Behaviors::Expectations.run_one`) — ~2s a
134
+ # boot, 76 chess behaviours = two and a half minutes of booting the
135
+ # same two files — can now boot once and reset between tests.
136
+ def reset_runtime_state!
137
+ @event_log.clear
138
+ @reaction_log.clear
139
+ @saga_log.clear
140
+ @saga_dispatch_log.clear
141
+ @policy_dispatch_log.clear
142
+ @saga_instances.clear
143
+ @repositories = {}
144
+ @projection_repositories = {}
145
+ rehydrate_sagas!
146
+ self
147
+ end
148
+
149
+ def capability_graph
150
+ @capability_graph ||= CapabilityGraph.new(self)
151
+ end
152
+
153
+ def read_repository(domain, aggregate)
154
+ key = [domain.to_s, aggregate.hecks_name]
155
+ binding = Ports::Projection.binds_for(self, domain, aggregate).first
156
+ return repository(domain, aggregate) unless binding
157
+
158
+ projection = (@projection_repositories[key] ||= begin
159
+ projection = Ports::Persistence::RepositoryFactory.build(self, domain, aggregate, binding,
160
+ recover: true, settings_verb: Ports::Projection::VERB)
161
+ projection
162
+ end)
163
+ authoritative = repository(domain, aggregate)
164
+ projection_current?(projection, authoritative) ? projection : authoritative
165
+ end
166
+
167
+ def projection_current?(projection, authoritative)
168
+ projected_entries = projection.entries
169
+ source_entries = authoritative.entries
170
+ return false unless projected_entries.length == source_entries.length
171
+ return false unless projected_entries.zip(source_entries).all? do |projected, source|
172
+ projected.operation == source.operation && projected.id == source.id && projected.state == source.state
173
+ end
174
+
175
+ projected_rows = projection.all.map(&:to_h).sort_by { |row| row.fetch(:id).to_s }
176
+ source_rows = authoritative.all.map(&:to_h).sort_by { |row| row.fetch(:id).to_s }
177
+ projected_rows == source_rows
178
+ rescue StandardError
179
+ false
180
+ end
181
+
182
+ # RECOVERED — see `add_hecksagon`'s own comment for provenance.
183
+ # Concatenates every list-shaped fact; `binds` in particular is
184
+ # additive because an overlay REBINDING an aggregate (a new
185
+ # `persisted_by` for the same aggregate/verb) is meant to shadow
186
+ # the base's own bind at resolution time, not erase it outright —
187
+ # `Ports::Persistence::BindingPolicy.resolve`'s own "exactly one
188
+ # authoritative bind" check is what actually catches a genuine
189
+ # double-bind; this merge only concatenates, it does not itself
190
+ # decide which of two binds for the same aggregate wins.
191
+ def merge_hecksagons(a, b)
192
+ Bluebook::Hecksagon.new(
193
+ domain: a.domain,
194
+ binds: a.binds + b.binds,
195
+ subscriptions: a.subscriptions + b.subscriptions,
196
+ framework_members: a.framework_members + b.framework_members,
197
+ vendored_bluebooks: a.vendored_bluebooks + b.vendored_bluebooks
198
+ )
199
+ end
200
+
201
+ # RECOVERED AND GENERALIZED — see `add_world`'s own comment. `realm`/
202
+ # `latest` are scalars, so the overlay's value wins when present,
203
+ # else the base's survives; `settings` is a shallow merge keyed by
204
+ # verb (and `"verb:adapter"`) — an overlay entry for a key the base
205
+ # also declares REPLACES that key's whole resolved hash (the same
206
+ # all-or-nothing shape `WorldBuilder#method_missing` already builds
207
+ # each entry as), it does not deep-merge field by field within it.
208
+ def merge_worlds(a, b)
209
+ Bluebook::World.new(
210
+ domain: a.domain,
211
+ realm: b.realm || a.realm,
212
+ latest: b.latest || a.latest,
213
+ settings: a.settings.merge(b.settings)
214
+ )
215
+ end
216
+ end
217
+ end
218
+ end