hecks 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. checksums.yaml +5 -5
  2. data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +127 -0
  4. data/lib/hecks/adapters/driven/d1.adapter +12 -0
  5. data/lib/hecks/adapters/driven/d1.rb +427 -0
  6. data/lib/hecks/adapters/driven/folder.adapter +3 -0
  7. data/lib/hecks/adapters/driven/folder.rb +199 -0
  8. data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
  9. data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
  10. data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
  11. data/lib/hecks/adapters/driven/governance_authorization.rb +101 -0
  12. data/lib/hecks/adapters/driven/heki/journal.rb +56 -0
  13. data/lib/hecks/adapters/driven/heki/saga_store.rb +99 -0
  14. data/lib/hecks/adapters/driven/heki/snapshot.rb +65 -0
  15. data/lib/hecks/adapters/driven/heki.adapter +4 -0
  16. data/lib/hecks/adapters/driven/heki.rb +179 -0
  17. data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
  18. data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
  19. data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
  20. data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
  21. data/lib/hecks/adapters/driven/lambda.adapter +4 -0
  22. data/lib/hecks/adapters/driven/lambda.rb +145 -0
  23. data/lib/hecks/adapters/driven/memory.adapter +3 -0
  24. data/lib/hecks/adapters/driven/memory.rb +103 -0
  25. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
  26. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
  27. data/lib/hecks/adapters/driven/postgres/codec.rb +88 -0
  28. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +207 -0
  29. data/lib/hecks/adapters/driven/postgres.adapter +5 -0
  30. data/lib/hecks/adapters/driven/postgres.rb +438 -0
  31. data/lib/hecks/adapters/driven/postgres_era.adapter +17 -0
  32. data/lib/hecks/adapters/driven/prism.adapter +3 -0
  33. data/lib/hecks/adapters/driven/prism.rb +80 -0
  34. data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
  35. data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
  36. data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
  37. data/lib/hecks/adapters/driven/sqlite/codec.rb +80 -0
  38. data/lib/hecks/adapters/driven/sqlite/projection.rb +172 -0
  39. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +192 -0
  40. data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
  41. data/lib/hecks/adapters/driven/sqlite.rb +306 -0
  42. data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
  43. data/lib/hecks/adapters/driven/system_clock.rb +14 -0
  44. data/lib/hecks/adapters/driven.rb +54 -0
  45. data/lib/hecks/adapters.rb +6 -0
  46. data/lib/hecks/behaviors/dsl.rb +125 -0
  47. data/lib/hecks/behaviors/expectations.rb +340 -0
  48. data/lib/hecks/behaviors/ir.rb +31 -0
  49. data/lib/hecks/behaviors/rspec.rb +42 -0
  50. data/lib/hecks/behaviors/runner.rb +96 -0
  51. data/lib/hecks/behaviors.rb +25 -0
  52. data/lib/hecks/bluebook/aggregate.rb +108 -0
  53. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
  54. data/lib/hecks/bluebook/assembly/build.rb +48 -0
  55. data/lib/hecks/bluebook/assembly/contract.rb +112 -0
  56. data/lib/hecks/bluebook/assembly/contracts.rb +438 -0
  57. data/lib/hecks/bluebook/assembly/marks.rb +228 -0
  58. data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
  59. data/lib/hecks/bluebook/assembly.rb +91 -0
  60. data/lib/hecks/bluebook/attribute.rb +96 -0
  61. data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
  62. data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
  63. data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
  64. data/lib/hecks/bluebook/behaviour/command.rb +116 -0
  65. data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
  66. data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
  67. data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
  68. data/lib/hecks/bluebook/behaviour/lifecycle.rb +68 -0
  69. data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
  70. data/lib/hecks/bluebook/behaviour/process_manager.rb +51 -0
  71. data/lib/hecks/bluebook/behaviour/query.rb +10 -0
  72. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
  73. data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
  74. data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
  75. data/lib/hecks/bluebook/chapter.rb +78 -0
  76. data/lib/hecks/bluebook/command.rb +124 -0
  77. data/lib/hecks/bluebook/domain_port.rb +102 -0
  78. data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
  79. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +1018 -0
  80. data/lib/hecks/bluebook/dsl/attribute_collector.rb +348 -0
  81. data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
  82. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1087 -0
  83. data/lib/hecks/bluebook/dsl/command_builder.rb +714 -0
  84. data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
  85. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
  86. data/lib/hecks/bluebook/dsl/entity_builder.rb +430 -0
  87. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
  88. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
  89. data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
  90. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
  91. data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
  92. data/lib/hecks/bluebook/dsl/policy_builder.rb +135 -0
  93. data/lib/hecks/bluebook/dsl/port_builder.rb +39 -0
  94. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
  95. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +306 -0
  96. data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
  97. data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
  98. data/lib/hecks/bluebook/dsl/rule_reference.rb +173 -0
  99. data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
  100. data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
  101. data/lib/hecks/bluebook/dsl/word_gate.rb +228 -0
  102. data/lib/hecks/bluebook/dsl/world_builder.rb +117 -0
  103. data/lib/hecks/bluebook/dsl.rb +45 -0
  104. data/lib/hecks/bluebook/entity.rb +103 -0
  105. data/lib/hecks/bluebook/expression/canonical_form.rb +123 -0
  106. data/lib/hecks/bluebook/expression/evaluator.rb +304 -0
  107. data/lib/hecks/bluebook/expression/projection.json +218 -0
  108. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
  109. data/lib/hecks/bluebook/expression/resolver.rb +781 -0
  110. data/lib/hecks/bluebook/expression.rb +13 -0
  111. data/lib/hecks/bluebook/hexagon.rb +60 -0
  112. data/lib/hecks/bluebook/lifecycle.rb +42 -0
  113. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
  114. data/lib/hecks/bluebook/meta_validator/judge.rb +621 -0
  115. data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
  116. data/lib/hecks/bluebook/meta_validator/port_judge.rb +51 -0
  117. data/lib/hecks/bluebook/meta_validator/readings.rb +360 -0
  118. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +351 -0
  119. data/lib/hecks/bluebook/meta_validator/shapes.rb +266 -0
  120. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
  121. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
  122. data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
  123. data/lib/hecks/bluebook/meta_validator.rb +542 -0
  124. data/lib/hecks/bluebook/model_check.rb +445 -0
  125. data/lib/hecks/bluebook/pattern_subset.rb +184 -0
  126. data/lib/hecks/bluebook/policy.rb +41 -0
  127. data/lib/hecks/bluebook/process_manager.rb +128 -0
  128. data/lib/hecks/bluebook/project_discovery.rb +30 -0
  129. data/lib/hecks/bluebook/project_loader.rb +40 -0
  130. data/lib/hecks/bluebook/project_register.rb +82 -0
  131. data/lib/hecks/bluebook/query.rb +61 -0
  132. data/lib/hecks/bluebook/read_model.rb +109 -0
  133. data/lib/hecks/bluebook/reference.rb +74 -0
  134. data/lib/hecks/bluebook/smoke_test.rb +166 -0
  135. data/lib/hecks/bluebook/synthesizer.rb +95 -0
  136. data/lib/hecks/bluebook/translation.rb +92 -0
  137. data/lib/hecks/bluebook/value_object.rb +66 -0
  138. data/lib/hecks/bluebook.rb +74 -0
  139. data/lib/hecks/codemod.rb +342 -0
  140. data/lib/hecks/construct.rb +71 -0
  141. data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
  142. data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
  143. data/lib/hecks/deploy/oidc.json +18 -0
  144. data/lib/hecks/doc/reference.rb +410 -0
  145. data/lib/hecks/embryonaut_bluebook.rb +75 -0
  146. data/lib/hecks/facade/cli_door.rb +119 -0
  147. data/lib/hecks/facade/cli_runner.rb +190 -0
  148. data/lib/hecks/facade/command_request.rb +105 -0
  149. data/lib/hecks/facade/handle.rb +173 -0
  150. data/lib/hecks/facade/json_door.rb +166 -0
  151. data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
  152. data/lib/hecks/facade/surface/chapter.rb +107 -0
  153. data/lib/hecks/facade/surface.rb +48 -0
  154. data/lib/hecks/facade.rb +44 -0
  155. data/lib/hecks/forms/app.rb +341 -0
  156. data/lib/hecks/forms/command_form_renderer.rb +113 -0
  157. data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
  158. data/lib/hecks/forms/field_renderer.rb +177 -0
  159. data/lib/hecks/forms/field_shape.rb +232 -0
  160. data/lib/hecks/forms/html.rb +84 -0
  161. data/lib/hecks/forms/index_renderer.rb +35 -0
  162. data/lib/hecks/forms/page.rb +157 -0
  163. data/lib/hecks/forms/params.rb +161 -0
  164. data/lib/hecks/forms/port_argument.rb +46 -0
  165. data/lib/hecks/forms/query_form_renderer.rb +114 -0
  166. data/lib/hecks/forms/record_renderer.rb +119 -0
  167. data/lib/hecks/forms/record_table.rb +68 -0
  168. data/lib/hecks/forms/reference_options.rb +30 -0
  169. data/lib/hecks/forms/value_object_shape.rb +46 -0
  170. data/lib/hecks/forms.rb +54 -0
  171. data/lib/hecks/fqn.rb +94 -0
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
  175. data/lib/hecks/framework/bluebook/governance.bluebook +145 -0
  176. data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
  177. data/lib/hecks/framework/oidc.json +39 -0
  178. data/lib/hecks/framework.rb +90 -0
  179. data/lib/hecks/freezer.rb +67 -0
  180. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +527 -0
  181. data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
  182. data/lib/hecks/fuzzing/isolated_boot.rb +286 -0
  183. data/lib/hecks/fuzzing/properties.rb +1192 -0
  184. data/lib/hecks/fuzzing/replay.rb +668 -0
  185. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
  186. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +80 -0
  187. data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
  188. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +250 -0
  189. data/lib/hecks/fuzzing/sequence_generator.rb +131 -0
  190. data/lib/hecks/fuzzing/value_generator.rb +200 -0
  191. data/lib/hecks/fuzzing.rb +12 -0
  192. data/lib/hecks/grammar/evolve.rb +324 -0
  193. data/lib/hecks/grammar/expression.bluebook +420 -0
  194. data/lib/hecks/grammar/expression_operators.json +1648 -0
  195. data/lib/hecks/grammar/grammar.hecksagon +20 -0
  196. data/lib/hecks/grammar/oidc.json +69 -0
  197. data/lib/hecks/grammar/translation.bluebook +203 -0
  198. data/lib/hecks/grammar.rb +178 -0
  199. data/lib/hecks/ir.rb +126 -0
  200. data/lib/hecks/language/adapter.bluebook +116 -0
  201. data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
  202. data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
  203. data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
  204. data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
  205. data/lib/hecks/language/bluebook/command.bluebook +471 -0
  206. data/lib/hecks/language/bluebook/entity.bluebook +392 -0
  207. data/lib/hecks/language/bluebook/policy.bluebook +189 -0
  208. data/lib/hecks/language/bluebook/process_manager.bluebook +381 -0
  209. data/lib/hecks/language/bluebook/projection.bluebook +267 -0
  210. data/lib/hecks/language/bluebook/query.bluebook +245 -0
  211. data/lib/hecks/language/bluebook/shape.bluebook +292 -0
  212. data/lib/hecks/language/bluebook/syntax.bluebook +448 -0
  213. data/lib/hecks/language/bluebook/vocabulary.bluebook +379 -0
  214. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
  215. data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
  216. data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
  217. data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
  218. data/lib/hecks/language/oidc.json +333 -0
  219. data/lib/hecks/language/port.bluebook +120 -0
  220. data/lib/hecks/language/translation/translation.bluebook +110 -0
  221. data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
  222. data/lib/hecks/language/world/wiring.bluebook +62 -0
  223. data/lib/hecks/language/world/world.bluebook +84 -0
  224. data/lib/hecks/literal.rb +125 -0
  225. data/lib/hecks/naming.rb +174 -0
  226. data/lib/hecks/ports/access_control.port +9 -0
  227. data/lib/hecks/ports/access_control.rb +62 -0
  228. data/lib/hecks/ports/agent/answers.rb +104 -0
  229. data/lib/hecks/ports/agent.port +8 -0
  230. data/lib/hecks/ports/agent.rb +167 -0
  231. data/lib/hecks/ports/authentication.port +6 -0
  232. data/lib/hecks/ports/authentication.rb +50 -0
  233. data/lib/hecks/ports/authorization.port +7 -0
  234. data/lib/hecks/ports/authorization.rb +62 -0
  235. data/lib/hecks/ports/clock.port +5 -0
  236. data/lib/hecks/ports/clock.rb +62 -0
  237. data/lib/hecks/ports/extraction.port +5 -0
  238. data/lib/hecks/ports/extraction.rb +37 -0
  239. data/lib/hecks/ports/identity_assignment.port +5 -0
  240. data/lib/hecks/ports/identity_assignment.rb +45 -0
  241. data/lib/hecks/ports/identity_generation.port +5 -0
  242. data/lib/hecks/ports/identity_generation.rb +49 -0
  243. data/lib/hecks/ports/identity_resolution.port +5 -0
  244. data/lib/hecks/ports/identity_resolution.rb +40 -0
  245. data/lib/hecks/ports/loading.port +4 -0
  246. data/lib/hecks/ports/loading.rb +13 -0
  247. data/lib/hecks/ports/persistence/append_only.rb +138 -0
  248. data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
  249. data/lib/hecks/ports/persistence/execution.rb +23 -0
  250. data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
  251. data/lib/hecks/ports/persistence/plugin.rb +54 -0
  252. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +169 -0
  253. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +128 -0
  254. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +161 -0
  255. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +61 -0
  256. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +304 -0
  257. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +171 -0
  258. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +190 -0
  259. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +478 -0
  260. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +166 -0
  261. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +314 -0
  262. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +168 -0
  263. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +170 -0
  264. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +134 -0
  265. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +137 -0
  266. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +89 -0
  267. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +85 -0
  268. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
  269. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +132 -0
  270. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +74 -0
  271. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +758 -0
  272. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +120 -0
  273. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +31 -0
  274. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +46 -0
  275. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +102 -0
  276. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +46 -0
  277. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +70 -0
  278. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +72 -0
  279. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +120 -0
  280. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +183 -0
  281. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +41 -0
  282. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +28 -0
  283. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +30 -0
  284. data/lib/hecks/ports/persistence/plugins/era/translation.rb +11 -0
  285. data/lib/hecks/ports/persistence/plugins/era.rb +48 -0
  286. data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
  287. data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
  288. data/lib/hecks/ports/persistence.port +4 -0
  289. data/lib/hecks/ports/persistence.rb +39 -0
  290. data/lib/hecks/ports/projection.port +4 -0
  291. data/lib/hecks/ports/projection.rb +96 -0
  292. data/lib/hecks/ports/query/in_memory.rb +60 -0
  293. data/lib/hecks/ports/query/ordering.rb +41 -0
  294. data/lib/hecks/ports/query.rb +36 -0
  295. data/lib/hecks/ports.rb +27 -0
  296. data/lib/hecks/projections/diagrams.rb +642 -0
  297. data/lib/hecks/projections/ir.rb +18 -0
  298. data/lib/hecks/projections/model/deviations.rb +98 -0
  299. data/lib/hecks/projections/model.rb +145 -0
  300. data/lib/hecks/projections/oidc.rb +110 -0
  301. data/lib/hecks/projections/parser_table.rb +159 -0
  302. data/lib/hecks/projections/reference.rb +38 -0
  303. data/lib/hecks/projections/shape.rb +44 -0
  304. data/lib/hecks/projections/statements.rb +110 -0
  305. data/lib/hecks/projections/vocabulary.rb +100 -0
  306. data/lib/hecks/projections.rb +32 -0
  307. data/lib/hecks/projector/cli_projector.rb +446 -0
  308. data/lib/hecks/projector/docs_projector.rb +321 -0
  309. data/lib/hecks/projector/exporter.rb +158 -0
  310. data/lib/hecks/projector/ir_projector.rb +18 -0
  311. data/lib/hecks/projector/narrate_projector.rb +243 -0
  312. data/lib/hecks/projector/target.rb +97 -0
  313. data/lib/hecks/projector.rb +186 -0
  314. data/lib/hecks/query_ir.rb +411 -0
  315. data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
  316. data/lib/hecks/query_specification/common/comparators.rb +25 -0
  317. data/lib/hecks/query_specification/common/comparison.rb +174 -0
  318. data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
  319. data/lib/hecks/query_specification/common/dsl.rb +58 -0
  320. data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
  321. data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
  322. data/lib/hecks/query_specification/common/null_policy.rb +92 -0
  323. data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
  324. data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
  325. data/lib/hecks/query_specification/common/options.rb +32 -0
  326. data/lib/hecks/query_specification/common/order_by.rb +9 -0
  327. data/lib/hecks/query_specification/common/specification.rb +10 -0
  328. data/lib/hecks/query_specification/common/where_clause.rb +9 -0
  329. data/lib/hecks/query_specification/field_path.rb +107 -0
  330. data/lib/hecks/query_specification/hop_path.rb +132 -0
  331. data/lib/hecks/query_specification/read_model/specification.rb +18 -0
  332. data/lib/hecks/query_specification.rb +14 -0
  333. data/lib/hecks/rendering.rb +48 -0
  334. data/lib/hecks/router/namespace_installer.rb +157 -0
  335. data/lib/hecks/router.rb +70 -0
  336. data/lib/hecks/runtime/aggregate_lock.rb +45 -0
  337. data/lib/hecks/runtime/boot_gates.rb +41 -0
  338. data/lib/hecks/runtime/caller.rb +66 -0
  339. data/lib/hecks/runtime/capability_graph.rb +44 -0
  340. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +135 -0
  341. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +260 -0
  342. data/lib/hecks/runtime/command_interpreter.rb +497 -0
  343. data/lib/hecks/runtime/command_rules/admissibility.rb +357 -0
  344. data/lib/hecks/runtime/command_rules/arithmetic.rb +260 -0
  345. data/lib/hecks/runtime/command_rules/authorization.rb +65 -0
  346. data/lib/hecks/runtime/command_rules/emission.rb +34 -0
  347. data/lib/hecks/runtime/command_rules/references.rb +189 -0
  348. data/lib/hecks/runtime/command_rules.rb +28 -0
  349. data/lib/hecks/runtime/dependency_planning.rb +245 -0
  350. data/lib/hecks/runtime/dispatcher.rb +301 -0
  351. data/lib/hecks/runtime/entity_element.rb +253 -0
  352. data/lib/hecks/runtime/entity_interpreter.rb +299 -0
  353. data/lib/hecks/runtime/errors.rb +122 -0
  354. data/lib/hecks/runtime/event.rb +51 -0
  355. data/lib/hecks/runtime/identity.rb +156 -0
  356. data/lib/hecks/runtime/instance.rb +175 -0
  357. data/lib/hecks/runtime/interpreting.rb +90 -0
  358. data/lib/hecks/runtime/loader.rb +184 -0
  359. data/lib/hecks/runtime/policy_interpreter.rb +366 -0
  360. data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
  361. data/lib/hecks/runtime/query_interpreter.rb +286 -0
  362. data/lib/hecks/runtime/reaction_invocation.rb +253 -0
  363. data/lib/hecks/runtime/read_model_interpreter.rb +341 -0
  364. data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
  365. data/lib/hecks/runtime/reference_hop.rb +99 -0
  366. data/lib/hecks/runtime/refusal_wording.rb +126 -0
  367. data/lib/hecks/runtime/registry/saga_persistence.rb +142 -0
  368. data/lib/hecks/runtime/registry/verification.rb +246 -0
  369. data/lib/hecks/runtime/registry.rb +279 -0
  370. data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
  371. data/lib/hecks/runtime/routing.rb +96 -0
  372. data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
  373. data/lib/hecks/runtime/saga_interpreter.rb +503 -0
  374. data/lib/hecks/runtime/saga_pending_dispatch.rb +45 -0
  375. data/lib/hecks/runtime/tenant_check.rb +84 -0
  376. data/lib/hecks/runtime/tenant_scope.rb +55 -0
  377. data/lib/hecks/runtime/value/admission.rb +130 -0
  378. data/lib/hecks/runtime/value/coercion.rb +565 -0
  379. data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
  380. data/lib/hecks/runtime/value.rb +125 -0
  381. data/lib/hecks/runtime.rb +107 -0
  382. data/lib/hecks/storehouse.rb +632 -0
  383. data/lib/hecks/version.rb +16 -0
  384. data/lib/hecks/vocabulary.rb +218 -0
  385. data/lib/hecks.rb +123 -6
  386. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +90 -0
  387. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +128 -0
  388. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +160 -0
  389. metadata +412 -222
  390. data/bin/hecks +0 -7
  391. data/bin/hecks-package +0 -65
  392. data/bin/hecks_console +0 -12
  393. data/bin/hecks_serverless +0 -6
  394. data/lib/cli/build.rb +0 -14
  395. data/lib/cli/command_runner.rb +0 -28
  396. data/lib/cli/console.rb +0 -10
  397. data/lib/cli/generate.rb +0 -37
  398. data/lib/cli/hecks-cli.rb +0 -27
  399. data/lib/cli/test.rb +0 -57
  400. data/lib/console/commands.rb +0 -8
  401. data/lib/console/hecks-console.rb +0 -1
  402. data/lib/packager/README.md +0 -0
  403. data/lib/packager/app_runner.rb +0 -21
  404. data/lib/packager/args.rb +0 -26
  405. data/lib/packager/compatibility/fixnum.rb +0 -6
  406. data/lib/packager/hecks.rb +0 -39
  407. data/lib/packager/query_runner.rb +0 -21
  408. data/lib/packager/resources/Dockerfile +0 -11
  409. data/lib/packager/resources/app_binary +0 -7
  410. data/lib/packager/resources/bundle_config +0 -3
  411. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
  412. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
  413. data/lib/serverless/Domain +0 -32
  414. data/lib/serverless/cli.rb +0 -75
  415. data/lib/serverless/resources/command_name.js +0 -5
  416. data/lib/serverless/resources/environment.js +0 -7
  417. data/lib/serverless/resources/handler.js.tt +0 -28
  418. data/lib/serverless/resources/run_binary.js +0 -22
  419. data/lib/serverless/resources/serverless.yml +0 -20
@@ -0,0 +1,120 @@
1
+ require "json"
2
+ require "digest"
3
+
4
+ module Hecks
5
+ module Runtime
6
+ # The storage-shape projection of a bluebook: exactly the parts of
7
+ # the IR that decide what persisted data looks like — aggregates,
8
+ # their attributes (with full value-object/entity structure and
9
+ # cardinality), references, the lifecycle field, and the identity
10
+ # paths. Everything behavioral — commands, invariants, queries,
11
+ # policies, lifecycle transitions, defaults, descriptions, comments,
12
+ # the declared routing `version:` — is excluded, so editing behavior
13
+ # never bumps an era and editing shape always does.
14
+ #
15
+ # Three attribute facts on the wire are constraints on PERSISTED
16
+ # VALUES, not behavior, and are still excluded — each decided, not
17
+ # overlooked:
18
+ #
19
+ # `optional` — required-ness is enforced at dispatch; stored rows
20
+ # are never re-validated on read, so flipping it
21
+ # strands nothing already written. Excluded.
22
+ # `pattern` — same argument: a fact about what may be WRITTEN
23
+ # next, not about what was stored. Excluded.
24
+ # `admits` — the sharpest of the three: narrowing a closed set
25
+ # CAN strand stored rows outside it, and the wire
26
+ # carries only the set's NAME, so a set whose members
27
+ # changed under a stable name is invisible even to a
28
+ # projection that included the fact (the same lesson
29
+ # recursive value-object drift taught). Excluded, and
30
+ # NAMED as a gap: constraint tightening has no
31
+ # translation-rule vocabulary to acknowledge it yet,
32
+ # so including it would mint era bumps nothing can
33
+ # explain. When the translation language grows a
34
+ # constraint-acknowledgment rule, `admits` (by member
35
+ # list, not by name) is first in line, and that
36
+ # change bumps FORM_VERSION.
37
+ #
38
+ # Projection runs over the canonical dump form (`to_h`, JSON
39
+ # round-tripped), so a verdict depends only on the IR — never on live
40
+ # object graphs. Structural comparison only, never a hash comparison.
41
+ module StorageShape
42
+ module_function
43
+
44
+ def project(bluebook)
45
+ domain = JSON.parse(JSON.generate(bluebook.to_h))
46
+ {
47
+ "name" => bluebook.name,
48
+ "aggregates" => (domain["aggregates"] || [])
49
+ .map { |aggregate| project_aggregate(aggregate) }
50
+ .sort_by { |aggregate| aggregate["name"] }
51
+ }
52
+ end
53
+
54
+ def same?(held, current) = project(held) == project(current)
55
+
56
+ # The canonical serialization the Ruby scaffold hashes at MINT time
57
+ # — the one moment identity is computed. Nothing ever recomputes a
58
+ # stored era name to verify it, so this form can evolve freely.
59
+ def canonical(bluebook) = JSON.generate(project(bluebook))
60
+
61
+ def mint_hash(bluebook) = Digest::SHA256.hexdigest(canonical(bluebook))
62
+
63
+ LABEL_LENGTH = 6
64
+ def mint_label(bluebook) = mint_hash(bluebook)[0, LABEL_LENGTH]
65
+
66
+ # The version of the canonical serialization above. Minted-once
67
+ # means a stored name stays valid across form changes — but only
68
+ # if each name records WHICH form minted it, so v1-named and
69
+ # v2-named eras coexist legibly. Stored beside every minted hash
70
+ # (names.tsv fourth field / hecks_eras.canon_form); bump this in
71
+ # the same change that alters project/canonical output.
72
+ FORM_VERSION = 1
73
+
74
+ def project_aggregate(aggregate)
75
+ {
76
+ "name" => aggregate["name"],
77
+ # The declared identity paths, AS A LIST, in declaration order —
78
+ # order is semantic (the paths join in order to form the id).
79
+ # No "id" fallback: an aggregate that declares nothing has [],
80
+ # and that is a real declared state, distinct from an aggregate
81
+ # identified by a field named "id".
82
+ "identity" => Array(aggregate["identified_by"]).map(&:to_s),
83
+ "lifecycle_field" => aggregate.dig("lifecycle", "field"),
84
+ "attributes" => (aggregate["attributes"] || [])
85
+ .map { |attribute| project_attribute(aggregate, attribute, []) }
86
+ .sort_by { |attribute| attribute["name"] }
87
+ }
88
+ end
89
+
90
+ def project_attribute(aggregate, attribute, seen)
91
+ {
92
+ "name" => attribute["name"].to_s,
93
+ "list" => attribute["list"] ? true : false,
94
+ "type" => type_signature(aggregate, attribute["type"].to_s, seen)
95
+ }
96
+ end
97
+
98
+ # A plain type name for a primitive; the type name plus its
99
+ # members' full signatures for a value object or entity — so two
100
+ # attributes with the same declared type name but different
101
+ # internals are never mistaken for unchanged.
102
+ def type_signature(aggregate, type_name, seen)
103
+ container = nested_type(aggregate, type_name)
104
+ return type_name if container.nil? || seen.include?(type_name)
105
+
106
+ {
107
+ "type" => type_name,
108
+ "members" => (container["attributes"] || [])
109
+ .map { |member| project_attribute(aggregate, member, seen + [type_name]) }
110
+ .sort_by { |member| member["name"] }
111
+ }
112
+ end
113
+
114
+ def nested_type(aggregate, type_name)
115
+ (aggregate["value_objects"] || []).find { |vo| vo["name"] == type_name } ||
116
+ (aggregate["entities"] || []).find { |entity| entity["name"] == type_name }
117
+ end
118
+ end
119
+ end
120
+ 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,46 @@
1
+ require_relative "../../../../../../runtime/errors"
2
+ require_relative "../../../../../../runtime/instance"
3
+ require_relative "../../../../../../runtime/value/invariant_violation"
4
+ require_relative "../../../../../../bluebook/model_check"
5
+
6
+ module Hecks
7
+ module Translation
8
+ module Audit
9
+ # Layer 1 — from the bluebook alone: every translated state must
10
+ # pass the new era's types, value-object invariants, and lifecycle.
11
+ # This is also where a NEW, stricter invariant that old records
12
+ # violate surfaces — there is no "grandfather old records"
13
+ # construct, and the remedy is relaxing the invariant or explicit
14
+ # remediation, never a translation rule.
15
+ module LayerOne
16
+ def layer_one!(violations, aggregate, after)
17
+ after.each do |id, state|
18
+ begin
19
+ symbolized = JSON.parse(JSON.generate(state), symbolize_names: true)
20
+ instance = Runtime::Instance.new(aggregate: aggregate, id: id, state: symbolized)
21
+ lifecycle = aggregate.lifecycle
22
+ next unless lifecycle
23
+
24
+ held = instance[lifecycle.field]
25
+ # `Lifecycle#states` answers default+targets only — a state
26
+ # legitimately declared just as a `from:` (a terminal
27
+ # transition's source, never anyone's target) is real and
28
+ # reachable but invisible to it. `ModelCheck.full_states`
29
+ # is the full declared set (default, every target, AND
30
+ # every from) that `fuzzing/properties.rb`'s own replay
31
+ # check already uses for this identical question — see its
32
+ # comment on this same hole.
33
+ allowed = Bluebook::ModelCheck.full_states(lifecycle)
34
+ unless held.nil? || allowed.include?(held.to_s)
35
+ violations << "#{aggregate.name}##{id}: #{lifecycle.field} is #{held.inspect}, " \
36
+ "a state this era's lifecycle never reaches"
37
+ end
38
+ rescue Runtime::InvariantViolation, Runtime::TypeMismatch => error
39
+ violations << "#{aggregate.name}##{id}: #{error.message}"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,102 @@
1
+ require_relative "../../../../../../ports/persistence/append_only"
2
+ require_relative "../../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_paths = declared.computes.flat_map { |compute| [compute.from, compute.to] }.map(&:to_s)
57
+
58
+ before.each do |id, state|
59
+ next unless after.key?(id)
60
+
61
+ entry = Ports::Persistence::Entry.new(operation: "save", id: id, state: state.transform_keys(&:to_sym))
62
+ expected = strip_compute_paths(normalize(rules.translate(entry).state), compute_paths)
63
+ actual = strip_compute_paths(normalize(after[id]), compute_paths)
64
+ next if expected == actual
65
+
66
+ diverged = (expected.keys | actual.keys).select { |key| expected[key] != actual[key] }
67
+ violations << "#{aggregate.name}##{id}: the translated state diverges from the reference " \
68
+ "transform at #{diverged.sort.join(', ')}"
69
+ end
70
+ end
71
+
72
+ def normalize(state) = JSON.parse(JSON.generate(state))
73
+
74
+ # Exempts exactly the paths a compute owns, not the whole
75
+ # top-level attribute it happens to live under. A bare path
76
+ # ("price_cents") is itself the compute's entire value — dropping
77
+ # the whole top-level key is correct, there's nothing else there
78
+ # to check. A DOTTED path ("price.cents") only owns that one
79
+ # member of the value object it reaches into; every sibling
80
+ # member (e.g. "price.currency") is untouched by the compute and
81
+ # must stay subject to the equivalence check below. Blanket-
82
+ # dropping the whole top-level key for a dotted compute used to
83
+ # exempt the entire attribute — silent data loss elsewhere in the
84
+ # same value object (a migration that nulls or drops a sibling
85
+ # field) produced zero violations, defeating the one gate whose
86
+ # entire purpose is to catch exactly that.
87
+ def strip_compute_paths(state, paths)
88
+ paths.each do |path|
89
+ segments = path.split(".")
90
+ if segments.length == 1
91
+ state.delete(segments.first)
92
+ else
93
+ parent = segments[0..-2].reduce(state) { |node, segment| node.is_a?(Hash) ? node[segment] : nil }
94
+ parent.delete(segments.last) if parent.is_a?(Hash)
95
+ end
96
+ end
97
+ state
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,46 @@
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
+ # `key?` decides which spelling answers, never `||` — a
37
+ # `false` genuinely held at this path must not fall through
38
+ # to the other spelling (usually absent) and read as `nil`,
39
+ # which would wrongly mark a `false`-valued attribute "unfed".
40
+ node.key?(segment) ? node[segment] : node[segment.to_sym]
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ 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 "../era_guard"
3
+ require_relative "../../../../../runtime/registry"
4
+ require_relative "../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