hecks 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. checksums.yaml +5 -5
  2. data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +127 -0
  4. data/lib/hecks/adapters/driven/d1.adapter +12 -0
  5. data/lib/hecks/adapters/driven/d1.rb +427 -0
  6. data/lib/hecks/adapters/driven/folder.adapter +3 -0
  7. data/lib/hecks/adapters/driven/folder.rb +199 -0
  8. data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
  9. data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
  10. data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
  11. data/lib/hecks/adapters/driven/governance_authorization.rb +101 -0
  12. data/lib/hecks/adapters/driven/heki/journal.rb +56 -0
  13. data/lib/hecks/adapters/driven/heki/saga_store.rb +99 -0
  14. data/lib/hecks/adapters/driven/heki/snapshot.rb +65 -0
  15. data/lib/hecks/adapters/driven/heki.adapter +4 -0
  16. data/lib/hecks/adapters/driven/heki.rb +179 -0
  17. data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
  18. data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
  19. data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
  20. data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
  21. data/lib/hecks/adapters/driven/lambda.adapter +4 -0
  22. data/lib/hecks/adapters/driven/lambda.rb +145 -0
  23. data/lib/hecks/adapters/driven/memory.adapter +3 -0
  24. data/lib/hecks/adapters/driven/memory.rb +103 -0
  25. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
  26. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
  27. data/lib/hecks/adapters/driven/postgres/codec.rb +88 -0
  28. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +207 -0
  29. data/lib/hecks/adapters/driven/postgres.adapter +5 -0
  30. data/lib/hecks/adapters/driven/postgres.rb +438 -0
  31. data/lib/hecks/adapters/driven/postgres_era.adapter +17 -0
  32. data/lib/hecks/adapters/driven/prism.adapter +3 -0
  33. data/lib/hecks/adapters/driven/prism.rb +80 -0
  34. data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
  35. data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
  36. data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
  37. data/lib/hecks/adapters/driven/sqlite/codec.rb +80 -0
  38. data/lib/hecks/adapters/driven/sqlite/projection.rb +172 -0
  39. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +192 -0
  40. data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
  41. data/lib/hecks/adapters/driven/sqlite.rb +306 -0
  42. data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
  43. data/lib/hecks/adapters/driven/system_clock.rb +14 -0
  44. data/lib/hecks/adapters/driven.rb +54 -0
  45. data/lib/hecks/adapters.rb +6 -0
  46. data/lib/hecks/behaviors/dsl.rb +125 -0
  47. data/lib/hecks/behaviors/expectations.rb +340 -0
  48. data/lib/hecks/behaviors/ir.rb +31 -0
  49. data/lib/hecks/behaviors/rspec.rb +42 -0
  50. data/lib/hecks/behaviors/runner.rb +96 -0
  51. data/lib/hecks/behaviors.rb +25 -0
  52. data/lib/hecks/bluebook/aggregate.rb +108 -0
  53. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
  54. data/lib/hecks/bluebook/assembly/build.rb +48 -0
  55. data/lib/hecks/bluebook/assembly/contract.rb +112 -0
  56. data/lib/hecks/bluebook/assembly/contracts.rb +438 -0
  57. data/lib/hecks/bluebook/assembly/marks.rb +228 -0
  58. data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
  59. data/lib/hecks/bluebook/assembly.rb +91 -0
  60. data/lib/hecks/bluebook/attribute.rb +96 -0
  61. data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
  62. data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
  63. data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
  64. data/lib/hecks/bluebook/behaviour/command.rb +116 -0
  65. data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
  66. data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
  67. data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
  68. data/lib/hecks/bluebook/behaviour/lifecycle.rb +68 -0
  69. data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
  70. data/lib/hecks/bluebook/behaviour/process_manager.rb +51 -0
  71. data/lib/hecks/bluebook/behaviour/query.rb +10 -0
  72. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
  73. data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
  74. data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
  75. data/lib/hecks/bluebook/chapter.rb +78 -0
  76. data/lib/hecks/bluebook/command.rb +124 -0
  77. data/lib/hecks/bluebook/domain_port.rb +102 -0
  78. data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
  79. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +1018 -0
  80. data/lib/hecks/bluebook/dsl/attribute_collector.rb +348 -0
  81. data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
  82. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1087 -0
  83. data/lib/hecks/bluebook/dsl/command_builder.rb +714 -0
  84. data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
  85. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
  86. data/lib/hecks/bluebook/dsl/entity_builder.rb +430 -0
  87. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
  88. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
  89. data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
  90. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
  91. data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
  92. data/lib/hecks/bluebook/dsl/policy_builder.rb +135 -0
  93. data/lib/hecks/bluebook/dsl/port_builder.rb +39 -0
  94. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
  95. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +306 -0
  96. data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
  97. data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
  98. data/lib/hecks/bluebook/dsl/rule_reference.rb +173 -0
  99. data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
  100. data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
  101. data/lib/hecks/bluebook/dsl/word_gate.rb +228 -0
  102. data/lib/hecks/bluebook/dsl/world_builder.rb +117 -0
  103. data/lib/hecks/bluebook/dsl.rb +45 -0
  104. data/lib/hecks/bluebook/entity.rb +103 -0
  105. data/lib/hecks/bluebook/expression/canonical_form.rb +123 -0
  106. data/lib/hecks/bluebook/expression/evaluator.rb +304 -0
  107. data/lib/hecks/bluebook/expression/projection.json +218 -0
  108. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
  109. data/lib/hecks/bluebook/expression/resolver.rb +781 -0
  110. data/lib/hecks/bluebook/expression.rb +13 -0
  111. data/lib/hecks/bluebook/hexagon.rb +60 -0
  112. data/lib/hecks/bluebook/lifecycle.rb +42 -0
  113. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
  114. data/lib/hecks/bluebook/meta_validator/judge.rb +621 -0
  115. data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
  116. data/lib/hecks/bluebook/meta_validator/port_judge.rb +51 -0
  117. data/lib/hecks/bluebook/meta_validator/readings.rb +360 -0
  118. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +351 -0
  119. data/lib/hecks/bluebook/meta_validator/shapes.rb +266 -0
  120. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
  121. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
  122. data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
  123. data/lib/hecks/bluebook/meta_validator.rb +542 -0
  124. data/lib/hecks/bluebook/model_check.rb +445 -0
  125. data/lib/hecks/bluebook/pattern_subset.rb +184 -0
  126. data/lib/hecks/bluebook/policy.rb +41 -0
  127. data/lib/hecks/bluebook/process_manager.rb +128 -0
  128. data/lib/hecks/bluebook/project_discovery.rb +30 -0
  129. data/lib/hecks/bluebook/project_loader.rb +40 -0
  130. data/lib/hecks/bluebook/project_register.rb +82 -0
  131. data/lib/hecks/bluebook/query.rb +61 -0
  132. data/lib/hecks/bluebook/read_model.rb +109 -0
  133. data/lib/hecks/bluebook/reference.rb +74 -0
  134. data/lib/hecks/bluebook/smoke_test.rb +166 -0
  135. data/lib/hecks/bluebook/synthesizer.rb +95 -0
  136. data/lib/hecks/bluebook/translation.rb +92 -0
  137. data/lib/hecks/bluebook/value_object.rb +66 -0
  138. data/lib/hecks/bluebook.rb +74 -0
  139. data/lib/hecks/codemod.rb +342 -0
  140. data/lib/hecks/construct.rb +71 -0
  141. data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
  142. data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
  143. data/lib/hecks/deploy/oidc.json +18 -0
  144. data/lib/hecks/doc/reference.rb +410 -0
  145. data/lib/hecks/embryonaut_bluebook.rb +75 -0
  146. data/lib/hecks/facade/cli_door.rb +119 -0
  147. data/lib/hecks/facade/cli_runner.rb +190 -0
  148. data/lib/hecks/facade/command_request.rb +105 -0
  149. data/lib/hecks/facade/handle.rb +173 -0
  150. data/lib/hecks/facade/json_door.rb +166 -0
  151. data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
  152. data/lib/hecks/facade/surface/chapter.rb +107 -0
  153. data/lib/hecks/facade/surface.rb +48 -0
  154. data/lib/hecks/facade.rb +44 -0
  155. data/lib/hecks/forms/app.rb +341 -0
  156. data/lib/hecks/forms/command_form_renderer.rb +113 -0
  157. data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
  158. data/lib/hecks/forms/field_renderer.rb +177 -0
  159. data/lib/hecks/forms/field_shape.rb +232 -0
  160. data/lib/hecks/forms/html.rb +84 -0
  161. data/lib/hecks/forms/index_renderer.rb +35 -0
  162. data/lib/hecks/forms/page.rb +157 -0
  163. data/lib/hecks/forms/params.rb +161 -0
  164. data/lib/hecks/forms/port_argument.rb +46 -0
  165. data/lib/hecks/forms/query_form_renderer.rb +114 -0
  166. data/lib/hecks/forms/record_renderer.rb +119 -0
  167. data/lib/hecks/forms/record_table.rb +68 -0
  168. data/lib/hecks/forms/reference_options.rb +30 -0
  169. data/lib/hecks/forms/value_object_shape.rb +46 -0
  170. data/lib/hecks/forms.rb +54 -0
  171. data/lib/hecks/fqn.rb +94 -0
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
  175. data/lib/hecks/framework/bluebook/governance.bluebook +145 -0
  176. data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
  177. data/lib/hecks/framework/oidc.json +39 -0
  178. data/lib/hecks/framework.rb +90 -0
  179. data/lib/hecks/freezer.rb +67 -0
  180. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +527 -0
  181. data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
  182. data/lib/hecks/fuzzing/isolated_boot.rb +286 -0
  183. data/lib/hecks/fuzzing/properties.rb +1192 -0
  184. data/lib/hecks/fuzzing/replay.rb +668 -0
  185. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
  186. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +80 -0
  187. data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
  188. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +250 -0
  189. data/lib/hecks/fuzzing/sequence_generator.rb +131 -0
  190. data/lib/hecks/fuzzing/value_generator.rb +200 -0
  191. data/lib/hecks/fuzzing.rb +12 -0
  192. data/lib/hecks/grammar/evolve.rb +324 -0
  193. data/lib/hecks/grammar/expression.bluebook +420 -0
  194. data/lib/hecks/grammar/expression_operators.json +1648 -0
  195. data/lib/hecks/grammar/grammar.hecksagon +20 -0
  196. data/lib/hecks/grammar/oidc.json +69 -0
  197. data/lib/hecks/grammar/translation.bluebook +203 -0
  198. data/lib/hecks/grammar.rb +178 -0
  199. data/lib/hecks/ir.rb +126 -0
  200. data/lib/hecks/language/adapter.bluebook +116 -0
  201. data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
  202. data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
  203. data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
  204. data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
  205. data/lib/hecks/language/bluebook/command.bluebook +471 -0
  206. data/lib/hecks/language/bluebook/entity.bluebook +392 -0
  207. data/lib/hecks/language/bluebook/policy.bluebook +189 -0
  208. data/lib/hecks/language/bluebook/process_manager.bluebook +381 -0
  209. data/lib/hecks/language/bluebook/projection.bluebook +267 -0
  210. data/lib/hecks/language/bluebook/query.bluebook +245 -0
  211. data/lib/hecks/language/bluebook/shape.bluebook +292 -0
  212. data/lib/hecks/language/bluebook/syntax.bluebook +448 -0
  213. data/lib/hecks/language/bluebook/vocabulary.bluebook +379 -0
  214. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
  215. data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
  216. data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
  217. data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
  218. data/lib/hecks/language/oidc.json +333 -0
  219. data/lib/hecks/language/port.bluebook +120 -0
  220. data/lib/hecks/language/translation/translation.bluebook +110 -0
  221. data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
  222. data/lib/hecks/language/world/wiring.bluebook +62 -0
  223. data/lib/hecks/language/world/world.bluebook +84 -0
  224. data/lib/hecks/literal.rb +125 -0
  225. data/lib/hecks/naming.rb +174 -0
  226. data/lib/hecks/ports/access_control.port +9 -0
  227. data/lib/hecks/ports/access_control.rb +62 -0
  228. data/lib/hecks/ports/agent/answers.rb +104 -0
  229. data/lib/hecks/ports/agent.port +8 -0
  230. data/lib/hecks/ports/agent.rb +167 -0
  231. data/lib/hecks/ports/authentication.port +6 -0
  232. data/lib/hecks/ports/authentication.rb +50 -0
  233. data/lib/hecks/ports/authorization.port +7 -0
  234. data/lib/hecks/ports/authorization.rb +62 -0
  235. data/lib/hecks/ports/clock.port +5 -0
  236. data/lib/hecks/ports/clock.rb +62 -0
  237. data/lib/hecks/ports/extraction.port +5 -0
  238. data/lib/hecks/ports/extraction.rb +37 -0
  239. data/lib/hecks/ports/identity_assignment.port +5 -0
  240. data/lib/hecks/ports/identity_assignment.rb +45 -0
  241. data/lib/hecks/ports/identity_generation.port +5 -0
  242. data/lib/hecks/ports/identity_generation.rb +49 -0
  243. data/lib/hecks/ports/identity_resolution.port +5 -0
  244. data/lib/hecks/ports/identity_resolution.rb +40 -0
  245. data/lib/hecks/ports/loading.port +4 -0
  246. data/lib/hecks/ports/loading.rb +13 -0
  247. data/lib/hecks/ports/persistence/append_only.rb +138 -0
  248. data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
  249. data/lib/hecks/ports/persistence/execution.rb +23 -0
  250. data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
  251. data/lib/hecks/ports/persistence/plugin.rb +54 -0
  252. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +169 -0
  253. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +128 -0
  254. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +161 -0
  255. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +61 -0
  256. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +304 -0
  257. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +171 -0
  258. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +190 -0
  259. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +478 -0
  260. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +166 -0
  261. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +314 -0
  262. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +168 -0
  263. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +170 -0
  264. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +134 -0
  265. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +137 -0
  266. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +89 -0
  267. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +85 -0
  268. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
  269. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +132 -0
  270. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +74 -0
  271. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +758 -0
  272. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +120 -0
  273. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +31 -0
  274. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +46 -0
  275. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +102 -0
  276. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +46 -0
  277. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +70 -0
  278. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +72 -0
  279. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +120 -0
  280. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +183 -0
  281. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +41 -0
  282. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +28 -0
  283. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +30 -0
  284. data/lib/hecks/ports/persistence/plugins/era/translation.rb +11 -0
  285. data/lib/hecks/ports/persistence/plugins/era.rb +48 -0
  286. data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
  287. data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
  288. data/lib/hecks/ports/persistence.port +4 -0
  289. data/lib/hecks/ports/persistence.rb +39 -0
  290. data/lib/hecks/ports/projection.port +4 -0
  291. data/lib/hecks/ports/projection.rb +96 -0
  292. data/lib/hecks/ports/query/in_memory.rb +60 -0
  293. data/lib/hecks/ports/query/ordering.rb +41 -0
  294. data/lib/hecks/ports/query.rb +36 -0
  295. data/lib/hecks/ports.rb +27 -0
  296. data/lib/hecks/projections/diagrams.rb +642 -0
  297. data/lib/hecks/projections/ir.rb +18 -0
  298. data/lib/hecks/projections/model/deviations.rb +98 -0
  299. data/lib/hecks/projections/model.rb +145 -0
  300. data/lib/hecks/projections/oidc.rb +110 -0
  301. data/lib/hecks/projections/parser_table.rb +159 -0
  302. data/lib/hecks/projections/reference.rb +38 -0
  303. data/lib/hecks/projections/shape.rb +44 -0
  304. data/lib/hecks/projections/statements.rb +110 -0
  305. data/lib/hecks/projections/vocabulary.rb +100 -0
  306. data/lib/hecks/projections.rb +32 -0
  307. data/lib/hecks/projector/cli_projector.rb +446 -0
  308. data/lib/hecks/projector/docs_projector.rb +321 -0
  309. data/lib/hecks/projector/exporter.rb +158 -0
  310. data/lib/hecks/projector/ir_projector.rb +18 -0
  311. data/lib/hecks/projector/narrate_projector.rb +243 -0
  312. data/lib/hecks/projector/target.rb +97 -0
  313. data/lib/hecks/projector.rb +186 -0
  314. data/lib/hecks/query_ir.rb +411 -0
  315. data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
  316. data/lib/hecks/query_specification/common/comparators.rb +25 -0
  317. data/lib/hecks/query_specification/common/comparison.rb +174 -0
  318. data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
  319. data/lib/hecks/query_specification/common/dsl.rb +58 -0
  320. data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
  321. data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
  322. data/lib/hecks/query_specification/common/null_policy.rb +92 -0
  323. data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
  324. data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
  325. data/lib/hecks/query_specification/common/options.rb +32 -0
  326. data/lib/hecks/query_specification/common/order_by.rb +9 -0
  327. data/lib/hecks/query_specification/common/specification.rb +10 -0
  328. data/lib/hecks/query_specification/common/where_clause.rb +9 -0
  329. data/lib/hecks/query_specification/field_path.rb +107 -0
  330. data/lib/hecks/query_specification/hop_path.rb +132 -0
  331. data/lib/hecks/query_specification/read_model/specification.rb +18 -0
  332. data/lib/hecks/query_specification.rb +14 -0
  333. data/lib/hecks/rendering.rb +48 -0
  334. data/lib/hecks/router/namespace_installer.rb +157 -0
  335. data/lib/hecks/router.rb +70 -0
  336. data/lib/hecks/runtime/aggregate_lock.rb +45 -0
  337. data/lib/hecks/runtime/boot_gates.rb +41 -0
  338. data/lib/hecks/runtime/caller.rb +66 -0
  339. data/lib/hecks/runtime/capability_graph.rb +44 -0
  340. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +135 -0
  341. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +260 -0
  342. data/lib/hecks/runtime/command_interpreter.rb +497 -0
  343. data/lib/hecks/runtime/command_rules/admissibility.rb +357 -0
  344. data/lib/hecks/runtime/command_rules/arithmetic.rb +260 -0
  345. data/lib/hecks/runtime/command_rules/authorization.rb +65 -0
  346. data/lib/hecks/runtime/command_rules/emission.rb +34 -0
  347. data/lib/hecks/runtime/command_rules/references.rb +189 -0
  348. data/lib/hecks/runtime/command_rules.rb +28 -0
  349. data/lib/hecks/runtime/dependency_planning.rb +245 -0
  350. data/lib/hecks/runtime/dispatcher.rb +301 -0
  351. data/lib/hecks/runtime/entity_element.rb +253 -0
  352. data/lib/hecks/runtime/entity_interpreter.rb +299 -0
  353. data/lib/hecks/runtime/errors.rb +122 -0
  354. data/lib/hecks/runtime/event.rb +51 -0
  355. data/lib/hecks/runtime/identity.rb +156 -0
  356. data/lib/hecks/runtime/instance.rb +175 -0
  357. data/lib/hecks/runtime/interpreting.rb +90 -0
  358. data/lib/hecks/runtime/loader.rb +184 -0
  359. data/lib/hecks/runtime/policy_interpreter.rb +366 -0
  360. data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
  361. data/lib/hecks/runtime/query_interpreter.rb +286 -0
  362. data/lib/hecks/runtime/reaction_invocation.rb +253 -0
  363. data/lib/hecks/runtime/read_model_interpreter.rb +341 -0
  364. data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
  365. data/lib/hecks/runtime/reference_hop.rb +99 -0
  366. data/lib/hecks/runtime/refusal_wording.rb +126 -0
  367. data/lib/hecks/runtime/registry/saga_persistence.rb +142 -0
  368. data/lib/hecks/runtime/registry/verification.rb +246 -0
  369. data/lib/hecks/runtime/registry.rb +279 -0
  370. data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
  371. data/lib/hecks/runtime/routing.rb +96 -0
  372. data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
  373. data/lib/hecks/runtime/saga_interpreter.rb +503 -0
  374. data/lib/hecks/runtime/saga_pending_dispatch.rb +45 -0
  375. data/lib/hecks/runtime/tenant_check.rb +84 -0
  376. data/lib/hecks/runtime/tenant_scope.rb +55 -0
  377. data/lib/hecks/runtime/value/admission.rb +130 -0
  378. data/lib/hecks/runtime/value/coercion.rb +565 -0
  379. data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
  380. data/lib/hecks/runtime/value.rb +125 -0
  381. data/lib/hecks/runtime.rb +107 -0
  382. data/lib/hecks/storehouse.rb +632 -0
  383. data/lib/hecks/version.rb +16 -0
  384. data/lib/hecks/vocabulary.rb +218 -0
  385. data/lib/hecks.rb +123 -6
  386. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +90 -0
  387. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +128 -0
  388. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +160 -0
  389. metadata +412 -222
  390. data/bin/hecks +0 -7
  391. data/bin/hecks-package +0 -65
  392. data/bin/hecks_console +0 -12
  393. data/bin/hecks_serverless +0 -6
  394. data/lib/cli/build.rb +0 -14
  395. data/lib/cli/command_runner.rb +0 -28
  396. data/lib/cli/console.rb +0 -10
  397. data/lib/cli/generate.rb +0 -37
  398. data/lib/cli/hecks-cli.rb +0 -27
  399. data/lib/cli/test.rb +0 -57
  400. data/lib/console/commands.rb +0 -8
  401. data/lib/console/hecks-console.rb +0 -1
  402. data/lib/packager/README.md +0 -0
  403. data/lib/packager/app_runner.rb +0 -21
  404. data/lib/packager/args.rb +0 -26
  405. data/lib/packager/compatibility/fixnum.rb +0 -6
  406. data/lib/packager/hecks.rb +0 -39
  407. data/lib/packager/query_runner.rb +0 -21
  408. data/lib/packager/resources/Dockerfile +0 -11
  409. data/lib/packager/resources/app_binary +0 -7
  410. data/lib/packager/resources/bundle_config +0 -3
  411. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
  412. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
  413. data/lib/serverless/Domain +0 -32
  414. data/lib/serverless/cli.rb +0 -75
  415. data/lib/serverless/resources/command_name.js +0 -5
  416. data/lib/serverless/resources/environment.js +0 -7
  417. data/lib/serverless/resources/handler.js.tt +0 -28
  418. data/lib/serverless/resources/run_binary.js +0 -22
  419. data/lib/serverless/resources/serverless.yml +0 -20
@@ -0,0 +1,621 @@
1
+ module Hecks
2
+ module Bluebook
3
+ module MetaValidator
4
+ # Offers every declaration in a built bluebook to the meta-domain.
5
+ #
6
+ # This used to be one hand-written branch per category, and the cost of that
7
+ # shape was fourteen verbs the language declared and the judge never offered
8
+ # — among them `Command.Argument` and `ValueObject.Field`, so a command's own
9
+ # arguments and a value object's own fields were NEVER judged. Every rule
10
+ # hanging off them was decoration. Nothing went red, because a branch that
11
+ # does not exist cannot fail.
12
+ #
13
+ # So there are no branches. The judge WALKS: it reads the plan the language
14
+ # makes of itself (Plan), and for each node offers the creating command, then
15
+ # each list through the command that appends to it, then each child. A verb
16
+ # in the plan with no offer is now impossible — there is no branch left in
17
+ # which to forget one, and spec/judge_coverage_spec holds it to that.
18
+ #
19
+ # What is NOT uniform lives in Readings, and only where the IR's SHAPE
20
+ # differs from the language's. Naming differences do not appear at all: the
21
+ # language spells its fields as the IR spells them.
22
+ #
23
+ # Only the DISPATCH half of the round trip lives here. Reconstruction is the
24
+ # experiment's business ; judging does not need it.
25
+ class Judge
26
+ include Readings
27
+
28
+ # Children offered BEFORE the parent's own lists, IN THIS ORDER. An
29
+ # attribute's type is offered as the id of the thing it names, so the
30
+ # value objects have to exist before anything that can name one — an
31
+ # aggregate's own attributes, AND an entity's own (M13: an entity is its
32
+ # own root, repeating the aggregate's whole shape one level down, so its
33
+ # attributes resolve against the SAME value-object pool). ValueObject
34
+ # first, Entity second, so an entity's own attributes are never offered
35
+ # before the value objects they may reference exist — a self-hosting
36
+ # casualty found live: the meta-grammar's own Handler/Dispatch/Member/
37
+ # Keyword/Argument entities (S17, ADR 0026) failed reference resolution
38
+ # on their own plain value-object-typed attributes (`HandlerText`,
39
+ # `MemberPosition`, ...) the moment entity attributes started being
40
+ # checked at all, because `@plan.names`' own (incidental) declaration
41
+ # order happened to walk Entity first. The order is stated here, not
42
+ # left to whatever order the plan's own category table iterates in —
43
+ # see `detail_node`'s own use of this constant, below.
44
+ EAGER_CHILDREN = { "Aggregate" => %w[ValueObject Entity] }.freeze
45
+
46
+ # Categories an ENTITY declares as well as an aggregate. The IR reuses
47
+ # Command and Query for a piece's own commands and queries, so the
48
+ # language reuses Command and Query — and the plan cannot express a second
49
+ # parent, because a category's parent is derived from the one `*_id` argument
50
+ # its creating command carries. This says the other edge out loud.
51
+ WITHIN_ENTITY = %w[Command Query].freeze
52
+
53
+ attr_reader :refusals
54
+
55
+ # THE RECORDS SURVIVE THE VERDICT.
56
+ #
57
+ # Judging a bluebook and HOLDING one differ by exactly this: whether anyone
58
+ # keeps the runtime the declarations were dispatched into. Nobody did, so
59
+ # `spec/round_trip_spec` reached the records by `Judge.allocate` and four
60
+ # `instance_variable_set` calls. Reading the chapter back is the point now,
61
+ # not a curiosity, so the runtime is simply readable.
62
+ attr_reader :runtime
63
+
64
+ def initialize(bluebook)
65
+ @bluebook = bluebook
66
+ @refusals = []
67
+ @runtime = MetaValidator.fresh_runtime
68
+ @plan = Plan.for(MetaValidator.grammar_registry)
69
+ judge!
70
+ end
71
+
72
+ private
73
+
74
+ # ABSENT is not EMPTY. The rules read "if you declare it, declare
75
+ # something" — a description never given is legal. Passing "" for a nil
76
+ # turns every one of them into "you must declare it".
77
+ #
78
+ # An Integer stays an Integer : RowCount declares `attribute :value,
79
+ # Integer`, so stringifying a member count fails the type gate rather than
80
+ # feeding the rule it was meant to feed.
81
+ def v(text)
82
+ return nil if text.nil?
83
+ return { value: text } if text.is_a?(Integer)
84
+
85
+ { value: text.to_s }
86
+ end
87
+
88
+ # A REFERENCE IS AN ID, AND AN ID IS A SCALAR.
89
+ #
90
+ # Every other field goes to the meta-domain as a one-field value object,
91
+ # because that is what it is. A reference is not: it carries the id of a
92
+ # head, and wrapping an id in an object was the language saying `{value:
93
+ # "Banking::Customer"}` where it meant `"Banking::Customer"`. The plan
94
+ # answers which arguments those are, read from the language's own IR, so
95
+ # nothing here needs to know the names.
96
+ def carried(plan, verb, argument, value)
97
+ return v(value) unless plan && verb && plan.references?(verb, argument)
98
+
99
+ value
100
+ end
101
+
102
+ def args(pairs) = pairs.reject { |_, value| value.nil? }
103
+
104
+ def offer(label)
105
+ yield
106
+ rescue Runtime::GivenNotMet, Runtime::InvariantViolation,
107
+ Runtime::TypeMismatch, Runtime::NotFound => e
108
+ # NotFound is a VERDICT, not noise. An attribute's type is a reference to
109
+ # its value object, so "no ValueObject with id …" IS the rule `attributes
110
+ # must use value-object types` refusing.
111
+ @refusals << "#{label}: #{e.message}"
112
+ rescue Runtime::UnknownVerb
113
+ nil
114
+ end
115
+
116
+ def send_to(verb, label, to: nil, **payload)
117
+ offer(label) { @runtime.dispatch(verb, to: to, with: args(payload)) }
118
+ end
119
+
120
+ def judge!
121
+ declare_node("Bluebook", @bluebook, nil, 0)
122
+ detail_node("Bluebook", @bluebook, nil, 0)
123
+ end
124
+
125
+ # DECLARED BEFORE DETAILED, for every set of siblings.
126
+ #
127
+ # A node used to be offered whole — declared, then its lists, then its
128
+ # children — one sibling at a time. Which means an aggregate's attributes
129
+ # were offered before its later siblings existed, and an attribute that
130
+ # POINTS AT another aggregate could only resolve if that aggregate happened
131
+ # to be declared earlier in the file. Banking survives on luck: Customer is
132
+ # written above Account.
133
+ #
134
+ # So siblings are declared in one pass and detailed in a second. It is the
135
+ # same ordering the walk already used one level down — value objects before
136
+ # the attributes that name them — lifted to the level above, and it is what
137
+ # lets a reference be a REFERENCE rather than a string nobody can check.
138
+ def declare_node(category, node, parent_id, index, extra = {}, receiver: nil)
139
+ plan = @plan.category(category)
140
+ return unless plan
141
+
142
+ declare(plan, category, node, identify(category, parent_id, node, index), parent_id, index, extra)
143
+ end
144
+
145
+ def detail_node(category, node, parent_id, index, extra = {}, receiver: nil)
146
+ plan = @plan.category(category)
147
+ return unless plan
148
+
149
+ id = identify(category, parent_id, node, index)
150
+ # `extra` is the CUMULATIVE identity of every entity-owned
151
+ # ancestor above this node (Handler's own `event_type` AND
152
+ # ProcessManager's own `bluebook`/`name`, by the time Dispatch
153
+ # is reached — S17, ADR 0026's two-level chain). `identity`
154
+ # adds THIS node's own on top — computed for EVERY category,
155
+ # entity-owned or not, because a node need not be entity-owned
156
+ # itself to OWN one (ValueObject isn't, and Member still needs
157
+ # its `aggregate:`/`name:`) — it is simply what THIS node's own
158
+ # `identified_by` resolves to, the same fields `identify` two
159
+ # lines up already derives the joined id FROM.
160
+ #
161
+ # `own` is the SUBSET actually spent on a dispatch payload —
162
+ # only when THIS category is itself entity-owned, since an
163
+ # ordinary category (Command's own "Rule"/"Argument", offered
164
+ # through the SAME `extra` SLOT for a DIFFERENT reason, see
165
+ # `within_entity` below) locates the record it attaches to
166
+ # through the parent id `id:` already carries, and merging
167
+ # unrecognized `aggregate:`/`entity_id:` into THEIR payload
168
+ # would have the runtime refuse them for an argument they
169
+ # never declared.
170
+ identity = extra.merge(node_identity(plan, category, node, index, parent_id))
171
+ receiver ||= { aggregate: id, entities: [] }
172
+ eager, later = children_of(category).partition { |child| eager?(category, child) }
173
+ # ORDERED AS `EAGER_CHILDREN` DECLARES, not as `children_of` happens to
174
+ # list them — `children_of` reads `@plan.names`, whose own order is an
175
+ # accident of which .bluebook file registered which category first,
176
+ # never a promise about which of two eager children exists before the
177
+ # other. `EAGER_CHILDREN`'s own array IS that promise (ValueObject
178
+ # before Entity), so the walk keeps only what this parent actually
179
+ # has, in the order the constant states — see that constant's own
180
+ # comment for the bug this exact reordering fixes.
181
+ eager = Array(EAGER_CHILDREN[category]) & eager
182
+
183
+ eager.each { |child| walk_all(child, node, id, entity_child_extra(child, identity), receiver: receiver) }
184
+ setters(plan, category, node, receiver)
185
+ # BEFORE `appends`, not after — the same reason `EAGER_CHILDREN`
186
+ # walks an aggregate's OWN entities before its OWN attributes
187
+ # (M13): a piece nested inside a piece (Handler's own
188
+ # `dispatches, list_of(Dispatch)` — S17, ADR 0026) must exist
189
+ # before this piece's own attribute list can reference it as a
190
+ # HELD entity, the same way `Account#ledger` needs Account's own
191
+ # entities walked eagerly. `nest_entities` is a no-op for every
192
+ # category but "Entity" (its own early return), so reordering it
193
+ # ahead of `appends` costs nothing for anything else that walks
194
+ # through here.
195
+ nest_entities(category, node, id, parent_id)
196
+ appends(plan, category, node, receiver, parent_id)
197
+ later.each { |child| walk_all(child, node, id, entity_child_extra(child, identity), receiver: receiver) }
198
+ within_entity(category, node, id, parent_id)
199
+ sealers(plan, category, receiver)
200
+ end
201
+
202
+ # WHAT A CHILD'S OWN `extra` STARTS FROM. An entity-owned child's
203
+ # own dotted dispatch needs every ANCESTOR's identity, which is
204
+ # exactly `identity` — already accumulated one level at a time by
205
+ # `detail_node` itself (regardless of whether each ancestor is
206
+ # ITSELF entity-owned — ValueObject contributes its own `aggregate:
207
+ # `/`name:` to Member's payload despite being an ordinary top-
208
+ # level category), so there is nothing left to re-derive here. An
209
+ # ordinary child (one with a real top-level aggregate of its own
210
+ # to dispatch a bare verb into) needs none of it.
211
+ def entity_child_extra(child, identity)
212
+ @plan.category(child)&.entity_owned ? identity : {}
213
+ end
214
+
215
+ # ONE NODE'S OWN IDENTITY, read off its own declaration — S17,
216
+ # ADR 0026. Three cases, the same three `identify`/`identity_part`
217
+ # already resolve one level up, unified here because a chain now
218
+ # walks more than one level (Handler -> Dispatch) and each level
219
+ # needs the SAME three answered about itself, not just the first:
220
+ #
221
+ # the parent link (plan.parent_key) -> `parent_id`, the id
222
+ # the walk already carries in from one level up
223
+ # a walk-minted one (POSITION) -> the walk INDEX itself ;
224
+ # never a stored field (Member's own header:
225
+ # "position is not a mint — it is read straight
226
+ # out of the source file")
227
+ # a real field (anything else) -> `field_value`, same
228
+ # reader every other field in this file uses
229
+ # (Handler's own `event_type`, Dispatch's own
230
+ # `command_name`)
231
+ #
232
+ # `carried` still decides bare-vs-wrapped the normal way ; POSITION
233
+ # is the one case with no verb to ask `carried` about (`plan.
234
+ # declare` is always nil for an entity-owned category — Plan#read's
235
+ # own comment says why), so it is minted straight as a value object,
236
+ # matching exactly what `declare`'s own field loop already mints a
237
+ # POSITION field as.
238
+ def node_identity(plan, category, node, index, parent_id)
239
+ plan.identity_paths.each_with_object({}) do |path, fields|
240
+ head = path.to_s.split(".").first
241
+ next if head == OWNER
242
+
243
+ if head == POSITION
244
+ fields[head.to_sym] = v(index)
245
+ else
246
+ raw = head == plan.parent_key.to_s ? parent_id : field_value(category, node, head.to_sym, parent_id)
247
+ fields[head.to_sym] = carried(plan, plan.declare, head, raw)
248
+ end
249
+ end
250
+ end
251
+
252
+ # THE FULL DOTTED PREFIX a category's own verbs hang off — the
253
+ # plain name for an ordinary category (its own top-level
254
+ # aggregate reaches every verb bare), or its PARENT's own prefix
255
+ # with this category's name appended, for an entity-owned one.
256
+ # Dispatch's own parent, Handler, is itself entity-owned (S17,
257
+ # ADR 0026's two-level chain — `ProcessManager.Handler.Dispatch`),
258
+ # so this recurses rather than reading one level and stopping.
259
+ def dotted_prefix(plan)
260
+ return plan.name unless plan.entity_owned
261
+
262
+ "#{dotted_prefix(@plan.category(plan.parent))}.#{plan.name}"
263
+ end
264
+
265
+ # ENTITY-OWNED categories have no top-level aggregate for the runtime
266
+ # to route a bare verb into any more — `Member`'s own "Pair" reaches
267
+ # the runtime as `ValueObject.Member.Pair`, and a NESTED one
268
+ # (`Dispatch`, inside `Handler`) reaches it as `ProcessManager.
269
+ # Handler.Dispatch.Bind` — the dotted shape `EntityInterpreter#call`
270
+ # already splits any real entity's own verb into, one hop per
271
+ # segment (`walk_entity_chain`, entity_interpreter.rb).
272
+ def verb_for(plan, verb)
273
+ "#{dotted_prefix(plan)}.#{verb}"
274
+ end
275
+
276
+ def walk_all(category, node, parent_id, extra = {}, receiver: nil)
277
+ reader = collection_reader(category)
278
+ return unless node.respond_to?(reader)
279
+
280
+ children = Array(node.public_send(reader))
281
+ children.each_with_index { |child, index| declare_node(category, child, parent_id, index, extra) }
282
+ children.each_with_index do |child, index|
283
+ child_plan = @plan.category(category)
284
+ child_receiver = if child_plan&.entity_owned
285
+ child_id = identify(category, parent_id, child, index)
286
+ root = receiver || { aggregate: parent_id, entities: [] }
287
+ { aggregate: root[:aggregate], entities: Array(root[:entities]) + [child_id] }
288
+ end
289
+ detail_node(category, child, parent_id, index, extra, receiver: child_receiver)
290
+ end
291
+ end
292
+
293
+ # A piece's commands and queries, addressed under the PIECE so two commands
294
+ # of the same name on an aggregate and on one of its entities cannot collide,
295
+ # while `aggregate` still names the aggregate the reference resolves
296
+ # against and `entity_id` says which piece declared it. `entity_id`
297
+ # keeps its own `_id` — an EXPLICIT `as:` on `reference_to Entity`,
298
+ # never touched by ADR 0025's rename (only the DEFAULT, un-aliased
299
+ # mint dropped the suffix; `aggregate` did precisely because
300
+ # `Command#reference_to Aggregate`/`Query#reference_to Aggregate`
301
+ # carry no `as:` of their own).
302
+ def within_entity(category, node, id, aggregate)
303
+ return unless category == "Entity"
304
+
305
+ WITHIN_ENTITY.each do |child|
306
+ plan = @plan.category(child)
307
+ walk_all(child, node, id, {
308
+ aggregate: carried(plan, plan&.declare, "aggregate", aggregate),
309
+ entity_id: carried(plan, plan&.declare, "entity_id", id)
310
+ })
311
+ end
312
+ end
313
+
314
+ # AN ENTITY MAY NEST FURTHER ENTITIES — S17, ADR 0026's own words:
315
+ # "That is what `entity` is for, and `entity` is declared by the
316
+ # language and used zero times in it." `Dispatch`, inside
317
+ # `Handler`, is the first real use. The GENERIC "Entity" category
318
+ # cannot express this through `children_of`/`EAGER_CHILDREN` the
319
+ # way Aggregate's own entities/value_objects can — there is only
320
+ # ONE "Entity" Plan category, describing what ANY entity looks
321
+ # like, not one per nesting level — so this recurses by hand,
322
+ # the same special case `within_entity` (above) already is for
323
+ # Command/Query.
324
+ #
325
+ # `owner` is the field this repurposes — `entity.bluebook`
326
+ # declares it (`attribute :owner, EntityText`) and it has held
327
+ # exactly one value since ADR 0025's rename: the SAME id
328
+ # `aggregate` already carries, kept as a wrapped-text COPY,
329
+ # never read back anywhere else in this codebase (grep finds no
330
+ # second reference). For a NESTED entity, the two finally
331
+ # diverge — `aggregate` stays the ROOT (Dispatch resolves
332
+ # exactly the way any other Entity record does, by its root
333
+ # aggregate), and `owner` becomes THIS entity's own DIRECT
334
+ # parent (Handler, not ProcessManager) — which is exactly the
335
+ # fact `Reconstruction#direct_entities` needs to tell a
336
+ # root-level entity apart from a nested one sharing the same
337
+ # root.
338
+ def nest_entities(category, node, id, aggregate)
339
+ return unless category == "Entity"
340
+
341
+ plan = @plan.category("Entity")
342
+ walk_all("Entity", node, aggregate, {
343
+ aggregate: carried(plan, plan&.declare, "aggregate", aggregate),
344
+ owner: carried(plan, plan&.declare, "owner", id)
345
+ })
346
+ end
347
+
348
+ # WHERE IT SITS AMONG ITS SIBLINGS IS A FACT ABOUT THE WALK, not about the
349
+ # node : a command does not know it is the third command on its aggregate.
350
+ # The walk knows, so the walk supplies it, and every other field still
351
+ # comes from the node. Declaration order used to survive only because the
352
+ # meta store happened to iterate in insertion order — an accident that an
353
+ # ask ordered any other way would have taken away, and Reconstruction is
354
+ # the one reader that must have the SOURCE'S order rather than a stable one.
355
+ POSITION = "position"
356
+
357
+ def declare(plan, category, node, id, parent_id, index, extra = {})
358
+ return unless plan.declare
359
+
360
+ payload = {}
361
+ payload[plan.parent_key.to_sym] = carried(plan, plan.declare, plan.parent_key, parent_id) if plan.parent_key
362
+ plan.fields.each do |field|
363
+ payload[field.to_sym] = if field == POSITION
364
+ v(index)
365
+ else
366
+ carried(plan, plan.declare, field, field_value(category, node, field.to_sym, parent_id))
367
+ end
368
+ end
369
+
370
+ send_to("Bluebook::#{verb_for(plan, plan.declare)}", id, to: id, **payload.merge(extra))
371
+ end
372
+
373
+ # A setter whose every source is absent is not dispatched. An aggregate
374
+ # with no lifecycle has no Lifecycle to offer, and a creating command has
375
+ # no root to act on — offering either as "" would make a rule refuse a
376
+ # bluebook that is perfectly well formed.
377
+ def setters(plan, category, node, receiver)
378
+ plan.setters.each do |setter|
379
+ payload = setter.targets.to_h do |target, argument|
380
+ [argument.to_sym, v(setter_value(category, node, target))]
381
+ end
382
+ next if payload.values.all?(&:nil?)
383
+
384
+ send_to("Bluebook::#{verb_for(plan, setter.verb)}", receiver[:aggregate], to: receiver, **payload)
385
+ end
386
+ end
387
+
388
+ def appends(plan, category, node, receiver, parent_id)
389
+ id = receiver[:entities].last || receiver[:aggregate]
390
+ owner_id = owning_aggregate_ref(category, id, parent_id)
391
+ plan.appends.each do |list_name, append|
392
+ rows_for(category, list_name, node).each_with_index do |row, index|
393
+ chosen = append_for(category, list_name, append, row, node)
394
+ # `position` IS THE WALK INDEX here exactly as it is in `declare` —
395
+ # an appended element that names its position (ValueObject.Member,
396
+ # S17) is ordered by where the walk found it, never by a field the
397
+ # row happens to hold.
398
+ payload = chosen.map.to_h do |field, argument|
399
+ value = if field.to_s == POSITION
400
+ v(index)
401
+ else
402
+ carried(@plan.category(category), chosen.verb, argument,
403
+ cell(category, list_name, row, field, id, chosen, owner_id))
404
+ end
405
+ [argument.to_sym, value]
406
+ end
407
+
408
+ send_to("Bluebook::#{verb_for(plan, chosen.verb)}", "#{id}##{list_name}[#{index}]",
409
+ to: receiver, **payload)
410
+ end
411
+ end
412
+ end
413
+
414
+ # WHICH AGGREGATE OWNS THE VALUE OBJECTS an attribute's TYPE may
415
+ # resolve against. An aggregate owns its own — `id` already names
416
+ # it. An entity never has value objects of its own (Entity
417
+ # deliberately never answers `value_object` — see entity.rb's own
418
+ # comment on why); its attributes read the SAME pool its
419
+ # enclosing aggregate declares, one level up the construct tree
420
+ # no matter how many entities deep this attribute is nested —
421
+ # `parent_id` names it because `detail_node`/`nest_entities`
422
+ # thread the ROOT aggregate's id down as `parent_id` at every
423
+ # entity level, never the direct (possibly entity) parent.
424
+ def owning_aggregate_ref(category, id, parent_id)
425
+ category == "Entity" ? parent_id : id
426
+ end
427
+
428
+ def sealers(plan, category, receiver)
429
+ id = receiver[:entities].last || receiver[:aggregate]
430
+ plan.sealers.each { |verb| send_to("Bluebook::#{verb_for(plan, verb)}", id, to: receiver) }
431
+ end
432
+
433
+ # An aggregate's or an entity's attribute names its value object by TYPE,
434
+ # and the language models that as a reference — so the type is offered as
435
+ # the value object's own id. This is the rule "attributes must use
436
+ # value-object types", enforced by reference resolution rather than by a
437
+ # predicate — for a HEAD's own attributes, aggregate or entity alike: an
438
+ # entity is its own root, repeating the aggregate's whole shape one level
439
+ # down (entity.rb's own words), and an undeclared type on an entity's
440
+ # attribute must fail the same reference resolution an aggregate's own
441
+ # does, not go unchecked because only "Aggregate.attributes" was ever
442
+ # asked.
443
+ # An attribute's type is offered as the ID OF THE THING IT NAMES, so the
444
+ # language resolves it as a reference and "the type is declared" costs no
445
+ # predicate. Three kinds, three ids: a value object and an entity both hang
446
+ # off this aggregate, so they share its prefix; another aggregate's head
447
+ # hangs off the chapter.
448
+ def cell(category, list_name, row, field, id, append, aggregate_id)
449
+ value = row_value(row, field)
450
+ # A default keeps its TYPE by being written as a literal — 0.0 rather than
451
+ # "0.0" — because the language holds it as text and text alone forgets.
452
+ return encode_literal(value) if field == :default
453
+ return value unless field == :type
454
+ # A REFERENCE names another head WHEREVER it is written — on a head, on
455
+ # a command, on a piece, on an ask — so it is offered as that head's
456
+ # id in all four. Only a HEAD's own attributes additionally qualify
457
+ # an ordinary type into a value object's id ; a command argument's
458
+ # type is text and stays text.
459
+ return points_at(row, id) if append.verb == "Reference"
460
+ return value unless attribute_list?(category, list_name)
461
+
462
+ Naming.identity([owning_aggregate_id(aggregate_id, value), value])
463
+ end
464
+
465
+ # A HEAD'S OWN ATTRIBUTES — an aggregate's, or an entity's (its own root,
466
+ # one level down). Every other "attributes" list belongs to something that
467
+ # is not a head at all (a command's arguments, a value object's own
468
+ # fields), and a type written there is a name, not a reference — the same
469
+ # distinction `cell`'s own comment draws.
470
+ def attribute_list?(category, list_name)
471
+ list_name.to_s == "attributes" && %w[Aggregate Entity].include?(category)
472
+ end
473
+
474
+ # `id` NAMES THE ATTRIBUTE'S OWN AGGREGATE, not necessarily the
475
+ # value object's — Wave 7's own translation.bluebook/translation_
476
+ # aggregate.bluebook split proved the difference live:
477
+ # TranslationAggregate's own `was`/every rename-rule's own `from`/
478
+ # `to`/... all deliberately reuse the SIBLING "Translation"
479
+ # aggregate's own `TranslationName` (that file's own header:
480
+ # "the shared TranslationName every non-identity field below
481
+ # uses"), a real, intentional cross-aggregate reuse — not the
482
+ # local-only ownership every OTHER real domain in this corpus
483
+ # happens to have used until now.
484
+ #
485
+ # `id` (already `Naming.identity([chapter, aggregate])`-joined)
486
+ # only ever composes with the LOCAL aggregate for real, non-
487
+ # entity-owned attributes — an entity's own `id` never matches
488
+ # any TOP-LEVEL aggregate here, so `local` stays nil and this
489
+ # returns `id` unchanged, exactly the prior behavior. Same for
490
+ # every attribute whose type IS locally declared (the overwhelming
491
+ # common case, Banking's own Customer/Account included) — this
492
+ # only ever changes the answer when the local aggregate does NOT
493
+ # declare `value` itself, falling back to the first (declaration-
494
+ # order) OTHER aggregate in the SAME chapter that does.
495
+ def owning_aggregate_id(id, value)
496
+ local = @bluebook.aggregates.find { |aggregate| Naming.identity([@bluebook.name, aggregate.name]) == id }
497
+ return id unless local
498
+ return id if names?(local, value)
499
+
500
+ owner = @bluebook.aggregates.find { |aggregate| aggregate != local && names?(aggregate, value) }
501
+ return id unless owner
502
+
503
+ Naming.identity([@bluebook.name, owner.name])
504
+ end
505
+
506
+ # Whichever construct kind `value` actually is — a value object
507
+ # (Attribute) or an entity this aggregate holds (Holds); `cell`'s
508
+ # own caller already knows which verb it dispatches, but not
509
+ # which collection to search here without re-deriving that same
510
+ # decision, so this simply checks both. The self-hosted grammar's
511
+ # own Bluebook:Syntax#attributes proved entities need the same
512
+ # cross-aggregate fallback value objects do — Syntax's own
513
+ # `Argument`-typed attribute names Command's entity, not one of
514
+ # Syntax's own.
515
+ def names?(aggregate, value)
516
+ aggregate.value_objects.any? { |vo| vo.hecks_name == value } ||
517
+ aggregate.entities.any? { |entity| entity.hecks_name == value }
518
+ end
519
+
520
+ # Which verb an attribute row belongs to. The plan cannot decide this — all
521
+ # three append to the same list — and what tells them apart is the row:
522
+ #
523
+ # Attribute its type names a value object of this aggregate
524
+ # Reference its type is Reference<X>, another aggregate's head
525
+ # Holds its type names an entity this aggregate declares
526
+ #
527
+ # NOTHING IS SKIPPED any more. Reference and Holds did not exist, so the
528
+ # walk dropped both kinds and the meta-domain silently did not contain
529
+ # Account#customer_id or Account#ledger.
530
+ # Each alternate carries its OWN map, read from the language. Borrowing the
531
+ # primary's map dispatched `type:` where Reference declares `points_at:`,
532
+ # and the payload gate caught it — which is the gate paying for itself.
533
+ # `reference_to` can be written in FOUR places — on a head, a command, a
534
+ # piece, an ask — and each keeps its own list of attributes, so each
535
+ # needs the Reference alternate. Only a head can hold a piece, so Holds
536
+ # stays where it was.
537
+ def append_for(category, list_name, append, row, node)
538
+ return append unless list_name.to_s == "attributes"
539
+ return alternate(category, "Reference") || append if reference_row?(row)
540
+ return alternate(category, "Holds") || append if entity_row?(row, node)
541
+
542
+ append
543
+ end
544
+
545
+ def alternate(category, verb)
546
+ @plan.category(category).alternates.find { |append| append.verb == verb }
547
+ end
548
+
549
+ def reference_row?(row) = row.respond_to?(:reference?) && row.reference?
550
+
551
+ # Only a HEAD declares pieces, and now that every attribute list reaches
552
+ # this, the node may be a command, a piece or an ask — none of which
553
+ # answer `entities` at all.
554
+ def entity_row?(row, node)
555
+ return false unless node.respond_to?(:entities)
556
+
557
+ Array(node.entities).any? { |entity| entity.hecks_name == row.type.to_s }
558
+ end
559
+
560
+ # A RECORD'S ID IS ITS DECLARED IDENTITY, JOINED — the same join the runtime
561
+ # does, off the same declaration, because there is only one way to name a
562
+ # thing and it should be written once.
563
+ #
564
+ # This was a branch per category: "#{parent}::#{name}" for an aggregate,
565
+ # "#{parent}.#{name}" for most, "#{parent}##{index}" for the three that had
566
+ # no name to use. It was the composite identity all along, hand-written here
567
+ # because the language could not say it — which is why the language having
568
+ # no identity of its own and this method existing were the same fact.
569
+ #
570
+ # A part resolves from one of three places, and the declaration says which:
571
+ # the parent reference is the walk's parent_id, `position` is where the walk
572
+ # is, and anything else is read off the node.
573
+ def identify(category, parent_id, node, index)
574
+ plan = @plan.category(category)
575
+ return declared_name(node) unless plan
576
+
577
+ Naming.identity(plan.identity_paths.map { |path| identity_part(plan, path, parent_id, node, index, category) })
578
+ end
579
+
580
+ # "owner_id" is a SECOND reserved head, beside `position` : it names
581
+ # whichever record is walking THIS one right now, aggregate or entity
582
+ # alike, without saying which — Command and Query read it so an
583
+ # entity's verbs are the entity's own. It is never a declared
584
+ # attribute (declaring one for a field that names two different types
585
+ # would be a lie about which), so it cannot be read through
586
+ # `field_value` ; it is read the same way `plan.parent_key` already is,
587
+ # because it IS that fact, spelled for the walk's immediate parent
588
+ # rather than for one specific kind of one.
589
+ OWNER = "owner_id"
590
+
591
+ def identity_part(plan, path, parent_id, node, index, category)
592
+ head = path.to_s.split(".").first
593
+ return parent_id.to_s if head == plan.parent_key.to_s || head == OWNER
594
+ return index.to_s if head == POSITION
595
+
596
+ v_scalar(field_value(category, node, head.to_sym, parent_id))
597
+ end
598
+
599
+ # The scalar inside whatever the reading handed back — a name is already one,
600
+ # a value object is not.
601
+ def v_scalar(held)
602
+ return held.to_s unless held.respond_to?(:to_h) && !held.is_a?(String)
603
+
604
+ held.to_h.values.first.to_s
605
+ end
606
+
607
+ def children_of(category)
608
+ @plan.names.select { |name| @plan.category(name).parent == category }
609
+ end
610
+
611
+ def eager?(category, child) = Array(EAGER_CHILDREN[category]).include?(child)
612
+
613
+ # Command -> commands, ValueObject -> value_objects, Query -> queries.
614
+ # Convention, not a table : the IR names a collection after what it holds.
615
+ # The pluraliser lives in Naming because there used to be two of them and
616
+ # one was wrong — see Naming.plural.
617
+ def collection_reader(category) = Naming.plural(Naming.snake(category))
618
+ end
619
+ end
620
+ end
621
+ end