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,559 @@
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. An attribute's type is
29
+ # offered as the id of the thing it names, so both the value objects and the
30
+ # entities have to exist before any attribute names one.
31
+ EAGER_CHILDREN = { "Aggregate" => %w[Entity ValueObject] }.freeze
32
+
33
+ # Categories an ENTITY declares as well as an aggregate. The IR reuses
34
+ # Command and Query for a piece's own commands and queries, so the
35
+ # language reuses Command and Query — and the plan cannot express a second
36
+ # parent, because a category's parent is derived from the one `*_id` argument
37
+ # its creating command carries. This says the other edge out loud.
38
+ WITHIN_ENTITY = %w[Command Query].freeze
39
+
40
+ attr_reader :refusals
41
+
42
+ # THE RECORDS SURVIVE THE VERDICT.
43
+ #
44
+ # Judging a bluebook and HOLDING one differ by exactly this: whether anyone
45
+ # keeps the runtime the declarations were dispatched into. Nobody did, so
46
+ # `spec/round_trip_spec` reached the records by `Judge.allocate` and four
47
+ # `instance_variable_set` calls. Reading the chapter back is the point now,
48
+ # not a curiosity, so the runtime is simply readable.
49
+ attr_reader :runtime
50
+
51
+ def initialize(bluebook)
52
+ @bluebook = bluebook
53
+ @refusals = []
54
+ @runtime = MetaValidator.fresh_runtime
55
+ @plan = Plan.for(MetaValidator.grammar_registry)
56
+ judge!
57
+ end
58
+
59
+ private
60
+
61
+ # ABSENT is not EMPTY. The rules read "if you declare it, declare
62
+ # something" — a description never given is legal. Passing "" for a nil
63
+ # turns every one of them into "you must declare it".
64
+ #
65
+ # An Integer stays an Integer : RowCount declares `attribute :value,
66
+ # Integer`, so stringifying a member count fails the type gate rather than
67
+ # feeding the rule it was meant to feed.
68
+ def v(text)
69
+ return nil if text.nil?
70
+ return { value: text } if text.is_a?(Integer)
71
+
72
+ { value: text.to_s }
73
+ end
74
+
75
+ # A REFERENCE IS AN ID, AND AN ID IS A SCALAR.
76
+ #
77
+ # Every other field goes to the meta-domain as a one-field value object,
78
+ # because that is what it is. A reference is not: it carries the id of a
79
+ # head, and wrapping an id in an object was the language saying `{value:
80
+ # "Banking::Customer"}` where it meant `"Banking::Customer"`. The plan
81
+ # answers which arguments those are, read from the language's own IR, so
82
+ # nothing here needs to know the names.
83
+ def carried(plan, verb, argument, value)
84
+ return v(value) unless plan && verb && plan.references?(verb, argument)
85
+
86
+ value
87
+ end
88
+
89
+ def args(pairs) = pairs.reject { |_, value| value.nil? }
90
+
91
+ def offer(label)
92
+ yield
93
+ rescue Runtime::GivenNotMet, Runtime::InvariantViolation,
94
+ Runtime::TypeMismatch, Runtime::NotFound => e
95
+ # NotFound is a VERDICT, not noise. An attribute's type is a reference to
96
+ # its value object, so "no ValueObject with id …" IS the rule `attributes
97
+ # must use value-object types` refusing.
98
+ @refusals << "#{label}: #{e.message}"
99
+ rescue Runtime::UnknownVerb
100
+ nil
101
+ end
102
+
103
+ def send_to(verb, label, to: nil, **payload)
104
+ offer(label) { @runtime.dispatch(verb, to: to, with: args(payload)) }
105
+ end
106
+
107
+ def judge!
108
+ declare_node("Bluebook", @bluebook, nil, 0)
109
+ detail_node("Bluebook", @bluebook, nil, 0)
110
+ end
111
+
112
+ # DECLARED BEFORE DETAILED, for every set of siblings.
113
+ #
114
+ # A node used to be offered whole — declared, then its lists, then its
115
+ # children — one sibling at a time. Which means an aggregate's attributes
116
+ # were offered before its later siblings existed, and an attribute that
117
+ # POINTS AT another aggregate could only resolve if that aggregate happened
118
+ # to be declared earlier in the file. Banking survives on luck: Customer is
119
+ # written above Account.
120
+ #
121
+ # So siblings are declared in one pass and detailed in a second. It is the
122
+ # same ordering the walk already used one level down — value objects before
123
+ # the attributes that name them — lifted to the level above, and it is what
124
+ # lets a reference be a REFERENCE rather than a string nobody can check.
125
+ def declare_node(category, node, parent_id, index, extra = {}, receiver: nil)
126
+ plan = @plan.category(category)
127
+ return unless plan
128
+
129
+ declare(plan, category, node, identify(category, parent_id, node, index), parent_id, index, extra)
130
+ end
131
+
132
+ def detail_node(category, node, parent_id, index, extra = {}, receiver: nil)
133
+ plan = @plan.category(category)
134
+ return unless plan
135
+
136
+ id = identify(category, parent_id, node, index)
137
+ # `extra` is the CUMULATIVE identity of every entity-owned
138
+ # ancestor above this node (Handler's own `event_type` AND
139
+ # ProcessManager's own `bluebook`/`name`, by the time Dispatch
140
+ # is reached — S17, ADR 0026's two-level chain). `identity`
141
+ # adds THIS node's own on top — computed for EVERY category,
142
+ # entity-owned or not, because a node need not be entity-owned
143
+ # itself to OWN one (ValueObject isn't, and Member still needs
144
+ # its `aggregate:`/`name:`) — it is simply what THIS node's own
145
+ # `identified_by` resolves to, the same fields `identify` two
146
+ # lines up already derives the joined id FROM.
147
+ #
148
+ # `own` is the SUBSET actually spent on a dispatch payload —
149
+ # only when THIS category is itself entity-owned, since an
150
+ # ordinary category (Command's own "Rule"/"Argument", offered
151
+ # through the SAME `extra` SLOT for a DIFFERENT reason, see
152
+ # `within_entity` below) locates the record it attaches to
153
+ # through the parent id `id:` already carries, and merging
154
+ # unrecognized `aggregate:`/`entity_id:` into THEIR payload
155
+ # would have the runtime refuse them for an argument they
156
+ # never declared.
157
+ identity = extra.merge(node_identity(plan, category, node, index, parent_id))
158
+ receiver ||= { aggregate: id, entities: [] }
159
+ eager, later = children_of(category).partition { |child| eager?(category, child) }
160
+
161
+ eager.each { |child| walk_all(child, node, id, entity_child_extra(child, identity), receiver: receiver) }
162
+ setters(plan, category, node, receiver)
163
+ appends(plan, category, node, receiver)
164
+ later.each { |child| walk_all(child, node, id, entity_child_extra(child, identity), receiver: receiver) }
165
+ within_entity(category, node, id, parent_id)
166
+ nest_entities(category, node, id, parent_id)
167
+ sealers(plan, category, receiver)
168
+ end
169
+
170
+ # WHAT A CHILD'S OWN `extra` STARTS FROM. An entity-owned child's
171
+ # own dotted dispatch needs every ANCESTOR's identity, which is
172
+ # exactly `identity` — already accumulated one level at a time by
173
+ # `detail_node` itself (regardless of whether each ancestor is
174
+ # ITSELF entity-owned — ValueObject contributes its own `aggregate:
175
+ # `/`name:` to Member's payload despite being an ordinary top-
176
+ # level category), so there is nothing left to re-derive here. An
177
+ # ordinary child (one with a real top-level aggregate of its own
178
+ # to dispatch a bare verb into) needs none of it.
179
+ def entity_child_extra(child, identity)
180
+ @plan.category(child)&.entity_owned ? identity : {}
181
+ end
182
+
183
+ # ONE NODE'S OWN IDENTITY, read off its own declaration — S17,
184
+ # ADR 0026. Three cases, the same three `identify`/`identity_part`
185
+ # already resolve one level up, unified here because a chain now
186
+ # walks more than one level (Handler -> Dispatch) and each level
187
+ # needs the SAME three answered about itself, not just the first:
188
+ #
189
+ # the parent link (plan.parent_key) -> `parent_id`, the id
190
+ # the walk already carries in from one level up
191
+ # a walk-minted one (POSITION) -> the walk INDEX itself ;
192
+ # never a stored field (Member's own header:
193
+ # "position is not a mint — it is read straight
194
+ # out of the source file")
195
+ # a real field (anything else) -> `field_value`, same
196
+ # reader every other field in this file uses
197
+ # (Handler's own `event_type`, Dispatch's own
198
+ # `command_name`)
199
+ #
200
+ # `carried` still decides bare-vs-wrapped the normal way ; POSITION
201
+ # is the one case with no verb to ask `carried` about (`plan.
202
+ # declare` is always nil for an entity-owned category — Plan#read's
203
+ # own comment says why), so it is minted straight as a value object,
204
+ # matching exactly what `declare`'s own field loop already mints a
205
+ # POSITION field as.
206
+ def node_identity(plan, category, node, index, parent_id)
207
+ plan.identity_paths.each_with_object({}) do |path, fields|
208
+ head = path.to_s.split(".").first
209
+ next if head == OWNER
210
+
211
+ if head == POSITION
212
+ fields[head.to_sym] = v(index)
213
+ else
214
+ raw = head == plan.parent_key.to_s ? parent_id : field_value(category, node, head.to_sym, parent_id)
215
+ fields[head.to_sym] = carried(plan, plan.declare, head, raw)
216
+ end
217
+ end
218
+ end
219
+
220
+ # THE FULL DOTTED PREFIX a category's own verbs hang off — the
221
+ # plain name for an ordinary category (its own top-level
222
+ # aggregate reaches every verb bare), or its PARENT's own prefix
223
+ # with this category's name appended, for an entity-owned one.
224
+ # Dispatch's own parent, Handler, is itself entity-owned (S17,
225
+ # ADR 0026's two-level chain — `ProcessManager.Handler.Dispatch`),
226
+ # so this recurses rather than reading one level and stopping.
227
+ def dotted_prefix(plan)
228
+ return plan.name unless plan.entity_owned
229
+
230
+ "#{dotted_prefix(@plan.category(plan.parent))}.#{plan.name}"
231
+ end
232
+
233
+ # ENTITY-OWNED categories have no top-level aggregate for the runtime
234
+ # to route a bare verb into any more — `Member`'s own "Pair" reaches
235
+ # the runtime as `ValueObject.Member.Pair`, and a NESTED one
236
+ # (`Dispatch`, inside `Handler`) reaches it as `ProcessManager.
237
+ # Handler.Dispatch.Bind` — the dotted shape `EntityInterpreter#call`
238
+ # already splits any real entity's own verb into, one hop per
239
+ # segment (`walk_entity_chain`, entity_interpreter.rb).
240
+ def verb_for(plan, verb)
241
+ "#{dotted_prefix(plan)}.#{verb}"
242
+ end
243
+
244
+ def walk_all(category, node, parent_id, extra = {}, receiver: nil)
245
+ reader = collection_reader(category)
246
+ return unless node.respond_to?(reader)
247
+
248
+ children = Array(node.public_send(reader))
249
+ children.each_with_index { |child, index| declare_node(category, child, parent_id, index, extra) }
250
+ children.each_with_index do |child, index|
251
+ child_plan = @plan.category(category)
252
+ child_receiver = if child_plan&.entity_owned
253
+ child_id = identify(category, parent_id, child, index)
254
+ root = receiver || { aggregate: parent_id, entities: [] }
255
+ { aggregate: root[:aggregate], entities: Array(root[:entities]) + [child_id] }
256
+ end
257
+ detail_node(category, child, parent_id, index, extra, receiver: child_receiver)
258
+ end
259
+ end
260
+
261
+ # A piece's commands and queries, addressed under the PIECE so two commands
262
+ # of the same name on an aggregate and on one of its entities cannot collide,
263
+ # while `aggregate` still names the aggregate the reference resolves
264
+ # against and `entity_id` says which piece declared it. `entity_id`
265
+ # keeps its own `_id` — an EXPLICIT `as:` on `reference_to Entity`,
266
+ # never touched by ADR 0025's rename (only the DEFAULT, un-aliased
267
+ # mint dropped the suffix; `aggregate` did precisely because
268
+ # `Command#reference_to Aggregate`/`Query#reference_to Aggregate`
269
+ # carry no `as:` of their own).
270
+ def within_entity(category, node, id, aggregate)
271
+ return unless category == "Entity"
272
+
273
+ WITHIN_ENTITY.each do |child|
274
+ plan = @plan.category(child)
275
+ walk_all(child, node, id, {
276
+ aggregate: carried(plan, plan&.declare, "aggregate", aggregate),
277
+ entity_id: carried(plan, plan&.declare, "entity_id", id)
278
+ })
279
+ end
280
+ end
281
+
282
+ # AN ENTITY MAY NEST FURTHER ENTITIES — S17, ADR 0026's own words:
283
+ # "That is what `entity` is for, and `entity` is declared by the
284
+ # language and used zero times in it." `Dispatch`, inside
285
+ # `Handler`, is the first real use. The GENERIC "Entity" category
286
+ # cannot express this through `children_of`/`EAGER_CHILDREN` the
287
+ # way Aggregate's own entities/value_objects can — there is only
288
+ # ONE "Entity" Plan category, describing what ANY entity looks
289
+ # like, not one per nesting level — so this recurses by hand,
290
+ # the same special case `within_entity` (above) already is for
291
+ # Command/Query.
292
+ #
293
+ # `owner` is the field this repurposes — `entity.bluebook`
294
+ # declares it (`attribute :owner, EntityText`) and it has held
295
+ # exactly one value since ADR 0025's rename: the SAME id
296
+ # `aggregate` already carries, kept as a wrapped-text COPY,
297
+ # never read back anywhere else in this codebase (grep finds no
298
+ # second reference). For a NESTED entity, the two finally
299
+ # diverge — `aggregate` stays the ROOT (Dispatch resolves
300
+ # exactly the way any other Entity record does, by its root
301
+ # aggregate), and `owner` becomes THIS entity's own DIRECT
302
+ # parent (Handler, not ProcessManager) — which is exactly the
303
+ # fact `Reconstruction#direct_entities` needs to tell a
304
+ # root-level entity apart from a nested one sharing the same
305
+ # root.
306
+ def nest_entities(category, node, id, aggregate)
307
+ return unless category == "Entity"
308
+
309
+ plan = @plan.category("Entity")
310
+ walk_all("Entity", node, aggregate, {
311
+ aggregate: carried(plan, plan&.declare, "aggregate", aggregate),
312
+ owner: carried(plan, plan&.declare, "owner", id)
313
+ })
314
+ end
315
+
316
+ # WHERE IT SITS AMONG ITS SIBLINGS IS A FACT ABOUT THE WALK, not about the
317
+ # node : a command does not know it is the third command on its aggregate.
318
+ # The walk knows, so the walk supplies it, and every other field still
319
+ # comes from the node. Declaration order used to survive only because the
320
+ # meta store happened to iterate in insertion order — an accident that an
321
+ # ask ordered any other way would have taken away, and Reconstruction is
322
+ # the one reader that must have the SOURCE'S order rather than a stable one.
323
+ POSITION = "position"
324
+
325
+ def declare(plan, category, node, id, parent_id, index, extra = {})
326
+ return unless plan.declare
327
+
328
+ payload = {}
329
+ payload[plan.parent_key.to_sym] = carried(plan, plan.declare, plan.parent_key, parent_id) if plan.parent_key
330
+ plan.fields.each do |field|
331
+ payload[field.to_sym] = if field == POSITION
332
+ v(index)
333
+ else
334
+ carried(plan, plan.declare, field, field_value(category, node, field.to_sym, parent_id))
335
+ end
336
+ end
337
+
338
+ send_to("Bluebook::#{verb_for(plan, plan.declare)}", id, to: id, **payload.merge(extra))
339
+ end
340
+
341
+ # A setter whose every source is absent is not dispatched. An aggregate
342
+ # with no lifecycle has no Lifecycle to offer, and a creating command has
343
+ # no root to act on — offering either as "" would make a rule refuse a
344
+ # bluebook that is perfectly well formed.
345
+ def setters(plan, category, node, receiver)
346
+ plan.setters.each do |setter|
347
+ payload = setter.targets.to_h do |target, argument|
348
+ [argument.to_sym, v(setter_value(category, node, target))]
349
+ end
350
+ next if payload.values.all?(&:nil?)
351
+
352
+ send_to("Bluebook::#{verb_for(plan, setter.verb)}", receiver[:aggregate], to: receiver, **payload)
353
+ end
354
+ end
355
+
356
+ def appends(plan, category, node, receiver)
357
+ id = receiver[:entities].last || receiver[:aggregate]
358
+ plan.appends.each do |list_name, append|
359
+ rows_for(category, list_name, node).each_with_index do |row, index|
360
+ chosen = append_for(category, list_name, append, row, node)
361
+ # `position` IS THE WALK INDEX here exactly as it is in `declare` —
362
+ # an appended element that names its position (ValueObject.Member,
363
+ # S17) is ordered by where the walk found it, never by a field the
364
+ # row happens to hold.
365
+ payload = chosen.map.to_h do |field, argument|
366
+ value = if field.to_s == POSITION
367
+ v(index)
368
+ else
369
+ carried(@plan.category(category), chosen.verb, argument,
370
+ cell(category, list_name, row, field, id, chosen))
371
+ end
372
+ [argument.to_sym, value]
373
+ end
374
+
375
+ send_to("Bluebook::#{verb_for(plan, chosen.verb)}", "#{id}##{list_name}[#{index}]",
376
+ to: receiver, **payload)
377
+ end
378
+ end
379
+ end
380
+
381
+ def sealers(plan, category, receiver)
382
+ id = receiver[:entities].last || receiver[:aggregate]
383
+ plan.sealers.each { |verb| send_to("Bluebook::#{verb_for(plan, verb)}", id, to: receiver) }
384
+ end
385
+
386
+ # An aggregate's attribute names its value object by TYPE, and the language
387
+ # models that as a reference — so the type is offered as the value object's
388
+ # own id. This is the rule "attributes must use value-object types",
389
+ # enforced by reference resolution rather than by a predicate.
390
+ # An attribute's type is offered as the ID OF THE THING IT NAMES, so the
391
+ # language resolves it as a reference and "the type is declared" costs no
392
+ # predicate. Three kinds, three ids: a value object and an entity both hang
393
+ # off this aggregate, so they share its prefix; another aggregate's head
394
+ # hangs off the chapter.
395
+ def cell(category, list_name, row, field, id, append)
396
+ value = row_value(row, field)
397
+ # A default keeps its TYPE by being written as a literal — 0.0 rather than
398
+ # "0.0" — because the language holds it as text and text alone forgets.
399
+ return encode_literal(value) if field == :default
400
+ return value unless field == :type
401
+ # A REFERENCE names another head WHEREVER it is written — on a head, on
402
+ # a command, on a piece, on an ask — so it is offered as that head's
403
+ # id in all four. Only the head's own attributes additionally qualify
404
+ # an ordinary type into a value object's id ; a command argument's
405
+ # type is text and stays text.
406
+ return points_at(row, id) if append.verb == "Reference"
407
+ return value unless "#{category}.#{list_name}" == "Aggregate.attributes"
408
+
409
+ Naming.identity([owning_aggregate_id(id, value), value])
410
+ end
411
+
412
+ # `id` NAMES THE ATTRIBUTE'S OWN AGGREGATE, not necessarily the
413
+ # value object's — Wave 7's own translation.bluebook/translation_
414
+ # aggregate.bluebook split proved the difference live:
415
+ # TranslationAggregate's own `was`/every rename-rule's own `from`/
416
+ # `to`/... all deliberately reuse the SIBLING "Translation"
417
+ # aggregate's own `TranslationName` (that file's own header:
418
+ # "the shared TranslationName every non-identity field below
419
+ # uses"), a real, intentional cross-aggregate reuse — not the
420
+ # local-only ownership every OTHER real domain in this corpus
421
+ # happens to have used until now.
422
+ #
423
+ # `id` (already `Naming.identity([chapter, aggregate])`-joined)
424
+ # only ever composes with the LOCAL aggregate for real, non-
425
+ # entity-owned attributes — an entity's own `id` never matches
426
+ # any TOP-LEVEL aggregate here, so `local` stays nil and this
427
+ # returns `id` unchanged, exactly the prior behavior. Same for
428
+ # every attribute whose type IS locally declared (the overwhelming
429
+ # common case, Banking's own Customer/Account included) — this
430
+ # only ever changes the answer when the local aggregate does NOT
431
+ # declare `value` itself, falling back to the first (declaration-
432
+ # order) OTHER aggregate in the SAME chapter that does.
433
+ def owning_aggregate_id(id, value)
434
+ local = @bluebook.aggregates.find { |aggregate| Naming.identity([@bluebook.name, aggregate.name]) == id }
435
+ return id unless local
436
+ return id if names?(local, value)
437
+
438
+ owner = @bluebook.aggregates.find { |aggregate| aggregate != local && names?(aggregate, value) }
439
+ return id unless owner
440
+
441
+ Naming.identity([@bluebook.name, owner.name])
442
+ end
443
+
444
+ # Whichever construct kind `value` actually is — a value object
445
+ # (Attribute) or an entity this aggregate holds (Holds); `cell`'s
446
+ # own caller already knows which verb it dispatches, but not
447
+ # which collection to search here without re-deriving that same
448
+ # decision, so this simply checks both. The self-hosted grammar's
449
+ # own Bluebook:Syntax#attributes proved entities need the same
450
+ # cross-aggregate fallback value objects do — Syntax's own
451
+ # `Argument`-typed attribute names Command's entity, not one of
452
+ # Syntax's own.
453
+ def names?(aggregate, value)
454
+ aggregate.value_objects.any? { |vo| vo.hecks_name == value } ||
455
+ aggregate.entities.any? { |entity| entity.hecks_name == value }
456
+ end
457
+
458
+ # Which verb an attribute row belongs to. The plan cannot decide this — all
459
+ # three append to the same list — and what tells them apart is the row:
460
+ #
461
+ # Attribute its type names a value object of this aggregate
462
+ # Reference its type is Reference<X>, another aggregate's head
463
+ # Holds its type names an entity this aggregate declares
464
+ #
465
+ # NOTHING IS SKIPPED any more. Reference and Holds did not exist, so the
466
+ # walk dropped both kinds and the meta-domain silently did not contain
467
+ # Account#customer_id or Account#ledger.
468
+ # Each alternate carries its OWN map, read from the language. Borrowing the
469
+ # primary's map dispatched `type:` where Reference declares `points_at:`,
470
+ # and the payload gate caught it — which is the gate paying for itself.
471
+ # `reference_to` can be written in FOUR places — on a head, a command, a
472
+ # piece, an ask — and each keeps its own list of attributes, so each
473
+ # needs the Reference alternate. Only a head can hold a piece, so Holds
474
+ # stays where it was.
475
+ def append_for(category, list_name, append, row, node)
476
+ return append unless list_name.to_s == "attributes"
477
+ return alternate(category, "Reference") || append if reference_row?(row)
478
+ return alternate(category, "Holds") || append if entity_row?(row, node)
479
+
480
+ append
481
+ end
482
+
483
+ def alternate(category, verb)
484
+ @plan.category(category).alternates.find { |append| append.verb == verb }
485
+ end
486
+
487
+ def reference_row?(row) = row.respond_to?(:reference?) && row.reference?
488
+
489
+ # Only a HEAD declares pieces, and now that every attribute list reaches
490
+ # this, the node may be a command, a piece or an ask — none of which
491
+ # answer `entities` at all.
492
+ def entity_row?(row, node)
493
+ return false unless node.respond_to?(:entities)
494
+
495
+ Array(node.entities).any? { |entity| entity.hecks_name == row.type.to_s }
496
+ end
497
+
498
+ # A RECORD'S ID IS ITS DECLARED IDENTITY, JOINED — the same join the runtime
499
+ # does, off the same declaration, because there is only one way to name a
500
+ # thing and it should be written once.
501
+ #
502
+ # This was a branch per category: "#{parent}::#{name}" for an aggregate,
503
+ # "#{parent}.#{name}" for most, "#{parent}##{index}" for the three that had
504
+ # no name to use. It was the composite identity all along, hand-written here
505
+ # because the language could not say it — which is why the language having
506
+ # no identity of its own and this method existing were the same fact.
507
+ #
508
+ # A part resolves from one of three places, and the declaration says which:
509
+ # the parent reference is the walk's parent_id, `position` is where the walk
510
+ # is, and anything else is read off the node.
511
+ def identify(category, parent_id, node, index)
512
+ plan = @plan.category(category)
513
+ return declared_name(node) unless plan
514
+
515
+ Naming.identity(plan.identity_paths.map { |path| identity_part(plan, path, parent_id, node, index, category) })
516
+ end
517
+
518
+ # "owner_id" is a SECOND reserved head, beside `position` : it names
519
+ # whichever record is walking THIS one right now, aggregate or entity
520
+ # alike, without saying which — Command and Query read it so an
521
+ # entity's verbs are the entity's own. It is never a declared
522
+ # attribute (declaring one for a field that names two different types
523
+ # would be a lie about which), so it cannot be read through
524
+ # `field_value` ; it is read the same way `plan.parent_key` already is,
525
+ # because it IS that fact, spelled for the walk's immediate parent
526
+ # rather than for one specific kind of one.
527
+ OWNER = "owner_id"
528
+
529
+ def identity_part(plan, path, parent_id, node, index, category)
530
+ head = path.to_s.split(".").first
531
+ return parent_id.to_s if head == plan.parent_key.to_s || head == OWNER
532
+ return index.to_s if head == POSITION
533
+
534
+ v_scalar(field_value(category, node, head.to_sym, parent_id))
535
+ end
536
+
537
+ # The scalar inside whatever the reading handed back — a name is already one,
538
+ # a value object is not.
539
+ def v_scalar(held)
540
+ return held.to_s unless held.respond_to?(:to_h) && !held.is_a?(String)
541
+
542
+ held.to_h.values.first.to_s
543
+ end
544
+
545
+ def children_of(category)
546
+ @plan.names.select { |name| @plan.category(name).parent == category }
547
+ end
548
+
549
+ def eager?(category, child) = Array(EAGER_CHILDREN[category]).include?(child)
550
+
551
+ # Command -> commands, ValueObject -> value_objects, Query -> queries.
552
+ # Convention, not a table : the IR names a collection after what it holds.
553
+ # The pluraliser lives in Naming because there used to be two of them and
554
+ # one was wrong — see Naming.plural.
555
+ def collection_reader(category) = Naming.plural(Naming.snake(category))
556
+ end
557
+ end
558
+ end
559
+ end