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,350 @@
1
+ module Hecks
2
+ module Bluebook
3
+ module MetaValidator
4
+ # The parts of a bluebook the walk cannot read by name alone.
5
+ #
6
+ # The language now spells its fields exactly as the IR spells them, so the
7
+ # judge reads almost everything straight through: `command.givens`,
8
+ # `value_object.invariants`, `read_model.aggregate_heads`. What remains here
9
+ # is not naming drift — it is places where the IR's SHAPE differs from the
10
+ # language's, and no amount of renaming would close that:
11
+ #
12
+ # transitions one declaration expands to SEVERAL rows, because `from`
13
+ # may be a list of states
14
+ # value_objects the IR holds objects ; the language holds their names
15
+ # normalisations not on the bluebook at all — they come from the canonical
16
+ # form table the expression grammar keeps
17
+ # members plain hashes, one Member root per row, pairs per entry
18
+ # lifecycle one IR object feeding two separate fields
19
+ #
20
+ # Everything in this file is a difference in shape. If something here is
21
+ # only a difference in NAME, it is in the wrong file: rename the language.
22
+ module Readings
23
+ # A list the walk is about to offer, as rows it can shape into dispatches.
24
+ #
25
+ # FROM THE TABLE. This was nine hand-written cases keyed "Category.list", and
26
+ # every one of them was a fact `Assembly::Contracts` is the right place to
27
+ # keep: which shaper turns this list into rows. A list with no shaper reads
28
+ # straight off the node, which is most of them.
29
+ def rows_for(category, list_name, node)
30
+ shaper = Assembly.contract(category).shaper(list_name)
31
+ return Array(node.public_send(list_name)) unless shaper
32
+
33
+ public_send(shaper, node)
34
+ end
35
+
36
+ # A where-clause is read through its own to_h, which is where the IR spells a
37
+ # symbol argument as ":ceiling". Reading the OBJECT instead lost the colon,
38
+ # and nothing downstream could tell an argument from a literal of the same
39
+ # name.
40
+ def where_rows(node) = Array(node.wheres).map(&:to_h)
41
+
42
+ # The language holds a value object's NAME here ; the IR holds the object.
43
+ def value_object_names(node) = node.value_objects.map { |shape| { name: shape.hecks_name } }
44
+
45
+ # AN IDENTITY IS A LIST OF PARTS, so it is offered one part at a time — the
46
+ # same way attributes and transitions are. The IR holds the paths ; the
47
+ # language holds a row per path, and the ORDER between them is the whole
48
+ # meaning, because the identity is their join.
49
+ def identity_rows(node) = node.identity_paths.map { |path| { value: path } }
50
+
51
+ # Through to_h, which is where Bluebook.render_value spells a symbol argument as
52
+ # ":source". The raw with_spec lost the colon, and a binding that reads an
53
+ # argument became indistinguishable from one carrying a literal string.
54
+ def with_spec_rows(node) = pair_rows(node.to_h[:with_spec])
55
+
56
+ # A read model carries the same options an ask does, plus its filters — see
57
+ # option_rows.
58
+ def read_model_option_rows(node) = option_rows(node, filters: true)
59
+
60
+ # The canonical-form table is the expression grammar's, not this chapter's, so
61
+ # the node is not consulted at all.
62
+ def normalisation_table(_node) = normalisation_rows
63
+
64
+ # `lifecycle :status do transition "Retire" => "retired", from: ["issued", "active"] end`
65
+ # is ONE declaration and TWO transitions. Offering it once would leave the
66
+ # second unjudged, which is the whole failure this judge exists to avoid.
67
+ def transition_rows(node)
68
+ lifecycle = node.respond_to?(:lifecycle) ? node.lifecycle : nil
69
+ return [] unless lifecycle
70
+
71
+ lifecycle.transitions.flat_map do |command, transition|
72
+ froms = transition.constrained? ? Array(transition.from) : [nil]
73
+ froms.map do |from|
74
+ { command: command, from_state: from, to_state: transition.target }
75
+ end
76
+ end
77
+ end
78
+
79
+ # An OPEN MAP — a member's fields, a dispatch's argument bindings — has no
80
+ # value object that can hold it, so each entry becomes its own row. This is
81
+ # why Member and Dispatch are roots in the language rather than lists.
82
+ def pair_rows(map)
83
+ Array(map&.to_h).map { |key, value| { key: key, value: value } }
84
+ end
85
+
86
+ # EVERY SPECIFICATION OPTION AN ASK CARRIES, flattened to rows.
87
+ #
88
+ # `offset`, `cursor`, `nulls`, `authorize` and `inspect_query` are
89
+ # five options, one compound (authorize names a policy AND a
90
+ # tenant). `extra_options_to_h` already spells every one of them
91
+ # and drops the absent ones, so this reads that rather than
92
+ # naming them here — a sixth option needs no change on either
93
+ # side.
94
+ #
95
+ # `filters: true` adds a read model's wheres, order_by and limit —
96
+ # `at` tells repeated rows apart, so two wheres do not collapse.
97
+ #
98
+ # THE LANGUAGE MAY HOLD MORE THAN `to_h` CARRIES, and this is where that
99
+ # mattered. Until 2026-08-11, `ReadModel#to_h` omitted all three —
100
+ # `extra_options_to_h` rejects them by name, still does — so a read
101
+ # model's filtering had never been in the wire contract, and I first
102
+ # read that as a wall: if the wire cannot carry it, the language cannot
103
+ # hold it, and a graph assembled from the language must lose it.
104
+ #
105
+ # That was the wrong conclusion. `to_h` is a PROJECTION ; the language
106
+ # is the SOURCE. They have to agree about everything
107
+ # to_h spells, not about everything the language knows. Held as option
108
+ # rows, the filters survived the round trip regardless of whether the
109
+ # wire carried them too — which is exactly why, when a LATER task
110
+ # (Rust read-model codegen) needed `wheres`/`order_by`/`limit` on the
111
+ # wire for an unrelated reason, `ReadModel#to_h` could be extended to
112
+ # spell them (the same mechanism `Query#to_h` already used) without
113
+ # touching this method at all: this reads `node.wheres`/`node.
114
+ # order_by`/`node.limit` off the live object directly below
115
+ # (`filter_options`), never off `to_h`, so the wire format moving did
116
+ # not move this.
117
+ #
118
+ # Named `wheres`, `order_by` and `limit` so they gather back into exactly the
119
+ # declaration keys the assembly already reads.
120
+ def option_rows(node, filters: false)
121
+ return [] unless node.respond_to?(:extra_options_to_h)
122
+
123
+ spelled = node.extra_options_to_h
124
+ spelled = filter_options(node).merge(spelled) if filters
125
+
126
+ spelled.flat_map do |option, held|
127
+ case held
128
+ when Array then held.each_with_index.flat_map { |one, at| parts(option, one, at) }
129
+ else parts(option, held, nil)
130
+ end
131
+ end
132
+ end
133
+
134
+ def filter_options(node)
135
+ {
136
+ wheres: Array(node.wheres).map(&:to_h),
137
+ order_by: node.order_by&.to_h,
138
+ limit: node.limit&.to_h
139
+ }.reject { |_, held| held.nil? || held == [] }
140
+ end
141
+
142
+ def parts(option, held, at)
143
+ Hash(held).map do |key, value|
144
+ { option: option.to_s, key: key.to_s, value: value, at: at&.to_s }
145
+ end
146
+ end
147
+
148
+ # A mutation is ONE declaration, but the language's Change holds a single
149
+ # field/kind/source triple — and an append binds SEVERAL fields at once
150
+ # (`append: { name: :name, amount: :amount }`). So an append is offered
151
+ # once per binding, and each one is judged.
152
+ #
153
+ # The judge used to send `field: v(""), kind: v("argument"), source: v("")`
154
+ # here — three stubbed values, so every rule about what a mutation reads
155
+ # was being handed a blank and could never refuse.
156
+ def mutation_rows(node)
157
+ Array(node.mutations).flat_map do |mutation|
158
+ # `:delegate` (CommandBuilder#delegates_to's own comment) rides
159
+ # the SAME multi-binding shape `:append` does — `with: {...}`
160
+ # is a field map, same as append's own `fields:`.
161
+ next set_row(mutation) unless [:append, :delegate].include?(mutation.op)
162
+
163
+ mutation.source.map do |field, argument|
164
+ # Spelled the way Mutation#appended_fields spells it, because
165
+ # Assembly::Marks reads this row back through the same reader it
166
+ # reads that field with. `then_set :marks, append: { direction:
167
+ # "out" }` binds a LITERAL, and storing it raw made it
168
+ # indistinguishable from an argument called out.
169
+ { target: mutation.target, op: mutation.op, field: field,
170
+ kind: argument.is_a?(Symbol) ? "argument" : "literal",
171
+ source: Literal.render(argument) }
172
+ end
173
+ end
174
+ end
175
+
176
+ # A set/increment/decrement reads one thing: a command argument, or a
177
+ # literal written into the bluebook.
178
+ def set_row(mutation)
179
+ classified = mutation.to_h[:source] || {}
180
+
181
+ [{ target: mutation.target, op: mutation.op, field: mutation.target,
182
+ kind: classified[:kind],
183
+ source: classified[:name] || encode_literal(classified[:value]) }]
184
+ end
185
+
186
+ # The normalisation table belongs to the expression grammar, not to any one
187
+ # bluebook — it is how the canonical form of a rule is spelled. The language
188
+ # models it because a bluebook's rules are canonicalised on the way in.
189
+ def normalisation_rows
190
+ table = Expression::CanonicalForm.table
191
+ return [] unless table
192
+
193
+ table.map do |entry|
194
+ {
195
+ strategy: entry[:strategy],
196
+ source_token: entry[:source_token],
197
+ replacement: entry[:replacement],
198
+ boundary: entry[:boundary],
199
+ position: entry[:position]
200
+ }
201
+ end
202
+ rescue StandardError
203
+ # The table is a convenience of the Ruby side ; a bluebook that cannot
204
+ # produce one is not malformed.
205
+ []
206
+ end
207
+
208
+ # What the BLUEBOOK calls a node, whichever kind of thing the node is.
209
+ #
210
+ # This used to sniff — `respond_to?(:hecks_name) ? … : node.name` — because
211
+ # only value objects had crossed over. Every construct answers now, so
212
+ # there is nothing to choose between. It disappears entirely when the DSL
213
+ # stops handing the judge nodes at all.
214
+ def declared_name(node) = node.hecks_name
215
+
216
+ # One field of a Declare payload. Mostly a reader of the same name — the
217
+ # exceptions are fields the IR keeps somewhere else, or not at all.
218
+ # READ FROM THE TABLE, not from a branch per category.
219
+ #
220
+ # These were eight hand-written cases — `Entity.owner`, `Member.shape`, two
221
+ # lifecycle members twice over, and three of a query's — each one restating
222
+ # something `Assembly::Contracts` already declares. A parent pointer is
223
+ # `:parent` there ; a folded field names the object and member it lives in.
224
+ # So the exceptions are looked up rather than repeated, and a new fold is one
225
+ # line in one file instead of two lines in two.
226
+ def field_value(category, node, field, parent_id)
227
+ return declared_name(node) if field == :name
228
+
229
+ contract = Assembly.contract(category)
230
+ # A setter names its target as a STRING and a Declare field arrives a Symbol,
231
+ # so the lookup keys on a Symbol either way. The case statement this replaced
232
+ # was type-blind because it interpolated ; a Hash is not.
233
+ named = field.to_sym
234
+ return parent_id if contract.kind_of(named) == :parent
235
+
236
+ object, member = contract.folded(named)
237
+ return through(node, object, member) if member
238
+
239
+ # `limit` is a language field AND an object in the IR — `Array(an_object)`
240
+ # wraps rather than destructures, so offering it stored
241
+ # "#<struct LimitSpec value=3>".
242
+ return node.limit&.to_h&.fetch(:value, nil) if "#{category}.#{field}" == "Query.limit"
243
+
244
+ # `provenance from: {...}` is a HASH offered into a text field, and
245
+ # handing it over raw let the runtime's own coercion spell it — which
246
+ # meant Ruby's `Hash#to_s`, whose spelling changed under us between
247
+ # 3.3 and 3.4. Encoded here, the same way `default:` already is and the
248
+ # same way Shapes#provenance reads it back.
249
+ return encode_literal(node.provenance) if field == :provenance
250
+
251
+ # `identified_by` is no longer a FIELD of any declaration — it is a list,
252
+ # filled by Identify one part at a time, so it is read through `identity_rows`
253
+ # like every other list rather than special-cased here. What this branch
254
+ # existed to protect is now structural : a path cannot come back as its head,
255
+ # because there is nowhere left that holds only a head.
256
+
257
+ node.respond_to?(field) ? node.public_send(field) : nil
258
+ end
259
+
260
+ # One member of the object a field folds into. `to_h` first, because the
261
+ # member names are the ones the IR SPELLS — a Lifecycle's `default`, an
262
+ # OrderBy's `direction` — and reading the object raw is how a colon or a type
263
+ # goes missing.
264
+ def through(node, object, member)
265
+ held = node.respond_to?(object) ? node.public_send(object) : nil
266
+ return nil unless held
267
+
268
+ member == :transitions ? held : held.to_h[member]
269
+ end
270
+
271
+ # What a setting command writes. A setter whose source is absent is not
272
+ # dispatched at all — ABSENT is not EMPTY, and offering "" would turn every
273
+ # "if you declare it, declare something" rule into "you must declare it".
274
+ def setter_value(category, node, target)
275
+ # `rows` folds into `closed_set` and `members` between them, with no single
276
+ # member to name, so it keeps its own reading — see Contract#folded.
277
+ return closed_set_size(node) if "#{category}.#{target}" == "ValueObject.rows"
278
+
279
+ object, member = Assembly.contract(category).folded(target.to_sym)
280
+ return through(node, object, member) if member
281
+
282
+ node.respond_to?(target) ? node.public_send(target) : nil
283
+ end
284
+
285
+ # Only a DECLARED closed set has a row count. An empty one is the defect,
286
+ # so `rows` must stay absent rather than arrive as zero.
287
+ def closed_set_size(node)
288
+ return nil unless node.respond_to?(:closed_set?) && node.closed_set?
289
+
290
+ Array(node.members).size
291
+ end
292
+
293
+ # `Reference<Customer>` is an IR ENCODING, not a domain fact. The fact is
294
+ # that the attribute points at Customer's head — so the language is offered
295
+ # that head's ID, and resolution does the rest. Encoding and decoding both
296
+ # live here, because this is where the IR's shape differs from the
297
+ # language's and nowhere else should know the spelling.
298
+ def points_at(row, aggregate_id)
299
+ return nil unless row.reference?
300
+
301
+ # THE CHAPTER THIS HEAD IS IN, AND THE HEAD IT POINTS AT — which is exactly
302
+ # how an aggregate is identified, so it is built the same way rather than
303
+ # spelled again with a separator of its own. This is dispatched as a REAL
304
+ # REFERENCE VALUE (`Aggregate.Reference`'s `points_at:`), resolved by
305
+ # `repository.find` against the target Aggregate-within-Meta record's OWN
306
+ # stored id — so it MUST equal what that record's identity actually
307
+ # derives, not a wire-format spelling. `reference_type`, below, is the
308
+ # separate later reader that un-derives it back into "Reference<X>".
309
+ Naming.identity([aggregate_id.split(Naming::IDENTITY_JOIN).first, row.type.target_name])
310
+ end
311
+
312
+ # A LITERAL, written so it can be read back exactly.
313
+ #
314
+ # The language holds a default and a literal mutation source as text, and
315
+ # `to_s` threw the type away: 0.0 came back "0.0", and `{ value: "good" }`
316
+ # came back its inspect string with nowhere to say it had been a hash. The
317
+ # language already stores code as text — `canonical: "cents >= 0"` — so an
318
+ # encoding is in keeping; it simply has to be SELF-DESCRIBING. That rule is
319
+ # now Hecks::Literal's, stated once and shared with every other
320
+ # to_h-bound literal field ; Shapes#decode_literal reads it back.
321
+ #
322
+ # nil stays nil rather than becoming "nil": absent is a real answer here,
323
+ # and the language's own field is optional.
324
+ def encode_literal(value) = value.nil? ? nil : Literal.render(value)
325
+
326
+ # The way back out: an aggregate id becomes the type the IR spells. The
327
+ # id is a JOIN of chapter + name (Naming::IDENTITY_JOIN, the same join
328
+ # `points_at` built it with, not the "::" a real bluebook's own type
329
+ # names never carry) ; the wire format wants only the bare name.
330
+ def reference_type(points_at_id) = "Reference<#{points_at_id.to_s.split(Naming::IDENTITY_JOIN).last}>"
331
+
332
+ # One value out of a row, named by the value object's field.
333
+ def row_value(row, field)
334
+ # A Hash FIRST. Hash answers to `key` (Hash#key(value)) and to `value` on
335
+ # some rows, so asking respond_to? before checking for a Hash reads a
336
+ # member pair through entirely the wrong method.
337
+ return row[field] if row.is_a?(Hash)
338
+ return row.public_send(field) if row.respond_to?(field)
339
+ # A Struct answers to [] but RAISES for a member it does not have, so it
340
+ # is read through to_h — a field the row simply lacks reads as absent.
341
+ return row.to_h[field] if row.respond_to?(:to_h) && !row.is_a?(String)
342
+
343
+ # A bare scalar row — `emits` is a list of event NAMES, and the
344
+ # Announcement value object has to call that string something.
345
+ row
346
+ end
347
+ end
348
+ end
349
+ end
350
+ end
@@ -0,0 +1,312 @@
1
+ module Hecks
2
+ module Bluebook
3
+ module MetaValidator
4
+ # A bluebook rebuilt FROM the meta-domain, in the shape the builder produces.
5
+ #
6
+ # This is the second half of the claim at the top of bluebook.bluebook —
7
+ # "loading a domain becomes dispatching commands into this meta-domain ; the
8
+ # IR it stores must equal the IR the DSL builder produces". The judge is the
9
+ # first half. This reads the records back and assembles `to_h`.
10
+ #
11
+ # It is the INVERSE OF THE WALK and shares its plan: the walk reads a node's
12
+ # lists through the command that appends to each, and this reads them back out
13
+ # of the rows those commands wrote. The retired `experiment/replay.rb` needed
14
+ # 420 hand-written lines because the dispatch half was hand-written too; there
15
+ # is nothing to hand-write when both directions are one table.
16
+ #
17
+ # This file is the TRAVERSAL. The hashes at its tips, and the encodings they
18
+ # undo, are in Shapes.
19
+ #
20
+ # IT READS LEVEL BY LEVEL, THROUGH `DeclaredIn`, AND NOT THROUGH THE READ
21
+ # MODEL — which is the difference between a reconstruction that can be the
22
+ # SOURCE and one that can only be a check.
23
+ #
24
+ # `Meta.whole_bluebook` gathers a chapter in a single read, and it sorts:
25
+ # `ReadModelInterpreter#matching` ends `.sort_by(&:id)` deliberately, because
26
+ # a store's iteration order is an accident, and a read model returning store
27
+ # order would let that accident leak into an answer.
28
+ # Right for a read model, fatal here — the order a bluebook declares its
29
+ # commands in is a FACT ABOUT THE SOURCE, and the IR is a contract field for
30
+ # field and index for index ; the export carries that order verbatim.
31
+ # `DeclaredIn` preserves it (spec/executes_spec says so), so this asks each
32
+ # level for its own children rather than filtering one sorted gather.
33
+ #
34
+ # The parent key of every level is read from the language's own plan, the same
35
+ # Plan the walk dispatches from — so the two directions really are one table,
36
+ # which is what the header above has always claimed.
37
+ #
38
+ # WHAT IT CANNOT REBUILD matters as much as what it can, and
39
+ # spec/round_trip_spec pins the difference as an exact set: a field the language
40
+ # does not hold appears there as a named gap, and a field it stops holding
41
+ # appears as a failure.
42
+ class Reconstruction
43
+ include Readings
44
+ include Shapes
45
+
46
+ def self.of(runtime, chapter) = new(runtime, chapter).to_h
47
+
48
+ def initialize(runtime, chapter)
49
+ @runtime = runtime
50
+ @plan = Plan.for(MetaValidator.grammar_registry)
51
+ @chapter = runtime.query("Bluebook::Bluebook.Called", name: { value: chapter }).first or
52
+ raise NotFound, "the meta-domain holds no bluebook called #{chapter.inspect}"
53
+ end
54
+
55
+ def to_h
56
+ {
57
+ name: text(@chapter[:name]),
58
+ version: text(@chapter[:version]),
59
+ vision: text(@chapter[:vision]),
60
+ classification: text(@chapter[:classification]),
61
+ formerly_known_as: text(@chapter[:formerly_known_as]),
62
+ attaches_to: attached_contexts(@chapter),
63
+ aggregates: declared("Aggregate", chapter_id).map { |row| aggregate(row) },
64
+ read_models: declared("ReadModel", chapter_id).map { |row| read_model(row) },
65
+ policies: declared("Policy", chapter_id).map { |row| policy(row) },
66
+ process_managers: declared("ProcessManager", chapter_id).map { |row| process_manager(row) }
67
+ }
68
+ end
69
+
70
+ private
71
+
72
+ def chapter_id = @chapter[:id].to_s
73
+
74
+ # Everything DECLARED IN one parent, in the order it was declared. The key
75
+ # is the one the language's own creating command carries, read from Plan.
76
+ def declared(category, parent_id)
77
+ key = @plan.category(category).parent_key
78
+ @runtime.query("Bluebook::#{category}.DeclaredIn", key.to_sym => { value: parent_id.to_s })
79
+ end
80
+
81
+ # ONE DECLARATION, BUILT FROM THE CONTRACT.
82
+ #
83
+ # There were eleven methods here, one per category, each spelling out the same
84
+ # keys `Assembly::Contracts` already names — which is the shape the judge used
85
+ # to have and paid fourteen unoffered verbs for. The keys come from the table
86
+ # now, and the exceptions come from its `reads:` column: a list needs a reader
87
+ # per element, a folded field is gathered rather than fetched, and everything
88
+ # else is one cell.
89
+ #
90
+ # `extra` is what the containment supplies — children, and the folded objects
91
+ # a row cannot hold on its own.
92
+ def declaration(category, row, extra = {})
93
+ contract = Assembly.contract(category)
94
+
95
+ contract.fields.each_with_object({}) do |(_keyword, (key, _build)), out|
96
+ next if extra.key?(key)
97
+
98
+ out[key] = read_row(contract.reader(key), key, row)
99
+ end.merge(extra)
100
+ end
101
+
102
+ def read_row(spec, key, row)
103
+ case spec
104
+ when nil then text(row[key])
105
+ when :symbol then text(row[key])&.to_sym
106
+ when :names then Array(row[key]).map { |held| text(held[:name]) }
107
+ when Array then read_shaped(spec, key, row)
108
+ else send(spec, row)
109
+ end
110
+ end
111
+
112
+ # The readers are the ones `Shapes` already has, called by name — no wrappers,
113
+ # so nothing shadows them. `:each_with_id` is the single exception that needs
114
+ # more than the element: an attribute's type came in as the ID of whatever it
115
+ # names, so reading it back needs the row it belongs to.
116
+ def read_shaped(spec, key, row)
117
+ shape, named = spec
118
+
119
+ case shape
120
+ when :each then Array(row[key]).map { |held| send(named, held) }
121
+ when :each_with_id then Array(row[key]).map { |held| send(named, held, row[:id]) }
122
+ when :call then send(named, row)
123
+ when :from then pairs(row[named])
124
+ end
125
+ end
126
+
127
+ def pairs(with) = Array(with).map { |binding| [text(binding[:key]), text(binding[:value])] }
128
+
129
+ # THE PARTS, IN THE ORDER THEY WENT IN, because the identity is their join
130
+ # and a join read out of order names a different record.
131
+ def identity_paths(row) = Array(row[:identified_by]).map { |part| text(part[:value]).to_s }
132
+
133
+ # THE CONTEXTS ONE CHAPTER NAMES ITSELF ONTO, in the order they were
134
+ # attached — same shape identity_paths reads back, one level up.
135
+ def attached_contexts(row) = Array(row[:attaches_to]).map { |part| text(part[:value]).to_s }
136
+
137
+ # Every cell of the meta-domain is a single-field value object, so a row
138
+ # arrives holding Values rather than Strings.
139
+ def text(cell)
140
+ return nil if cell.nil?
141
+ return cell.to_h.values.first if cell.respond_to?(:to_h) && !cell.is_a?(String)
142
+
143
+ cell
144
+ end
145
+
146
+ # An aggregate's OWN verbs and asks — the ones no entity declared. Both carry
147
+ # the parent link either way, because that is the head the reference resolves
148
+ # against, so the entity ones have to be told apart by `entity_id`. Rejecting
149
+ # from an ORDERED read keeps the order.
150
+ def own(category, aggregate_id)
151
+ declared(category, aggregate_id).reject { |row| text(row[:entity_id]).to_s != "" }
152
+ end
153
+
154
+ # A piece's own verbs and asks. There is no `DeclaredIn` keyed by entity, so
155
+ # this reads the aggregate's — in declaration order — and keeps the ones that
156
+ # name this piece. `row[:aggregate]` — an ENTITY row's own `reference_to
157
+ # Aggregate` (bare, no `_id` since ADR 0025) — not `entity_id`, which
158
+ # STAYS suffixed below : that one is Command/Query's own EXPLICIT `as:`,
159
+ # never touched by the rename.
160
+ def within(category, row)
161
+ declared(category, text(row[:aggregate]))
162
+ .select { |held| text(held[:entity_id]).to_s == row[:id].to_s }
163
+ end
164
+
165
+ def aggregate(row)
166
+ id = row[:id]
167
+
168
+ {
169
+ name: text(row[:name]),
170
+ description: text(row[:description]),
171
+ identified_by: identity_paths(row),
172
+ attributes: Array(row[:attributes]).map { |field| attribute(field, id) },
173
+ value_objects: declared("ValueObject", id).map { |shape| value_object(shape) },
174
+ commands: own("Command", id).map { |verb| command(verb) },
175
+ # THE AGGREGATE BOUNDARY, and the precondition a command may
176
+ # reference by name (S10, ADR 0025 — "Rules") — the same
177
+ # `rule` reader `command`'s own givens/ensures already use
178
+ # (Assembly::Contracts' generic `declaration()` path), read
179
+ # here by hand because `aggregate(row)` itself is hand-typed,
180
+ # unlike `command`/`value_object`/`query` below.
181
+ invariants: Array(row[:invariants]).map { |held| rule(held) },
182
+ preconditions: Array(row[:preconditions]).map { |held| rule(held) },
183
+ # S12, ADR 0025 — same reason invariants/preconditions are
184
+ # read by hand two lines up: `aggregate(row)` is hand-typed.
185
+ projected_fields: Array(row[:projected_fields]).map { |held| projected_field(held) },
186
+ lifecycle: lifecycle(row),
187
+ entities: direct_entities(id, id).map { |piece| entity(piece) },
188
+ queries: own("Query", id).map { |ask| query(ask) },
189
+ provenance: provenance(row)
190
+ }
191
+ end
192
+
193
+ def value_object(row) = declaration("ValueObject", row)
194
+
195
+ def closed_set_of(row) = !text(row[:rows]).nil?
196
+
197
+ # A closed set's admitted rows. S17, ADR 0026 — Member is a genuine
198
+ # ENTITY now (`entity "Member" do ... end`, nested under
199
+ # ValueObject), created by `ValueObject.Member` (an ordinary append,
200
+ # the same way `Account.LogEntry` creates a LedgerEntry) and mutated
201
+ # by its own dotted `ValueObject.Member.Pair`. Its data therefore
202
+ # lives INLINE on the value object's own dispatched state — same as
203
+ # any other entity list — not behind a separate `DeclaredIn` query:
204
+ # there is no such query any more, because there is no top-level
205
+ # "Member" aggregate left to hold one. Pairs are still an OPEN MAP,
206
+ # which no value object can hold, so they still come back one pair
207
+ # at a time.
208
+ def members_row(row) = members_of(row)
209
+
210
+ def members_of(value_object_row)
211
+ Array(value_object_row[:members]).map do |member|
212
+ Array(member[:pairs]).map { |pair| [text(pair[:key]).to_s, text(pair[:value]).to_s] }
213
+ end
214
+ end
215
+
216
+ def command(row) = declaration("Command", row)
217
+
218
+ def query(row) = declaration("Query", row).merge(options_of(row))
219
+
220
+ # EVERY DIRECT ENTITY OF ONE OWNER — S17, ADR 0026. `declared
221
+ # ("Entity", root_id)` returns EVERY entity sharing the same
222
+ # ROOT aggregate, nested or not (Dispatch and Handler both carry
223
+ # `aggregate: process_manager_id`) — `owner` is the field that
224
+ # actually tells them apart : `Judge#nest_entities`'s own
225
+ # comment explains why it is the one to repurpose. Called with
226
+ # `owner_id == root_id` for an aggregate's own direct entities
227
+ # (Handler), and with `owner_id == some entity's own id` for
228
+ # THAT entity's own nested ones (Dispatch, owned by Handler).
229
+ def direct_entities(root_id, owner_id)
230
+ declared("Entity", root_id).select { |held| text(held[:owner]).to_s == owner_id.to_s }
231
+ end
232
+
233
+ def entity(row)
234
+ {
235
+ name: text(row[:name]),
236
+ description: text(row[:description]),
237
+ # The same shape as an aggregate's now. It was a String here and a
238
+ # Symbol there — the IR was not uniform about it, and only a round trip
239
+ # ever said so.
240
+ identified_by: identity_paths(row),
241
+ attributes: Array(row[:attributes]).map { |field| shape_field(field) },
242
+ # ADR 0028 — the SAME shape `aggregate(row)`'s own
243
+ # `preconditions:` reads two hand-typed methods up, read by
244
+ # hand for the identical reason: `entity(row)` is hand-typed
245
+ # too, unlike `command`/`value_object`/`query`.
246
+ preconditions: Array(row[:preconditions]).map { |held| rule(held) },
247
+ # Round 7 — the SAME shape `aggregate(row)`'s own
248
+ # `invariants:` reads, one level down: a piece's own shape
249
+ # rule, checked against every instance of this piece.
250
+ invariants: Array(row[:invariants]).map { |held| rule(held) },
251
+ commands: within("Command", row).map { |verb| command(verb) },
252
+ queries: within("Query", row).map { |ask| query(ask) },
253
+ # S17, ADR 0026 — Dispatch, inside Handler : an entity's own
254
+ # NESTED entities, found the same way its own direct ones
255
+ # were one level up.
256
+ entities: direct_entities(text(row[:aggregate]), row[:id]).map { |piece| entity(piece) },
257
+ # An entity has its own state machine, and the language has held it all
258
+ # along — Entity.Lifecycle and Entity.Transition were two of the fourteen
259
+ # verbs that started firing when the judge became a walk. Only the
260
+ # reconstruction had never asked for them.
261
+ lifecycle: lifecycle(row)
262
+ }
263
+ end
264
+
265
+ # Assembled from three fields, because the IR keeps one object where the
266
+ # language keeps the parts — the same difference Readings holds for the walk,
267
+ # in the other direction.
268
+ def lifecycle(row)
269
+ field = text(row[:state_field])
270
+ return nil if field.to_s.empty?
271
+
272
+ {
273
+ field: field,
274
+ default: text(row[:state_start]),
275
+ transitions: Array(row[:transitions]).map { |move| transition(move) }
276
+ }
277
+ end
278
+
279
+ def policy(row) = declaration("Policy", row)
280
+
281
+ # S17, ADR 0026 — Handler is a genuine entity now, nested under
282
+ # ProcessManager, created by `ProcessManager.Handler` (an
283
+ # ordinary append, the same way `Account.LogEntry` creates a
284
+ # LedgerEntry) and mutated by its own dotted `ProcessManager.
285
+ # Handler.Dispatch`/`...Dispatch.Bind`. Its data therefore lives
286
+ # INLINE on the process manager's own dispatched state — same as
287
+ # any other entity list — not behind a separate `DeclaredIn`
288
+ # query : there is no such query any more, the same fix
289
+ # `Reconstruction#members_of` already made for Member.
290
+ def process_manager(row)
291
+ declaration("ProcessManager", row,
292
+ handlers: Array(row[:handlers]).map { |leg| handler(leg) })
293
+ end
294
+
295
+ # S17, ADR 0026 — Dispatch, one level further in : nested under
296
+ # Handler, its data lives inline on the HANDLER row this method
297
+ # was just handed (`row` here IS one element of `handlers`,
298
+ # above), not behind any query either.
299
+ def handler(row)
300
+ declaration("Handler", row,
301
+ dispatches: Array(row[:dispatches]).map { |leg| dispatch(leg) })
302
+ end
303
+
304
+ def dispatch(row) = declaration("Dispatch", row)
305
+
306
+ def read_model(row)
307
+ declaration("ReadModel", row).merge(query_name: text(row[:query_name])).merge(options_of(row))
308
+ end
309
+ end
310
+ end
311
+ end
312
+ end