hecks 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (408) hide show
  1. checksums.yaml +5 -5
  2. data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +127 -0
  4. data/lib/hecks/adapters/driven/d1.adapter +12 -0
  5. data/lib/hecks/adapters/driven/d1.rb +403 -0
  6. data/lib/hecks/adapters/driven/folder.adapter +3 -0
  7. data/lib/hecks/adapters/driven/folder.rb +199 -0
  8. data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
  9. data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
  10. data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
  11. data/lib/hecks/adapters/driven/governance_authorization.rb +62 -0
  12. data/lib/hecks/adapters/driven/heki/journal.rb +51 -0
  13. data/lib/hecks/adapters/driven/heki/saga_store.rb +0 -0
  14. data/lib/hecks/adapters/driven/heki/snapshot.rb +38 -0
  15. data/lib/hecks/adapters/driven/heki.adapter +4 -0
  16. data/lib/hecks/adapters/driven/heki.rb +148 -0
  17. data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
  18. data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
  19. data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
  20. data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
  21. data/lib/hecks/adapters/driven/lambda.adapter +4 -0
  22. data/lib/hecks/adapters/driven/lambda.rb +131 -0
  23. data/lib/hecks/adapters/driven/memory.adapter +3 -0
  24. data/lib/hecks/adapters/driven/memory.rb +103 -0
  25. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
  26. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
  27. data/lib/hecks/adapters/driven/postgres/codec.rb +80 -0
  28. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +168 -0
  29. data/lib/hecks/adapters/driven/postgres.adapter +5 -0
  30. data/lib/hecks/adapters/driven/postgres.rb +391 -0
  31. data/lib/hecks/adapters/driven/postgres_era/lineage/era_store.rb +171 -0
  32. data/lib/hecks/adapters/driven/postgres_era/lineage/field_cache.rb +190 -0
  33. data/lib/hecks/adapters/driven/postgres_era/lineage/head_compiler.rb +429 -0
  34. data/lib/hecks/adapters/driven/postgres_era/lineage/mint_transaction.rb +166 -0
  35. data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +286 -0
  36. data/lib/hecks/adapters/driven/postgres_era/lineage/resumable_backfill.rb +168 -0
  37. data/lib/hecks/adapters/driven/postgres_era/lineage/tail_merge.rb +163 -0
  38. data/lib/hecks/adapters/driven/postgres_era/lineage/transform_installer.rb +114 -0
  39. data/lib/hecks/adapters/driven/postgres_era/lineage.rb +137 -0
  40. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb +89 -0
  41. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +82 -0
  42. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
  43. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/minter.rb +132 -0
  44. data/lib/hecks/adapters/driven/postgres_era/lineage_manager.rb +74 -0
  45. data/lib/hecks/adapters/driven/postgres_era.adapter +6 -0
  46. data/lib/hecks/adapters/driven/postgres_era.rb +671 -0
  47. data/lib/hecks/adapters/driven/prism.adapter +3 -0
  48. data/lib/hecks/adapters/driven/prism.rb +80 -0
  49. data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
  50. data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
  51. data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
  52. data/lib/hecks/adapters/driven/sqlite/codec.rb +63 -0
  53. data/lib/hecks/adapters/driven/sqlite/projection.rb +105 -0
  54. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +180 -0
  55. data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
  56. data/lib/hecks/adapters/driven/sqlite.rb +285 -0
  57. data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
  58. data/lib/hecks/adapters/driven/system_clock.rb +14 -0
  59. data/lib/hecks/adapters/driven.rb +36 -0
  60. data/lib/hecks/adapters.rb +6 -0
  61. data/lib/hecks/behaviors/dsl.rb +96 -0
  62. data/lib/hecks/behaviors/expectations.rb +280 -0
  63. data/lib/hecks/behaviors/ir.rb +31 -0
  64. data/lib/hecks/behaviors/rspec.rb +42 -0
  65. data/lib/hecks/behaviors/runner.rb +96 -0
  66. data/lib/hecks/behaviors.rb +25 -0
  67. data/lib/hecks/bluebook/aggregate.rb +108 -0
  68. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
  69. data/lib/hecks/bluebook/assembly/build.rb +48 -0
  70. data/lib/hecks/bluebook/assembly/contract.rb +112 -0
  71. data/lib/hecks/bluebook/assembly/contracts.rb +409 -0
  72. data/lib/hecks/bluebook/assembly/marks.rb +227 -0
  73. data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
  74. data/lib/hecks/bluebook/assembly.rb +78 -0
  75. data/lib/hecks/bluebook/attribute.rb +96 -0
  76. data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
  77. data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
  78. data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
  79. data/lib/hecks/bluebook/behaviour/command.rb +116 -0
  80. data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
  81. data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
  82. data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
  83. data/lib/hecks/bluebook/behaviour/lifecycle.rb +51 -0
  84. data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
  85. data/lib/hecks/bluebook/behaviour/process_manager.rb +38 -0
  86. data/lib/hecks/bluebook/behaviour/query.rb +10 -0
  87. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
  88. data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
  89. data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
  90. data/lib/hecks/bluebook/chapter.rb +68 -0
  91. data/lib/hecks/bluebook/command.rb +124 -0
  92. data/lib/hecks/bluebook/domain_port.rb +102 -0
  93. data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
  94. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +906 -0
  95. data/lib/hecks/bluebook/dsl/attribute_collector.rb +327 -0
  96. data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
  97. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1018 -0
  98. data/lib/hecks/bluebook/dsl/command_builder.rb +626 -0
  99. data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
  100. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
  101. data/lib/hecks/bluebook/dsl/entity_builder.rb +305 -0
  102. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
  103. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
  104. data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
  105. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
  106. data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
  107. data/lib/hecks/bluebook/dsl/policy_builder.rb +120 -0
  108. data/lib/hecks/bluebook/dsl/port_builder.rb +30 -0
  109. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
  110. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +207 -0
  111. data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
  112. data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
  113. data/lib/hecks/bluebook/dsl/rule_reference.rb +172 -0
  114. data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
  115. data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
  116. data/lib/hecks/bluebook/dsl/word_gate.rb +221 -0
  117. data/lib/hecks/bluebook/dsl/world_builder.rb +77 -0
  118. data/lib/hecks/bluebook/dsl.rb +45 -0
  119. data/lib/hecks/bluebook/entity.rb +103 -0
  120. data/lib/hecks/bluebook/expression/canonical_form.rb +55 -0
  121. data/lib/hecks/bluebook/expression/evaluator.rb +261 -0
  122. data/lib/hecks/bluebook/expression/projection.json +170 -0
  123. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
  124. data/lib/hecks/bluebook/expression/resolver.rb +630 -0
  125. data/lib/hecks/bluebook/expression.rb +13 -0
  126. data/lib/hecks/bluebook/hexagon.rb +60 -0
  127. data/lib/hecks/bluebook/lifecycle.rb +42 -0
  128. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
  129. data/lib/hecks/bluebook/meta_validator/judge.rb +559 -0
  130. data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
  131. data/lib/hecks/bluebook/meta_validator/port_judge.rb +47 -0
  132. data/lib/hecks/bluebook/meta_validator/readings.rb +350 -0
  133. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +312 -0
  134. data/lib/hecks/bluebook/meta_validator/shapes.rb +246 -0
  135. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
  136. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
  137. data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
  138. data/lib/hecks/bluebook/meta_validator.rb +452 -0
  139. data/lib/hecks/bluebook/model_check.rb +318 -0
  140. data/lib/hecks/bluebook/pattern_subset.rb +120 -0
  141. data/lib/hecks/bluebook/policy.rb +41 -0
  142. data/lib/hecks/bluebook/process_manager.rb +86 -0
  143. data/lib/hecks/bluebook/project_discovery.rb +30 -0
  144. data/lib/hecks/bluebook/project_loader.rb +40 -0
  145. data/lib/hecks/bluebook/project_register.rb +82 -0
  146. data/lib/hecks/bluebook/query.rb +61 -0
  147. data/lib/hecks/bluebook/read_model.rb +109 -0
  148. data/lib/hecks/bluebook/reference.rb +74 -0
  149. data/lib/hecks/bluebook/smoke_test.rb +166 -0
  150. data/lib/hecks/bluebook/synthesizer.rb +95 -0
  151. data/lib/hecks/bluebook/translation.rb +92 -0
  152. data/lib/hecks/bluebook/value_object.rb +58 -0
  153. data/lib/hecks/bluebook.rb +74 -0
  154. data/lib/hecks/codemod.rb +342 -0
  155. data/lib/hecks/construct.rb +71 -0
  156. data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
  157. data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
  158. data/lib/hecks/deploy/oidc.json +18 -0
  159. data/lib/hecks/doc/reference.rb +389 -0
  160. data/lib/hecks/embryonaut_bluebook.rb +75 -0
  161. data/lib/hecks/facade/cli_door.rb +116 -0
  162. data/lib/hecks/facade/cli_runner.rb +190 -0
  163. data/lib/hecks/facade/command_request.rb +105 -0
  164. data/lib/hecks/facade/handle.rb +173 -0
  165. data/lib/hecks/facade/json_door.rb +154 -0
  166. data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
  167. data/lib/hecks/facade/surface/chapter.rb +107 -0
  168. data/lib/hecks/facade/surface.rb +48 -0
  169. data/lib/hecks/facade.rb +44 -0
  170. data/lib/hecks/forms/app.rb +300 -0
  171. data/lib/hecks/forms/command_form_renderer.rb +113 -0
  172. data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
  173. data/lib/hecks/forms/field_renderer.rb +170 -0
  174. data/lib/hecks/forms/field_shape.rb +232 -0
  175. data/lib/hecks/forms/html.rb +53 -0
  176. data/lib/hecks/forms/index_renderer.rb +35 -0
  177. data/lib/hecks/forms/page.rb +157 -0
  178. data/lib/hecks/forms/params.rb +132 -0
  179. data/lib/hecks/forms/query_form_renderer.rb +114 -0
  180. data/lib/hecks/forms/record_renderer.rb +115 -0
  181. data/lib/hecks/forms/record_table.rb +63 -0
  182. data/lib/hecks/forms/reference_options.rb +30 -0
  183. data/lib/hecks/forms/value_object_shape.rb +46 -0
  184. data/lib/hecks/forms.rb +54 -0
  185. data/lib/hecks/fqn.rb +94 -0
  186. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
  187. data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
  188. data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
  189. data/lib/hecks/framework/bluebook/governance.bluebook +130 -0
  190. data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
  191. data/lib/hecks/framework/oidc.json +39 -0
  192. data/lib/hecks/framework.rb +90 -0
  193. data/lib/hecks/freezer.rb +67 -0
  194. data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
  195. data/lib/hecks/fuzzing/isolated_boot.rb +92 -0
  196. data/lib/hecks/fuzzing/properties.rb +1146 -0
  197. data/lib/hecks/fuzzing/replay.rb +635 -0
  198. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
  199. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +54 -0
  200. data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
  201. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +185 -0
  202. data/lib/hecks/fuzzing/sequence_generator.rb +130 -0
  203. data/lib/hecks/fuzzing/value_generator.rb +177 -0
  204. data/lib/hecks/fuzzing.rb +12 -0
  205. data/lib/hecks/grammar/evolve.rb +291 -0
  206. data/lib/hecks/grammar/expression.bluebook +417 -0
  207. data/lib/hecks/grammar/expression_operators.json +1152 -0
  208. data/lib/hecks/grammar/grammar.hecksagon +20 -0
  209. data/lib/hecks/grammar/oidc.json +69 -0
  210. data/lib/hecks/grammar/translation.bluebook +203 -0
  211. data/lib/hecks/grammar.rb +178 -0
  212. data/lib/hecks/ir.rb +126 -0
  213. data/lib/hecks/language/adapter.bluebook +116 -0
  214. data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
  215. data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
  216. data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
  217. data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
  218. data/lib/hecks/language/bluebook/command.bluebook +437 -0
  219. data/lib/hecks/language/bluebook/entity.bluebook +316 -0
  220. data/lib/hecks/language/bluebook/policy.bluebook +172 -0
  221. data/lib/hecks/language/bluebook/process_manager.bluebook +264 -0
  222. data/lib/hecks/language/bluebook/projection.bluebook +267 -0
  223. data/lib/hecks/language/bluebook/query.bluebook +245 -0
  224. data/lib/hecks/language/bluebook/shape.bluebook +292 -0
  225. data/lib/hecks/language/bluebook/syntax.bluebook +447 -0
  226. data/lib/hecks/language/bluebook/vocabulary.bluebook +364 -0
  227. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
  228. data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
  229. data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
  230. data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
  231. data/lib/hecks/language/oidc.json +313 -0
  232. data/lib/hecks/language/port.bluebook +92 -0
  233. data/lib/hecks/language/translation/translation.bluebook +110 -0
  234. data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
  235. data/lib/hecks/language/world/wiring.bluebook +62 -0
  236. data/lib/hecks/language/world/world.bluebook +84 -0
  237. data/lib/hecks/literal.rb +125 -0
  238. data/lib/hecks/naming.rb +121 -0
  239. data/lib/hecks/ports/access_control.port +4 -0
  240. data/lib/hecks/ports/access_control.rb +62 -0
  241. data/lib/hecks/ports/agent/answers.rb +104 -0
  242. data/lib/hecks/ports/agent.port +4 -0
  243. data/lib/hecks/ports/agent.rb +167 -0
  244. data/lib/hecks/ports/authentication.port +4 -0
  245. data/lib/hecks/ports/authentication.rb +50 -0
  246. data/lib/hecks/ports/authorization.port +4 -0
  247. data/lib/hecks/ports/authorization.rb +59 -0
  248. data/lib/hecks/ports/clock.port +4 -0
  249. data/lib/hecks/ports/clock.rb +62 -0
  250. data/lib/hecks/ports/extraction.port +4 -0
  251. data/lib/hecks/ports/extraction.rb +37 -0
  252. data/lib/hecks/ports/identity_assignment.port +4 -0
  253. data/lib/hecks/ports/identity_assignment.rb +45 -0
  254. data/lib/hecks/ports/identity_generation.port +4 -0
  255. data/lib/hecks/ports/identity_generation.rb +49 -0
  256. data/lib/hecks/ports/identity_resolution.port +4 -0
  257. data/lib/hecks/ports/identity_resolution.rb +40 -0
  258. data/lib/hecks/ports/loading.port +4 -0
  259. data/lib/hecks/ports/loading.rb +13 -0
  260. data/lib/hecks/ports/persistence/append_only.rb +102 -0
  261. data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
  262. data/lib/hecks/ports/persistence/execution.rb +18 -0
  263. data/lib/hecks/ports/persistence/lineage.rb +277 -0
  264. data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
  266. data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
  267. data/lib/hecks/ports/persistence.port +4 -0
  268. data/lib/hecks/ports/persistence.rb +39 -0
  269. data/lib/hecks/ports/projection.port +4 -0
  270. data/lib/hecks/ports/projection.rb +76 -0
  271. data/lib/hecks/ports/query/in_memory.rb +60 -0
  272. data/lib/hecks/ports/query/ordering.rb +41 -0
  273. data/lib/hecks/ports/query.rb +36 -0
  274. data/lib/hecks/ports.rb +27 -0
  275. data/lib/hecks/projections/diagrams.rb +413 -0
  276. data/lib/hecks/projections/ir.rb +18 -0
  277. data/lib/hecks/projections/model/deviations.rb +98 -0
  278. data/lib/hecks/projections/model.rb +145 -0
  279. data/lib/hecks/projections/oidc.rb +110 -0
  280. data/lib/hecks/projections/parser_table.rb +159 -0
  281. data/lib/hecks/projections/reference.rb +38 -0
  282. data/lib/hecks/projections/shape.rb +31 -0
  283. data/lib/hecks/projections/statements.rb +110 -0
  284. data/lib/hecks/projections/vocabulary.rb +100 -0
  285. data/lib/hecks/projections.rb +32 -0
  286. data/lib/hecks/projector/cli_projector.rb +437 -0
  287. data/lib/hecks/projector/docs_projector.rb +321 -0
  288. data/lib/hecks/projector/exporter.rb +141 -0
  289. data/lib/hecks/projector/ir_projector.rb +18 -0
  290. data/lib/hecks/projector/narrate_projector.rb +243 -0
  291. data/lib/hecks/projector/target.rb +97 -0
  292. data/lib/hecks/projector.rb +186 -0
  293. data/lib/hecks/query_ir.rb +392 -0
  294. data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
  295. data/lib/hecks/query_specification/common/comparators.rb +25 -0
  296. data/lib/hecks/query_specification/common/comparison.rb +174 -0
  297. data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
  298. data/lib/hecks/query_specification/common/dsl.rb +58 -0
  299. data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
  300. data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
  301. data/lib/hecks/query_specification/common/null_policy.rb +80 -0
  302. data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
  303. data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
  304. data/lib/hecks/query_specification/common/options.rb +32 -0
  305. data/lib/hecks/query_specification/common/order_by.rb +9 -0
  306. data/lib/hecks/query_specification/common/specification.rb +10 -0
  307. data/lib/hecks/query_specification/common/where_clause.rb +9 -0
  308. data/lib/hecks/query_specification/field_path.rb +89 -0
  309. data/lib/hecks/query_specification/hop_path.rb +130 -0
  310. data/lib/hecks/query_specification/read_model/specification.rb +18 -0
  311. data/lib/hecks/query_specification.rb +14 -0
  312. data/lib/hecks/rendering.rb +48 -0
  313. data/lib/hecks/router/namespace_installer.rb +157 -0
  314. data/lib/hecks/router.rb +70 -0
  315. data/lib/hecks/runtime/caller.rb +50 -0
  316. data/lib/hecks/runtime/capability_graph.rb +44 -0
  317. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +124 -0
  318. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +248 -0
  319. data/lib/hecks/runtime/command_interpreter.rb +413 -0
  320. data/lib/hecks/runtime/command_rules/admissibility.rb +307 -0
  321. data/lib/hecks/runtime/command_rules/arithmetic.rb +254 -0
  322. data/lib/hecks/runtime/command_rules/authorization.rb +64 -0
  323. data/lib/hecks/runtime/command_rules/emission.rb +34 -0
  324. data/lib/hecks/runtime/command_rules/references.rb +181 -0
  325. data/lib/hecks/runtime/command_rules.rb +28 -0
  326. data/lib/hecks/runtime/dependency_planning.rb +231 -0
  327. data/lib/hecks/runtime/dispatcher.rb +286 -0
  328. data/lib/hecks/runtime/entity_element.rb +253 -0
  329. data/lib/hecks/runtime/entity_interpreter.rb +228 -0
  330. data/lib/hecks/runtime/era_check.rb +136 -0
  331. data/lib/hecks/runtime/era_guard/shape_diff.rb +128 -0
  332. data/lib/hecks/runtime/era_guard.rb +193 -0
  333. data/lib/hecks/runtime/era_tamper.rb +61 -0
  334. data/lib/hecks/runtime/errors.rb +100 -0
  335. data/lib/hecks/runtime/event.rb +51 -0
  336. data/lib/hecks/runtime/identity.rb +128 -0
  337. data/lib/hecks/runtime/instance.rb +120 -0
  338. data/lib/hecks/runtime/interpreting.rb +69 -0
  339. data/lib/hecks/runtime/loader.rb +143 -0
  340. data/lib/hecks/runtime/policy_interpreter.rb +366 -0
  341. data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
  342. data/lib/hecks/runtime/query_interpreter.rb +254 -0
  343. data/lib/hecks/runtime/reaction_invocation.rb +245 -0
  344. data/lib/hecks/runtime/read_model_interpreter.rb +266 -0
  345. data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
  346. data/lib/hecks/runtime/reference_hop.rb +99 -0
  347. data/lib/hecks/runtime/refusal_wording.rb +124 -0
  348. data/lib/hecks/runtime/registry/saga_persistence.rb +70 -0
  349. data/lib/hecks/runtime/registry/verification.rb +158 -0
  350. data/lib/hecks/runtime/registry.rb +218 -0
  351. data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
  352. data/lib/hecks/runtime/routing.rb +96 -0
  353. data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
  354. data/lib/hecks/runtime/saga_interpreter.rb +301 -0
  355. data/lib/hecks/runtime/storage_shape.rb +120 -0
  356. data/lib/hecks/runtime/tenant_check.rb +84 -0
  357. data/lib/hecks/runtime/tenant_scope.rb +55 -0
  358. data/lib/hecks/runtime/value/admission.rb +112 -0
  359. data/lib/hecks/runtime/value/coercion.rb +500 -0
  360. data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
  361. data/lib/hecks/runtime/value.rb +125 -0
  362. data/lib/hecks/runtime.rb +95 -0
  363. data/lib/hecks/translation/audit/approval_digest.rb +31 -0
  364. data/lib/hecks/translation/audit/layer_one.rb +37 -0
  365. data/lib/hecks/translation/audit/layer_two.rb +77 -0
  366. data/lib/hecks/translation/audit/unfed_report.rb +42 -0
  367. data/lib/hecks/translation/audit.rb +70 -0
  368. data/lib/hecks/translation/reattest.rb +72 -0
  369. data/lib/hecks/translation/rule_compiler.rb +120 -0
  370. data/lib/hecks/translation/scaffold/differ.rb +183 -0
  371. data/lib/hecks/translation/scaffold/renderer.rb +41 -0
  372. data/lib/hecks/translation/scaffold/writer.rb +28 -0
  373. data/lib/hecks/translation/scaffold.rb +30 -0
  374. data/lib/hecks/translation.rb +11 -0
  375. data/lib/hecks/version.rb +16 -0
  376. data/lib/hecks/vocabulary.rb +213 -0
  377. data/lib/hecks.rb +118 -6
  378. metadata +401 -222
  379. data/bin/hecks +0 -7
  380. data/bin/hecks-package +0 -65
  381. data/bin/hecks_console +0 -12
  382. data/bin/hecks_serverless +0 -6
  383. data/lib/cli/build.rb +0 -14
  384. data/lib/cli/command_runner.rb +0 -28
  385. data/lib/cli/console.rb +0 -10
  386. data/lib/cli/generate.rb +0 -37
  387. data/lib/cli/hecks-cli.rb +0 -27
  388. data/lib/cli/test.rb +0 -57
  389. data/lib/console/commands.rb +0 -8
  390. data/lib/console/hecks-console.rb +0 -1
  391. data/lib/packager/README.md +0 -0
  392. data/lib/packager/app_runner.rb +0 -21
  393. data/lib/packager/args.rb +0 -26
  394. data/lib/packager/compatibility/fixnum.rb +0 -6
  395. data/lib/packager/hecks.rb +0 -39
  396. data/lib/packager/query_runner.rb +0 -21
  397. data/lib/packager/resources/Dockerfile +0 -11
  398. data/lib/packager/resources/app_binary +0 -7
  399. data/lib/packager/resources/bundle_config +0 -3
  400. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
  401. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
  402. data/lib/serverless/Domain +0 -32
  403. data/lib/serverless/cli.rb +0 -75
  404. data/lib/serverless/resources/command_name.js +0 -5
  405. data/lib/serverless/resources/environment.js +0 -7
  406. data/lib/serverless/resources/handler.js.tt +0 -28
  407. data/lib/serverless/resources/run_binary.js +0 -22
  408. data/lib/serverless/resources/serverless.yml +0 -20
@@ -0,0 +1,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,51 @@
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
+ matches.find { |t| applies_from?(t, current_state) } || matches.first
41
+ end
42
+
43
+ def applies_from?(transition, current)
44
+ return true unless transition.from
45
+
46
+ Array(transition.from).map(&:to_s).include?(current.to_s)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ 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,38 @@
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
+ Bluebook::Saga.new(trigger: Bluebook::ProcessManager::REFUSED, from_state: leg.from_state,
31
+ to_state: leg.to_state, reversals: leg.dispatches)
32
+ end
33
+
34
+ def saga? = !saga.nil?
35
+ end
36
+ end
37
+ end
38
+ 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,68 @@
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
+ aggregates: many(:aggregates),
39
+ read_models: many(:read_models),
40
+ policies: many(:policies),
41
+ process_managers: many(:process_managers),
42
+ attaches_to: :attaches_to,
43
+ canonical_form: -> { Expression::CanonicalForm.table }
44
+ )
45
+
46
+ attr_reader :name, :version, :vision, :aggregates, :policies, :process_managers,
47
+ :classification, :read_models, :ports, :formerly_known_as, :attaches_to
48
+
49
+ def initialize(name:, version: nil, vision: nil, aggregates: [], policies: [],
50
+ process_managers: [], classification: nil, read_models: [], formerly_known_as: nil,
51
+ attaches_to: [])
52
+ @policies = policies
53
+ @process_managers = process_managers
54
+ @name = name.to_s
55
+ @hecks_name = @name
56
+ @hecks_root = true
57
+ @version = version&.to_s
58
+ @vision = vision
59
+ @aggregates = aggregates
60
+ @read_models = read_models
61
+ @classification = classification&.to_s
62
+ @formerly_known_as = formerly_known_as&.to_s
63
+ @attaches_to = Array(attaches_to).map(&:to_s)
64
+ settle
65
+ end
66
+ end
67
+ end
68
+ 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].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
@@ -0,0 +1,102 @@
1
+ require_relative "behaviour/domain_port"
2
+
3
+ module Hecks
4
+ module Bluebook
5
+ # THE PRIMARY/DRIVING HALF OF HEXAGONAL ARCHITECTURE (Cockburn) — called
6
+ # BY an adapter living outside the bluebook entirely, never by the
7
+ # domain calling out. That is already `Hecks.port` (persistence,
8
+ # projection, extraction, loading) plus `Ports::*` : the secondary/
9
+ # driven half, unchanged by this.
10
+ #
11
+ # An operation carries no `given`/`ensures`/`then_set` — a port is the
12
+ # anti-corruption boundary that turns an external call into a fact in
13
+ # this domain's own vocabulary, not a second place business rules live.
14
+ # Those stay on whatever command a `policy` triggers in reaction to the
15
+ # event an operation emits.
16
+ class PortOperation
17
+ include Hecks::IR
18
+ include Behaviour::PortOperation
19
+
20
+ emits_ir(name: :hecks_name, attributes: many(:attributes), emits: :emits)
21
+
22
+ attr_reader :hecks_name, :attributes, :emits, :direction, :answers, :refuses, :to
23
+
24
+ # TWO DIRECTIONS THROUGH ONE DOOR.
25
+ #
26
+ # `:inbound` is what this class has always been — `tells`, spelled
27
+ # `operation` before it had a twin: an external fact arriving, turned
28
+ # into this domain's own event. It emits and that is all; there is no
29
+ # channel back to whoever called.
30
+ #
31
+ # `:outbound` is `asks` — the domain wanting something from outside
32
+ # and having to live with either answer. It names BOTH: `answers` for
33
+ # what the adapter came back with, `refuses` for what it said instead.
34
+ # Naming only the happy one would put the failure somewhere the model
35
+ # cannot see, which is the whole reason a boundary is worth modelling.
36
+ def initialize(name:, attributes: [], emits: [], direction: :inbound, answers: nil, refuses: nil, to: nil)
37
+ @hecks_name = name.to_s
38
+ @attributes = attributes
39
+ @emits = emits
40
+ @direction = direction.to_sym
41
+ @answers = answers
42
+ @refuses = refuses
43
+ @to = to
44
+ @attributes_by_name = attributes.to_h { |attribute| [attribute.name, attribute] }
45
+ end
46
+
47
+ def outbound? = @direction == :outbound
48
+ def inbound? = @direction == :inbound
49
+
50
+ # No root reference of its own — unlike a command, every attribute
51
+ # EQUALLY describes the payload, including whichever one identifies
52
+ # the record its emitted event belongs to. Kept only so
53
+ # CommandInterpreter::ArgumentGate's `reference_key` can ask for it
54
+ # without learning this isn't a command.
55
+
56
+ # `direction`/`answers`/`refuses` are deliberately OUTSIDE `emits_ir`'s
57
+ # declared shape and added here only for an outbound operation — an
58
+ # ordinary inbound one (`tells`, still spelled `operation` everywhere
59
+ # in the existing corpus) keeps the EXACT prior IR shape, byte for
60
+ # byte. Pizzas' `PaymentGateway` port is inbound-only and is checked
61
+ # against `hecks-parse`'s own Rust output for byte-identity
62
+ # (parser_parity_spec.rb) — the Rust side has no notion of `asks` yet,
63
+ # so an unconditional new key here would break that parity for a
64
+ # domain that never asked for the feature. Only a chapter that
65
+ # actually declares `asks` (this extraction's own QualityControl
66
+ # ledger, not yet in any Rust-parity corpus) pays for it.
67
+ # `to` — SAME "deliberately outside emits_ir, merged in only when
68
+ # present" treatment as direction/answers/refuses just above, and
69
+ # for the identical reason: an operation still spelled the old way
70
+ # (`reference_to` inside the block, shadow-parsing only — see
71
+ # reference_to_impl's own comment) or one that simply hasn't
72
+ # migrated yet keeps the EXACT prior IR shape, byte for byte,
73
+ # instead of an unconditional new key breaking parser_parity_spec
74
+ # for every domain that never touched this.
75
+ def to_h
76
+ shape = super
77
+ shape = shape.merge(direction: @direction.to_s, answers: @answers, refuses: @refuses) unless inbound?
78
+ shape = shape.merge(to: @to) if @to
79
+ shape
80
+ end
81
+ end
82
+
83
+ # A named group of operations an aggregate (or, later, a chapter)
84
+ # exposes to whatever adapter calls in — the contract that today lives
85
+ # only as a bare `verb`/`signal` pair in a standalone `.port` file.
86
+ # Superseding those is the goal ; for now they keep working untouched,
87
+ # and this is additive.
88
+ class DomainPort
89
+ include Hecks::IR
90
+ include Behaviour::DomainPort
91
+
92
+ emits_ir(name: :name, operations: many(:operations))
93
+
94
+ attr_reader :name, :operations
95
+
96
+ def initialize(name:, operations: [])
97
+ @name = name.to_s
98
+ @operations = operations
99
+ end
100
+ end
101
+ end
102
+ end