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,20 @@
1
+ # The expression and translation grammars, bound so they boot standing
2
+ # alone. Both are read-only in practice — Hecks::Grammar parses
3
+ # against them — but a binding is declared rather than defaulted.
4
+ Hecks.hecksagon "Expression" do
5
+ # Every command here declares `role "Language designer"` — only real
6
+ # access control once Governance can check it against something (S8).
7
+ uses_framework "Governance"
8
+ persisted_by "Memory"
9
+ end
10
+
11
+ Hecks.hecksagon "Translation" do
12
+ # Same reason as Expression, above — S8.
13
+ uses_framework "Governance"
14
+ persisted_by "Memory"
15
+ end
16
+
17
+ Hecks.hecksagon "Governance" do
18
+ Governance::RoleAssignment.persisted_by("Memory")
19
+ Governance::RoleTransition.persisted_by("Memory")
20
+ end
@@ -0,0 +1,69 @@
1
+ {
2
+ "audience": "Expression",
3
+ "scopes": [
4
+ {
5
+ "scope": "expression:expression.admit",
6
+ "verb": "Expression::Expression.Admit",
7
+ "role": "System"
8
+ },
9
+ {
10
+ "scope": "expression:expression.extract",
11
+ "verb": "Expression::Expression.Extract",
12
+ "role": "System"
13
+ },
14
+ {
15
+ "scope": "expression:expression.note",
16
+ "verb": "Expression::Expression.Note",
17
+ "role": "System"
18
+ },
19
+ {
20
+ "scope": "expression:expression.reject",
21
+ "verb": "Expression::Expression.Reject",
22
+ "role": "System"
23
+ },
24
+ {
25
+ "scope": "expression:normalisation.admit",
26
+ "verb": "Expression::Normalisation.Admit",
27
+ "role": "Language designer"
28
+ },
29
+ {
30
+ "scope": "expression:normalisation.propose",
31
+ "verb": "Expression::Normalisation.Propose",
32
+ "role": "Language designer"
33
+ },
34
+ {
35
+ "scope": "expression:normalisation.read",
36
+ "verb": "Expression::Normalisation.Read",
37
+ "role": "Language designer"
38
+ },
39
+ {
40
+ "scope": "expression:normalisation.retire",
41
+ "verb": "Expression::Normalisation.Retire",
42
+ "role": "Language designer"
43
+ },
44
+ {
45
+ "scope": "expression:operator.admit",
46
+ "verb": "Expression::Operator.Admit",
47
+ "role": "Language designer"
48
+ },
49
+ {
50
+ "scope": "expression:operator.propose",
51
+ "verb": "Expression::Operator.Propose",
52
+ "role": "Language designer"
53
+ },
54
+ {
55
+ "scope": "expression:operator.render",
56
+ "verb": "Expression::Operator.Render",
57
+ "role": "Language designer"
58
+ },
59
+ {
60
+ "scope": "expression:operator.retire",
61
+ "verb": "Expression::Operator.Retire",
62
+ "role": "Language designer"
63
+ }
64
+ ],
65
+ "roles": [
66
+ "Language designer",
67
+ "System"
68
+ ]
69
+ }
@@ -0,0 +1,203 @@
1
+
2
+ Hecks.bluebook "Translation" do
3
+ vision "The declared bridge from one era of a domain's storage shape to the next — every rule checkable, every loss written down, carried as one edge file."
4
+ generic
5
+
6
+ aggregate "Rule" do
7
+ description "One admitted way a value survives a shape change — its kind, where it reads from, where it writes to, and how it executes in every target that must apply it."
8
+
9
+ identified_by :source
10
+
11
+ attribute :kind, Kind
12
+ attribute :source, SourcePath
13
+ attribute :destination, DestinationPath
14
+ attribute :executions, list_of(Execution)
15
+
16
+ lifecycle :status, default: "proposed" do
17
+ transition "Admit" => "admitted", from: "proposed"
18
+ transition "Retire" => "retired", from: "admitted"
19
+ end
20
+
21
+ # THE CLOSED SET OF RULE KINDS — declared as members, not a hand-rolled
22
+ # OR-chain, so the set is data a conformance spec can hold the live
23
+ # builder to (spec/translation_vocabulary_conformance_spec.rb). Nine of
24
+ # these are TranslationAggregateBuilder's own rule methods ; "retired" is
25
+ # the edge-level declaration (TranslationBuilder#retired), in the set
26
+ # because it is one admitted way a shape survives — by not surviving.
27
+ value_object "Kind" do
28
+ attribute :value, String,
29
+ one_of: ["rename", "move", "convert", "drop", "retype", "retired", "compute", "rekey",
30
+ "backfill", "unresolved"]
31
+ end
32
+
33
+ value_object "SourcePath" do
34
+ attribute :value, String
35
+ invariant("a rule reads from somewhere") { !value.to_s.empty? }
36
+ end
37
+
38
+ value_object "DestinationPath" do
39
+ attribute :value, String
40
+ end
41
+
42
+ value_object "Target" do
43
+ attribute :value, String
44
+ end
45
+
46
+ value_object "Form" do
47
+ attribute :value, String
48
+ end
49
+
50
+ value_object "Execution" do
51
+ attribute :target, String
52
+ attribute :form, String
53
+
54
+ invariant("an execution must name its target") { !target.to_s.empty? }
55
+ invariant("an execution must have a form") { !form.to_s.empty? }
56
+ end
57
+
58
+ command "Propose" do
59
+ role "Author"
60
+ goal "Put one way of surviving a shape change forward"
61
+
62
+ attribute :kind, Kind
63
+ attribute :source, SourcePath
64
+ attribute :destination, DestinationPath
65
+
66
+ emits "RuleProposed"
67
+ end
68
+
69
+ command "Execute" do
70
+ role "Author"
71
+ goal "Declare how this rule runs in one target"
72
+
73
+ reference_to Rule
74
+ attribute :target, Target
75
+ attribute :form, Form
76
+
77
+ given("a retired rule takes no new executions") { status != "retired" }
78
+
79
+ sets :executions, append: { target: :target, form: :form }
80
+
81
+ emits "RuleExecuted"
82
+ end
83
+
84
+ command "Admit" do
85
+ role "Author"
86
+ goal "Accept the rule — it executes identically in every target"
87
+
88
+ reference_to Rule
89
+
90
+ given("a rule must execute in every target before it is admitted") { executions.size >= 2 }
91
+
92
+ emits "RuleAdmitted"
93
+ end
94
+
95
+ command "Retire" do
96
+ role "Author"
97
+ goal "Remove the rule from the language"
98
+
99
+ reference_to Rule
100
+
101
+ emits "RuleRetired"
102
+ end
103
+ end
104
+
105
+ aggregate "Map" do
106
+ description "One translation edge between two minted eras — the ordered declarations that carry a domain's data across one shape change, sealed before anything boots through it."
107
+
108
+ identified_by :domain
109
+
110
+ attribute :domain, DomainName
111
+ attribute :from_shape, ShapeHash
112
+ attribute :to_shape, ShapeHash
113
+ attribute :declarations, list_of(Declaration)
114
+
115
+ lifecycle :status, default: "drafted" do
116
+ transition "Seal" => "sealed", from: "drafted"
117
+ transition "Supersede" => "superseded", from: "sealed"
118
+ end
119
+
120
+ value_object "DomainName" do
121
+ attribute :value, String
122
+ invariant("an edge names the domain it translates") { !value.to_s.empty? }
123
+ end
124
+
125
+ value_object "ShapeHash" do
126
+ attribute :value, String
127
+ invariant("an era is named by its minted hash") { !value.to_s.empty? }
128
+ end
129
+
130
+ value_object "Kind" do
131
+ attribute :value, String
132
+ end
133
+
134
+ value_object "SourcePath" do
135
+ attribute :value, String
136
+ end
137
+
138
+ value_object "DestinationPath" do
139
+ attribute :value, String
140
+ end
141
+
142
+ value_object "Reason" do
143
+ attribute :value, String
144
+ invariant("a superseded edge says why") { !value.to_s.empty? }
145
+ end
146
+
147
+ value_object "Declaration" do
148
+ attribute :kind, String
149
+ attribute :source, String
150
+ attribute :destination, String
151
+
152
+ invariant("a declaration knows its kind") { !kind.to_s.empty? }
153
+ end
154
+
155
+ command "Draft" do
156
+ role "Author"
157
+ goal "Open the edge between two eras"
158
+
159
+ attribute :domain, DomainName
160
+ attribute :from_shape, ShapeHash
161
+ attribute :to_shape, ShapeHash
162
+
163
+ emits "MapDrafted"
164
+ end
165
+
166
+ command "Declare" do
167
+ role "Author"
168
+ goal "Write one rule into the edge"
169
+
170
+ reference_to Map
171
+ attribute :kind, Kind
172
+ attribute :source, SourcePath
173
+ attribute :destination, DestinationPath
174
+
175
+ given("a sealed edge takes no further declarations") { status == "drafted" }
176
+
177
+ sets :declarations, append: { kind: :kind, source: :source, destination: :destination }
178
+
179
+ emits "RuleDeclared"
180
+ end
181
+
182
+ command "Seal" do
183
+ role "Author"
184
+ goal "Declare the edge complete — data can cross it"
185
+
186
+ reference_to Map
187
+
188
+ given("an empty edge explains nothing") { declarations.size >= 1 }
189
+
190
+ emits "MapSealed"
191
+ end
192
+
193
+ command "Supersede" do
194
+ role "Author"
195
+ goal "Record that a corrected edge replaced this one"
196
+
197
+ reference_to Map
198
+ attribute :reason, Reason
199
+
200
+ emits "MapSuperseded"
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,178 @@
1
+ require "json"
2
+
3
+ # The whole framework, deliberately: booting a grammar chapter exercises
4
+ # the DSL, the meta-validator, and the runtime — and nothing the entry
5
+ # point loads ever requires this file back, so the require is acyclic.
6
+ require_relative "../hecks"
7
+
8
+ module Hecks
9
+ # The sublanguage grammar domains (grammar/*.bluebook) and the one boot
10
+ # path for reading them as DATA — the expression chapter replayed
11
+ # through its own admission ledger, so anything derived from it (the
12
+ # operator projections, the conformance specs) reads the set that
13
+ # actually survived the Admit gates, never a hand-copied list.
14
+ #
15
+ # Booted on CALL, never at require: the Prism adapter normalises every
16
+ # predicate through CanonicalForm while a bluebook loads, so the
17
+ # expression machinery cannot boot the chapter that configures it —
18
+ # this module exists precisely so generators and specs boot it in a
19
+ # scratch registry instead.
20
+ module Grammar
21
+ DIR = File.expand_path("grammar", __dir__)
22
+ LEDGER = File.join(DIR, "expression_operators.json")
23
+
24
+ module_function
25
+
26
+ # Boot the expression chapter and replay the admission ledger through
27
+ # its real commands. A refused step raises — a generator running off
28
+ # a half-admitted ledger would project a table the gates never
29
+ # accepted.
30
+ def expression
31
+ registry = Runtime::Registry.new
32
+ root = File.expand_path("../..", __dir__)
33
+ Hecks.with_registry(registry) do
34
+ Kernel.load(File.join(root, "lib/hecks/ports/persistence.port"))
35
+ Kernel.load(File.join(root, "lib/hecks/ports/extraction.port"))
36
+ Kernel.load(File.join(root, "lib/hecks/adapters/driven/memory.adapter"))
37
+ Kernel.load(File.join(root, "lib/hecks/adapters/driven/prism.adapter"))
38
+ Kernel.load(File.join(DIR, "expression.bluebook"))
39
+ end
40
+ dispatcher = Runtime::Dispatcher.new(registry)
41
+
42
+ JSON.parse(File.read(LEDGER)).fetch("steps").each do |step|
43
+ args = symbolize(step.fetch("args"))
44
+ begin
45
+ dispatcher.dispatch(step.fetch("verb"), **args)
46
+ rescue *Runtime::DOMAIN_REFUSALS => refusal
47
+ raise Runtime::WiringError,
48
+ "the admission ledger refused at #{step['verb']} #{step['args']} — #{refusal.message}"
49
+ end
50
+ end
51
+
52
+ dispatcher
53
+ end
54
+
55
+ def admitted_operators(dispatcher = expression)
56
+ records(dispatcher, "Operator").select { |op| op[:status] == "admitted" }.map do |op|
57
+ { symbol: op[:symbol].value, category: op[:category].value,
58
+ precedence: op[:precedence].value, arity: op[:arity].value,
59
+ renderings: Array(op[:renderings]).map { |r| { target: r[:target], form: r[:form] } } }
60
+ end
61
+ end
62
+
63
+ def admitted_normalisations(dispatcher = expression)
64
+ records(dispatcher, "Normalisation")
65
+ .select { |rule| rule[:status] == "admitted" }
66
+ .sort_by { |rule| rule[:position].value }
67
+ .map do |rule|
68
+ { strategy: rule[:strategy].value, source_token: rule[:source_token].value,
69
+ replacement: rule[:replacement].value, boundary: rule[:boundary].value,
70
+ position: rule[:position].value }
71
+ end
72
+ end
73
+
74
+ def records(dispatcher, aggregate_name)
75
+ registry = dispatcher.registry
76
+ aggregate = registry.bluebook("Expression").aggregate(aggregate_name)
77
+ registry.repository("Expression", aggregate).all
78
+ end
79
+
80
+ # THE OPERATORS THE LANGUAGE STANDS ON. Every guard and invariant in
81
+ # the language's own chapters — the meta-domain (Bluebook, World) and
82
+ # the grammar chapters beside this file — evaluates through the very
83
+ # operator table the ledger admits. An operator one of those
84
+ # predicates uses is SELF-BEARING: retire it and the language can no
85
+ # longer read its own rules — found the hard way, as a projection
86
+ # missing `!=` that could not boot the chapter to fix itself. This
87
+ # derives the set, with a usage site per operator, so the generator
88
+ # and the conformance spec can refuse the retirement BY NAME instead
89
+ # of wedging.
90
+ def self_bearing_operators
91
+ sites = Hash.new { |h, k| h[k] = [] }
92
+
93
+ chapters = Bluebook::MetaValidator.grammar_registry
94
+ .then { |reg| %w[Bluebook World].map { |name| reg.bluebook(name) } }
95
+ chapters += grammar_chapters
96
+
97
+ chapters.compact.each do |chapter|
98
+ chapter.aggregates.each do |aggregate|
99
+ aggregate.commands.each do |command|
100
+ command.givens.each do |given|
101
+ operators_in(given.canonical).each do |symbol|
102
+ sites[symbol] << "#{chapter.name} #{aggregate.name}.#{command.hecks_name}"
103
+ end
104
+ end
105
+ end
106
+ aggregate.value_objects.each do |value_object|
107
+ value_object.invariants.each do |invariant|
108
+ operators_in(invariant.canonical).each do |symbol|
109
+ sites[symbol] << "#{chapter.name} #{aggregate.name}::#{value_object.hecks_name}"
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ sites.transform_values(&:uniq)
117
+ end
118
+
119
+ # Every grammar/*.bluebook chapter, booted the same way the corpus
120
+ # boots them — each alone, in a scratch registry.
121
+ def grammar_chapters
122
+ Dir[File.join(DIR, "*.bluebook")].sort.map do |chapter|
123
+ registry = Runtime::Registry.new
124
+ root = File.expand_path("../..", __dir__)
125
+ Hecks.with_registry(registry) do
126
+ Kernel.load(File.join(root, "lib/hecks/ports/persistence.port"))
127
+ Kernel.load(File.join(root, "lib/hecks/ports/extraction.port"))
128
+ Kernel.load(File.join(root, "lib/hecks/adapters/driven/memory.adapter"))
129
+ Kernel.load(File.join(root, "lib/hecks/adapters/driven/prism.adapter"))
130
+ Kernel.load(chapter)
131
+ end
132
+ registry.bluebooks.values.first
133
+ end
134
+ end
135
+
136
+ # Which admitted operators one canonical text evaluates through —
137
+ # the evaluator's own parse, walked for its operator nodes, leaves
138
+ # walked for the resolver's arithmetic.
139
+ def operators_in(canonical)
140
+ evaluator = Bluebook::Expression::Evaluator
141
+ node = begin
142
+ evaluator.parse(canonical)
143
+ rescue StandardError
144
+ return []
145
+ end
146
+ walk_operators(node, evaluator).uniq
147
+ end
148
+
149
+ def walk_operators(node, evaluator)
150
+ resolver = Bluebook::Expression::Resolver
151
+ case node
152
+ when evaluator::Or then ["||"] + walk_operators(node.left, evaluator) + walk_operators(node.right, evaluator)
153
+ when evaluator::And then ["&&"] + walk_operators(node.left, evaluator) + walk_operators(node.right, evaluator)
154
+ when evaluator::Not then ["!"] + walk_operators(node.node, evaluator)
155
+ when evaluator::Compare
156
+ [node.operator.symbol] + walk_operators(node.left, evaluator) + walk_operators(node.right, evaluator)
157
+ when evaluator::Include
158
+ [".include?"] + walk_operators(node.haystack, evaluator) + walk_operators(node.needle, evaluator)
159
+ when evaluator::Resolve then walk_operators(node.expr, evaluator)
160
+ when resolver::Addition then ["+"] + walk_operators(node.left, evaluator) + walk_operators(node.right, evaluator)
161
+ when resolver::Modulo then [".modulo"] + walk_operators(node.receiver,
162
+ evaluator) + walk_operators(node.divisor, evaluator)
163
+ when Struct
164
+ node.members.flat_map { |member| walk_operators(node[member], evaluator) }
165
+ else
166
+ []
167
+ end
168
+ end
169
+
170
+ def symbolize(value)
171
+ case value
172
+ when Hash then value.to_h { |k, v| [k.to_sym, symbolize(v)] }
173
+ when Array then value.map { |v| symbolize(v) }
174
+ else value
175
+ end
176
+ end
177
+ end
178
+ end
data/lib/hecks/ir.rb ADDED
@@ -0,0 +1,126 @@
1
+ module Hecks
2
+ # WHAT A CONSTRUCT EMITS, DECLARED RATHER THAN WRITTEN OUT.
3
+ #
4
+ # `IR` is a thing this framework PRODUCES, not a thing its model IS.
5
+ # The language self-hosts, and its own bluebook declares aggregates
6
+ # named `Bluebook`, `Aggregate`, `Command`, `Entity`, `ValueObject`,
7
+ # `Policy`, `ReadModel` — there is no `IR` aggregate anywhere in the
8
+ # grammar. Where the grammar's own vision line does say IR it means the
9
+ # emission: "the IR it STORES must equal the IR the DSL builder
10
+ # PRODUCES". `IR_VERSION` says the same thing structurally — a version
11
+ # stamped on `to_h`'s output rather than on the object is a version of
12
+ # the EMISSION, which only makes sense if the two are different things.
13
+ #
14
+ # So emitting IR is a CAPABILITY a construct has, and this is that
15
+ # capability: `include Hecks::IR` and declare the shape once.
16
+ # The model it emits FROM is `Hecks::Bluebook` — a chapter class
17
+ # nesting everything a chapter declares. It is deliberately not named
18
+ # after this, its own output.
19
+ #
20
+ # Before this, eighteen constructs each hand-wrote a `to_h` that said
21
+ # the same four things in the same order — read a field, recurse into a
22
+ # child, recurse into a list, or compute something — and the shape of a
23
+ # construct was knowable only by reading a method body. Declared, it is
24
+ # data: `ir_spec` can be walked by anything that wants to know what a
25
+ # construct carries, which is the whole point of hanging emission off
26
+ # the model rather than burying it.
27
+ #
28
+ # include Hecks::IR # an instance-shaped construct
29
+ #
30
+ # emits_ir(
31
+ # name: :name, # send it
32
+ # identified_by: :identity_paths, # ...under a different key
33
+ # list: :list?, # ...predicates are fine
34
+ # attributes: many(:attributes), # map(&:to_h)
35
+ # lifecycle: one(:lifecycle), # &.to_h, nil-safe
36
+ # canonical_form: -> { CanonicalForm.table } # anything else
37
+ # )
38
+ #
39
+ # KEY ORDER IS THE DECLARATION ORDER, and that is load-bearing rather
40
+ # than cosmetic: `spec/golden/ir/*.json` pins the emitted form exactly,
41
+ # so a reordered declaration is a changed artifact and the golden specs
42
+ # will say so.
43
+ module IR
44
+ # THE TWO SHAPES A CONSTRUCT COMES IN, and why this module has two
45
+ # doors instead of hiding the difference.
46
+ #
47
+ # `Bluebook`/`Aggregate`/`Policy`/`ReadModel` are ordinary objects —
48
+ # metadata records, one instance per declaration. `Command`/`Entity`/
49
+ # `ValueObject` are anonymous CLASSES (`Class.new(self)`, see
50
+ # `Command.declare`), because those three are referenced as TYPES in
51
+ # a bluebook (`attribute :price, Money`) and a type has to be a real
52
+ # Ruby constant to be named.
53
+ #
54
+ # That split is real and not worth papering over, so:
55
+ #
56
+ # include Hecks::IR # instance-shaped — to_h is an instance method
57
+ # extend Hecks::IR # class-shaped — to_h is a class method
58
+ #
59
+ # Both get the same `emits_ir` and the same emission rules.
60
+ def self.included(base)
61
+ base.extend(Declares)
62
+ base.include(Emits)
63
+ end
64
+
65
+ def self.extended(base)
66
+ base.extend(Declares)
67
+ base.extend(Emits)
68
+ end
69
+
70
+ # A field that holds a list of constructs — each one emits itself.
71
+ Many = Struct.new(:source)
72
+ # A field that holds one construct, or nothing. `&.to_h`, never a
73
+ # crash on an undeclared lifecycle.
74
+ One = Struct.new(:source)
75
+
76
+ module Declares
77
+ def emits_ir(**spec)
78
+ @ir_spec = spec
79
+ end
80
+
81
+ def many(source) = Many.new(source)
82
+ def one(source) = One.new(source)
83
+
84
+ # Walks the superclass chain so a `Class.new(ValueObject)` — which
85
+ # is what every declared value object actually IS — inherits the
86
+ # shape its base declared, rather than each anonymous subclass
87
+ # having to redeclare it.
88
+ def ir_spec
89
+ return @ir_spec if defined?(@ir_spec) && @ir_spec
90
+
91
+ superclass.ir_spec if respond_to?(:superclass) && superclass.respond_to?(:ir_spec)
92
+ end
93
+ end
94
+
95
+ module Emits
96
+ def to_h
97
+ spec = ir_spec_for(self)
98
+ raise Undeclared, "#{self} emits IR but never declared its shape with emits_ir" unless spec
99
+
100
+ spec.to_h { |key, rule| [key, emit(rule)] }
101
+ end
102
+
103
+ private
104
+
105
+ # An instance reads its class's declaration; a class-shaped
106
+ # construct IS the declaration holder.
107
+ def ir_spec_for(construct)
108
+ return construct.ir_spec if construct.respond_to?(:ir_spec)
109
+
110
+ construct.class.ir_spec
111
+ end
112
+
113
+ def emit(rule)
114
+ case rule
115
+ when Many then public_send(rule.source).map(&:to_h)
116
+ when One then public_send(rule.source)&.to_h
117
+ when Symbol then public_send(rule)
118
+ when Proc then instance_exec(&rule)
119
+ else raise Undeclared, "#{rule.inspect} is not a way to emit a field"
120
+ end
121
+ end
122
+ end
123
+
124
+ class Undeclared < StandardError; end
125
+ end
126
+ end