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,95 @@
1
+ # Hecks::Runtime
2
+ #
3
+ # The runtime layer's face. `lib/hecks/runtime/` held thirteen files and
4
+ # no parent, so the layer had no surface of its own : `lib/hecks.rb`
5
+ # required each file by hand, and the runtime's own state — the registry a
6
+ # declaration is being collected into — sat on the top-level Hecks module
7
+ # beside the DSL words.
8
+ #
9
+ # What lives here is what RUNS a domain :
10
+ #
11
+ # Runtime.boot(path) load a directory and return its Dispatcher
12
+ # Runtime.with_registry(r) bind the ambient registry for the duration of a load
13
+ # Runtime.current_registry the registry a declaration is landing in, or nil
14
+ #
15
+ # What does NOT live here is what DECLARES one — `Hecks.bluebook`,
16
+ # `.hecksagon`, `.port`, `.adapter`, `.world` are loading words and stay on the
17
+ # top-level module, which now reads as a facade over this.
18
+ #
19
+ # runtime = Hecks::Runtime.boot("examples/pizzas/bluebook")
20
+ # runtime.dispatch("Pizzas::Pizza.CreatePizza", name: "Margherita")
21
+ #
22
+ # NOTE, and it is the reason this file exists : `current_registry` is still
23
+ # process-global. Each boot builds a fresh Registry, and `Loader.bind_runtime`
24
+ # installs a fresh facade door whose modules close over THAT boot's dispatcher
25
+ # — no class-level runtime binding remains, so two boots in one process no
26
+ # longer share dispatch state, only the top-level NAME (the last-bound door
27
+ # wins the constant, which is what per-boot install means). Owning the state
28
+ # here was the first move ; the dispatcher reachable per-runtime rather than
29
+ # through a constant was the second, and it is done — the door is the only
30
+ # constant left, and it is a projection, not the runtime.
31
+
32
+ require_relative "runtime/event"
33
+ require_relative "runtime/value"
34
+ require_relative "runtime/identity"
35
+ require_relative "runtime/instance"
36
+ require_relative "runtime/registry"
37
+ require_relative "runtime/capability_graph"
38
+ require_relative "runtime/errors"
39
+ require_relative "runtime/refusal_wording"
40
+ require_relative "runtime/caller"
41
+ require_relative "runtime/routing"
42
+ require_relative "runtime/command_rules"
43
+ require_relative "runtime/dependency_planning"
44
+ require_relative "runtime/command_interpreter"
45
+ require_relative "runtime/port_operation_interpreter"
46
+ require_relative "runtime/entity_interpreter"
47
+ require_relative "runtime/query_interpreter"
48
+ require_relative "runtime/read_model_interpreter"
49
+ require_relative "runtime/policy_interpreter"
50
+ require_relative "runtime/saga_interpreter"
51
+ require_relative "runtime/dispatcher"
52
+ require_relative "runtime/rebuild_sweep"
53
+ require_relative "runtime/era_guard"
54
+ require_relative "runtime/storage_shape"
55
+ require_relative "runtime/era_tamper"
56
+ require_relative "runtime/era_check"
57
+ require_relative "runtime/tenant_check"
58
+ require_relative "runtime/loader"
59
+
60
+ module Hecks
61
+ module Runtime
62
+ class << self
63
+ # The registry declarations are currently landing in, or nil outside a
64
+ # boot. Read by the DSL collectors on the top-level module and by the
65
+ # extraction port.
66
+ attr_reader :current_registry
67
+
68
+ # Load a bluebook directory and return the Dispatcher bound to it.
69
+ # `install_facade:`, `environment:` — see Loader.boot.
70
+ def boot(path, shared: nil, install_facade: true, environment: nil)
71
+ Loader.boot(path, shared: shared, install_facade: install_facade, environment: environment)
72
+ end
73
+
74
+ # `paths` form — see Loader.boot_files.
75
+ def boot_files(paths, shared: nil, install_facade: true, environment: nil)
76
+ Loader.boot_files(paths, shared: shared, install_facade: install_facade, environment: environment)
77
+ end
78
+
79
+ # Bind the ambient registry for the duration of the block, restoring
80
+ # whatever was there before. Nesting is safe ; a raise still restores.
81
+ def with_registry(registry)
82
+ previous = @current_registry
83
+ @current_registry = registry
84
+ yield
85
+ ensure
86
+ @current_registry = previous
87
+ end
88
+
89
+ # Bind the ambient caller (see Runtime::Caller) for the duration of
90
+ # the block — who a command's declared `role`, if any, is checked
91
+ # against.
92
+ def as_caller(role:, actor_id: nil, &block) = Caller.as(role: role, actor_id: actor_id, &block)
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,31 @@
1
+ require "digest"
2
+ require_relative "../../projector/exporter"
3
+
4
+ module Hecks
5
+ module Translation
6
+ module Audit
7
+ # ── the human gate, made real ──────────────────────────────────
8
+ #
9
+ # For the five portable rule kinds the mint verifies the edge
10
+ # mechanically (Layer 2 IS the cross-execution equivalence gate).
11
+ # A compute has no mechanical verification — the Layer-3 sample a
12
+ # human reads is the only one there is — so a compute edge cannot
13
+ # mint until someone has run `bin/translation_audit … --approve`.
14
+ #
15
+ # The approval binds to WHAT WAS ACTUALLY REVIEWED, on both axes:
16
+ # a content digest of the parsed edge (change the edge's meaning
17
+ # and the approval lapses; a comment does not), and the journal's
18
+ # high-water ordinal at review time, recorded IN the target
19
+ # database — a token in the repo could not bind to the data, and
20
+ # samples reviewed against staging or against production-as-of-T
21
+ # say nothing about a database that has moved on. Mint stays
22
+ # non-interactive and its lock stays short; the human decision
23
+ # happens in the audit tool, where the samples are.
24
+ module ApprovalDigest
25
+ def edge_digest(edge)
26
+ Digest::SHA256.hexdigest(JSON.generate(Projector::Exporter.translation_hash(edge)))
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,37 @@
1
+ require_relative "../../runtime/errors"
2
+ require_relative "../../runtime/instance"
3
+ require_relative "../../runtime/value/invariant_violation"
4
+
5
+ module Hecks
6
+ module Translation
7
+ module Audit
8
+ # Layer 1 — from the bluebook alone: every translated state must
9
+ # pass the new era's types, value-object invariants, and lifecycle.
10
+ # This is also where a NEW, stricter invariant that old records
11
+ # violate surfaces — there is no "grandfather old records"
12
+ # construct, and the remedy is relaxing the invariant or explicit
13
+ # remediation, never a translation rule.
14
+ module LayerOne
15
+ def layer_one!(violations, aggregate, after)
16
+ after.each do |id, state|
17
+ begin
18
+ symbolized = JSON.parse(JSON.generate(state), symbolize_names: true)
19
+ instance = Runtime::Instance.new(aggregate: aggregate, id: id, state: symbolized)
20
+ lifecycle = aggregate.lifecycle
21
+ next unless lifecycle
22
+
23
+ held = instance[lifecycle.field]
24
+ allowed = lifecycle.states | [lifecycle.default]
25
+ unless held.nil? || allowed.include?(held)
26
+ violations << "#{aggregate.name}##{id}: #{lifecycle.field} is #{held.inspect}, " \
27
+ "a state this era's lifecycle never reaches"
28
+ end
29
+ rescue Runtime::InvariantViolation, Runtime::TypeMismatch => error
30
+ violations << "#{aggregate.name}##{id}: #{error.message}"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,77 @@
1
+ require_relative "../../ports/persistence/append_only"
2
+ require_relative "../../ports/persistence/lineage"
3
+
4
+ module Hecks
5
+ module Translation
6
+ module Audit
7
+ # Layer 2 — from the edge alone: per-rule value preservation, no
8
+ # leftover source keys, and id-set conservation across the edge.
9
+ module LayerTwo
10
+ # Per-rule value preservation and leftover source keys are checked
11
+ # against the reference transform IN FULL — never rule by rule in
12
+ # isolation, because rules interact (a rename whose value a later
13
+ # move partially consumes preserves exactly what the transform
14
+ # says it preserves, no more). This makes every mint a run of the
15
+ # cross-execution equivalence gate for the five portable rule
16
+ # kinds: the compiled SQL produced `after`; the port's entry-JSON
17
+ # transform produces `expected`; they must agree byte-for-byte on
18
+ # every path a compute doesn't own. Compute paths are exempt — the
19
+ # SQL is their only implementation, and the Layer-3 sample is
20
+ # their only review. A rekeyed aggregate is exempt from this WHOLE
21
+ # per-record check, for the same reason and one more: there is no
22
+ # old-id → new-id correspondence to look `after` up by once the id
23
+ # itself is what changed.
24
+ def layer_two!(violations, aggregate, declared, before, after)
25
+ rekeyed = declared && !declared.rekeys.empty?
26
+
27
+ # A rekey legitimately changes the id SET (that's the entire
28
+ # point) — set-equality would flag every honest rekey as data
29
+ # loss. What must still hold is RECORD COUNT: a botched rekey
30
+ # colliding two distinct old ids onto one new id, or dropping one
31
+ # (its SQL returning NULL), shows up as the count going down —
32
+ # caught here without needing to track the old→new mapping
33
+ # itself.
34
+ if rekeyed
35
+ unless before.keys.size == after.keys.size
36
+ violations << "#{aggregate.name}: the record count changed across a rekeying edge " \
37
+ "(#{before.keys.size} before, #{after.keys.size} after) — a rekey must not " \
38
+ "collide two distinct ids onto one, or drop one"
39
+ end
40
+ elsif before.keys.sort != after.keys.sort
41
+ gained = after.keys - before.keys
42
+ lost = before.keys - after.keys
43
+ violations << "#{aggregate.name}: the id set changed across the edge " \
44
+ "(lost #{lost.sort.inspect}, gained #{gained.sort.inspect})"
45
+ end
46
+ return unless declared
47
+ # No correspondence between an old id and its rekeyed row exists
48
+ # in Ruby (the rekey's SQL is its only implementation, same as
49
+ # compute's) — exempt from the per-record equivalence gate for
50
+ # the same reason compute paths already are, extended to the
51
+ # whole record since an old-id lookup into `after` can never
52
+ # succeed once the id itself has changed.
53
+ return if rekeyed
54
+
55
+ rules = Ports::Persistence::Lineage.from_declared(declared, aggregate.name)
56
+ compute_tops = declared.computes.flat_map { |compute| [compute.from, compute.to] }
57
+ .map { |path| path.to_s.split(".").first }
58
+
59
+ before.each do |id, state|
60
+ next unless after.key?(id)
61
+
62
+ entry = Ports::Persistence::Entry.new(operation: "save", id: id, state: state.transform_keys(&:to_sym))
63
+ expected = normalize(rules.translate(entry).state).reject { |key, _| compute_tops.include?(key) }
64
+ actual = normalize(after[id]).reject { |key, _| compute_tops.include?(key) }
65
+ next if expected == actual
66
+
67
+ diverged = (expected.keys | actual.keys).select { |key| expected[key] != actual[key] }
68
+ violations << "#{aggregate.name}##{id}: the translated state diverges from the reference " \
69
+ "transform at #{diverged.sort.join(', ')}"
70
+ end
71
+ end
72
+
73
+ def normalize(state) = JSON.parse(JSON.generate(state))
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,42 @@
1
+ module Hecks
2
+ module Translation
3
+ module Audit
4
+ # New-era attributes that nothing feeds: absent from every
5
+ # translated state, produced by no rule, and carrying no default.
6
+ # A report, not a violation — the remedy is a `default:` on the
7
+ # attribute, and the loud refusal for a REQUIRED one comes from
8
+ # Layer 1 the moment an invariant reads it.
9
+ module UnfedReport
10
+ def unfed(aggregate, declared, after)
11
+ fed = if declared
12
+ (declared.renames.values.map(&:to_s) +
13
+ declared.moves.map(&:to) +
14
+ declared.converts.map(&:to) +
15
+ declared.computes.map(&:to)).map { |path| path.to_s.split(".").first }
16
+ else
17
+ []
18
+ end
19
+ aggregate.attributes.filter_map do |attribute|
20
+ name = attribute.name.to_s
21
+ next if fed.include?(name)
22
+ next unless attribute.default.nil?
23
+ next if after.any? { |_, state| !dig_path(state, name).nil? }
24
+ next if after.empty?
25
+
26
+ name
27
+ end
28
+ end
29
+
30
+ def dig_path(state, path)
31
+ return nil if state.nil?
32
+
33
+ path.to_s.split(".").reduce(state) do |node, segment|
34
+ break nil unless node.is_a?(Hash)
35
+
36
+ node[segment] || node[segment.to_sym]
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,70 @@
1
+ require "json"
2
+
3
+ require_relative "audit/layer_one"
4
+ require_relative "audit/layer_two"
5
+ require_relative "audit/unfed_report"
6
+ require_relative "audit/approval_digest"
7
+
8
+ module Hecks
9
+ module Translation
10
+ # The audit derives its assertions; nobody hand-lists them. One file
11
+ # per layer beside this one: audit/layer_one.rb (the bluebook's own
12
+ # rules), audit/layer_two.rb (the edge against the reference
13
+ # transform), audit/unfed_report.rb (what nothing feeds), and
14
+ # audit/approval_digest.rb (the human gate's binding). Layer 3 — the
15
+ # before/after sample a HUMAN approves, intent not being derivable —
16
+ # is assembled right here in `check`.
17
+ module Audit
18
+ Verdict = Struct.new(:violations, :dropped, :unfed, :samples, keyword_init: true) do
19
+ def ok? = violations.empty?
20
+ end
21
+
22
+ SAMPLE_SIZE = 5
23
+
24
+ extend LayerOne
25
+ extend LayerTwo
26
+ extend UnfedReport
27
+ extend ApprovalDigest
28
+
29
+ module_function
30
+
31
+ # aggregate — the CURRENT era's IR; declared — this edge's rules
32
+ # for it (may be nil); before/after — {id => state hash}, source
33
+ # era's latest vs translated.
34
+ def check(aggregate:, declared:, before:, after:)
35
+ violations = []
36
+
37
+ layer_one!(violations, aggregate, after)
38
+ layer_two!(violations, aggregate, declared, before, after)
39
+
40
+ Verdict.new(
41
+ violations: violations,
42
+ dropped: declared ? declared.drops.map(&:to_s) : [],
43
+ unfed: unfed(aggregate, declared, after),
44
+ samples: samples_for(declared, before, after)
45
+ )
46
+ end
47
+
48
+ def samples_for(declared, before, after)
49
+ return rekeyed_samples(before, after) if declared && !declared.rekeys.empty?
50
+
51
+ before.keys.sort.first(SAMPLE_SIZE).map { |id| { id: id, before: before[id], after: after[id] } }
52
+ end
53
+
54
+ # A rekey changes the id itself, so `before`'s and `after`'s
55
+ # keyspaces share nothing — pairing by matching id (the ordinary
56
+ # path above) would show every `after` as nil, telling a human
57
+ # nothing. Nothing in-process can compute the old→new
58
+ # correspondence either (the rekey's SQL is its only
59
+ # implementation, same as compute's own). Shown side by side
60
+ # instead, each labelled by its OWN id: real records going in,
61
+ # real records coming out — not claimed to correspond one-to-one,
62
+ # but enough for the human this rule's only verification depends
63
+ # on to actually see real shapes and values, not a wall of null.
64
+ def rekeyed_samples(before, after)
65
+ before.keys.sort.first(SAMPLE_SIZE).map { |id| { id: "#{id} (before)", before: before[id], after: nil } } +
66
+ after.keys.sort.first(SAMPLE_SIZE).map { |id| { id: "#{id} (after)", before: nil, after: after[id] } }
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,72 @@
1
+ require "tempfile"
2
+ require_relative "../runtime/era_guard"
3
+ require_relative "../runtime/registry"
4
+ require_relative "../runtime/storage_shape"
5
+
6
+ module Hecks
7
+ module Translation
8
+ # The question re-attestation must answer before anything else: did
9
+ # the edit change the era's SHAPE, or only its text? Cosmetic edits
10
+ # (comments, whitespace, behavior) re-freeze safely; a shape change
11
+ # would retroactively redefine what era N *meant* for data already
12
+ # written under it, and refuses hard — there is no --accept past
13
+ # this guard.
14
+ #
15
+ # This does not violate minted-once. That prohibition exists so
16
+ # boot-time RECOGNITION never depends on canonicalization stability;
17
+ # this is operator-initiated repair, where a false negative is a
18
+ # loud refusal, never a silent misread.
19
+ module Reattest
20
+ module_function
21
+
22
+ def shape_guard!(domain:, ordinal:, text:, stored_hash:, stored_projection: nil)
23
+ bluebook = shadow(text)
24
+ unless bluebook
25
+ raise Runtime::WiringError,
26
+ "cannot re-attest era #{ordinal} of #{domain}: the edited text does not load as a " \
27
+ "bluebook — a held era text is bootable source; restore a loadable text"
28
+ end
29
+
30
+ # The stored PROJECTION is the preferred comparison: structural,
31
+ # version-free, and the same mechanism every boot trusts — so
32
+ # this guard never depends on canonicalization stability, and a
33
+ # future canonical-form version cannot make cosmetic edits to
34
+ # old-form eras false-refuse as shape changes. The hash path
35
+ # survives only as a fallback for stores that predate stored
36
+ # projections.
37
+ if stored_projection
38
+ edited = JSON.parse(JSON.generate(Runtime::StorageShape.project(bluebook)))
39
+ return :cosmetic if edited == stored_projection
40
+
41
+ raise Runtime::WiringError,
42
+ "cannot re-attest era #{ordinal} of #{domain}: the edit changed the era's SHAPE, not just " \
43
+ "its text — the text no longer projects to the shape frozen for era #{ordinal}. " \
44
+ "Attesting would retroactively redefine what era #{ordinal} meant for data already written " \
45
+ "under it; restore a text with the original shape"
46
+ end
47
+ return :unnamed unless stored_hash
48
+
49
+ computed = Runtime::StorageShape.mint_hash(bluebook)
50
+ return :cosmetic if computed == stored_hash
51
+
52
+ raise Runtime::WiringError,
53
+ "cannot re-attest era #{ordinal} of #{domain}: the edit changed the era's SHAPE, not just " \
54
+ "its text — its name #{stored_hash[0, Runtime::StorageShape::LABEL_LENGTH]} was minted from " \
55
+ "a different shape (the text now projects to #{computed[0, Runtime::StorageShape::LABEL_LENGTH]}). " \
56
+ "Attesting would retroactively redefine what era #{ordinal} meant for data already written " \
57
+ "under it; restore a text with the original shape"
58
+ end
59
+
60
+ def shadow(source)
61
+ file = Tempfile.new(["hecks-reattest-", ".bluebook"])
62
+ file.write(source)
63
+ file.flush
64
+ Runtime::EraGuard.shadow_parse(source, file.path)
65
+ rescue StandardError, SyntaxError
66
+ nil
67
+ ensure
68
+ file&.close!
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,120 @@
1
+ require "json"
2
+
3
+ module Hecks
4
+ module Translation
5
+ # The closed, PURE half of Postgres's own SQL compiler
6
+ # (adapters/driven/postgres_era/lineage/head_compiler.rb) — the part
7
+ # that turns one TranslationAggregate's declared rules into a
8
+ # jsonb-transforming SQL expression. No database connection, no
9
+ # watermark, no era chain, no catalog lookup: those stay exactly
10
+ # where they were, in head_compiler.rb's own per-mint assembly,
11
+ # which calls into this module instead of defining these methods
12
+ # itself.
13
+ #
14
+ # Extracted here — not left as private methods on
15
+ # Adapters::PostgresEra::Lineage — so a SECOND, adapter-agnostic
16
+ # caller (Exporter.translation_aggregate's build-time SQL export,
17
+ # feeding rust/host's own future boot-time mint) can call the exact
18
+ # SAME code Ruby's own mint path runs, not a hand-ported duplicate
19
+ # that could silently drift the way `Exporter.translation_hash`
20
+ # drifted from `hecks_eras`/`hecks_approvals`' real schema before
21
+ # this file existed (rekeys/backfills were missing for years).
22
+ module RuleCompiler
23
+ module_function
24
+
25
+ # One edge's rules over one jsonb state, compiled as a nested
26
+ # expression tree — hecks_tr_* helpers composed innermost-first
27
+ # in the reference transform's phase order (renames, moves,
28
+ # converts, drops), computes last. `retype` compiles to nothing:
29
+ # stored state never carries a type name.
30
+ def compile_rules(declared)
31
+ expression = "state"
32
+ declared.renames.each do |old_name, new_name|
33
+ expression = "hecks_tr_rename(#{expression}, #{text_literal(old_name)}, #{text_literal(new_name)})"
34
+ end
35
+ declared.moves.each do |move|
36
+ expression = "hecks_tr_move(#{expression}, #{path_literal(move.from)}, #{path_literal(move.to)}, " \
37
+ "#{text_literal("move #{move.from} to: #{move.to}")})"
38
+ end
39
+ declared.converts.each do |convert|
40
+ pairs = JSON.generate(convert.values.map { |key, value| [key, value] })
41
+ expression = "hecks_tr_convert(#{expression}, #{path_literal(convert.from)}, #{path_literal(convert.to)}, " \
42
+ "#{text_literal(pairs)}::jsonb, #{text_literal(convert.from)}, " \
43
+ "#{text_literal("convert #{convert.from} to: #{convert.to}")})"
44
+ end
45
+ declared.drops.each do |name|
46
+ expression = "hecks_tr_drop(#{expression}, #{path_literal(name)})"
47
+ end
48
+ declared.computes.each do |compute|
49
+ expression = compile_compute(expression, compute)
50
+ end
51
+ expression
52
+ end
53
+
54
+ # Whether THIS edge's declared rules for this aggregate include a
55
+ # rekey — checked directly off the raw IR object, the same way
56
+ # every other rule kind is already read in `compile_rules`
57
+ # (`declared.computes`, `declared.moves`, ...), not through the
58
+ # `Ports::Persistence::Lineage` wrapper the app-level consumers
59
+ # (coverage_check.rb, minter.rb, layer_two.rb) go through — this
60
+ # module builds SQL straight off the IR either way.
61
+ def rekeyed?(declared) = declared && !declared.rekeys.empty?
62
+
63
+ # THE ONLY TWO PLACES `aggregate_id` NEEDS TO CHANGE — guarded so
64
+ # the generated SQL for the overwhelming common case (no rekey
65
+ # declared) stays the bare `aggregate_id` passthrough it always
66
+ # was — this CASE only appears in an edge that actually declares
67
+ # one.
68
+ def id_case(guard, declared)
69
+ "CASE WHEN #{guard} THEN #{compile_id_expression(declared)} ELSE aggregate_id END AS aggregate_id"
70
+ end
71
+
72
+ # THE REKEY'S OWN SQL — reading `state` directly, not the
73
+ # progressively-built `expression` chain `compile_compute` reads
74
+ # from. A rekey doesn't consume or move any field the way a move
75
+ # or compute does, so there is no same-edge rename/move ordering
76
+ # it needs to see first — it reads the record's stored fields
77
+ # exactly as they already are, the same `__s` convention
78
+ # `compile_compute` exposes.
79
+ def compile_id_expression(declared)
80
+ rekey = declared.rekeys.first
81
+ "(SELECT (#{rekey.sql}) FROM (SELECT (state) AS __s) __outer)"
82
+ end
83
+
84
+ # A compute is the one rule whose SQL is its only implementation
85
+ # — evaluated exclusively inside the compiled head, never
86
+ # in-process. The old field is exposed under its own name (as
87
+ # text, exactly as the author's expression expects to cast it).
88
+ def compile_compute(expression, compute)
89
+ from = compute.from.to_s
90
+ to = compute.to.to_s
91
+ "(SELECT CASE WHEN __s ? #{text_literal(from)} THEN " \
92
+ "hecks_tr_insert(__s - #{text_literal(from)}, #{path_literal(to)}, to_jsonb((#{compute.sql})), " \
93
+ "#{text_literal("compute #{from} to: #{to}")}) " \
94
+ "ELSE __s END " \
95
+ "FROM (SELECT (#{expression}) AS __s) __outer, " \
96
+ "LATERAL (SELECT (__s ->> #{text_literal(from)}) AS #{quote(from)}) __fields)"
97
+ end
98
+
99
+ # `PG::Connection.quote_ident` needs the `pg` gem LOADED, not
100
+ # connected — required here, lazily, the same "a domain that
101
+ # never wires PostgresEra should never need the gem" reasoning
102
+ # `PostgresEra.connect_for`'s own `require "pg"` already holds
103
+ # itself to, so a build tool that exports translations for a
104
+ # non-Postgres-bound domain (there are none today, but nothing
105
+ # here should assume there never will be) doesn't gain a hard
106
+ # dependency on `pg` just by loading this file.
107
+ def quote(name)
108
+ require "pg"
109
+ PG::Connection.quote_ident(name.to_s)
110
+ end
111
+
112
+ def text_literal(text) = "'#{text.to_s.gsub("'", "''")}'"
113
+
114
+ def path_literal(path)
115
+ segments = path.to_s.split(".").map { |segment| text_literal(segment) }
116
+ "ARRAY[#{segments.join(', ')}]::text[]"
117
+ end
118
+ end
119
+ end
120
+ end