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,81 @@
1
+ module Hecks
2
+ module Bluebook
3
+ module DSL
4
+ module ConstShim
5
+ class << self
6
+ attr_accessor :resolver
7
+
8
+ def with(resolver)
9
+ previous = @resolver
10
+ @resolver = resolver
11
+ yield
12
+ ensure
13
+ @resolver = previous
14
+ end
15
+
16
+ def active? = !@resolver.nil?
17
+ end
18
+
19
+ # THE SCOPED-CONSTANT BRIDGE (ADR 0025, docs/dsl-work-slices.md's
20
+ # S0b) — a Symbol cannot answer `::`, so `Account::Debit` and
21
+ # `admits: Account::LedgerDirection` could never resolve past
22
+ # their FIRST segment while `Account` returned a bare Symbol
23
+ # (`resolver = ->(const) { const }`, bluebook_builder.rb's own
24
+ # comment on why that was right for a BARE name): Ruby's `::`
25
+ # operator raises `TypeError` on the returned value before any
26
+ # DSL code runs at all, unless that value is itself a Module.
27
+ #
28
+ # A REAL `Module` subclass, not a decorated Symbol, for exactly
29
+ # that reason — nothing else answers `::`. Every existing
30
+ # consumer keeps working duck-typed, not because nothing
31
+ # changed: `Attribute#spell`'s `type.is_a?(Module)` branch now
32
+ # fires where it used to fall to `type.to_s`, and
33
+ # `Naming.demodulise` (`path.split("::").last`) gives the
34
+ # IDENTICAL string either way for a single segment — the two
35
+ # branches were already equivalent for a name with no `::` in
36
+ # it, which is every bareword before this.
37
+ class ScopedConstant < Module
38
+ def self.for(path) = new(path.to_s)
39
+
40
+ def initialize(path)
41
+ super()
42
+ @path = path
43
+ end
44
+
45
+ # ONE MORE SEGMENT, the same way an unresolved const anywhere
46
+ # else does — `Account::Debit::Anything` keeps chaining rather
47
+ # than refusing, since nothing here knows how deep a reference
48
+ # is meant to go; the DSL keyword that finally reads `.to_s`
49
+ # is the one place that does.
50
+ def const_missing(name) = ScopedConstant.for("#{@path}::#{name}")
51
+
52
+ def to_s = @path
53
+ def to_sym = @path.to_sym
54
+ def inspect = @path
55
+ def hecks_path = @path
56
+
57
+ def ==(other) = other.is_a?(ScopedConstant) ? @path == other.hecks_path : @path.to_sym == other
58
+ alias eql? ==
59
+ def hash = @path.hash
60
+ end
61
+
62
+ # A SCOPED NAME IS WRITTEN AS TEXT, NOT AS A CONSTANT PATH — see the
63
+ # note on `admits:` in AttributeCollector. A resolver returning a
64
+ # Module (so that `Vocabulary::QueryComparator` reaches a second
65
+ # `const_missing`) was tried and cannot be made to hold : `Facade::
66
+ # Surface` installs EVERY aggregate name as a top-level constant, so
67
+ # once any facade is built, `Vocabulary` resolves to that real module
68
+ # and never reaches this hook at all. A spelling that works only
69
+ # before a facade exists is worse than one that always works.
70
+ module Hook
71
+ def const_missing(name)
72
+ resolver = ConstShim.resolver
73
+ resolver ? resolver.call(name) : super
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ Object.singleton_class.prepend(Hecks::Bluebook::DSL::ConstShim::Hook)
@@ -0,0 +1,79 @@
1
+ require_relative "word_gate"
2
+ module Hecks
3
+ module Bluebook
4
+ module DSL
5
+ class DomainPortBuilder
6
+ GRAMMAR_CONTEXT = "DomainPort"
7
+
8
+ include WordGate
9
+
10
+ def initialize(name, owner: nil)
11
+ @name = name
12
+ @owner = owner
13
+ @operations = []
14
+ end
15
+
16
+ # WHAT THE OUTSIDE TELLS US — an external fact arriving, translated
17
+ # into this domain's own word for it. Spelled `operation` before it
18
+ # had a twin, and `operation` still works: the corpus is full of it,
19
+ # and renaming a word costs every chapter that uses it for no gain a
20
+ # reader can feel.
21
+ #
22
+ # RENAMED FROM `tells` — item #13's full metaprogrammed dispatch
23
+ # (slice 4c). `operation`/`tells` are TWO separate Keyword rows
24
+ # (a word admitting two forms) that both name `calls: "tells_impl"`
25
+ # — the routing between the two spellings now lives in the table,
26
+ # not in a Ruby `alias`. Not bootstrap-reachable (checked
27
+ # directly), so no BOOTSTRAP_CALLS_FALLBACK entry needed.
28
+ def tells_impl(name, to: nil, &block)
29
+ @operations << PortOperationBuilder.build(name, to: to, owner: @owner, direction: :inbound, &block)
30
+ end
31
+
32
+ # WHAT WE ASK OF THE OUTSIDE — the direction this language did not
33
+ # have. Before this, a domain could be CALLED by an adapter and never
34
+ # call one. An `asks` is dispatched like any other port operation, so
35
+ # a `policy` can trigger it off an event, and it comes back as one of
36
+ # the two events it named — which is what makes the outside world
37
+ # something the model can reason about rather than a place exceptions
38
+ # come from.
39
+ #
40
+ # RENAMED FROM `asks` — item #13's full metaprogrammed dispatch
41
+ # (slice 4c), same reasoning as tells_impl above.
42
+ def asks_impl(name, to: nil, &block)
43
+ @operations << PortOperationBuilder.build(name, to: to, owner: @owner, direction: :outbound, &block)
44
+ end
45
+
46
+ # THE DRIVEN HALF OF THE SAME WORD. `operation`/`emits` translates an
47
+ # inbound fact into this domain's own event vocabulary — there is no
48
+ # channel back to a caller beyond the events it emits. `verb` is the
49
+ # opposite direction: the domain calling OUT to a swappable adapter
50
+ # and getting a real value back (a checkout URL, a fetched document),
51
+ # exactly what `Hecks.port "name" do verb "x" end` already builds —
52
+ # this is that same `Port`, reached from the same `port` call
53
+ # `operation` already lives under, so a project's own resource ports
54
+ # read next to their binding instead of in a separate file. One port,
55
+ # one shape or the other — never both.
56
+ # `verb` — item #13's full metaprogrammed dispatch, slice 1
57
+ # (whole-project table-unification survey): a bare, kind-driven
58
+ # coerce-and-assign with nothing else, now executed by
59
+ # `GenericDispatch`.
60
+
61
+ def build
62
+ raise Malformed, "#{@name} declares both a verb and operations — a port is one or the other, not both" if @verb && !@operations.empty?
63
+
64
+ return Port.new(name: @name, verb: @verb, signal: :reply) if @verb
65
+
66
+ raise Malformed, "#{@name} declares no verb and no operations" if @operations.empty?
67
+
68
+ DomainPort.new(name: @name, operations: @operations)
69
+ end
70
+
71
+ def self.build(name, owner: nil, &block)
72
+ builder = new(name, owner: owner)
73
+ builder.instance_eval(&block) if block
74
+ builder.build
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,430 @@
1
+ require_relative "word_gate"
2
+ module Hecks
3
+ module Bluebook
4
+ module DSL
5
+ class EntityBuilder
6
+ GRAMMAR_CONTEXT = "Entity"
7
+
8
+ include AttributeCollector
9
+ include IdentityDeclaration
10
+ include RuleReference
11
+ include WordGate
12
+
13
+ def initialize(name, owner_value_objects: [], owner_named_givens: {},
14
+ identity_name_prefix: nil, identity_value_object_installer: nil,
15
+ aggregate_name: nil, chapter_entity_named_givens: {}, chapter_entity_pending_givens: [])
16
+ @name = name
17
+ @commands = []
18
+ @queries = []
19
+ @entities = []
20
+ @named_givens = {}
21
+ @invariants = []
22
+ @owner_value_objects = owner_value_objects
23
+ @identity_name_prefix = identity_name_prefix || Naming.demodulise(name)
24
+ @identity_value_object_installer = identity_value_object_installer
25
+ # THE AGGREGATE-WIDE cross-entity given pool — ONE hash, the
26
+ # SAME object, threaded unchanged through every piece nested
27
+ # under one aggregate however deep (the identical shape
28
+ # `@owner_value_objects` already threads — see this class'
29
+ # own `entity` comment). `given`'s own block form writes
30
+ # through to it; a SIBLING piece's bare command-level
31
+ # reference reads from it via `CommandBuilder#
32
+ # reference_named_given`.
33
+ @owner_named_givens = owner_named_givens
34
+ # ONE LEVEL WIDER STILL — the CHAPTER-WIDE, ENTITY-SCOPED pool
35
+ # (the piece analogue of `AggregateBuilder#@chapter_named_givens`,
36
+ # one level down). `@aggregate_name` names THIS piece's own
37
+ # root, so the write-through below can key itself
38
+ # "AggregateName.EntityName" — the same dotted addressing
39
+ # convention `declared_by:` already uses chapter-wide, one
40
+ # level up. See `#given_impl`'s own comment for what this
41
+ # closes and `docs/implemented/resolution-rules/
42
+ # chapter-entity-given.md` for the full algorithm.
43
+ @aggregate_name = aggregate_name || Naming.demodulise(name)
44
+ @chapter_entity_named_givens = chapter_entity_named_givens
45
+ @chapter_entity_pending_givens = chapter_entity_pending_givens
46
+ # DEFERRED CONSTRUCTION — see `AggregateBuilder#drain_pending!`'s
47
+ # own comment; the identical mechanism, one level down, so a
48
+ # nested piece's own commands (Dispatch inside Handler) see
49
+ # every SIBLING entity/command/query this piece goes on to
50
+ # declare, not just whatever came before it textually.
51
+ @pending_entities = []
52
+ @pending_commands = []
53
+ @pending_queries = []
54
+ end
55
+
56
+ def description(value) = @description = value
57
+
58
+ # THE SAME FIELD AggregateBuilder's OWN reference_to BUILDS — a
59
+ # piece can hold a reference to another root exactly the way its
60
+ # own head can (Card.assignee_id, a Team's own id), just never
61
+ # to another PIECE, since there's no cross-piece addressing
62
+ # anywhere in this language to resolve one against.
63
+ # RENAMED FROM `reference_to` — item #13's full metaprogrammed
64
+ # dispatch (slice 4b). Bootstrap-reachable, in
65
+ # GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.
66
+ def reference_to_impl(type, as: nil, optional: false)
67
+ target = Naming.demodulise(type)
68
+ relationship_attribute(target, :reference_to,
69
+ as || default_reference_name(target), optional: optional)
70
+ end
71
+
72
+ # has_many_impl/has_one_impl are DSL declaration keywords a domain
73
+ # author writes as bare has_many/has_one (matching belongs_to_impl
74
+ # alongside them) — not real predicates, so renaming to many?/one?
75
+ # per Naming/PredicatePrefix would break every bluebook that
76
+ # declares one. New on Entity (Wave 6, identity-and-relationships
77
+ # arc) — pieces never had relationship words before; named
78
+ # `*_impl` to match AggregateBuilder's own siblings, item #13's
79
+ # full metaprogrammed dispatch convention.
80
+ # rubocop:disable Naming/PredicatePrefix
81
+ def has_many_impl(type, as: nil, **options)
82
+ unless options.empty?
83
+ raise Malformed, "#{@name}.has_many takes no #{options.keys.first}: — an empty list already means none"
84
+ end
85
+
86
+ plural = Naming.demodulise(type)
87
+ relationship_attribute(Naming.singularize(plural), :has_many,
88
+ as || Naming.snake(plural).to_sym, list: true)
89
+ end
90
+
91
+ def has_one_impl(type, as: nil, optional: false)
92
+ target = Naming.demodulise(type)
93
+ relationship_attribute(target, :has_one, as || Naming.snake(target).to_sym, optional: optional)
94
+ end
95
+ # rubocop:enable Naming/PredicatePrefix
96
+
97
+ def belongs_to_impl(type, as: nil, optional: false)
98
+ target = Naming.demodulise(type)
99
+ relationship_attribute(target, :belongs_to, as || Naming.snake(target).to_sym, optional: optional)
100
+ end
101
+
102
+ # A PIECE is known by a field, not by a whole value object.
103
+ # `identified_by :sequence` names the SCALAR inside it, which is
104
+ # what an id actually is — a LedgerEntry is entry 3, not entry
105
+ # {"value":3}. `identified_by` itself is AttributeCollector's own
106
+ # shared method (S9) — the two constructs cannot drift apart in
107
+ # how they spell an identity, including composite
108
+ # (`identified_by :branch_code, :box_number`), which a piece may
109
+ # declare for the same reason a head may.
110
+
111
+ # `from:` — see `AggregateBuilder#command`'s own comment; the
112
+ # SAME guard, checked against this PIECE's own lifecycle field
113
+ # (S10, ADR 0025 — a piece's own state machine is checkable the
114
+ # same way a head's is).
115
+ # RENAMED FROM `command`/`query`/`entity`/`lifecycle` (all below)
116
+ # — item #13's full metaprogrammed dispatch (slice 4c), same
117
+ # reasoning as AggregateBuilder's own siblings: bootstrap-
118
+ # reachable, in BOOTSTRAP_CALLS_FALLBACK.
119
+ def command_impl(name, from: nil, &block)
120
+ @pending_commands << [name, from, block]
121
+ end
122
+
123
+ def query_impl(name, &block)
124
+ @pending_queries << [name, block]
125
+ end
126
+
127
+ # S17, ADR 0026 — A PIECE NESTED INSIDE A PIECE. "A `Dispatch`
128
+ # [has] no life outside its `Handler`" (the ADR's own words) —
129
+ # the same reason `Member` nests inside `ValueObject`, one level
130
+ # further in. `owner_value_objects` passes straight through
131
+ # unchanged, not re-derived from this entity's own attributes —
132
+ # a piece mints no value objects of its own at any depth, so a
133
+ # NESTED piece's bare `identified_by :field` still resolves
134
+ # against the SAME root aggregate's value objects an outer
135
+ # piece's already does (`AggregateBuilder#entity`'s own comment
136
+ # names this pool ; there is exactly one of them, however deep
137
+ # the nesting goes).
138
+ def entity_impl(name, &block)
139
+ @pending_entities << [name, block]
140
+ end
141
+
142
+ def lifecycle_impl(field, default:, &block)
143
+ @lifecycle = LifecycleBuilder.build(field, default: default, &block)
144
+ end
145
+
146
+ # A PRECONDITION SHARED ACROSS THIS PIECE'S OWN COMMANDS, DECLARED
147
+ # ONCE — the same move `AggregateBuilder#given` already makes,
148
+ # one level down. Real, live redundancy this closes: banking's
149
+ # own `LedgerEntry.Amend`/`LedgerEntry.Reverse` each repeated
150
+ # `given("customer is active") { parent.customer.status ==
151
+ # "active" }` and `given("account is open") { parent.status ==
152
+ # "open" }`, byte for byte, because a piece had no way to declare
153
+ # either once and reference it back — only the AGGREGATE could.
154
+ # DECLARE BEFORE THE COMMANDS THAT REFERENCE IT, the same
155
+ # ordering `AggregateBuilder#given`'s own comment names — though
156
+ # since ADR 0028, `command` only queues a descriptor and actually
157
+ # builds at `#drain_pending!` time, well after this whole block
158
+ # (including every `given` in it) has already run, so textual
159
+ # order within the block no longer actually matters here; named
160
+ # for the reader anyway, since `given`'s own resolution logic
161
+ # (`CommandBuilder#reference_named_given`) still reads whatever
162
+ # `@named_givens` holds AT THE COMMAND'S OWN BUILD TIME, not by
163
+ # magic.
164
+ # RENAMED FROM `given` — item #13's full metaprogrammed dispatch
165
+ # (slice 4b), same reasoning as reference_to_impl above.
166
+ #
167
+ # BARE — NO BLOCK — REFERENCES ANOTHER PIECE'S OWN DECLARATION,
168
+ # ANYWHERE IN THE CHAPTER, not just a sibling under this same
169
+ # aggregate — one level wider than round 4's own cross-entity
170
+ # sharing, mirroring `AggregateBuilder#given_impl`'s own
171
+ # chapter-wide shape exactly one level down. Real, live corpus
172
+ # this closes: `Account::LedgerEntry` and `SafeDepositBox::Visit`
173
+ # — two pieces under two DIFFERENT aggregates — independently
174
+ # typed `given("customer is active") { parent.customer.status ==
175
+ # "active" }` byte for byte; neither the aggregate-level chapter
176
+ # pool (a DIFFERENT canonical — bare `customer.status`, the
177
+ # wrong scope for a piece's own command) nor the existing
178
+ # same-aggregate cross-entity pool (`@owner_named_givens`, scoped
179
+ # to ONE aggregate's own entity tree) could reach across the
180
+ # aggregate boundary. Resolved against `@chapter_entity_named_
181
+ # givens`, keyed "AggregateName.EntityName" — see
182
+ # `#reference_named_chapter_entity_given`'s own comment for the
183
+ # algorithm and `docs/implemented/resolution-rules/
184
+ # chapter-entity-given.md` for the full write-up.
185
+ #
186
+ # `declared_by:` is a PLAIN STRING ("Account.LedgerEntry"), not a
187
+ # constant — unlike `AggregateBuilder#given_impl`'s own
188
+ # `declared_by:`, which names a real aggregate constant. A piece
189
+ # has no first-class, independently-addressable reference
190
+ # anywhere in this language (only its owning aggregate does);
191
+ # inventing one to make this ONE argument spelling symmetrical
192
+ # with the aggregate-level word is a real, separate, unscoped
193
+ # feature this fix does not need — ships textual now, the same
194
+ # way `admits:` shipped textual before its own constant-bridge
195
+ # existed, revisited only if a genuine, separate need for
196
+ # constant-addressed pieces shows up later.
197
+ def given_impl(description, declared_by: nil, &predicate)
198
+ return reference_named_chapter_entity_given(description, declared_by: declared_by) unless predicate
199
+
200
+ named = build_rule(Given, description, predicate, owner_name: @name, word: "given",
201
+ extraction_failure: "its source could not be read, so no other runtime could ever evaluate it")
202
+ @named_givens[description] = named
203
+ # WRITE-THROUGH, first-declared-wins (`||=`) — a SECOND piece
204
+ # under the same aggregate independently declaring the exact
205
+ # same description stays purely local to itself (no silent
206
+ # overwrite of whatever the first piece already shared;
207
+ # real, live case a fuzzer or a future codemod could easily
208
+ # surface: two pieces phrasing an UNRELATED rule identically
209
+ # by coincidence, same as an aggregate-level given already
210
+ # tolerates today).
211
+ @owner_named_givens[description] ||= named
212
+ # WRITE-THROUGH, PER OWNER — the chapter-wide analogue of the
213
+ # line above, keyed by [description, this piece's own dotted
214
+ # "Aggregate.Entity" name] rather than description alone, the
215
+ # identical reasoning `AggregateBuilder#given_impl`'s own
216
+ # chapter write-through gives: two DIFFERENT pieces (anywhere
217
+ # in the chapter) independently declaring the SAME description
218
+ # are two DISTINCT candidates a later bare reference chooses
219
+ # between (via `declared_by:` once there is more than one),
220
+ # never silently merged into one slot.
221
+ @chapter_entity_named_givens[description] ||= {}
222
+ @chapter_entity_named_givens[description]["#{@aggregate_name}.#{@name}"] ||= named
223
+ end
224
+
225
+ # A PIECE'S OWN SHAPE RULE (S10, ADR 0025's own "Rules" shape,
226
+ # one level down from `ValueObjectBuilder#invariant`, whose
227
+ # extraction/error pattern this mirrors) — checked against
228
+ # EVERY INSTANCE of this piece the aggregate holds, not once
229
+ # against the aggregate's own flat state
230
+ # (`Admissibility#enforce_invariants`'s own recursive walk).
231
+ # No reference-by-name form (unlike `given`) — no known corpus
232
+ # need for a piece's own invariant to be shared with a SIBLING
233
+ # piece yet; if that need shows up, it is `given`'s own
234
+ # cross-entity write-through pattern to extend, not a reason to
235
+ # invent a second one here speculatively.
236
+ # RENAMED FROM `invariant` — item #13's full metaprogrammed
237
+ # dispatch (slice 4b), same reasoning as given_impl above.
238
+ def invariant_impl(description, &predicate)
239
+ @invariants << build_rule(Invariant, description, predicate, owner_name: @name, word: "invariant",
240
+ extraction_failure: "it would be a rule the IR cannot carry")
241
+ end
242
+
243
+ def build
244
+ drain_pending!
245
+ resolve_pending_identity!
246
+ seal_lifecycle_guards
247
+ install_closed_sets!
248
+ Entity.declare(
249
+ name: @name,
250
+ description: @description,
251
+ identified_by: @identity_paths,
252
+ attributes: attributes,
253
+ commands: @commands,
254
+ queries: @queries,
255
+ entities: @entities,
256
+ preconditions: @named_givens.values,
257
+ invariants: @invariants,
258
+ lifecycle: @lifecycle
259
+ )
260
+ end
261
+
262
+ def self.build(name, owner_value_objects: [], owner_named_givens: {},
263
+ identity_name_prefix: nil, identity_value_object_installer: nil,
264
+ aggregate_name: nil, chapter_entity_named_givens: {}, chapter_entity_pending_givens: [], &block)
265
+ builder = new(name, owner_value_objects: owner_value_objects, owner_named_givens: owner_named_givens,
266
+ identity_name_prefix: identity_name_prefix,
267
+ identity_value_object_installer: identity_value_object_installer,
268
+ aggregate_name: aggregate_name,
269
+ chapter_entity_named_givens: chapter_entity_named_givens,
270
+ chapter_entity_pending_givens: chapter_entity_pending_givens)
271
+ builder.instance_eval(&block) if block
272
+ builder.build
273
+ end
274
+
275
+ private
276
+
277
+ # PRIMITIVE 2 (RuleReference#resolve_owner_keyed) — the CHAPTER-
278
+ # WIDE, ENTITY-SCOPED analogue of `AggregateBuilder#
279
+ # reference_named_chapter_given`; the three branches below are
280
+ # this construct's OWN refusal wording, not shared, matching that
281
+ # method's own precedent (`declared_by:` only exists on `given`
282
+ # so far, at either scope). UNRESOLVED is deferred, not raised
283
+ # here — see `#pending_chapter_entity_given`, below.
284
+ #
285
+ # WRITES THROUGH TO `@owner_named_givens` TOO — not just
286
+ # `@named_givens` — or this piece resolving a description via the
287
+ # WIDER, chapter pool would leave the NARROWER, same-aggregate
288
+ # pool (`EntityBuilder#given_impl`'s own block-form write-through)
289
+ # never populated for this description, breaking any SIBLING
290
+ # piece's existing command-level bare reference
291
+ # (`CommandBuilder#reference_named_given`) that depends on it —
292
+ # real, live corpus: `SafeDepositBox::KeyIssuance.Return`'s own
293
+ # bare `given("customer is active")` resolves through
294
+ # `@owner_named_givens`, populated by `Visit`'s declaration
295
+ # whether `Visit` types the predicate itself OR (now) references
296
+ # `Account::LedgerEntry`'s instead — this write keeps that
297
+ # working unchanged either way, `||=` so nothing here overrides
298
+ # an actual local declaration if one is ever added later.
299
+ def reference_named_chapter_entity_given(description, declared_by:)
300
+ verify_resolves_via!("given", "Entity", "owner_keyed")
301
+ candidates = resolve_owner_keyed(@chapter_entity_named_givens, description)
302
+
303
+ named =
304
+ if declared_by
305
+ candidates[declared_by] || pending_chapter_entity_given(description, declared_by: declared_by)
306
+ elsif candidates.size == 1
307
+ candidates.values.first
308
+ elsif candidates.empty?
309
+ pending_chapter_entity_given(description, declared_by: nil)
310
+ else
311
+ raise(Malformed,
312
+ "#{@aggregate_name}::#{@name}'s given #{description.inspect} is ambiguous " \
313
+ "across the chapter's own pieces — #{candidates.keys.join(', ')} each declare " \
314
+ "a DIFFERENT predicate under this same description; name which one with " \
315
+ "declared_by: (e.g. given(#{description.inspect}, declared_by: " \
316
+ "#{candidates.keys.first.inspect}))")
317
+ end
318
+
319
+ @named_givens[description] = named
320
+ @owner_named_givens[description] ||= named
321
+ end
322
+
323
+ # A CHAPTER MAY BE SPLIT ACROSS FILES — the identical reason
324
+ # `AggregateBuilder#pending_chapter_given` defers rather than
325
+ # raising the moment a bare reference outruns what's loaded so
326
+ # far. Hands back a PLACEHOLDER `Given`, embedded by Ruby object
327
+ # reference in this piece's own `preconditions`, and queues the
328
+ # request in `@chapter_entity_pending_givens` —
329
+ # `BluebookBuilder#resolve_pending_chapter_entity_givens!`
330
+ # mutates it in place once every file in the chapter has loaded.
331
+ def pending_chapter_entity_given(description, declared_by:)
332
+ placeholder = Given.new(description: description, canonical: nil, predicate: nil)
333
+ @chapter_entity_pending_givens << { entity: "#{@aggregate_name}.#{@name}", description: description,
334
+ declared_by: declared_by, placeholder: placeholder }
335
+ placeholder
336
+ end
337
+
338
+ # A PIECE'S OWN `one_of` LANDS ON ITS AGGREGATE. A type-position
339
+ # `one_of("never_moved", "moved")` on an entity attribute
340
+ # synthesizes a closed-set value object — and until this, that
341
+ # object was built and then DROPPED: `Entity.declare` carries no
342
+ # value objects, so the synthesized set existed nowhere in the
343
+ # finished graph. The attribute stayed typed "Moved" with nothing
344
+ # to resolve it: runtime admission had no closed set to enforce
345
+ # (the one_of was decorative), and the fuzzer's ValueGenerator
346
+ # crashed every run on the first domain to declare one — a chess
347
+ # King/Rook's own castling flag — with `does not know primitive
348
+ # type "Moved"`. Installed through the SAME hook an entity's own
349
+ # identity value object already rides to the aggregate
350
+ # (`identity_value_object_installer`, threaded unchanged through
351
+ # nested pieces). Two sibling pieces synthesizing the same set
352
+ # (King's and Rook's own `moved`) install it once; the same NAME
353
+ # with a DIFFERENT member list is refused as the collision it is,
354
+ # never first-wins silently.
355
+ def install_closed_sets!
356
+ return unless @identity_value_object_installer
357
+
358
+ closed_sets.each do |value_object|
359
+ existing = @owner_value_objects.find { |candidate| candidate.hecks_name == value_object.hecks_name }
360
+ if existing
361
+ next if existing.to_h == value_object.to_h
362
+
363
+ raise Malformed,
364
+ "#{@name}'s one_of synthesizes #{value_object.hecks_name.inspect}, but the aggregate already " \
365
+ "holds a different #{value_object.hecks_name.inspect} — name the closed set's field differently"
366
+ end
367
+
368
+ @identity_value_object_installer.call(value_object)
369
+ end
370
+ end
371
+
372
+ # See `AggregateBuilder#drain_pending!`'s own comment — the
373
+ # identical mechanism, one level down. Entities first and fully
374
+ # built (so a nested command's own `append:` resolution can read
375
+ # a sibling piece's `.attributes`), then commands, then queries.
376
+ def drain_pending!
377
+ @entities = @pending_entities.map do |name, block|
378
+ EntityBuilder.build(name, owner_value_objects: @owner_value_objects,
379
+ owner_named_givens: @owner_named_givens,
380
+ identity_name_prefix: "#{@identity_name_prefix}#{Naming.demodulise(name)}",
381
+ identity_value_object_installer: @identity_value_object_installer,
382
+ aggregate_name: @aggregate_name,
383
+ chapter_entity_named_givens: @chapter_entity_named_givens,
384
+ chapter_entity_pending_givens: @chapter_entity_pending_givens,
385
+ &block)
386
+ end
387
+
388
+ @commands = @pending_commands.map do |name, from, block|
389
+ CommandBuilder.build(name, owner: @name, from: from, named_givens: @named_givens,
390
+ owner_attributes: attributes,
391
+ owner_constructs: @owner_value_objects + @entities,
392
+ entity_shared_givens: @owner_named_givens, &block)
393
+ end
394
+
395
+ @queries = @pending_queries.map do |name, block|
396
+ QueryBuilder.build(name, owner_attributes: attributes, &block)
397
+ end
398
+ end
399
+
400
+ # See `AggregateBuilder#seal_lifecycle_guards`'s own comment —
401
+ # the identical check, one level down.
402
+ def seal_lifecycle_guards
403
+ return if @lifecycle
404
+
405
+ @commands.each do |command|
406
+ next unless command.from
407
+
408
+ raise Malformed,
409
+ "#{@name}.#{command.hecks_name} guards from: #{Array(command.from).inspect}, but " \
410
+ "#{@name} declares no lifecycle — from: checks a lifecycle field, and there is " \
411
+ "none here to check"
412
+ end
413
+ end
414
+
415
+ # `identified_by`'s own resolution pool (AttributeCollector#resolve_
416
+ # pending_identity!'s hook, S9) — a piece mints no value objects of
417
+ # its own, so a bare field's own type resolves against its OWNER
418
+ # aggregate's, passed in at declaration (`AggregateBuilder#entity`).
419
+ def identity_pool = @owner_value_objects
420
+
421
+ def identity_value_object_name = "#{@identity_name_prefix}Identity"
422
+
423
+ def install_identity_value_object!(value_object)
424
+ @identity_value_object_installer&.call(value_object)
425
+ @owner_value_objects << value_object unless @owner_value_objects.include?(value_object)
426
+ end
427
+ end
428
+ end
429
+ end
430
+ end