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,128 @@
1
+ require_relative "behaviour/process_manager"
2
+ require_relative "../ir"
3
+
4
+ module Hecks
5
+ module Bluebook
6
+ # `compensates` — a SECOND `DispatchSpec`, shape-identical to this
7
+ # one, naming the command that undoes THIS dispatch specifically
8
+ # (see `ProcessManagerBuilder::HandlerBuilder#dispatch_impl`'s own
9
+ # comment). `nil` for a dispatch with nothing to undo (a pure
10
+ # bookkeeping mark, or one whose own effect is superseded by a later
11
+ # command rather than needing its own compensation). Never nested
12
+ # further — a compensation is not itself compensable; no known
13
+ # corpus need, and ADR 0025's own "a word earns its place by being
14
+ # used" bar would refuse a second level speculatively.
15
+ DispatchSpec = Struct.new(:command_name, :with_spec, :compensates, keyword_init: true) do
16
+ # A Struct already answers to_h; including the mixin puts the
17
+ # DECLARED emission ahead of Struct's own in the ancestry, which
18
+ # is what makes the shape data rather than a method body.
19
+ include Hecks::IR
20
+
21
+ emits_ir(
22
+ command_name: -> { command_name.to_s },
23
+ with_spec: -> { with_spec.map { |key, value| [key.to_s, Bluebook.render_value(value)] } },
24
+ compensates: one(:compensates)
25
+ )
26
+ end
27
+
28
+ ProcessManagerHandler = Struct.new(:event_type, :from_state, :to_state,
29
+ :dispatches, keyword_init: true) do
30
+ include Hecks::IR
31
+
32
+ emits_ir(
33
+ event_type: -> { event_type.to_s },
34
+ from_state: -> { from_state.to_s },
35
+ to_state: -> { to_state.to_s },
36
+ dispatches: many(:dispatches)
37
+ )
38
+ end
39
+
40
+ # The compensation half of a procedure, as its own thing.
41
+ #
42
+ # A PROCESS MANAGER coordinates: legs, states, an opinion about who goes
43
+ # next. A SAGA undoes: what makes the world good again when a leg it
44
+ # dispatched is refused. Two concepts, and the industry slurs them into one
45
+ # word — so here they are two objects, and a procedure either has a saga or
46
+ # does not.
47
+ #
48
+ # `undoes` is the ordered list of commands the compensation sends — a
49
+ # STATIC PREVIEW, declaration order (`Behaviour::ProcessManager#saga`),
50
+ # not one instance's own runtime history. Per-dispatch compensation
51
+ # (`compensates`, on the step it compensates for) moved most of what
52
+ # a saga undoes off this leg's own hand-written body and onto
53
+ # whichever forward dispatch each one undoes — this reads every
54
+ # declared `compensates` across the WHOLE saga first, then whatever
55
+ # this leg's own hand-written body still lists, for compensation
56
+ # that isn't expressible as "undo command X." WHICH of a declared
57
+ # `compensates` actually fires for one instance, and in what order
58
+ # (newest-first, completed-legs-only), is `SagaInterpreter`'s own
59
+ # dynamic `completed_compensations` — a per-instance runtime fact
60
+ # this declaration-only object could never hold.
61
+ #
62
+ # NAMING COLLISION, ONCE FLAGGED, NOW RESOLVED — `command`'s own
63
+ # `corrects event, reverses: true` (docs/implemented/decisions/0036-
64
+ # corrects-is-an-appended-fact-not-a-rewrite.md) already claimed
65
+ # `reverses` for a different meaning: auto-deriving a command's OWN
66
+ # corrective mutation from a past EVENT, not a saga's own
67
+ # compensating leg from a past DISPATCH. This feature keeps
68
+ # `reverses` reserved for `corrects` and uses `compensates` for
69
+ # per-dispatch saga compensation instead — a deliberate choice, not
70
+ # an accidental collision.
71
+ Saga = Struct.new(:trigger, :from_state, :to_state, :compensations, keyword_init: true) do
72
+ def undoes = compensations.map(&:command_name)
73
+
74
+ def to_s = "#{trigger} → #{to_state} (#{undoes.join(', ')})"
75
+ end
76
+
77
+ class ProcessManager
78
+ # The BLUEBOOK's name for this construct, asked the same way of a class
79
+ # that has crossed over and of an IR object that has not. Collapses into
80
+ # Construct when this one crosses.
81
+ # The trigger of a compensating leg. Not an event name — no aggregate
82
+ # announces that a leg the procedure dispatched was declined — so it lives
83
+ # here beside the thing it triggers rather than in the runtime that
84
+ # notices it. Declared in the language's Trigger vocabulary, which
85
+ # spec/vocabulary_conformance_spec holds to this constant.
86
+ REFUSED = Hecks::Vocabulary.fetch("Trigger").first
87
+
88
+ include Hecks::IR
89
+ include Behaviour::ProcessManager
90
+
91
+ emits_ir(
92
+ name: :name,
93
+ # M11 — `&.`, not `.`: a DSL-built process manager always carries
94
+ # a real `correlates_by` (`ProcessManagerBuilder#build` refuses to
95
+ # mint one without it), but the IR class itself defaults it to
96
+ # `nil` and is what `Assembly::Build`'s `:identity` reader
97
+ # (`value&.to_sym`) round-trips against. A bare `.to_s` mapped
98
+ # that absent case to `""`, indistinguishable on the wire from a
99
+ # real empty name and read back as the wrong, non-nil `:""`
100
+ # instead of `nil` — the same nil-erasure S1 fixed for
101
+ # `render_value`, one field over. `correlates_by` is always a
102
+ # bare Symbol (`SagaInterpreter` hash-looks-up a payload by it),
103
+ # never a `Literal`-encoded polymorphic value, so this stays a
104
+ # local `&.` rather than routing through `Literal.render` — that
105
+ # would wrap a real value in a leading `:` and break both the
106
+ # `:identity` reader's plain `to_sym` and the pinned golden IR
107
+ # fixtures' bare-string spelling (`"reference.value"`).
108
+ correlates_by: -> { correlates_by&.to_s },
109
+ starts_on: :starts_on,
110
+ ends_on: :ends_on,
111
+ states: :states,
112
+ handlers: many(:handlers)
113
+ )
114
+
115
+ attr_reader :name, :correlates_by, :starts_on, :ends_on, :states, :handlers
116
+
117
+ def initialize(name:, correlates_by: nil, starts_on: nil, ends_on: nil,
118
+ states: [], handlers: [])
119
+ @name = name.to_s
120
+ @correlates_by = correlates_by
121
+ @starts_on = starts_on
122
+ @ends_on = ends_on
123
+ @states = states
124
+ @handlers = handlers
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,30 @@
1
+ require "find"
2
+
3
+ module Hecks
4
+ module Bluebook
5
+ # Finds domain declaration folders without loading or executing them.
6
+ class ProjectDiscovery
7
+ SKIPPED_DIRECTORIES = %w[.git .bundle data node_modules target tmp vendor generated coverage].freeze
8
+
9
+ attr_reader :root
10
+
11
+ def initialize(root)
12
+ @root = File.expand_path(root)
13
+ end
14
+
15
+ def bluebook_directories
16
+ found = []
17
+ Find.find(root) do |path|
18
+ if File.directory?(path)
19
+ if SKIPPED_DIRECTORIES.include?(File.basename(path))
20
+ Find.prune
21
+ elsif File.basename(path) == "bluebook" && Dir.glob(File.join(path, "*.bluebook")).any?
22
+ found << path
23
+ end
24
+ end
25
+ end
26
+ found.sort
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,40 @@
1
+ require_relative "project_discovery"
2
+ require_relative "project_register"
3
+
4
+ module Hecks
5
+ module Bluebook
6
+ # Boots each discovered directory and feeds its declarations into the
7
+ # deterministic project register. Discovery and FQN registration live in
8
+ # their own objects so either can be used without executing domains.
9
+ class ProjectLoader
10
+ Entry = ProjectRegister::Entry
11
+ DuplicateFqn = ProjectRegister::DuplicateFqn
12
+ MissingRealm = ProjectRegister::MissingRealm
13
+ LatestMismatch = ProjectRegister::LatestMismatch
14
+
15
+ attr_reader :root, :discovery, :register
16
+
17
+ def self.load(root) = new(root).load
18
+
19
+ def initialize(root, discovery: ProjectDiscovery.new(root), register: ProjectRegister.new)
20
+ @root = File.expand_path(root)
21
+ @discovery = discovery
22
+ @register = register
23
+ end
24
+
25
+ def load
26
+ discovery.bluebook_directories.each do |directory|
27
+ runtime = Runtime.boot(directory)
28
+ register.register(runtime.registry.bluebooks.values, runtime.registry, runtime, directory)
29
+ end
30
+ self
31
+ end
32
+
33
+ def entries = register.entries
34
+ def fetch(address) = register.fetch(address)
35
+ def include?(address) = register.include?(address)
36
+ def commands = register.commands
37
+ def queries = register.queries
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,82 @@
1
+ module Hecks
2
+ module Bluebook
3
+ # The callable catalogue for a discovered project. It knows how Bluebook
4
+ # declarations become public FQNs, but never searches or boots folders.
5
+ class ProjectRegister
6
+ Entry = Struct.new(:fqn, :source_directory, :dispatcher, :declared_verb, :domain_version, keyword_init: true) do
7
+ def command? = fqn.command?
8
+ def query? = fqn.query?
9
+ end
10
+
11
+ class DuplicateFqn < StandardError; end
12
+ class MissingRealm < StandardError; end
13
+ class LatestMismatch < StandardError; end
14
+
15
+ attr_reader :entries
16
+
17
+ def initialize
18
+ @entries = {}
19
+ end
20
+
21
+ def fetch(address) = entries.fetch(address.to_s)
22
+ def include?(address) = entries.key?(address.to_s)
23
+ def commands = entries.values.select(&:command?)
24
+ def queries = entries.values.select(&:query?)
25
+
26
+ def register(bluebooks, registry, dispatcher, directory)
27
+ bluebooks.each do |bluebook|
28
+ world = registry.world(bluebook.name)
29
+ realm = world&.realm
30
+ raise MissingRealm, "#{bluebook.name} in #{directory} has no world realm" if realm.to_s.empty?
31
+ if world.latest && bluebook.version && world.latest != bluebook.version
32
+ raise LatestMismatch,
33
+ "#{bluebook.name} in #{directory} declares latest #{world.latest.inspect}, not #{bluebook.version.inspect}"
34
+ end
35
+
36
+ bluebook.aggregates.each do |aggregate|
37
+ aggregate.commands.each do |command|
38
+ add(Fqn.command(realm: realm, domain: bluebook.name, version: bluebook.version,
39
+ aggregate: aggregate.hecks_name, command: command.hecks_name), directory, dispatcher, command.hecks_name, bluebook.version) if bluebook.version
40
+ add(Fqn.command(realm: realm, domain: bluebook.name, aggregate: aggregate.hecks_name,
41
+ command: command.hecks_name), directory, dispatcher, command.hecks_name, bluebook.version) if current?(
42
+ bluebook, world
43
+ )
44
+ end
45
+ aggregate.queries.each do |query|
46
+ name = Naming.snake(query.name)
47
+ add(Fqn.query(realm: realm, domain: bluebook.name, version: bluebook.version,
48
+ aggregate: aggregate.hecks_name, query: name), directory, dispatcher, query.name, bluebook.version) if bluebook.version
49
+ add(Fqn.query(realm: realm, domain: bluebook.name, aggregate: aggregate.hecks_name,
50
+ query: name), directory, dispatcher, query.name, bluebook.version) if current?(bluebook, world)
51
+ end
52
+ end
53
+ bluebook.read_models.each do |model|
54
+ add(Fqn.query(realm: realm, domain: bluebook.name, version: bluebook.version,
55
+ query: model.query_name), directory, dispatcher, model.query_name, bluebook.version) if bluebook.version
56
+ add(Fqn.query(realm: realm, domain: bluebook.name, query: model.query_name),
57
+ directory, dispatcher, model.query_name, bluebook.version) if current?(bluebook, world)
58
+ end
59
+ end
60
+ self
61
+ end
62
+
63
+ private
64
+
65
+ def current?(bluebook, world)
66
+ bluebook.version.nil? || world.latest == bluebook.version
67
+ end
68
+
69
+ def add(fqn, directory, dispatcher, declared_verb, domain_version)
70
+ existing = entries[fqn.to_s]
71
+ if existing
72
+ raise DuplicateFqn, "#{fqn} is declared in both #{existing.source_directory} and #{directory}"
73
+ end
74
+
75
+ entries[fqn.to_s] = Entry.new(
76
+ fqn: fqn, source_directory: directory, dispatcher: dispatcher,
77
+ declared_verb: declared_verb, domain_version: domain_version
78
+ )
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,61 @@
1
+ require_relative "behaviour/query"
2
+
3
+ module Hecks
4
+ module Bluebook
5
+ def self.render_value(value) = Literal.render(value)
6
+
7
+ # A query — an ASK, declared on an aggregate or on one of its entities.
8
+ #
9
+ # It crosses over as an INSTANCE rather than a class, and the reason is worth
10
+ # stating because it is the boundary of the pattern. A query inherits its
11
+ # whole body from `QuerySpecification::Common::Options` — `wheres`,
12
+ # `order_by`, `limit`, `offset`, `cursor`,
13
+ # `authorization`, `null_semantics`, `inspection` — and those
14
+ # are INSTANCE methods that the runtime and the SQLite adapter both read.
15
+ # Hoisting the declaration onto a metaclass would put the identity and the
16
+ # specification on opposite sides of the object.
17
+ #
18
+ # And nothing is lost, because `Class#name` was the only reason a construct
19
+ # ever needed a second word for its own name. An instance answers `name`
20
+ # truthfully. So a query gains an owner and an identity — which is what the
21
+ # graph is for — and keeps the name it always had.
22
+ class Query < QuerySpecification::Common::Options
23
+ include Construct
24
+
25
+ include Hecks::IR
26
+ include Behaviour::Query
27
+
28
+ emits_ir(
29
+ name: :name,
30
+ description: :description,
31
+ attributes: many(:attributes),
32
+ wheres: many(:wheres),
33
+ order_by: one(:order_by),
34
+ limit: one(:limit)
35
+ )
36
+
37
+ attr_reader :name, :description, :attributes
38
+
39
+ def initialize(name:, description: nil, attributes: [], wheres: [],
40
+ order_by: nil, limit: nil, offset: nil, cursor: nil,
41
+ authorization: nil, null_semantics: nil,
42
+ inspection: nil)
43
+ null_semantics ||= QuerySpecification::Common::NullSemantics.default
44
+ super(wheres: wheres, order_by: order_by, limit: limit, offset: offset, cursor: cursor,
45
+ authorization: authorization,
46
+ null_semantics: null_semantics, inspection: inspection)
47
+ @name = name.to_s
48
+ @hecks_name = @name
49
+ @description = description
50
+ @attributes = attributes
51
+ end
52
+
53
+ # A query's shape is NOT fixed — `extra_options_to_h` carries
54
+ # whatever options the specification layer grew (count, median,
55
+ # group_by, scope_to). Declared emission covers the settled part
56
+ # and `super` hands it over; the tail stays dynamic, which is the
57
+ # honest description of it.
58
+ def to_h = super.merge(extra_options_to_h)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,109 @@
1
+ require_relative "behaviour/read_model"
2
+
3
+ module Hecks
4
+ module Bluebook
5
+ # A read model — an ask that gathers heads from more than one aggregate.
6
+ #
7
+ # Like a query it crosses over as an INSTANCE, and for the same reason: its
8
+ # body is inherited from `QuerySpecification::ReadModel::Specification`, whose
9
+ # readers the runtime and the SQLite adapter both call on the object. It gains
10
+ # an identity and an owner ; it keeps the name it always had, because only a
11
+ # CLASS ever had a competing answer for `name`.
12
+ class ReadModel < QuerySpecification::ReadModel::Specification
13
+ include Construct
14
+
15
+ include Hecks::IR
16
+ include Behaviour::ReadModel
17
+
18
+ emits_ir(
19
+ name: :name,
20
+ description: :description,
21
+ reference_name: :reference_name,
22
+ reference_target: :reference_target,
23
+ query_name: :query_name,
24
+ wheres: many(:wheres),
25
+ order_by: one(:order_by),
26
+ limit: one(:limit)
27
+ )
28
+
29
+ attr_reader :name, :description, :reference_name, :reference_target, :aggregate_heads, :group_by,
30
+ :count, :median_field
31
+
32
+ # `reference_name:`/`reference_target:` are nil for a ROOTLESS read
33
+ # model (no `reference_to` declared) — `&.` throughout, rather than
34
+ # the `.to_s`/`.to_sym` this used to require unconditionally, so
35
+ # `reference_target.nil?` stays a real, checkable fact for the
36
+ # interpreter instead of silently becoming `""`.
37
+ def initialize(name:, description: nil, reference_name: nil, reference_target: nil, aggregate_heads: [],
38
+ group_by: [], count: nil, median_field: nil, **options)
39
+ super(joins: aggregate_heads, **options)
40
+ @name = name.to_s
41
+ @hecks_name = @name
42
+ @description = description
43
+ @reference_name = reference_name&.to_sym
44
+ @reference_target = reference_target&.to_s
45
+ @aggregate_heads = aggregate_heads
46
+ # Hash rows (`{field: :agg}`), same shape as `aggregate_heads` —
47
+ # `group_by_fields` is the convenience reader everything but
48
+ # `to_h`/the Judge's own generic walk actually wants.
49
+ @group_by = group_by
50
+ # ABSENT IS NOT FALSE. `@count` stays nil rather than becoming
51
+ # `false` when undeclared — the same "if you declare it, declare
52
+ # something" reading `Lifecycle`'s own optional fields rely on
53
+ # (MetaValidator::Judge#setters skips a setter whose every
54
+ # source is `nil`, so an ALWAYS-false `@count` would dispatch
55
+ # `ReadModel.Count` on every read model that never wrote the
56
+ # word at all). `!!count` collapses a truthy DSL `true` (or a
57
+ # reconstructed `true`) to the same `true`/`nil` pair either path
58
+ # produces — never `false`.
59
+ @count = count ? true : nil
60
+ @median_field = median_field&.to_sym
61
+ end
62
+
63
+ # `.to_sym` regardless of source — the DIRECT builder path stores
64
+ # symbols, but `Reconstruction::Shapes#group_by_field` (the
65
+ # replayed-from-the-meta-domain path every REAL boot actually
66
+ # goes through) reads the field back as a String, the same way
67
+ # `aggregate_heads`' own `:as` does. Row hash KEYS built by
68
+ # `Value.materialize_unwrapped` are symbols (`attr.name`), so
69
+ # this has to be too, or `row[field]` in `nest` silently misses.
70
+
71
+ # `wheres`/`order_by`/`limit` are spelled explicitly here, the SAME
72
+ # mechanism `Query#to_h` already uses (query.rb, read directly
73
+ # before this was written) — always present, `wheres` a (possibly
74
+ # empty) array and `order_by`/`limit` nil when undeclared, exactly
75
+ # like a Query's. `extra_options_to_h` still excludes these three by
76
+ # name (options.rb), so nothing here double-spells them; the two
77
+ # constructs now share one encoding for the same three words rather
78
+ # than a reader needing to learn a second one. See this file's own
79
+ # `filtered_head_name` and language/bluebook/syntax.bluebook's
80
+ # `ReadModel` `where`/`order_by`/`limit` member rows for the history
81
+ # of why this WAS narrower, and 2026-08-11's read-model where/
82
+ # order_by/limit task for why it stopped being.
83
+ # Same dynamic tail as a Query's, plus two collections whose
84
+ # ROWS are plain hashes rather than constructs — they are
85
+ # normalised here rather than by `many`, which recurses through
86
+ # `to_h` and would have nothing to call.
87
+ # `count`/`median_field` are ABSENT (no key at all), not `nil`,
88
+ # when undeclared — unlike `group_by` (always a `[]`, never
89
+ # missing) they are the FIRST fields this construct has ever
90
+ # added that no OLDER real corpus member could possibly carry, so
91
+ # merging them unconditionally would have put a `null` onto every
92
+ # existing read model's own wire shape (banking's
93
+ # `ComplianceDashboard`/`CustomerPortfolio`, the language's own
94
+ # `WholeBluebook`, ...) for a fact nothing about them changed —
95
+ # the exact `extra_options_to_h` reads for `cursor`/`offset`/etc,
96
+ # applied here for the same reason.
97
+ def to_h
98
+ reductions = {}
99
+ reductions[:count] = true if @count
100
+ reductions[:median_field] = @median_field.to_s if @median_field
101
+ super
102
+ .merge(aggregate_heads: @aggregate_heads.map { |head| head.merge(as: head[:as].to_s) })
103
+ .merge(group_by: @group_by.map { |row| row.merge(field: row[:field].to_s) })
104
+ .merge(reductions)
105
+ .merge(extra_options_to_h)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,74 @@
1
+ module Hecks
2
+ module Bluebook
3
+ # An attribute that points at another aggregate's head.
4
+ #
5
+ # This used to be the STRING `"Reference<Customer>"`, minted by
6
+ # `AggregateBuilder#reference_to` and `CommandBuilder#cross_reference` from
7
+ # a real constant that had just been handed in, and then parsed back apart
8
+ # by a regex in the command interpreter, by string equality in the read-model
9
+ # interpreter and the SQLite adapter, and by `delete_prefix` in the bluebook
10
+ # builder. Five readers of a spelling one writer invented.
11
+ #
12
+ # It holds the TARGET instead, and answers `resolve` with the target's
13
+ # Aggregate. Resolution is LAZY and deliberately so: `reference_to
14
+ # Customer` may name an aggregate declared lower in the file — banking's
15
+ # Account points at Customer and survives only because Customer happens to
16
+ # be written above — so the edge cannot be resolved at declaration time.
17
+ # It resolves through the chapter's own IR (`Bluebook#aggregate`),
18
+ # which is scoped by construction : the lookup cannot walk anywhere but
19
+ # this chapter's declared heads, so a same-named aggregate in another
20
+ # loaded domain is unreachable rather than defended against.
21
+ #
22
+ # `to_s` still spells `"Reference<Customer>"`, because `Attribute#to_h` is
23
+ # part of the pinned byte-for-byte export contract. IR objects in
24
+ # the graph, strings in the export.
25
+ class Reference
26
+ attr_reader :target_name
27
+
28
+ # The Aggregate whose declaration carries this reference — the way
29
+ # up to the chapter, stamped once every sibling has been read.
30
+ attr_accessor :declared_in
31
+
32
+ def initialize(target_name)
33
+ @target_name = Naming.demodulise(target_name).to_s
34
+ end
35
+
36
+ # The Aggregate this points at, or nil when the target belongs to
37
+ # ANOTHER domain — a cross-domain target may legitimately not be loaded,
38
+ # the same reading `across` policies get.
39
+ def resolve
40
+ unless declared_in
41
+ raise DSL::Malformed,
42
+ "#{self} cannot say which aggregate declares it, so it cannot " \
43
+ "resolve — a reference that resolves to nothing is checked " \
44
+ "against nothing"
45
+ end
46
+
47
+ declared_in.hecks_owner&.aggregate(@target_name)
48
+ end
49
+
50
+ # The IR spelling, the one the export carries.
51
+ def to_s = "Reference<#{@target_name}>"
52
+ def inspect = "#<Reference #{@target_name}>"
53
+
54
+ # VALUE EQUALITY, not identity — without this, two references to
55
+ # the SAME target, parsed from two SEPARATE bluebook reads (era
56
+ # N's own boot and a held era's own shadow reconstruction,
57
+ # coverage_check.rb's own comparison), are different objects and
58
+ # compare unequal by Ruby's default `==`. `EraGuard::ShapeDiff
59
+ # #diff_type`'s `held_type != current_type` check then reads as
60
+ # true for EVERY reference_to attribute on EVERY mint, regardless
61
+ # of whether the reference actually changed — a real refusal for
62
+ # attributes nothing about. `declared_in` (which aggregate carries
63
+ # this reference) is deliberately excluded: the same reference
64
+ # attribute exists once, but its `held_type`/`current_type` come
65
+ # from separately-parsed bluebooks with structurally different
66
+ # (if same-shaped) owning aggregates, and `declared_in` is a
67
+ # cross-reference for `resolve`, not part of what this attribute
68
+ # itself IS.
69
+ def ==(other) = other.is_a?(Reference) && target_name == other.target_name
70
+ alias eql? ==
71
+ def hash = [self.class, target_name].hash
72
+ end
73
+ end
74
+ end