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,50 @@
1
+ module Hecks
2
+ module Bluebook
3
+ module Behaviour
4
+ # WHAT A HECKSAGON DOES — the lookups over its declared binds.
5
+ module Hecksagon
6
+ # Aggregate-specific bind wins when one was declared; otherwise
7
+ # falls back to a domain-level default (`b.aggregate.nil?` — see
8
+ # `HecksagonBuilder#method_missing`). Checked directly rather than
9
+ # via `aggregate_name`/`Naming.demodulise`, which would need its own
10
+ # nil-handling for the default row.
11
+ def bind_for(aggregate_name, verb)
12
+ @binds.find { |b| b.aggregate_name == aggregate_name.to_s && b.verb.to_s == verb.to_s } ||
13
+ @binds.find { |b| b.aggregate.nil? && b.verb.to_s == verb.to_s }
14
+ end
15
+
16
+ def binds_for(aggregate_name, verb)
17
+ specific = @binds.select { |b| b.aggregate_name == aggregate_name.to_s && b.verb.to_s == verb.to_s }
18
+ return specific if specific.any?
19
+
20
+ @binds.select { |b| b.aggregate.nil? && b.verb.to_s == verb.to_s }
21
+ end
22
+ end
23
+
24
+ # WHAT A WORLD DOES — settings lookup, with the adapter-specific
25
+ # entry falling back to the verb's own.
26
+ module World
27
+ def for_verb(verb) = @settings.fetch(verb.to_s, {})
28
+
29
+ # The generic `verb` entry (`persisted_by("Heki") do dir :default
30
+ # end`) only answers for the adapter it actually names — falling
31
+ # back to it unconditionally applies one adapter's settings to an
32
+ # unrelated one. Real, corpus-caught bug: a hecksagon binding two
33
+ # aggregates to two different adapters under the SAME verb (one to
34
+ # Heki, one to Memory) sent Memory's lookup down Heki's generic
35
+ # entry, then failed `check_settings` with "Memory does not
36
+ # declare :dir" — the generic entry's own `settings[:adapter]`
37
+ # names Heki, not Memory, so the fallback was never actually for
38
+ # this bind. `{}` is exactly right when nothing was configured for
39
+ # THIS adapter — Memory, which takes no values at all.
40
+ def for_binding(verb, adapter)
41
+ qualified = @settings["#{verb}:#{adapter.to_s.downcase}"]
42
+ return qualified if qualified
43
+
44
+ generic = for_verb(verb)
45
+ generic[:adapter].to_s.downcase == adapter.to_s.downcase ? generic : {}
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,68 @@
1
+ module Hecks
2
+ module Bluebook
3
+ module Behaviour
4
+ # WHAT A LIFECYCLE DOES. Its declared half is a field, a starting
5
+ # state and a transition list. Everything here READS that — which
6
+ # states exist, which transition a command takes, and how one
7
+ # declared transition expands into the several rows the emission
8
+ # carries when `from` names more than one source state.
9
+ module Lifecycle
10
+ def states
11
+ ([default] + transitions.map { |_command, t| t.target }).uniq
12
+ end
13
+
14
+ def transitions_for(command)
15
+ transitions.select { |name, _| name == command.to_s }.map { |_, t| t }
16
+ end
17
+
18
+ def target_for(command, current_state = nil)
19
+ match_transition(command, current_state)&.target
20
+ end
21
+
22
+ private
23
+
24
+ # ONE DECLARED TRANSITION IS SEVERAL ROWS when `from` names more
25
+ # than one source state — the emission carries them flat, so the
26
+ # fan-out happens here rather than in whatever reads it.
27
+ def expand(command, transition)
28
+ sources = transition.from.nil? ? [nil] : Array(transition.from)
29
+
30
+ sources.map do |source|
31
+ { command: command, to_state: transition.target, from_state: source }
32
+ end
33
+ end
34
+
35
+ def match_transition(command, current_state)
36
+ matches = transitions_for(command)
37
+ return nil if matches.empty?
38
+ return matches.first unless current_state
39
+
40
+ # NOT `|| matches.first` — that used to silently hand back an
41
+ # ARBITRARY declared transition for `command` whenever none of
42
+ # them actually admitted `current_state`, picking a `target`
43
+ # that command dispatch would in fact have refused (that
44
+ # refusal is `CommandRules::Admissibility#admissible_transition`'s
45
+ # own job, which raises `LifecycleRefused` for exactly this
46
+ # case rather than guessing — this module has no state subject
47
+ # to build that refusal message from, only the two bare
48
+ # values callers passed in). Zero production callers reach
49
+ # this branch today (both real dispatch paths that check a
50
+ # transition go through `admissible_transition`, not
51
+ # `target_for`/`match_transition`), so this raise is a
52
+ # backstop, not a live behaviour change — same shape as
53
+ # `CommandRules::Arithmetic#sign_of`'s own fix.
54
+ matches.find { |t| applies_from?(t, current_state) } ||
55
+ raise(Runtime::WiringError,
56
+ "no transition for #{command.inspect} admits state #{current_state.inspect} " \
57
+ "— add a from: covering it, or check admissibility before calling #target_for")
58
+ end
59
+
60
+ def applies_from?(transition, current)
61
+ return true unless transition.from
62
+
63
+ Array(transition.from).map(&:to_s).include?(current.to_s)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,52 @@
1
+ module Hecks
2
+ module Bluebook
3
+ module Behaviour
4
+ # WHAT A POLICY DOES. Its declared half is four plain fields; these
5
+ # are the readings taken off them.
6
+ module Policy
7
+ # The BLUEBOOK's name for this construct, asked the same way of a class
8
+ # that has crossed over and of an IR object that has not. Collapses into
9
+ # Construct when this one crosses.
10
+ def hecks_name = @name
11
+
12
+ def event_qualifier = Naming.qualifier(@on_event)
13
+
14
+ def event_name = Naming.unqualified(@on_event)
15
+
16
+ # WHETHER THIS POLICY FANS OUT — `for_each` names a query, and a
17
+ # non-empty one turns a single reaction into one dispatch per row
18
+ # the query answers. Read by the interpreter that runs the fan-out
19
+ # and by the property that checks it dispatched once per row.
20
+ def fans_out? = !@for_each.to_s.empty?
21
+
22
+ # WHETHER THIS POLICY IS GUARDED — a non-empty `where` decides
23
+ # whether the policy fires at all, read against the triggering
24
+ # event's own payload.
25
+ def guarded? = !@where.to_s.empty?
26
+
27
+ # THE FAN-OUT QUERY'S ROUTE, split the way the runtime runs it:
28
+ # `[query_domain, aggregate_name, query_name]`. The query runs
29
+ # against the triggering event's OWN domain unless `for_each`
30
+ # names one ("Domain::Aggregate.query"). Deliberately independent
31
+ # of `across`/`target_domain`, which name where `trigger` fires,
32
+ # not where the fan-out's own query runs.
33
+ def for_each_route(default_domain)
34
+ path, query_name = @for_each.to_s.split(".", 2)
35
+ domain, aggregate = path.to_s.include?("::") ? path.split("::", 2) : [default_domain.to_s, path]
36
+ [domain, aggregate, query_name]
37
+ end
38
+
39
+ # THE ARGUMENT NAME A FAN-OUT DISPATCH mints each matched row's id
40
+ # under used to be minted HERE, unconditionally, as `<aggregate>
41
+ # _id` — a real bug: a target command declared ON the very
42
+ # aggregate it self-references (`Account.Freeze`) is addressed by
43
+ # its OWN identity field's name, not a synthetic foreign key, and
44
+ # every such dispatch refused. That question is not this policy's
45
+ # to answer at all — it depends on the TARGET COMMAND'S OWN
46
+ # declared shape, not on the aggregate name alone — so it now
47
+ # lives on `Behaviour::Command#addressing_key_for`, asked of the
48
+ # resolved target command by `PolicyInterpreter#addressing_key_for`.
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,51 @@
1
+ module Hecks
2
+ module Bluebook
3
+ module Behaviour
4
+ # WHAT A PROCESS MANAGER DOES. Its declared half is the trigger,
5
+ # the states and the handlers; the compensation half — `saga` — is
6
+ # DERIVED from the handler that answers a refusal, not declared.
7
+ module ProcessManager
8
+ def hecks_name = @name
9
+
10
+ def handler_for(event) = @handlers.find { |h| h.event_type == event.to_s }
11
+
12
+ # WHETHER A STATE IS ONE THIS PROCEDURE DECLARES — asked of a value
13
+ # a real run left a saga instance holding (its live or rehydrated
14
+ # state), the way `Lifecycle#states` is asked of an aggregate's
15
+ # resting field. A rehydrated instance in a state no handler could
16
+ # have reached is corruption the durable round-trip introduced.
17
+ def declares_state?(state) = @states.map(&:to_s).include?(state.to_s)
18
+
19
+ def correlation_head = @correlates_by.to_s.split(".").first.to_sym
20
+
21
+ # The compensation half of a procedure, read off the handler that
22
+ # answers REFUSED.
23
+ #
24
+ # nil for a procedure with no answer to a refusal, which is a legitimate
25
+ # thing to be — a hiring pipeline cannot un-interview anybody.
26
+ def saga
27
+ leg = handler_for(Bluebook::ProcessManager::REFUSED)
28
+ return nil unless leg
29
+
30
+ # `compensations` — a STATIC PREVIEW, declaration order, not
31
+ # one instance's own runtime history (which legs a given
32
+ # instance actually completed is per-instance state,
33
+ # `SagaInterpreter`'s own `completed_compensations`, not a
34
+ # fact `Saga` — a pure declaration reading — could ever hold).
35
+ # Every `compensates` ANY handler's own dispatch declares,
36
+ # forward declaration order, THEN whatever this leg's own
37
+ # hand-written body still lists — coexistence, not replacement
38
+ # (`ProcessManagerBuilder::HandlerBuilder#dispatch_impl`'s own
39
+ # comment): a saga can derive some of its compensation and
40
+ # still hand-write the rest for what isn't expressible as
41
+ # "undo command X".
42
+ derived = handlers.flat_map { |handler| handler.dispatches.filter_map(&:compensates) }
43
+ Bluebook::Saga.new(trigger: Bluebook::ProcessManager::REFUSED, from_state: leg.from_state,
44
+ to_state: leg.to_state, compensations: derived + leg.dispatches)
45
+ end
46
+
47
+ def saga? = !saga.nil?
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,10 @@
1
+ module Hecks
2
+ module Bluebook
3
+ module Behaviour
4
+ # WHAT A QUERY DOES beyond holding its declared shape.
5
+ module Query
6
+ def attribute(named) = @attributes.find { |a| a.name == named.to_sym }
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,29 @@
1
+ module Hecks
2
+ module Bluebook
3
+ module Behaviour
4
+ # WHAT A READ MODEL DOES. Its declared half is the gathered heads
5
+ # and the query shape; these are readings taken off them.
6
+ module ReadModel
7
+ def group_by_fields = @group_by.map { |row| row[:field].to_sym }
8
+
9
+ # `!!` rather than a bare `@count` — the DSL/reconstruction
10
+ # boundary (ReadModel#initialize) already normalises to
11
+ # `true`/`nil`, so this is belt and braces against a future
12
+ # caller constructing a ReadModel by hand with `count: false`.
13
+ def count? = !!@count
14
+
15
+ def query_name = Naming.snake(@name)
16
+
17
+ # WHICH GATHERED HEAD THE FILTERING APPLIES TO, so the read-model
18
+ # interpreter can ask this directly rather than re-deriving or
19
+ # re-checking it.
20
+ def filtered_head_name
21
+ return nil unless wheres.any? || order_by || limit || offset || authorization&.tenant ||
22
+ @group_by.any? || count? || @median_field
23
+
24
+ @aggregate_heads.find { |head| head[:many] }&.fetch(:as)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,81 @@
1
+ module Hecks
2
+ module Bluebook
3
+ module Behaviour
4
+ # THE BEHAVIOUR MORE THAN ONE CONSTRUCT SHARES.
5
+ #
6
+ # None of this is new duplication. `attribute(named)` was written
7
+ # out four separate times — on Aggregate, Entity, Command and
8
+ # PortOperation — and the identity derivation twice, byte for byte,
9
+ # for as long as those constructs have existed. Splitting holding
10
+ # from doing is what made them visible: the same method in four
11
+ # files reads as four methods, and the same method in four modules
12
+ # named `Behaviour::*` reads as one repeated.
13
+ #
14
+ # Written to work whether the construct is INCLUDED into (an
15
+ # ordinary object like Aggregate) or EXTENDED into (a class-shaped
16
+ # one like Command), because every one of them reads instance
17
+ # variables and nothing else.
18
+
19
+ # A CONSTRUCT WHOSE IDENTITY IS A JOIN OF DECLARED PATHS.
20
+ #
21
+ # THE PATHS, IN DECLARATION ORDER, because the identity IS their join.
22
+ # "number.value" says which field carries the identity ; several paths
23
+ # say the identity is made of several facts, which is what anything
24
+ # named beneath another thing needs. `identity_heads` are the attributes
25
+ # those paths start at — what every reader that looks up or coerces an
26
+ # attribute actually wants — and `identified_by` is the single head,
27
+ # offered ONLY when there is one path to have a head of. A composite has
28
+ # no single head, and answering with the first would be a guess ; the
29
+ # readers that need all of them ask for `identity_heads`.
30
+ module Identified
31
+ def derive_identity
32
+ @identity_paths = Array(@identified_by).map { |path| path.to_s }.reject(&:empty?)
33
+ @identity_heads = @identity_paths.map { |path| path.split(".").first.to_sym }.uniq
34
+ @identified_by = @identity_heads.size == 1 ? @identity_heads.first : nil
35
+ end
36
+ end
37
+
38
+ # A CONSTRUCT THAT ANSWERS FOR ITS OWN DECLARATIONS BY NAME.
39
+ #
40
+ # Indexed once, since the declarations are final by the time the
41
+ # construct exists — every dispatch asks these finders by name, and a
42
+ # linear scan repeated on every call was doing work the declared shape
43
+ # had already settled at boot.
44
+ #
45
+ # `index_by_name` takes the collections to index rather than naming
46
+ # them, because WHICH collections a construct has is exactly the part
47
+ # that differs: an aggregate has five, an entity three, a command one.
48
+ module Indexed
49
+ # Keyed by SYMBOL — an attribute is asked for by its declared
50
+ # symbol name everywhere in the runtime.
51
+ def index_attributes(attributes)
52
+ @attributes_by_name = attributes.to_h { |held| [held.name, held] }
53
+ end
54
+
55
+ # Keyed by STRING and by `hecks_name` — a command, query or value
56
+ # object is a construct whose Ruby `name` is something else
57
+ # entirely (a constant path, or nothing at all for an anonymous
58
+ # class), so the DECLARED name is the only one worth indexing.
59
+ def index_by_hecks_name(collection)
60
+ collection.to_h { |held| [held.hecks_name, held] }
61
+ end
62
+
63
+ def attribute(named) = @attributes_by_name[named.to_sym]
64
+ def command(named) = @commands_by_name[named.to_s]
65
+ def query(named) = @queries_by_name[named.to_s]
66
+ end
67
+
68
+ # A CONSTRUCT THAT OWNS WHAT IT DECLARES.
69
+ #
70
+ # Owner links are only ever read lazily — hecks_fqn at ask time,
71
+ # Reference#resolve at dispatch time — so the moment the construct
72
+ # is complete is the right stamping point: its declarations are
73
+ # final, and nothing outside needs to remember to stamp them.
74
+ module Owns
75
+ def stamp(*children)
76
+ children.flatten.each { |child| child.hecks_owner = self }
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,33 @@
1
+ module Hecks
2
+ module Bluebook
3
+ module Behaviour
4
+ # WHAT A VALUE OBJECT DOES. EXTENDED, not included — a value object
5
+ # is a CLASS (`Class.new(self)`, one per declared shape), so its
6
+ # behaviour is singleton behaviour, and the holding half's `absorb`
7
+ # is what a generated constructor would be.
8
+ module ValueObject
9
+ # A one_of DECLARED but left empty used to be indistinguishable from no
10
+ # one_of at all — both are `members: []` — so the rule about it could
11
+ # only live in the builder. Recording the declaration lets the language
12
+ # judge it, the same way an empty attribute NAME survives into the IR
13
+ # and is judged there.
14
+ def closed_set? = @closed_set
15
+
16
+ def attribute(named) = attributes.find { |held| held.name == named.to_sym }
17
+
18
+ # A single-attribute value object (EmailAddress{address},
19
+ # CustomerNumber{value}) is a NAME for a scalar, not a genuine
20
+ # group — [[feedback_name_the_scalar_field]]. Four call sites
21
+ # already inline this exact check (`attributes.size == 1` /
22
+ # `attributes.first`) — `forms/field_shape.rb` (twice),
23
+ # `adapters/driven/sql_query_builder.rb`,
24
+ # `fuzzing/invalid_value_generator.rb` — kept as-is for now
25
+ # (not migrated to this reader), but any NEW caller should
26
+ # reach for this rather than add a fifth inline copy.
27
+ def sole_attribute
28
+ attributes.first if attributes.size == 1
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,78 @@
1
+ require_relative "behaviour/chapter"
2
+ require_relative "../ir"
3
+
4
+ module Hecks
5
+ module Bluebook
6
+ # THE CHAPTER, and no longer the namespace it lives in.
7
+ #
8
+ # `Hecks::Bluebook` was briefly a CLASS, because dropping the
9
+ # old `IR::` segment would otherwise have produced
10
+ # `Bluebook::Bluebook` — a class shadowing its own enclosing module.
11
+ # Naming the construct what the code already called it everywhere
12
+ # (a chapter; this file was already chapter.rb) removes the clash
13
+ # at the source, so the namespace goes back to being a module.
14
+ #
15
+ # Measured before changing: `Bluebook` was used AS a class in five
16
+ # places and as a namespace in seventy-seven files.
17
+ class Chapter
18
+ include Construct
19
+ include Behaviour::Chapter
20
+ include Hecks::IR
21
+
22
+ # THE SCHEMA'S OWN VERSION — not a domain's `version:` (Banking's
23
+ # "v1", a business fact the AUTHOR chose), but the shape `to_h`
24
+ # itself emits. A consumer reading exported IR with no Ruby DSL to
25
+ # cross-check against (a build-time generator, a stored snapshot)
26
+ # needs to know WHICH shape it's holding before it can safely
27
+ # interpret any of the rest of it. Bump this when `to_h`'s own
28
+ # shape changes in a way a consumer would need to know about —
29
+ # not when a domain's own declarations change.
30
+ IR_VERSION = 1
31
+
32
+ emits_ir(
33
+ ir_version: -> { IR_VERSION },
34
+ name: :name,
35
+ version: :version,
36
+ vision: :vision,
37
+ classification: :classification,
38
+ # M10 — a domain rename (`formerly_known_as "OldName"`) drives a
39
+ # real Postgres schema rename at boot (EraResolver reads
40
+ # `bluebook.formerly_known_as` off the live Ruby object) and the
41
+ # meta-validator's cache key is `SHA256(JSON(bluebook.to_h))` — so
42
+ # a fact this method didn't spell was a fact two chapters
43
+ # differing ONLY by their old name could hash identically on,
44
+ # same as read-model filters before them. Spelled here for the
45
+ # same reason `version`/`vision` are: a plain field, present
46
+ # (possibly nil) rather than silently absent.
47
+ formerly_known_as: :formerly_known_as,
48
+ aggregates: many(:aggregates),
49
+ read_models: many(:read_models),
50
+ policies: many(:policies),
51
+ process_managers: many(:process_managers),
52
+ attaches_to: :attaches_to,
53
+ canonical_form: -> { Expression::CanonicalForm.table }
54
+ )
55
+
56
+ attr_reader :name, :version, :vision, :aggregates, :policies, :process_managers,
57
+ :classification, :read_models, :ports, :formerly_known_as, :attaches_to
58
+
59
+ def initialize(name:, version: nil, vision: nil, aggregates: [], policies: [],
60
+ process_managers: [], classification: nil, read_models: [], formerly_known_as: nil,
61
+ attaches_to: [])
62
+ @policies = policies
63
+ @process_managers = process_managers
64
+ @name = name.to_s
65
+ @hecks_name = @name
66
+ @hecks_root = true
67
+ @version = version&.to_s
68
+ @vision = vision
69
+ @aggregates = aggregates
70
+ @read_models = read_models
71
+ @classification = classification&.to_s
72
+ @formerly_known_as = formerly_known_as&.to_s
73
+ @attaches_to = Array(attaches_to).map(&:to_s)
74
+ settle
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,124 @@
1
+ require_relative "behaviour/command"
2
+ require_relative "../vocabulary"
3
+
4
+ module Hecks
5
+ module Bluebook
6
+ Given = Struct.new(:description, :canonical, :predicate, keyword_init: true)
7
+
8
+ Mutation = Struct.new(:target, :op, :source, keyword_init: true) do
9
+ include Hecks::IR
10
+ include Behaviour::Mutation
11
+
12
+ # `sign:` — item #5 of the whole-project table-unification survey.
13
+ # `increment`/`decrement`'s own +1/-1 used to be re-derived from the
14
+ # op NAME by two independent Rust codegen scripts (rust/project/
15
+ # mutations.rb, rust/codegen/src/mutations.rs — a ternary on
16
+ # `op == "increment"` in each), even though the fact was already
17
+ # table-driven on the Ruby runtime side
18
+ # (Runtime::CommandRules::Arithmetic::MUTATION_OPS, held equal to
19
+ # Vocabulary::MutationOp by spec/vocabulary_conformance_spec.rb).
20
+ # Reads `Vocabulary::MutationOp` directly (plain data, no framework
21
+ # dependency — safe during parsing, same reason `RuleReference`'s
22
+ # own bootstrap concerns don't apply here) rather than requiring
23
+ # Runtime::CommandRules from the bluebook/IR layer, which would be a
24
+ # real layering inversion (runtime depends on bluebook, not the
25
+ # reverse). "" (not nil) for ops with no sign, matching every other
26
+ # optional IR text field's own absent-is-empty-string convention.
27
+ def self.sign_for(op)
28
+ Hecks::Vocabulary.rows("MutationOp").find { |row| row["name"] == op.to_s }&.fetch("sign", "") || ""
29
+ end
30
+
31
+ emits_ir(target: :target, op: :op, sign: -> { Mutation.sign_for(op) })
32
+
33
+ # THE ONE GENUINELY BRANCHING EMISSION in the model: an append (or a
34
+ # DELEGATE — `CommandBuilder#delegates_to`'s own comment gives the
35
+ # full reasoning for reusing this exact wire shape rather than
36
+ # inventing a parallel one) binds several fields at once and carries
37
+ # `fields:`, everything else carries a single `source:`. Declared
38
+ # emission covers the fixed head; `super` supplies it and this adds
39
+ # the tail, which is why a construct with a variable shape needs no
40
+ # new mixin API.
41
+ def to_h
42
+ return super.merge(fields: appended_fields) if [:append, :delegate, :corrects].include?(op)
43
+
44
+ super.merge(source: classified_source)
45
+ end
46
+ end
47
+
48
+ # A command, as a RUBY CLASS.
49
+ #
50
+ # NOT nested as a constant, and that is a finding rather than a shortcut. A
51
+ # command and a value object may legitimately share a name inside one
52
+ # aggregate — the language does it six times, and means it: the command
53
+ # `Argument` is the verb that appends to the `arguments` list whose element
54
+ # type is the value object `Argument`, and `Plan` reads exactly that pairing.
55
+ # So `Bluebook::Command::Argument` cannot be both, and a single constant
56
+ # namespace cannot index a kind-ambiguous name. The same follows for
57
+ # `hecks_fqn` : `Bluebook::Command.Argument` names both, which is why the judge's
58
+ # ids only work per-category, each in its own repository. IDENTITY IS
59
+ # (KIND, FQN), not fqn.
60
+ #
61
+ # It is a declaration holder anyway, because that is where the EDGES live.
62
+ # `acts_on` answers with the owning construct itself — the Aggregate,
63
+ # or the entity holder for a piece's verb — rather than the name of one.
64
+ # The invocation door (`pizza.add_topping`) is the facade's business, a
65
+ # per-boot projection ; no verb method is defined here.
66
+ class Command
67
+ extend Construct
68
+ extend Hecks::IR
69
+ extend Behaviour::Command
70
+
71
+ emits_ir(
72
+ name: :hecks_name,
73
+ role: :role,
74
+ goal: :goal,
75
+ references: :references,
76
+ attributes: many(:attributes),
77
+ givens: -> { givens.map { |rule| { description: rule.description, canonical: rule.canonical } } },
78
+ ensures: -> { ensures.map { |rule| { description: rule.description, canonical: rule.canonical } } },
79
+ mutations: many(:mutations),
80
+ emits: :emits,
81
+ # THE LIFECYCLE STATE THIS COMMAND IS ADMISSIBLE FROM (S10, ADR
82
+ # 0025 — "lifecycle state becomes a command guard") — a GUARD,
83
+ # not a transition: `command "Debit", from: "open"` replaces
84
+ # `given("account is open") { status == "open" }`, checked
85
+ # against the owning construct's own lifecycle field the same
86
+ # way `admissible_transition` already checks a real state
87
+ # change, but names no target state and moves nothing. Nil for
88
+ # a command with no such guard — most commands.
89
+ from: -> { from },
90
+ provenance: :provenance
91
+ )
92
+
93
+ class << self
94
+ attr_reader :role, :goal, :attributes, :givens, :ensures, :mutations, :emits, :references,
95
+ :from, :provenance
96
+
97
+ def declare(name:, role: nil, goal: nil, attributes: [], givens: [], ensures: [],
98
+ mutations: [], emits: [], references: nil, from: nil, provenance: nil)
99
+ verb = Class.new(self)
100
+ verb.hecks_name = name.to_s
101
+ verb.absorb(role: role, goal: goal, attributes: attributes, givens: givens,
102
+ ensures: ensures, mutations: mutations, emits: emits, references: references&.to_s,
103
+ from: from, provenance: provenance)
104
+ verb
105
+ end
106
+
107
+ def absorb(role:, goal:, attributes:, givens:, ensures:, mutations:, emits:, references:,
108
+ from: nil, provenance: nil)
109
+ @role = role
110
+ @goal = goal
111
+ @attributes = attributes
112
+ @givens = givens
113
+ @ensures = ensures
114
+ @mutations = mutations
115
+ @emits = emits
116
+ @references = references
117
+ @from = from
118
+ @provenance = provenance
119
+ settle
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end