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,321 @@
1
+ require_relative "../naming"
2
+
3
+ module Hecks
4
+ module Projector
5
+ # A BLUEBOOK, PROJECTED AS ITS OWN USAGE DOCUMENTATION.
6
+ #
7
+ # WHAT THIS IS FOR. A chapter in this corpus already contains everything a
8
+ # user of it needs: what each aggregate is (`description`), what each verb
9
+ # is for (`goal`) and who issues it (`role`), which states it moves
10
+ # between, what it refuses and in whose words (`given`, `invariant`,
11
+ # `ensures`), and what each list is worth reading (`description` on a
12
+ # query). None of that reaches the person who has to CALL the domain.
13
+ # They read the source, or they read a document somebody wrote beside the
14
+ # source and stopped updating.
15
+ #
16
+ # `docs/implemented/reference/` is the precedent and the contrast. `bin/reference`
17
+ # generates it from the language's own Syntax chapter, so the reference
18
+ # for the DSL cannot drift from the DSL. This is the same trick one level
19
+ # down: the usage document for a DOMAIN, generated from that domain, so it
20
+ # cannot drift from the domain either.
21
+ #
22
+ # WHY IT IS A PROJECTION AND NOT A GENERATOR SCRIPT. `Projector` is the
23
+ # repository's registry of "canonical IR in, external artifact out" (§30
24
+ # of the implementation plan), and this is exactly that shape: one
25
+ # bluebook's IR in, markdown out, no runtime needed and no store touched.
26
+ # Registered as `:docs` beside `:ir`, and reachable the way every
27
+ # projector is — `Projector.call(:docs, bluebook: ...)`.
28
+ #
29
+ # AND AS A METHOD, which is the half that makes it get used.
30
+ # `Facade::Surface` already installs a module per chapter carrying
31
+ # `vision` and `aggregates`; `docs` joins them, so a booted domain answers
32
+ # `QualityControl.docs` and an aggregate door answers
33
+ # `QualityControl::Bug.docs`. A document you have to remember a script for
34
+ # is a document nobody reads.
35
+ #
36
+ # WHAT IT DELIBERATELY DOES NOT DO: invent. Every sentence below comes out
37
+ # of the chapter. Where a chapter says nothing — an aggregate with no
38
+ # `description`, a command with no `goal` — the document says nothing
39
+ # rather than filling the gap with a restatement of the name, because a
40
+ # generated paragraph that only rephrases an identifier teaches a reader
41
+ # to skim the ones that do not.
42
+ module DocsProjector
43
+ module_function
44
+
45
+ # `options[:heading]` sets the top heading level (default 1), so a
46
+ # caller splicing this into a larger document can push it down.
47
+ def call(bluebook:, options: {})
48
+ depth = (options[:heading] || 1).to_i
49
+ only = options[:aggregate]
50
+
51
+ out = []
52
+ out << chapter_header(bluebook, depth) unless only
53
+ Array(aggregates(bluebook, only)).each { |aggregate| out << aggregate_section(aggregate, only ? depth : depth + 1) }
54
+ out << closing(bluebook, depth + 1) unless only
55
+ out.compact.join("\n").rstrip + "\n"
56
+ end
57
+
58
+ # A NAME THAT NAMES NOTHING IS REFUSED, not answered with an empty
59
+ # document. Shipped the other way first: `options[:aggregate]` that
60
+ # matched no head returned "" and exit 0, which is the silent-wrong-
61
+ # answer shape this repository has already been bitten by twice in the
62
+ # query engine. A misspelling should cost a sentence, not a puzzle.
63
+ def aggregates(bluebook, only)
64
+ return bluebook.aggregates unless only
65
+
66
+ bluebook.aggregates.find { |aggregate| aggregate.hecks_name == only.to_s } ||
67
+ raise(Runtime::NotFound,
68
+ "#{bluebook.name} declares no aggregate named #{only.to_s.inspect} — " \
69
+ "it declares #{bluebook.aggregates.map(&:hecks_name).sort.join(', ')}")
70
+ end
71
+
72
+ def h(depth, text) = "#{'#' * depth} #{text}"
73
+
74
+ # ── the chapter ───────────────────────────────────────────────────
75
+
76
+ def chapter_header(bluebook, depth)
77
+ out = [h(depth, bluebook.name), ""]
78
+ # THE VISION FIRST AND AS A QUOTE. It is the one sentence in a chapter
79
+ # written for somebody who does not know the domain yet.
80
+ out += ["> #{bluebook.vision}", ""] if bluebook.vision
81
+ out << "#{bluebook.classification.to_s.capitalize} domain." if bluebook.classification
82
+ out << "Previously known as `#{bluebook.formerly_known_as}`." if bluebook.formerly_known_as
83
+ out << ""
84
+ out << "Aggregates: #{bluebook.aggregates.map { |a| "[#{a.hecks_name}](##{anchor(a.hecks_name)})" }.join(', ')}."
85
+ out << ""
86
+ out.join("\n")
87
+ end
88
+
89
+ def anchor(name) = Naming.snake(name).tr("_", "-")
90
+
91
+ # WHAT HAPPENS WITHOUT ANYBODY ASKING — the part of a domain a caller
92
+ # cannot discover from any verb list, and the part most likely to surprise
93
+ # them. A policy means one dispatch causes another, sometimes into a
94
+ # different domain entirely; a saga means a sequence is being driven on
95
+ # their behalf and can end in more than one place.
96
+ def closing(bluebook, depth)
97
+ out = []
98
+
99
+ unless bluebook.policies.empty?
100
+ out << h(depth, "Reactions")
101
+ out << ""
102
+ out << "These fire on their own. Issuing the verb on the left also causes the one on the right."
103
+ out << ""
104
+ rows = bluebook.policies.map do |policy|
105
+ ["`#{policy.on_event}`", "`#{policy.trigger_command}`", policy.target_domain || bluebook.name]
106
+ end
107
+ out << table(%w[on\ event dispatches in], rows)
108
+ end
109
+
110
+ bluebook.process_managers.each do |saga|
111
+ shape = saga.to_h
112
+ out << h(depth, "#{shape[:name]} (a saga)")
113
+ out << ""
114
+ out << "Starts on `#{shape[:starts_on]}`, ends on `#{shape[:ends_on]}`, " \
115
+ "correlated by `#{shape[:correlates_by]}`."
116
+ out << ""
117
+ out << "States: #{Array(shape[:states]).map { |s| "`#{s}`" }.join(' → ')}."
118
+ out << ""
119
+ end
120
+
121
+ out.empty? ? nil : out.join("\n")
122
+ end
123
+
124
+ # ── one aggregate ─────────────────────────────────────────────────
125
+
126
+ def aggregate_section(aggregate, depth)
127
+ out = [h(depth, aggregate.hecks_name), ""]
128
+ out += [aggregate.description, ""] if aggregate.description
129
+
130
+ out << "Identified by `#{aggregate.identity_heads.join('`, `')}`." unless aggregate.identity_heads.empty?
131
+ refs = aggregate.attributes.select(&:reference?)
132
+ out << "References #{refs.map { |r| "`#{r.type.target_name}`" }.join(', ')}." unless refs.empty?
133
+ out << ""
134
+
135
+ out << attributes_table(aggregate)
136
+ out << lifecycle_section(aggregate, depth + 1)
137
+ out << verbs_section(aggregate, depth + 1)
138
+ out << queries_section(aggregate.queries, depth + 1, "Questions you can ask")
139
+
140
+ aggregate.entities.each { |entity| out << entity_section(aggregate, entity, depth + 1) }
141
+
142
+ out.compact.join("\n")
143
+ end
144
+
145
+ def entity_section(aggregate, entity, depth)
146
+ out = [h(depth, "#{entity.hecks_name} (within #{aggregate.hecks_name})"), ""]
147
+ out += [entity.description, ""] if entity.description
148
+ # THE THING A CALLER GETS WRONG FIRST. An entity has no door of its
149
+ # own: its verb is spelled through the aggregate that holds it, and
150
+ # the parent's id travels alongside the entity's own identity.
151
+ out << "Addressed through its holder — `#{aggregate.hecks_name}.#{entity.hecks_name}.<Verb>`, " \
152
+ "passing the #{aggregate.hecks_name}'s `id` and this element's " \
153
+ "`#{entity.identity_heads.join('`, `')}`."
154
+ out << ""
155
+ out << attributes_table(entity)
156
+ out << lifecycle_section(entity, depth + 1)
157
+ out << verbs_section(entity, depth + 1)
158
+ out << queries_section(entity.queries, depth + 1, "Questions you can ask")
159
+ out.compact.join("\n")
160
+ end
161
+
162
+ # ── the shape ─────────────────────────────────────────────────────
163
+
164
+ def attributes_table(holder)
165
+ attributes = holder.attributes.reject(&:reference?)
166
+ return nil if attributes.empty?
167
+
168
+ rows = attributes.map do |attribute|
169
+ ["`#{attribute.name}`", shape_of(attribute, holder), rules_of(attribute, holder)]
170
+ end
171
+ table(%w[attribute shape rules], rows)
172
+ end
173
+
174
+ # A VALUE OBJECT'S FIELDS, NOT ITS NAME. `commit` typed `CommitRef` tells
175
+ # a caller nothing; `{ value: String }` tells them what to send, which is
176
+ # the single most common thing to get wrong at this boundary — a bare
177
+ # scalar where an object is wanted.
178
+ def shape_of(attribute, holder)
179
+ value_object = value_object_for(attribute, holder)
180
+ inner =
181
+ if value_object
182
+ "{ " + value_object.attributes.map { |f| "#{f.name}: #{f.type}" }.join(", ") + " }"
183
+ else
184
+ attribute.type.to_s
185
+ end
186
+ shape = attribute.list? ? "list of #{inner}" : inner
187
+ attribute.optional? ? "#{shape} *(optional)*" : shape
188
+ end
189
+
190
+ def rules_of(attribute, holder)
191
+ value_object = value_object_for(attribute, holder)
192
+ rules = []
193
+ rules << "one of #{closed_members(value_object).map { |m| "`#{m}`" }.join(', ')}" if closed_members(value_object).any?
194
+ Array(value_object&.attributes).each do |field|
195
+ rules << "`#{field.name}` matches `#{field.pattern}`" if field.pattern
196
+ rules << "`#{field.name}` defaults to `#{field.default.inspect}`" unless field.default.nil?
197
+ end
198
+ rules += Array(value_object&.invariants).map(&:description)
199
+ rules << "defaults to `#{attribute.default.inspect}`" unless attribute.default.nil?
200
+ rules.empty? ? "" : rules.join("; ")
201
+ end
202
+
203
+ def closed_members(value_object)
204
+ return [] unless value_object&.closed_set?
205
+
206
+ value_object.members.flat_map(&:values).uniq
207
+ end
208
+
209
+ # An entity holds no value objects of its own — its argument types are
210
+ # declared on the aggregate above it.
211
+ def value_object_for(attribute, holder)
212
+ scopes = [holder, holder.respond_to?(:hecks_owner) ? holder.hecks_owner : nil].compact
213
+ scopes.each do |scope|
214
+ next unless scope.respond_to?(:value_objects)
215
+
216
+ found = scope.value_objects.find { |v| v.hecks_name == attribute.type.to_s }
217
+ return found if found
218
+ end
219
+ nil
220
+ end
221
+
222
+ # ── the machine ───────────────────────────────────────────────────
223
+
224
+ def lifecycle_section(holder, depth)
225
+ lifecycle = holder.lifecycle or return nil
226
+
227
+ rows = lifecycle.transitions.map do |name, transition|
228
+ ["`#{name}`", "`#{Array(transition.from).join('`, `')}`", "`#{transition.target}`"]
229
+ end
230
+ [h(depth, "Lifecycle (`#{lifecycle.field}`)"), "",
231
+ "Starts at `#{lifecycle.default}`. A verb not listed here can be issued from any state.", "",
232
+ table(%w[verb from to], rows)].join("\n")
233
+ end
234
+
235
+ # ── the verbs ─────────────────────────────────────────────────────
236
+
237
+ def verbs_section(holder, depth)
238
+ commands = holder.commands
239
+ return nil if commands.empty?
240
+
241
+ out = [h(depth, "Verbs"), ""]
242
+ commands.each { |command| out << command_entry(command, holder, depth + 1) }
243
+ out.join("\n")
244
+ end
245
+
246
+ def command_entry(command, holder, depth)
247
+ out = [h(depth, "#{command.hecks_name}#{command.creates? ? ' *(creates)*' : ''}"), ""]
248
+ out += [command.goal, ""] if command.goal
249
+ out << "Issued by: **#{command.role}**." if command.role
250
+ out << ""
251
+
252
+ arguments = command.attributes
253
+ unless arguments.empty?
254
+ rows = arguments.map do |attribute|
255
+ shape = attribute.reference? ? "id of a `#{attribute.type.target_name}`" : shape_of(attribute, holder)
256
+ ["`#{attribute.name}`", shape, attribute.optional? ? "" : "required"]
257
+ end
258
+ out << table(%w[argument shape needed], rows)
259
+ end
260
+
261
+ refusals = refusals_of(command, holder)
262
+ unless refusals.empty?
263
+ out << "Refused when:"
264
+ out << ""
265
+ refusals.each { |refusal| out << "- #{refusal}" }
266
+ out << ""
267
+ end
268
+
269
+ out << "Guarantees: #{command.ensures.map(&:description).join('; ')}." unless command.ensures.empty?
270
+ out << "Emits `#{command.emits.join('`, `')}`." unless command.emits.empty?
271
+ out << ""
272
+ out.join("\n")
273
+ end
274
+
275
+ # EVERY WAY THIS VERB CAN SAY NO, gathered from the three places a
276
+ # chapter states them — the lifecycle it is an edge of, its own
277
+ # `given`s, and the fact that a reference has to resolve. A caller
278
+ # reading only the argument list learns none of these, and they are
279
+ # most of what a domain is.
280
+ def refusals_of(command, holder)
281
+ refusals = []
282
+
283
+ lifecycle = holder.lifecycle
284
+ froms = lifecycle && lifecycle.transitions.filter_map do |name, transition|
285
+ Array(transition.from) if name.to_s == command.hecks_name
286
+ end.flatten.uniq
287
+ refusals << "`#{lifecycle.field}` is anything other than #{froms.map { |f| "`#{f}`" }.join(' or ')}" if froms && !froms.empty?
288
+
289
+ command.attributes.select(&:reference?).each do |reference|
290
+ refusals << "no `#{reference.type.target_name}` exists for the id given as `#{reference.name}`"
291
+ end
292
+
293
+ refusals + command.givens.map { |given| "not: #{given.description}" }
294
+ end
295
+
296
+ # ── the reads ─────────────────────────────────────────────────────
297
+
298
+ def queries_section(queries, depth, title)
299
+ return nil if queries.empty?
300
+
301
+ out = [h(depth, title), ""]
302
+ queries.each do |query|
303
+ shape = query.to_h
304
+ takes = Array(shape[:attributes]).map { |a| "`#{a[:name]}`" }.join(", ")
305
+ out << "**#{query.hecks_name}**#{takes.empty? ? '' : " (#{takes})"} "
306
+ out << (query.description ? "#{query.description} " : "")
307
+ filters = Array(shape[:wheres]).map { |w| "`#{w[:field]} #{w[:op]} #{w[:value].inspect}`" }
308
+ out << "Filters: #{filters.join(', ')}." unless filters.empty?
309
+ out << ""
310
+ end
311
+ out.join("\n")
312
+ end
313
+
314
+ def table(headers, rows)
315
+ lines = ["| #{headers.join(' | ')} |", "|#{headers.map { '---' }.join('|')}|"]
316
+ rows.each { |row| lines << "| #{row.join(' | ')} |" }
317
+ (lines + [""]).join("\n")
318
+ end
319
+ end
320
+ end
321
+ end
@@ -0,0 +1,141 @@
1
+ require "json"
2
+ require_relative "../runtime/era_check"
3
+ require_relative "../translation/rule_compiler"
4
+
5
+ module Hecks
6
+ module Projector
7
+ module Exporter
8
+ module_function
9
+
10
+ def call(registry)
11
+ registry.bluebooks.transform_values(&:to_h)
12
+ end
13
+
14
+ def json(registry)
15
+ JSON.pretty_generate(call(registry))
16
+ end
17
+
18
+ # A BINDING fact, deliberately NOT folded into `call`/`bluebook.to_h`
19
+ # above — the canonical IR is runtime-independent by design (ADR
20
+ # 0001: it describes what a bluebook DECLARES, never which adapter
21
+ # a deployment happens to bind it to), and "is this aggregate bound
22
+ # to a lineage-capable adapter" is exactly the kind of fact that
23
+ # answer can change per-deployment without the bluebook's own shape
24
+ # changing at all. Consumers that need it (bin/project_rust's own
25
+ # `ir.json` sidecar, rust/host's runtime era-aware seed overlay —
26
+ # dispatch.rs) merge this in as a SEPARATE top-level key, the same
27
+ # way `translations` already sits beside `call`'s output rather than
28
+ # inside it.
29
+ #
30
+ # Reuses `Runtime::EraCheck`'s own capability predicates rather than
31
+ # re-deriving them — the boot-time gate and this export must never
32
+ # answer differently for the same aggregate.
33
+ def lineage(registry, domain_name)
34
+ bluebook = registry.bluebooks.fetch(domain_name)
35
+ capable = bluebook.aggregates.select do |aggregate|
36
+ adapter_name = Runtime::EraCheck.adapter_for(registry, domain_name, aggregate)
37
+ Runtime::EraCheck.lineage_capable?(registry, adapter_name)
38
+ end
39
+
40
+ { capable_aggregates: capable.map { |aggregate| { name: aggregate.name, storage_name: aggregate.storage_name } } }
41
+ end
42
+
43
+ # Translation IR, always as an array, WITH each aggregate's
44
+ # precompiled SQL attached (`compiled_translation_aggregate`) —
45
+ # this is the export a consumer embeds (`ir.json`'s `translations`
46
+ # key), never the bare digest-relevant shape `edge_digest` hashes
47
+ # (see that method's own header for why the two must stay
48
+ # separate). `values:` tables serialize as `[key, value]` pairs,
49
+ # never an object, because JSON object keys are always strings and
50
+ # a convert's keys are typed.
51
+ def translations(registry)
52
+ registry.translations.map { |translation| compiled_translation_hash(translation) }
53
+ end
54
+
55
+ def compiled_translation_hash(translation)
56
+ {
57
+ domain: translation.domain,
58
+ from: translation.from,
59
+ to: translation.to,
60
+ retired: translation.retired,
61
+ aggregates: translation.aggregates.map { |aggregate| compiled_translation_aggregate(aggregate) }
62
+ }
63
+ end
64
+
65
+ def translations_json(registry)
66
+ JSON.pretty_generate(translations(registry))
67
+ end
68
+
69
+ # THE DIGEST-RELEVANT SHAPE — `ApprovalDigest.edge_digest` hashes
70
+ # EXACTLY this, and only this, for exactly the reason `compiled_
71
+ # translation_aggregate` below must never be used for that
72
+ # purpose: a digest bound to the COMPILED SQL, not just the
73
+ # DECLARED rules, would invalidate an existing human approval the
74
+ # moment `Translation::RuleCompiler`'s own output format changed
75
+ # for any reason — a compiler refactor, a cosmetic SQL-formatting
76
+ # change — even when the declared rules an approver actually
77
+ # reviewed never changed at all. The approval binds to WHAT WAS
78
+ # DECLARED, not to what a particular compiler build happened to
79
+ # emit from it.
80
+ def translation_hash(translation)
81
+ {
82
+ domain: translation.domain,
83
+ from: translation.from,
84
+ to: translation.to,
85
+ retired: translation.retired,
86
+ aggregates: translation.aggregates.map { |aggregate| translation_aggregate(aggregate) }
87
+ }
88
+ end
89
+
90
+ def translation_aggregate(aggregate)
91
+ {
92
+ name: aggregate.name,
93
+ was: aggregate.was,
94
+ renames: aggregate.renames.transform_keys(&:to_s).transform_values(&:to_s),
95
+ moves: aggregate.moves.map { |move| { from: move.from, to: move.to } },
96
+ converts: aggregate.converts.map do |convert|
97
+ { from: convert.from, to: convert.to, values: convert.values.map { |key, value| [key, value] } }
98
+ end,
99
+ drops: aggregate.drops.map(&:to_s),
100
+ retypes: aggregate.retypes.map { |retype| { from: retype.from, to: retype.to } },
101
+ computes: aggregate.computes.map { |compute| { from: compute.from, to: compute.to, sql: compute.sql } },
102
+ # PREVIOUSLY MISSING — found live while planning Rust-side mint
103
+ # support. An edge carrying only a rekey (no compute) had its
104
+ # approval bind to nothing rekey-specific at all: any two
105
+ # rekey edges with otherwise-identical renames/moves/converts/
106
+ # drops/retypes/computes produced the SAME digest regardless
107
+ # of what their `rekey sql:` actually said, and a rekey's own
108
+ # SQL could change without invalidating an existing approval.
109
+ # Same bug shape for `backfills` (present, just never
110
+ # exported). Fixing this CHANGES every existing rekey/
111
+ # backfill edge's digest — any approval already recorded for
112
+ # one is invalidated by this fix and must be re-reviewed.
113
+ rekeys: aggregate.rekeys.map { |rekey| { sql: rekey.sql } },
114
+ backfills: aggregate.backfills.map { |backfill| { name: backfill.name.to_s, default: backfill.default } }
115
+ }
116
+ end
117
+
118
+ # THE EXPORT SHAPE — `translation_aggregate`'s own digest-relevant
119
+ # fields, plus the precompiled SQL (`compiled_state_expression`/
120
+ # `compiled_id_expression`) a consumer embedding this JSON
121
+ # (rust/host's own boot-time mint) needs to execute the edge
122
+ # without compiling SQL itself. The SAME call head_compiler.rb's
123
+ # own `compile_rules(declared)`/`id_case(guard, declared)` make at
124
+ # mint time, run here once at build/export time instead —
125
+ # `Translation::RuleCompiler` is the ONE place this expression is
126
+ # built, called from both here and from head_compiler.rb's real
127
+ # per-mint assembly, so a consumer gets Ruby's own compiler's
128
+ # output verbatim, never a second, independently-authored SQL
129
+ # compiler that could drift from this one. `compiled_id_
130
+ # expression` is nil unless this edge rekeys — the bare
131
+ # `aggregate_id` passthrough head_compiler.rb itself falls back to
132
+ # for the overwhelming common case.
133
+ def compiled_translation_aggregate(aggregate)
134
+ translation_aggregate(aggregate).merge(
135
+ compiled_state_expression: Translation::RuleCompiler.compile_rules(aggregate),
136
+ compiled_id_expression: Translation::RuleCompiler.rekeyed?(aggregate) ? Translation::RuleCompiler.compile_id_expression(aggregate) : nil
137
+ )
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,18 @@
1
+ module Hecks
2
+ module Projector
3
+ # The trivial case, on purpose: canonical IR projected AS canonical
4
+ # IR. `Bluebook#to_h` already satisfies every §30 acceptance
5
+ # criterion on its own — no live runtime needed to call it, output is
6
+ # deterministic (the same golden fixtures `spec/ir_golden_spec.rb`
7
+ # pins), and target-version metadata rides along for free (`§7`'s
8
+ # `ir_version:` is the first key `to_h` emits). Registering it proves
9
+ # the framework's call shape against a real, already-load-bearing
10
+ # target before any genuinely new projector (`:rust`, `:ul`,
11
+ # `:openid`) has to.
12
+ module IRProjector
13
+ module_function
14
+
15
+ def call(bluebook:, options: {}) = bluebook.to_h
16
+ end
17
+ end
18
+ end