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,134 @@
1
+ module Hecks
2
+ module Adapters
3
+ class PostgresEra
4
+ class Lineage
5
+ module TransformInstaller
6
+ # The jsonb rule transforms — installed once, idempotently. Kept
7
+ # equal to the port's reference entry-JSON transform by the
8
+ # cross-execution equivalence spec; the SQL here is a compilation
9
+ # target, not a second source of truth.
10
+ #
11
+ # LOCKED, unlike every other statement `ensure_base!` runs — those
12
+ # are all `CREATE ... IF NOT EXISTS`/`ADD COLUMN IF NOT EXISTS`,
13
+ # which Postgres itself resolves safely under concurrent boots.
14
+ # `CREATE OR REPLACE FUNCTION` is not: it always rewrites the
15
+ # `pg_proc` row, so two sessions racing to (re)install the SAME
16
+ # function — these six are shared/global, not per-domain, so any
17
+ # two domains' concurrent first-boots can collide here — hit a
18
+ # real `PG::InternalError: tuple concurrently updated`, not a
19
+ # graceful no-op. `nested_transaction` is the same
20
+ # already-open-transaction-safe wrapper `ensure_field_cache!`
21
+ # uses for its own advisory lock; a fixed, domain-independent key
22
+ # is correct since these functions have no domain of their own.
23
+ def install_transforms!
24
+ nested_transaction("hecks_tr_functions") do
25
+ @db.exec_params("SELECT pg_advisory_xact_lock(hashtext('hecks_tr_functions'))", [])
26
+ install_transform_functions!
27
+ end
28
+ end
29
+
30
+ def install_transform_functions!
31
+ @db.exec(<<~SQL)
32
+ CREATE OR REPLACE FUNCTION hecks_tr_extract(state jsonb, path text[], OUT remaining jsonb, OUT value jsonb, OUT present boolean)
33
+ LANGUAGE plpgsql IMMUTABLE AS $fn$
34
+ DECLARE parent jsonb; leaf text;
35
+ BEGIN
36
+ remaining := state;
37
+ present := false;
38
+ leaf := path[array_upper(path, 1)];
39
+ IF array_length(path, 1) = 1 THEN
40
+ IF state ? leaf THEN
41
+ present := true;
42
+ value := state -> leaf;
43
+ remaining := state - leaf;
44
+ END IF;
45
+ RETURN;
46
+ END IF;
47
+ parent := state #> path[1:array_upper(path, 1) - 1];
48
+ IF jsonb_typeof(parent) = 'object' AND parent ? leaf THEN
49
+ present := true;
50
+ value := parent -> leaf;
51
+ parent := parent - leaf;
52
+ IF parent = '{}'::jsonb THEN
53
+ remaining := state - path[1];
54
+ ELSE
55
+ remaining := jsonb_set(state, path[1:array_upper(path, 1) - 1], parent);
56
+ END IF;
57
+ END IF;
58
+ END $fn$
59
+ SQL
60
+ # ADVERSARIAL FINDING: a destination whose top segment already
61
+ # holds a value — most commonly a reference, a bare scalar id
62
+ # — used to be silently overwritten with an empty object the
63
+ # moment a dotted destination needed to nest under it. That is
64
+ # a drop that never declared itself, the one thing this
65
+ # language exists to make explicit (see hecks_tr_convert's own
66
+ # refusal below, the same shape) — refused here instead, with
67
+ # the Ruby reference transform (ports/persistence/lineage.rb's
68
+ # `insert`) raising the identical wording.
69
+ @db.exec(<<~SQL)
70
+ CREATE OR REPLACE FUNCTION hecks_tr_insert(state jsonb, path text[], value jsonb, rule_label text) RETURNS jsonb
71
+ LANGUAGE plpgsql IMMUTABLE AS $fn$
72
+ BEGIN
73
+ IF array_length(path, 1) = 1 THEN
74
+ RETURN state || jsonb_build_object(path[1], value);
75
+ END IF;
76
+ IF state ? path[1] AND jsonb_typeof(state -> path[1]) <> 'object' THEN
77
+ RAISE EXCEPTION 'cannot %: % already holds %, not a value this can nest under — moving into it would discard that value silently. Rename or drop % first.',
78
+ rule_label, path[1], state -> path[1], path[1];
79
+ END IF;
80
+ IF state -> path[1] IS NULL THEN
81
+ state := state || jsonb_build_object(path[1], '{}'::jsonb);
82
+ END IF;
83
+ RETURN jsonb_set(state, path, value);
84
+ END $fn$
85
+ SQL
86
+ @db.exec(<<~SQL)
87
+ CREATE OR REPLACE FUNCTION hecks_tr_rename(state jsonb, old_name text, new_name text) RETURNS jsonb
88
+ LANGUAGE sql IMMUTABLE AS $fn$
89
+ SELECT CASE WHEN state ? old_name
90
+ THEN (state - old_name) || jsonb_build_object(new_name, state -> old_name)
91
+ ELSE state END
92
+ $fn$
93
+ SQL
94
+ @db.exec(<<~SQL)
95
+ CREATE OR REPLACE FUNCTION hecks_tr_move(state jsonb, from_path text[], to_path text[], rule_label text) RETURNS jsonb
96
+ LANGUAGE plpgsql IMMUTABLE AS $fn$
97
+ DECLARE extracted record;
98
+ BEGIN
99
+ SELECT * INTO extracted FROM hecks_tr_extract(state, from_path);
100
+ IF NOT extracted.present THEN RETURN state; END IF;
101
+ RETURN hecks_tr_insert(extracted.remaining, to_path, extracted.value, rule_label);
102
+ END $fn$
103
+ SQL
104
+ @db.exec(<<~SQL)
105
+ CREATE OR REPLACE FUNCTION hecks_tr_convert(state jsonb, from_path text[], to_path text[], pairs jsonb, from_label text, rule_label text) RETURNS jsonb
106
+ LANGUAGE plpgsql IMMUTABLE AS $fn$
107
+ DECLARE extracted record; pair jsonb;
108
+ BEGIN
109
+ SELECT * INTO extracted FROM hecks_tr_extract(state, from_path);
110
+ IF NOT extracted.present THEN RETURN state; END IF;
111
+ FOR pair IN SELECT * FROM jsonb_array_elements(pairs) LOOP
112
+ IF pair -> 0 = extracted.value THEN
113
+ RETURN hecks_tr_insert(extracted.remaining, to_path, pair -> 1, rule_label);
114
+ END IF;
115
+ END LOOP;
116
+ RAISE EXCEPTION 'cannot translate %: % has no mapping in its convert''s values: table. Add % => ... to cover it.',
117
+ from_label, extracted.value, extracted.value;
118
+ END $fn$
119
+ SQL
120
+ @db.exec(<<~SQL)
121
+ CREATE OR REPLACE FUNCTION hecks_tr_drop(state jsonb, path text[]) RETURNS jsonb
122
+ LANGUAGE plpgsql IMMUTABLE AS $fn$
123
+ DECLARE extracted record;
124
+ BEGIN
125
+ SELECT * INTO extracted FROM hecks_tr_extract(state, path);
126
+ RETURN extracted.remaining;
127
+ END $fn$
128
+ SQL
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,137 @@
1
+ require "json"
2
+ require "digest"
3
+
4
+ require_relative "lineage/provisioning"
5
+ require_relative "lineage/era_store"
6
+ require_relative "lineage/mint_transaction"
7
+ require_relative "lineage/tail_merge"
8
+ require_relative "lineage/resumable_backfill"
9
+ require_relative "lineage/head_compiler"
10
+ require_relative "lineage/field_cache"
11
+ require_relative "lineage/transform_installer"
12
+ require_relative "../../../../../naming"
13
+
14
+ module Hecks
15
+ module Adapters
16
+ class PostgresEra
17
+ # The lineage topology inside one Postgres database: one journal
18
+ # per domain, LIST-partitioned by era, with a single ordinal
19
+ # sequence spanning partitions (total order across eras is
20
+ # structural); a `hecks_eras` table holding each era's frozen
21
+ # source text, its once-minted hash/label, and the watermark cut
22
+ # into its ancestor; and, per aggregate, a HEAD derived from the
23
+ # journal — never a table anything rewrites.
24
+ #
25
+ # For era 1 the head is a plain view (latest save per id). From
26
+ # era 2 on, the ancestor tail is a MATERIALIZED view whose
27
+ # definition is the compiled, chained edge sequence — one CTE per
28
+ # original edge, in mint order, never a flattened merged rule set
29
+ # — with the watermark baked into the definition, so post-cut
30
+ # old-era writes cannot leak into the new head even on REFRESH.
31
+ # Live current-era writes overlay it through the head view.
32
+ #
33
+ # Writing to a superseded schema drops the instant the new one
34
+ # materializes: ONE shared row policy admits INSERTs to whichever
35
+ # era was just established (era 1 at first hold, era N at mint),
36
+ # and advancing it is part of the SAME transaction that builds the
37
+ # new era's matview. There is no persisted per-role fork — any
38
+ # granted role, app or the table's own OWNER (FORCE ROW LEVEL
39
+ # SECURITY applies this to the owner too, not only ordinary
40
+ # roles), writes the current era or nothing, from the moment that
41
+ # transaction commits. A stale-era write during the narrow window
42
+ # before that commit — RLS is checked once, when the statement
43
+ # EXECUTES, never re-checked at commit, so a transaction that
44
+ # inserted while the old era was still current can still land
45
+ # after a concurrent mint has already moved the fence on — is
46
+ # exactly what diverged_count/merge_tail exist to reconcile; it is
47
+ # the residual of an unavoidable race (ordinals are
48
+ # sequence-assigned, not transactional — see below), not a
49
+ # supported way to keep operating two schemas side by side. Only
50
+ # an actual Postgres superuser (or a role granted BYPASSRLS)
51
+ # sits above FORCE and keeps writing at will, forever.
52
+ #
53
+ # Lineage order is ordinal-assignment order: the ordinal comes from a
54
+ # sequence, and a sequence's `nextval()` is never rolled back with its
55
+ # transaction — accepted and documented rather than papered over.
56
+ #
57
+ # ONE PART OF THAT IS CLOSED. Two concurrent PLAIN writes could call
58
+ # `nextval()` in one order and COMMIT in the other — nothing about a
59
+ # single autocommit INSERT statement stops a slower one from finishing
60
+ # after a faster one that started later — so "ordinal order" and
61
+ # "commit order" were formally two different total orders even with no
62
+ # mint anywhere near either write. `PostgresEra#append` now holds
63
+ # `pg_advisory_xact_lock(hashtext('hecks_ordinal:' || domain))` for the
64
+ # length of its own transaction, a DIFFERENT key from `mint_era!` and
65
+ # `merge_tail!`'s `hecks_eras:domain` — so plain writes serialize
66
+ # against EACH OTHER only, never against a mint, and ordinal order
67
+ # equals commit order for them now.
68
+ #
69
+ # THE OTHER PART IS NOT, ON PURPOSE. A stale-era write during the
70
+ # narrow window before a mint's fence-move commits is the SAME race by
71
+ # a different name — and closing it would mean a plain write
72
+ # serializing against a mint, which is exactly the guarantee
73
+ # `postgres_lineage_spec.rb`'s "an old checkout keeps writing its own
74
+ # era THROUGH a mint" pins the ABSENCE of. That race stays the
75
+ # residual `diverged_count`/`merge_tail!` exist to reconcile, not
76
+ # something a plain write should ever block for.
77
+ #
78
+ # One concern per file under lineage/: provisioning (DDL and the
79
+ # RLS posture), era_store (the hecks_eras rows and their integrity),
80
+ # mint_transaction (the one transaction that makes an era real),
81
+ # tail_merge (the one deliberate merge command), resumable_backfill
82
+ # (the one chunked/lock-free/resumable scan loop, shared by
83
+ # head_compiler's own backfill and field_cache's), head_compiler
84
+ # (the chained-edge SQL a head derives through), field_cache (the
85
+ # per-where-field read cache that lets a query skip the reduction
86
+ # entirely), transform_installer (the hecks_tr_* jsonb helpers).
87
+ class Lineage
88
+ include Provisioning
89
+ include EraStore
90
+ include MintTransaction
91
+ include TailMerge
92
+ include ResumableBackfill
93
+ include HeadCompiler
94
+ include FieldCache
95
+ include TransformInstaller
96
+
97
+ JOURNAL_COLUMNS = "ordinal, era, aggregate, aggregate_id, operation, state, mirrors".freeze
98
+
99
+ attr_reader :db, :domain, :formerly_known_as
100
+
101
+ def initialize(db, domain, formerly_known_as: nil)
102
+ @db = db
103
+ @domain = domain.to_s
104
+ @formerly_known_as = formerly_known_as&.to_s
105
+ end
106
+
107
+ def journal = "hecks_journal_#{Naming.snake(@domain)}"
108
+ def quoted_journal = quote(journal)
109
+ def sequence = "#{journal}_ordinal"
110
+ def partition(era) = "#{journal}_era_#{era}"
111
+ def head_view(storage_name) = "#{storage_name}_head"
112
+ # The transactionally-upserted read cache behind head_view — one row
113
+ # per LIVE id, keyed by id, carrying the ordinal it was last written
114
+ # at. Scoped by ERA, not just storage_name — an aggregate that
115
+ # ISN'T renamed across a mint keeps the SAME storage_name in both
116
+ # eras, so storage_name alone would have era N+1 sharing one
117
+ # physical table with era N: a freshly-minted era would inherit
118
+ # every pre-mint (and, worse, pre-rekey/pre-translation) row
119
+ # instead of starting empty. era-qualified naming is what
120
+ # `partition`/`matview` already do for exactly this reason.
121
+ def head_snapshot(storage_name, era) = "#{storage_name}_head_snapshot_#{era}"
122
+ def matview(storage_name, era, label) = "#{storage_name}_lineage_#{era}_#{label}"
123
+
124
+ private
125
+
126
+ def quote(name) = PG::Connection.quote_ident(name.to_s)
127
+
128
+ def text_literal(text) = "'#{text.to_s.gsub("'", "''")}'"
129
+
130
+ def path_literal(path)
131
+ segments = path.to_s.split(".").map { |segment| text_literal(segment) }
132
+ "ARRAY[#{segments.join(', ')}]::text[]"
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,89 @@
1
+ require_relative "../../lineage"
2
+ require_relative "../../era_guard"
3
+ require_relative "../../../../../../runtime/identity"
4
+ require_relative "../../../../../../runtime/registry"
5
+ require_relative "../../translation/audit"
6
+
7
+ module Hecks
8
+ module Adapters
9
+ class PostgresEra
10
+ module LineageManager
11
+ # What a mint must prove before it may happen: the edge covers
12
+ # the whole diff, no identity path was re-keyed, and the audit's
13
+ # first two layers pass over the LIVE compiled chain.
14
+ module CoverageCheck
15
+ # Layer 1 against THIS edge specifically: every vanished or
16
+ # retyped path in the held→current diff must be explained, and
17
+ # every held aggregate must still be claimed. The refusal is
18
+ # EraGuard's own, byte for byte.
19
+ def check_coverage!(registry, bluebook, held_bluebook, edge)
20
+ bluebook.aggregates.each do |aggregate|
21
+ rules = Ports::Persistence::Lineage.from_declared(edge.for_aggregate(aggregate.name), aggregate.name)
22
+ held_aggregate = held_bluebook.aggregate(rules&.ancestor_name || aggregate.name)
23
+ next unless held_aggregate
24
+
25
+ check_identity_unchanged!(bluebook, aggregate, held_aggregate, rules)
26
+ uncovered = Runtime::EraGuard.uncovered_attributes(aggregate, held_aggregate, rules)
27
+ Runtime::EraGuard.refuse_uncovered!(bluebook, aggregate, uncovered) unless uncovered.empty?
28
+
29
+ unsafe = Runtime::EraGuard.unsafe_additions(aggregate, held_aggregate, rules)
30
+ Runtime::EraGuard.refuse_unsafe_addition!(bluebook, aggregate, unsafe) unless unsafe.empty?
31
+ end
32
+ Runtime::EraGuard.check_vanished_aggregates!(registry, bluebook, held_bluebook)
33
+ end
34
+
35
+ # An identity-path change is a RE-KEYING, not an ordinary
36
+ # translation — stored ids were fixed at write time under the old
37
+ # key, so this refuses UNLESS the edge declares a `rekey` for
38
+ # this aggregate covering exactly that. `rules.rekey?` (see
39
+ # `Ports::Persistence::Lineage`'s own comment) is the single
40
+ # source of truth every consumer of this fact asks — this is not
41
+ # a second, independent check of `declared.rekeys`.
42
+ def check_identity_unchanged!(bluebook, aggregate, held_aggregate, rules)
43
+ # The FULL declared path lists, in declaration order — never the
44
+ # single-head shortcut, which is nil for every composite identity
45
+ # and so would let two different composites compare as unchanged.
46
+ return if held_aggregate.identity_paths == aggregate.identity_paths
47
+ return if rules&.rekey?
48
+
49
+ held_identity = Runtime::Identity.reading(held_aggregate)
50
+ current_identity = Runtime::Identity.reading(aggregate)
51
+ raise Runtime::WiringError,
52
+ "cannot mint an era for #{bluebook.name}::#{aggregate.name}: its identity path changed " \
53
+ "(#{held_identity} → #{current_identity}), and that is a re-keying, not a translation — " \
54
+ "stored ids were minted under #{held_identity}, and no rule declares rows the same " \
55
+ "entity under a new key. Keep the identity path, declare a rekey rule, or migrate the " \
56
+ "data explicitly"
57
+ end
58
+
59
+ # Layers 1 and 2 of the audit, over the LIVE compiled chain —
60
+ # before anything is minted, so a refusal leaves no half-born
61
+ # era. (A convert meeting an unmapped value raises inside the
62
+ # preview query itself: same rollback-shaped outcome.)
63
+ def audit!(bluebook, lineage, chain, ordinal, edge)
64
+ violations = []
65
+ bluebook.aggregates.each do |aggregate|
66
+ declared = edge.for_aggregate(aggregate.name)
67
+ after = begin
68
+ lineage.translated_latest(aggregate, ordinal, chain)
69
+ rescue PG::Error => error
70
+ raise Runtime::WiringError, "cannot mint era #{ordinal} of #{bluebook.name}: #{error.message.strip}"
71
+ end
72
+ before = if chain.size > 1
73
+ lineage.translated_latest(aggregate, ordinal, chain[0..-2])
74
+ else
75
+ lineage.ancestor_latest(aggregate, ordinal, chain)
76
+ end
77
+ verdict = Translation::Audit.check(aggregate: aggregate, declared: declared, before: before, after: after)
78
+ violations.concat(verdict.violations)
79
+ end
80
+ return if violations.empty?
81
+
82
+ raise Runtime::WiringError,
83
+ "cannot mint era #{ordinal} of #{bluebook.name}: the audit refused —\n - #{violations.join("\n - ")}"
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,85 @@
1
+ require_relative "../lineage"
2
+ require_relative "../../storage_shape"
3
+
4
+ module Hecks
5
+ module Adapters
6
+ class PostgresEra
7
+ module LineageManager
8
+ # The boot-time resolution: which era IS this checkout? First
9
+ # boot holds era 1; a quiet reboot changes nothing; a
10
+ # held-but-superseded shape boots read-only-toward-the-fence; an
11
+ # unheld shape goes to the minter.
12
+ module EraResolver
13
+ def check!(registry:, bluebook:, current_text:, settings:, directory: nil)
14
+ db = PostgresEra.connect_for(bluebook.name, settings)
15
+ lineage = Lineage.new(db, bluebook.name, formerly_known_as: bluebook.formerly_known_as)
16
+ lineage.ensure_base!
17
+ # Both spellings honored, key? first — never `||`, which cannot
18
+ # tell a genuinely stored `false` apart from an absent key (see
19
+ # PostgresEra.setting's own comment for the full reasoning).
20
+ role = settings.key?(:role) ? settings[:role] : settings["role"]
21
+
22
+ held = lineage.eras
23
+ if held.empty?
24
+ lineage.hold_first!(current_text, projection: Runtime::StorageShape.project(bluebook))
25
+ bluebook.aggregates.each { |aggregate| lineage.ensure_first_head!(aggregate.storage_name) }
26
+ # hold_first! already established era 1 as current for
27
+ # EVERY role; this one just needs its own privileges.
28
+ lineage.grant_role!(role, aggregates: bluebook.aggregates, era: 1) if role
29
+ return
30
+ end
31
+
32
+ current_shape = Runtime::StorageShape.project(bluebook)
33
+ shapes = held.map { |era| [era, Runtime::StorageShape.project(shadow(era[:held_text]))] }
34
+ if bluebook.formerly_known_as
35
+ # A held era's shape is re-derived from its own frozen
36
+ # historical text (shadow(era[:held_text])), so its "name"
37
+ # is permanently the old one — the frozen text is authentic
38
+ # record ("this domain really was called that, at the
39
+ # time") and must never be rewritten. Only the in-memory
40
+ # shape used for THIS comparison is normalized, and only
41
+ # where it exactly matches the declared old name, so an
42
+ # unrelated domain that happens to shape-match some other
43
+ # domain's history still shows up as a real mismatch.
44
+ shapes = shapes.map do |era, shape|
45
+ [era, shape["name"] == bluebook.formerly_known_as ? shape.merge("name" => bluebook.name) : shape]
46
+ end
47
+ end
48
+
49
+ latest, latest_shape = shapes.last
50
+ if latest_shape == current_shape
51
+ bluebook.aggregates.each { |aggregate| lineage.ensure_first_head!(aggregate.storage_name) } if latest[:ordinal] == 1
52
+ # A quiet reboot changes no era, so there is nothing to
53
+ # advance — only this role's own privileges, if it is new.
54
+ lineage.grant_role!(role, aggregates: bluebook.aggregates, era: latest[:ordinal]) if role
55
+ registry.resolved_eras[bluebook.name] = latest[:ordinal]
56
+ return
57
+ end
58
+
59
+ matched, = shapes.find { |_, shape| shape == current_shape }
60
+ if matched
61
+ # A held-but-superseded era — an old checkout still running.
62
+ # It may keep BOOTING and READING (PostgresEra is the one
63
+ # adapter that recognizes this rather than refusing), but it
64
+ # may not keep WRITING: the shared era fence was already
65
+ # advanced past this ordinal by whichever mint superseded
66
+ # it, and nothing here may roll that back. Granting only
67
+ # this role's privileges, never advance_era!, is what keeps
68
+ # that true — see advance_era!'s own warning against being
69
+ # called with a superseded ordinal.
70
+ lineage.grant_role!(role, aggregates: bluebook.aggregates, era: matched[:ordinal]) if role
71
+ registry.resolved_eras[bluebook.name] = matched[:ordinal]
72
+ return
73
+ end
74
+
75
+ registry.resolved_eras[bluebook.name] =
76
+ mint!(registry, bluebook, current_text, lineage, latest,
77
+ role: role, directory: directory)
78
+ ensure
79
+ db&.close
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,43 @@
1
+ require_relative "../lineage"
2
+ require_relative "../../../../../../runtime/registry"
3
+ require_relative "../../translation/audit"
4
+
5
+ module Hecks
6
+ module Adapters
7
+ class PostgresEra
8
+ module LineageManager
9
+ # Tail-merge, driven from bin/merge_tail: interleave the old
10
+ # world's post-cut writes into the head by their recorded global
11
+ # ordinals, under the full audit, in one transaction.
12
+ module MergeCoordinator
13
+ def merge!(registry:, bluebook:, settings:, winners: {})
14
+ db = PostgresEra.connect_for(bluebook.name, settings)
15
+ lineage = Lineage.new(db, bluebook.name, formerly_known_as: bluebook.formerly_known_as)
16
+ lineage.ensure_base!
17
+
18
+ held = lineage.eras
19
+ raise Runtime::WiringError, "nothing to merge — #{bluebook.name} stands at era 1" if held.size < 2
20
+
21
+ latest = held.last
22
+ chain = edge_chain(registry, bluebook, held[0..-2], latest[:label])
23
+
24
+ audit = lambda do
25
+ violations = []
26
+ bluebook.aggregates.each do |aggregate|
27
+ rows = db.exec("SELECT id, state FROM #{PG::Connection.quote_ident(lineage.head_view(aggregate.storage_name))}")
28
+ .to_h { |row| [row["id"], JSON.parse(row["state"])] }
29
+ verdict = Translation::Audit.check(aggregate: aggregate, declared: nil, before: rows, after: rows)
30
+ violations.concat(verdict.violations)
31
+ end
32
+ violations
33
+ end
34
+
35
+ lineage.merge_tail!(aggregates: bluebook.aggregates, edges: chain, winners: winners, audit: audit)
36
+ ensure
37
+ db&.close
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,132 @@
1
+ require_relative "../../../../../../runtime/registry"
2
+ require_relative "../../storage_shape"
3
+ require_relative "../../translation/audit"
4
+ require_relative "../../translation/scaffold"
5
+
6
+ module Hecks
7
+ module Adapters
8
+ class PostgresEra
9
+ module LineageManager
10
+ # The mint path: name the source era, find the one edge that
11
+ # leaves it, hold the compute approval to what was actually
12
+ # reviewed, check coverage, audit the live chain, and mint — or
13
+ # refuse toward the authoring loop by name.
14
+ module Minter
15
+ def mint!(registry, bluebook, current_text, lineage, latest, role: nil, directory: nil)
16
+ ensure_named!(lineage, latest)
17
+ latest = lineage.eras.last
18
+
19
+ hash = Runtime::StorageShape.mint_hash(bluebook)
20
+ label = hash[0, Runtime::StorageShape::LABEL_LENGTH]
21
+ ordinal = latest[:ordinal] + 1
22
+
23
+ edges = registry.translations.select { |t| t.domain == bluebook.name && t.from == latest[:label] }
24
+ if edges.empty?
25
+ refuse_toward_the_scaffold!(registry, bluebook, lineage, latest, ordinal, directory)
26
+ end
27
+ if edges.size > 1
28
+ raise Runtime::WiringError,
29
+ "cannot boot #{bluebook.name}: #{edges.size} translation edges leave era #{latest[:ordinal]} " \
30
+ "(#{latest[:label]}) — eras fork mechanically; keep one edge per source shape"
31
+ end
32
+
33
+ edge = edges.first
34
+ unless edge.to == label
35
+ raise Runtime::WiringError,
36
+ "cannot boot #{bluebook.name}: the translation edge from #{latest[:label]} targets " \
37
+ "#{edge.to}, but the current shape is #{label} — the edge is stale; re-run bin/scaffold_translation"
38
+ end
39
+
40
+ # A compute's (and, the same way, a rekey's) only verification
41
+ # is the audit's human-approved sample — mint stays
42
+ # non-interactive by requiring the approval to already exist,
43
+ # recorded IN THIS DATABASE by `bin/translation_audit …
44
+ # --approve` and bound to what was actually reviewed: this
45
+ # edge's parsed content, and the journal as it stood when the
46
+ # samples were read. A journal that has advanced past the
47
+ # review invalidates it — the approved samples no longer cover
48
+ # the data.
49
+ if edge.aggregates.any? { |declared| !declared.computes.empty? || !declared.rekeys.empty? }
50
+ approval = lineage.approval_for(from: edge.from, to: edge.to)
51
+ unless approval && approval[:edge_digest] == Translation::Audit.edge_digest(edge)
52
+ raise Runtime::WiringError,
53
+ "cannot mint era #{ordinal} of #{bluebook.name}: this edge carries a compute or rekey " \
54
+ "rule, and the audit's human-approved sample is its only verification — run " \
55
+ "bin/translation_audit with --approve, then boot again"
56
+ end
57
+ tip = lineage.last_ordinal
58
+ if approval[:reviewed_ordinal] != tip
59
+ raise Runtime::WiringError,
60
+ "cannot mint era #{ordinal} of #{bluebook.name}: the journal advanced past the approved " \
61
+ "review (ordinal #{approval[:reviewed_ordinal]} reviewed, #{tip} now) — the samples a " \
62
+ "human approved no longer cover the data; re-run bin/translation_audit with --approve"
63
+ end
64
+ end
65
+
66
+ check_coverage!(registry, bluebook, shadow(latest[:held_text]), edge)
67
+
68
+ chain = edge_chain(registry, bluebook, lineage.eras, label)
69
+ audit!(bluebook, lineage, chain, ordinal, edge)
70
+ lineage.mint_era!(
71
+ ordinal: ordinal, hash: hash, label: label, held_text: current_text,
72
+ aggregates: bluebook.aggregates, edges: chain, role: role,
73
+ projection: Runtime::StorageShape.project(bluebook)
74
+ )
75
+ ordinal
76
+ end
77
+
78
+ # No edge yet: the boot refuses toward the authoring loop —
79
+ # naming both tools and the era ordinal. With HECKS_SCAFFOLD=1
80
+ # the boot RUNS the scaffold first (an explicit flag, never a
81
+ # silent side-effect) and the refusal names the file it wrote.
82
+ def refuse_toward_the_scaffold!(registry, bluebook, lineage, latest, ordinal, directory)
83
+ if ENV["HECKS_SCAFFOLD"] == "1" && directory
84
+ path = scaffold!(registry, bluebook, lineage, latest, directory)
85
+ raise Runtime::WiringError,
86
+ "cannot boot #{bluebook.name}: the shape changed (era #{ordinal}) — wrote #{path}; " \
87
+ "review it (resolve every unresolved), check it with bin/translation_audit, then boot again"
88
+ end
89
+
90
+ raise Runtime::WiringError,
91
+ "cannot boot #{bluebook.name}: the shape changed (era #{ordinal}) and no translation edge " \
92
+ "covers it — run bin/scaffold_translation to write the edge, " \
93
+ "check it with bin/translation_audit, then boot again"
94
+ end
95
+
96
+ # Diff the held era against the current shape and write the edge
97
+ # file — confident rules inline, ambiguities as parse-refusing
98
+ # `unresolved` lines. Returns the file path.
99
+ def scaffold!(registry, bluebook, lineage, latest, directory)
100
+ ensure_named!(lineage, latest)
101
+ latest = lineage.eras.last
102
+
103
+ hash = Runtime::StorageShape.mint_hash(bluebook)
104
+ held_bluebook = shadow(latest[:held_text])
105
+ diffed = Translation::Scaffold.diff(held_bluebook, bluebook)
106
+ edge = Translation::Scaffold::Edge.new(
107
+ domain: bluebook.name,
108
+ from: latest[:label],
109
+ to: hash[0, Runtime::StorageShape::LABEL_LENGTH],
110
+ ordinal: latest[:ordinal] + 1,
111
+ label: hash[0, Runtime::StorageShape::LABEL_LENGTH],
112
+ aggregates: diffed[:aggregates],
113
+ retired: diffed[:retired]
114
+ )
115
+ Translation::Scaffold.write!(directory, edge)
116
+ end
117
+
118
+ # Era names are minted once. An era held before any drift was
119
+ # seen has no name yet; it gets one the moment an edge needs to
120
+ # leave it.
121
+ def ensure_named!(lineage, era)
122
+ return if era[:hash]
123
+
124
+ held_bluebook = shadow(era[:held_text])
125
+ hash = Runtime::StorageShape.mint_hash(held_bluebook)
126
+ lineage.mint_name!(era[:ordinal], hash, hash[0, Runtime::StorageShape::LABEL_LENGTH])
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end